Skip to content

Commit

Permalink
feat: policy evaluation plan (eclipse-edc#4442)
Browse files Browse the repository at this point in the history
* feat: policy evaluation plan
  • Loading branch information
wolf4ood authored Aug 30, 2024
1 parent c2d8c85 commit b8ea9a2
Show file tree
Hide file tree
Showing 18 changed files with 1,352 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@

package org.eclipse.edc.policy.engine;

import org.eclipse.edc.policy.engine.plan.PolicyEvaluationPlanner;
import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction;
import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintFunction;
import org.eclipse.edc.policy.engine.spi.PolicyContext;
import org.eclipse.edc.policy.engine.spi.PolicyEngine;
import org.eclipse.edc.policy.engine.spi.RuleFunction;
import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan;
import org.eclipse.edc.policy.engine.validation.PolicyValidator;
import org.eclipse.edc.policy.engine.validation.RuleValidator;
import org.eclipse.edc.policy.evaluator.PolicyEvaluator;
Expand Down Expand Up @@ -48,7 +50,7 @@
*/
public class PolicyEngineImpl implements PolicyEngine {

private static final String ALL_SCOPES_DELIMITED = ALL_SCOPES + DELIMITER;
public static final String ALL_SCOPES_DELIMITED = ALL_SCOPES + DELIMITER;

private final Map<String, List<ConstraintFunctionEntry<Rule>>> constraintFunctions = new TreeMap<>();

Expand All @@ -65,6 +67,10 @@ public PolicyEngineImpl(ScopeFilter scopeFilter, RuleValidator ruleValidator) {
this.ruleValidator = ruleValidator;
}

public static boolean scopeFilter(String entry, String scope) {
return ALL_SCOPES_DELIMITED.equals(entry) || scope.startsWith(entry);
}

@Override
public Policy filter(Policy policy, String scope) {
return scopeFilter.applyScope(policy, scope);
Expand Down Expand Up @@ -148,6 +154,29 @@ public Result<Void> validate(Policy policy) {
return validatorBuilder.build().validate(policy);
}

@Override
public PolicyEvaluationPlan createEvaluationPlan(String scope, Policy policy) {
var delimitedScope = scope + DELIMITER;
var planner = PolicyEvaluationPlanner.Builder.newInstance(delimitedScope).ruleValidator(ruleValidator);

preValidators.forEach(planner::preValidators);
postValidators.forEach(planner::postValidators);

constraintFunctions.forEach((functionScope, entry) -> entry.forEach(constraintEntry -> {
planner.evaluationFunction(functionScope, constraintEntry.key, constraintEntry.type, constraintEntry.function);
}));

dynamicConstraintFunctions.forEach(dynFunctions ->
planner.evaluationFunction(dynFunctions.scope, dynFunctions.type, dynFunctions.function)
);

ruleFunctions.forEach((functionScope, entry) -> entry.forEach(functionEntry -> {
planner.evaluationFunction(functionScope, functionEntry.type, functionEntry.function);
}));

return policy.accept(planner.build());
}

@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public <R extends Rule> void registerFunction(String scope, Class<R> type, String key, AtomicConstraintFunction<R> function) {
Expand Down Expand Up @@ -176,10 +205,6 @@ public void registerPostValidator(String scope, BiFunction<Policy, PolicyContext
postValidators.computeIfAbsent(scope + DELIMITER, k -> new ArrayList<>()).add(validator);
}

private boolean scopeFilter(String entry, String scope) {
return ALL_SCOPES_DELIMITED.equals(entry) || scope.startsWith(entry);
}

@NotNull
private Result<Void> failValidator(String type, BiFunction<Policy, PolicyContext, Boolean> validator, PolicyContext context) {
return failure(context.hasProblems() ? context.getProblems() : List.of(type + " failed: " + validator.getClass().getName()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
/*
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.policy.engine.plan;

import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction;
import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintFunction;
import org.eclipse.edc.policy.engine.spi.PolicyContext;
import org.eclipse.edc.policy.engine.spi.RuleFunction;
import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan;
import org.eclipse.edc.policy.engine.spi.plan.step.AndConstraintStep;
import org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep;
import org.eclipse.edc.policy.engine.spi.plan.step.ConstraintStep;
import org.eclipse.edc.policy.engine.spi.plan.step.DutyStep;
import org.eclipse.edc.policy.engine.spi.plan.step.OrConstraintStep;
import org.eclipse.edc.policy.engine.spi.plan.step.PermissionStep;
import org.eclipse.edc.policy.engine.spi.plan.step.ProhibitionStep;
import org.eclipse.edc.policy.engine.spi.plan.step.RuleFunctionStep;
import org.eclipse.edc.policy.engine.spi.plan.step.RuleStep;
import org.eclipse.edc.policy.engine.spi.plan.step.ValidatorStep;
import org.eclipse.edc.policy.engine.spi.plan.step.XoneConstraintStep;
import org.eclipse.edc.policy.engine.validation.RuleValidator;
import org.eclipse.edc.policy.model.AndConstraint;
import org.eclipse.edc.policy.model.AtomicConstraint;
import org.eclipse.edc.policy.model.Constraint;
import org.eclipse.edc.policy.model.Duty;
import org.eclipse.edc.policy.model.MultiplicityConstraint;
import org.eclipse.edc.policy.model.Operator;
import org.eclipse.edc.policy.model.OrConstraint;
import org.eclipse.edc.policy.model.Permission;
import org.eclipse.edc.policy.model.Policy;
import org.eclipse.edc.policy.model.Prohibition;
import org.eclipse.edc.policy.model.Rule;
import org.eclipse.edc.policy.model.XoneConstraint;
import org.eclipse.edc.spi.result.Result;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
import java.util.TreeMap;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import static org.eclipse.edc.policy.engine.PolicyEngineImpl.scopeFilter;

public class PolicyEvaluationPlanner implements Policy.Visitor<PolicyEvaluationPlan>, Rule.Visitor<RuleStep<? extends Rule>>, Constraint.Visitor<ConstraintStep> {

private final Stack<Rule> ruleContext = new Stack<>();
private final List<BiFunction<Policy, PolicyContext, Boolean>> preValidators = new ArrayList<>();
private final List<BiFunction<Policy, PolicyContext, Boolean>> postValidators = new ArrayList<>();
private final Map<String, List<ConstraintFunctionEntry<Rule>>> constraintFunctions = new TreeMap<>();
private final List<DynamicAtomicConstraintFunctionEntry<Rule>> dynamicConstraintFunctions = new ArrayList<>();
private final List<RuleFunctionFunctionEntry<Rule>> ruleFunctions = new ArrayList<>();
private final String delimitedScope;

private RuleValidator ruleValidator;

private PolicyEvaluationPlanner(String delimitedScope) {
this.delimitedScope = delimitedScope;
}

@Override
public AndConstraintStep visitAndConstraint(AndConstraint constraint) {
var steps = validateMultiplicityConstraint(constraint);
return new AndConstraintStep(steps, constraint);
}

@Override
public OrConstraintStep visitOrConstraint(OrConstraint constraint) {
var steps = validateMultiplicityConstraint(constraint);
return new OrConstraintStep(steps, constraint);

}

@Override
public XoneConstraintStep visitXoneConstraint(XoneConstraint constraint) {
var steps = validateMultiplicityConstraint(constraint);
return new XoneConstraintStep(steps, constraint);
}

@Override
public AtomicConstraintStep visitAtomicConstraint(AtomicConstraint constraint) {
var currentRule = currentRule();
var leftValue = constraint.getLeftExpression().accept(s -> s.getValue().toString());
var function = getFunctions(leftValue, currentRule.getClass());
var isFiltered = !ruleValidator.isInScope(leftValue, delimitedScope) || function == null;

return new AtomicConstraintStep(constraint, isFiltered, currentRule, function);
}

@Override
public PolicyEvaluationPlan visitPolicy(Policy policy) {

var builder = PolicyEvaluationPlan.Builder.newInstance();

preValidators.stream().map(ValidatorStep::new).forEach(builder::preValidator);
postValidators.stream().map(ValidatorStep::new).forEach(builder::postValidator);

policy.getPermissions().stream().map(permission -> permission.accept(this))
.map(PermissionStep.class::cast)
.forEach(builder::permission);

policy.getObligations().stream().map(obligation -> obligation.accept(this))
.map(DutyStep.class::cast)
.forEach(builder::obligation);

policy.getProhibitions().stream().map(permission -> permission.accept(this))
.map(ProhibitionStep.class::cast)
.forEach(builder::prohibition);

return builder.build();
}

@Override
public PermissionStep visitPermission(Permission permission) {
var permissionStepBuilder = PermissionStep.Builder.newInstance();
visitRule(permission, permissionStepBuilder);

permission.getDuties().stream().map(this::visitDuty)
.forEach(permissionStepBuilder::dutyStep);

return permissionStepBuilder.build();
}

@Override
public ProhibitionStep visitProhibition(Prohibition prohibition) {
var prohibitionStepBuilder = ProhibitionStep.Builder.newInstance();
visitRule(prohibition, prohibitionStepBuilder);
return prohibitionStepBuilder.build();
}

@Override
public DutyStep visitDuty(Duty duty) {
var prohibitionStepBuilder = DutyStep.Builder.newInstance();
visitRule(duty, prohibitionStepBuilder);
return prohibitionStepBuilder.build();
}

private AtomicConstraintFunction<Rule> getFunctions(String key, Class<? extends Rule> ruleKind) {
return constraintFunctions.getOrDefault(key, new ArrayList<>())
.stream()
.filter(entry -> ruleKind.isAssignableFrom(entry.type()))
.map(entry -> entry.function)
.findFirst()
.or(() -> dynamicConstraintFunctions
.stream()
.filter(f -> ruleKind.isAssignableFrom(f.type))
.filter(f -> f.function.canHandle(key))
.map(entry -> wrapDynamicFunction(key, entry.function))
.findFirst())
.orElse(null);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private <R extends Rule> void visitRule(R rule, RuleStep.Builder builder) {

try {
ruleContext.push(rule);
builder.filtered(shouldIgnoreRule(rule));
builder.rule(rule);

for (var functionEntry : ruleFunctions) {
if (rule.getClass().isAssignableFrom(functionEntry.type)) {
builder.ruleFunction(new RuleFunctionStep(functionEntry.function, rule));
}
}

rule.getConstraints().stream()
.map(constraint -> constraint.accept(this))
.forEach(builder::constraint);

} finally {
ruleContext.pop();
}

}

private Rule currentRule() {
return ruleContext.peek();
}

private boolean shouldIgnoreRule(Rule rule) {
return rule.getAction() != null && !ruleValidator.isBounded(rule.getAction().getType());
}

private List<ConstraintStep> validateMultiplicityConstraint(MultiplicityConstraint multiplicityConstraint) {
return multiplicityConstraint.getConstraints()
.stream()
.map(c -> c.accept(this))
.collect(Collectors.toList());
}

private <R extends Rule> AtomicConstraintFunction<R> wrapDynamicFunction(String key, DynamicAtomicConstraintFunction<R> function) {
return new AtomicConstraintFunctionWrapper<>(key, function);
}

private record ConstraintFunctionEntry<R extends Rule>(
Class<R> type,
AtomicConstraintFunction<R> function) {
}

private record DynamicAtomicConstraintFunctionEntry<R extends Rule>(
Class<R> type,
DynamicAtomicConstraintFunction<R> function) {
}

private record RuleFunctionFunctionEntry<R extends Rule>(
Class<R> type,
RuleFunction<R> function) {
}

private record AtomicConstraintFunctionWrapper<R extends Rule>(
String leftOperand,
DynamicAtomicConstraintFunction<R> inner) implements AtomicConstraintFunction<R> {

@Override
public boolean evaluate(Operator operator, Object rightValue, R rule, PolicyContext context) {
return inner.evaluate(leftOperand, operator, rightValue, rule, context);
}

@Override
public Result<Void> validate(Operator operator, Object rightValue, R rule) {
return inner.validate(leftOperand, operator, rightValue, rule);
}
}

public static class Builder {
private final PolicyEvaluationPlanner planner;

private Builder(String scope) {
planner = new PolicyEvaluationPlanner(scope);
}

public static PolicyEvaluationPlanner.Builder newInstance(String scope) {
return new PolicyEvaluationPlanner.Builder(scope);
}

public Builder ruleValidator(RuleValidator ruleValidator) {
planner.ruleValidator = ruleValidator;
return this;
}

public Builder preValidator(String scope, BiFunction<Policy, PolicyContext, Boolean> validator) {

if (scopeFilter(scope, planner.delimitedScope)) {
planner.preValidators.add(validator);
}

return this;
}

public Builder preValidators(String scope, List<BiFunction<Policy, PolicyContext, Boolean>> validators) {
validators.forEach(validator -> preValidator(scope, validator));
return this;
}

public Builder postValidator(String scope, BiFunction<Policy, PolicyContext, Boolean> validator) {
if (scopeFilter(scope, planner.delimitedScope)) {
planner.postValidators.add(validator);
}
return this;
}

public Builder postValidators(String scope, List<BiFunction<Policy, PolicyContext, Boolean>> validators) {
validators.forEach(validator -> postValidator(scope, validator));
return this;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public <R extends Rule> Builder evaluationFunction(String scope, String key, Class<R> ruleKind, AtomicConstraintFunction<R> function) {

if (scopeFilter(scope, planner.delimitedScope)) {
planner.constraintFunctions.computeIfAbsent(key, k -> new ArrayList<>())
.add(new ConstraintFunctionEntry(ruleKind, function));
}
return this;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public <R extends Rule> Builder evaluationFunction(String scope, Class<R> ruleKind, DynamicAtomicConstraintFunction<R> function) {
if (scopeFilter(scope, planner.delimitedScope)) {
planner.dynamicConstraintFunctions.add(new DynamicAtomicConstraintFunctionEntry(ruleKind, function));
}
return this;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public <R extends Rule> Builder evaluationFunction(String scope, Class<R> ruleKind, RuleFunction<R> function) {
if (scopeFilter(scope, planner.delimitedScope)) {
planner.ruleFunctions.add(new RuleFunctionFunctionEntry(ruleKind, function));
}
return this;
}

public PolicyEvaluationPlanner build() {
Objects.requireNonNull(planner.ruleValidator, "Rule validator should not be null");
return planner;
}

}
}
Loading

0 comments on commit b8ea9a2

Please sign in to comment.