Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: bind policy scope to policy context hierarchy #4535

Merged
merged 2 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

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.AtomicConstraintRuleFunction;
import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction;
import org.eclipse.edc.policy.engine.spi.PolicyContext;
import org.eclipse.edc.policy.engine.spi.PolicyValidatorFunction;
import org.eclipse.edc.policy.engine.spi.RuleFunction;
import org.eclipse.edc.policy.engine.spi.PolicyRuleFunction;
import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule;
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;
Expand All @@ -37,14 +37,12 @@
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;
Expand All @@ -54,17 +52,16 @@
import java.util.TreeMap;
import java.util.stream.Collectors;

import static org.eclipse.edc.policy.engine.PolicyEngineImpl.scopeFilter;
import static org.eclipse.edc.policy.engine.spi.PolicyEngine.DELIMITER;

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<PolicyValidatorFunction> preValidators = new ArrayList<>();
private final List<PolicyValidatorFunction> 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 List<PolicyValidatorRule<? extends PolicyContext>> preValidators = new ArrayList<>();
private final List<PolicyValidatorRule<? extends PolicyContext>> postValidators = new ArrayList<>();
private final Map<String, List<ConstraintFunctionEntry<Rule, ? extends PolicyContext>>> constraintFunctions = new TreeMap<>();
private final List<DynamicAtomicConstraintFunctionEntry<Rule, ? extends PolicyContext>> dynamicConstraintFunctions = new ArrayList<>();
private final List<RuleFunctionFunctionEntry<Rule, ? extends PolicyContext>> ruleFunctions = new ArrayList<>();
private final String delimitedScope;
private final String scope;

Expand Down Expand Up @@ -98,19 +95,19 @@ public XoneConstraintStep visitXoneConstraint(XoneConstraint constraint) {
public AtomicConstraintStep visitAtomicConstraint(AtomicConstraint constraint) {
var currentRule = currentRule();
var leftValue = constraint.getLeftExpression().accept(s -> s.getValue().toString());
var function = getFunctions(leftValue, currentRule.getClass());
var functionName = getFunctionName(leftValue, currentRule.getClass());

var filteringReasons = new ArrayList<String>();

if (!ruleValidator.isInScope(leftValue, delimitedScope)) {
filteringReasons.add("leftOperand '%s' is not bound to scope '%s'".formatted(leftValue, scope));
}

if (function == null) {
if (functionName == null) {
filteringReasons.add("leftOperand '%s' is not bound to any function within scope '%s'".formatted(leftValue, scope));
}

return new AtomicConstraintStep(constraint, filteringReasons, currentRule, function);
return new AtomicConstraintStep(constraint, filteringReasons, currentRule, functionName);
}

@Override
Expand Down Expand Up @@ -161,17 +158,17 @@ public DutyStep visitDuty(Duty duty) {
return prohibitionStepBuilder.build();
}

private AtomicConstraintFunction<Rule> getFunctions(String key, Class<? extends Rule> ruleKind) {
private String getFunctionName(String key, Class<? extends Rule> ruleKind) {
return constraintFunctions.getOrDefault(key, new ArrayList<>())
.stream()
.filter(entry -> ruleKind.isAssignableFrom(entry.type()))
.map(entry -> entry.function)
.map(entry -> entry.function.name())
.findFirst()
.or(() -> dynamicConstraintFunctions
.stream()
.filter(f -> ruleKind.isAssignableFrom(f.type))
.filter(f -> f.function.canHandle(key))
.map(entry -> wrapDynamicFunction(key, entry.function))
.map(f -> f.function.name())
.findFirst())
.orElse(null);
}
Expand Down Expand Up @@ -215,43 +212,19 @@ private List<ConstraintStep> validateMultiplicityConstraint(MultiplicityConstrai
.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>(
private record ConstraintFunctionEntry<R extends Rule, C extends PolicyContext>(
Class<R> type,
AtomicConstraintFunction<R> function) {
AtomicConstraintRuleFunction<R, C> function) {
}

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

private record RuleFunctionFunctionEntry<R extends Rule>(
private record RuleFunctionFunctionEntry<R extends Rule, C extends PolicyContext>(
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);
}

@Override
public String name() {
return inner.name();
}
PolicyRuleFunction<R, C> function) {
}

public static class Builder {
Expand All @@ -270,55 +243,33 @@ public Builder ruleValidator(RuleValidator ruleValidator) {
return this;
}

public Builder preValidator(String scope, PolicyValidatorFunction validator) {

if (scopeFilter(scope, planner.delimitedScope)) {
planner.preValidators.add(validator);
}
public <C extends PolicyContext> Builder preValidator(PolicyValidatorRule<C> validator) {
planner.preValidators.add(validator);

return this;
}

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

public Builder postValidator(String scope, PolicyValidatorFunction validator) {
if (scopeFilter(scope, planner.delimitedScope)) {
planner.postValidators.add(validator);
}
return this;
}

public Builder postValidators(String scope, List<PolicyValidatorFunction> validators) {
validators.forEach(validator -> postValidator(scope, validator));
public <C extends PolicyContext> Builder postValidator(PolicyValidatorRule<C> validator) {
planner.postValidators.add(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));
}
public <R extends Rule, C extends PolicyContext> Builder evaluationFunction(String key, Class<R> ruleKind, AtomicConstraintRuleFunction<R, C> function) {
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));
}
public <R extends Rule, C extends PolicyContext> Builder evaluationFunction(Class<R> ruleKind, DynamicAtomicConstraintRuleFunction<R, C> function) {
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));
}
public <R extends Rule, C extends PolicyContext> Builder evaluationFunction(Class<R> ruleKind, PolicyRuleFunction<R, C> function) {
planner.ruleFunctions.add(new RuleFunctionFunctionEntry(ruleKind, function));
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
package org.eclipse.edc.policy.engine.validation;

import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction;
import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction;
import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintFunction;
import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction;
import org.eclipse.edc.policy.engine.spi.PolicyContext;
import org.eclipse.edc.policy.model.AndConstraint;
import org.eclipse.edc.policy.model.AtomicConstraint;
Expand All @@ -39,7 +41,6 @@
import java.util.Objects;
import java.util.Stack;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
Expand All @@ -59,8 +60,8 @@ public class PolicyValidator implements Policy.Visitor<Result<Void>>, Rule.Visit

private final Stack<Rule> ruleContext = new Stack<>();

private final Map<String, List<ConstraintFunctionEntry<Rule>>> constraintFunctions = new TreeMap<>();
private final List<DynamicAtomicConstraintFunctionEntry<Rule>> dynamicConstraintFunctions = new ArrayList<>();
private final Map<String, List<ConstraintFunctionEntry<Rule, ? extends PolicyContext>>> constraintFunctions = new TreeMap<>();
private final List<DynamicAtomicConstraintFunctionEntry<Rule, ? extends PolicyContext>> dynamicConstraintFunctions = new ArrayList<>();
private RuleValidator ruleValidator;

public Result<Void> validate(Policy policy) {
Expand Down Expand Up @@ -133,12 +134,12 @@ private Result<Void> validateLeftExpression(Rule rule, String leftOperand) {
}

private Result<Void> validateConstraint(String leftOperand, Operator operator, Object rightOperand, Rule rule) {
var functions = getFunctions(leftOperand, rule.getClass());
var functions = getValidations(leftOperand, rule.getClass());
if (functions.isEmpty()) {
return Result.failure("left operand '%s' is not bound to any functions: Rule { %s }".formatted(leftOperand, rule));
} else {
return functions.stream()
.map(f -> f.validate(operator, rightOperand, rule))
.map(f -> f.validate(leftOperand, operator, rightOperand, rule))
.reduce(Result.success(), Result::merge);
}
}
Expand All @@ -164,29 +165,30 @@ private Result<Void> validateAction(Rule rule) {
}
}

private <R extends Rule> List<AtomicConstraintFunction<Rule>> getFunctions(String key, Class<R> ruleKind) {
private <R extends Rule, C extends PolicyContext> List<PolicyValidation> getValidations(String key, Class<R> ruleKind) {
// first look-up for an exact match
var functions = constraintFunctions.getOrDefault(key, new ArrayList<>())
.stream()
.filter(entry -> ruleKind.isAssignableFrom(entry.type()))
.map(entry -> entry.function)
.collect(Collectors.toList());
.map(entry -> (PolicyValidation) (leftOperand, operator, rightOperand, rule) ->
entry.function.validate(operator, rightOperand, rule))
.toList();

// if not found inspect the dynamic functions
if (functions.isEmpty()) {
functions = dynamicConstraintFunctions
return dynamicConstraintFunctions
.stream()
.filter(f -> ruleKind.isAssignableFrom(f.type))
.filter(f -> f.function.canHandle(key))
.map(entry -> wrapDynamicFunction(key, entry.function))
.filter(entry -> ruleKind.isAssignableFrom(entry.type))
.filter(entry -> entry.function.canHandle(key))
.map(entry -> (PolicyValidation) entry.function::validate)
.toList();
}

return functions;
}

private <R extends Rule> AtomicConstraintFunction<R> wrapDynamicFunction(String key, DynamicAtomicConstraintFunction<R> function) {
return new AtomicConstraintFunctionWrapper<>(key, function);
private interface PolicyValidation {
Result<Void> validate(String leftOperand, Operator operator, Object rightOperand, Rule rule);
}

private Rule currentRule() {
Expand All @@ -210,14 +212,14 @@ public Builder ruleValidator(RuleValidator ruleValidator) {
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public <R extends Rule> Builder evaluationFunction(String key, Class<R> ruleKind, AtomicConstraintFunction<R> function) {
public <R extends Rule, C extends PolicyContext> Builder evaluationFunction(String key, Class<R> ruleKind, AtomicConstraintRuleFunction<R, C> function) {
validator.constraintFunctions.computeIfAbsent(key, k -> new ArrayList<>())
.add(new ConstraintFunctionEntry(ruleKind, function));
return this;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public <R extends Rule> Builder dynamicEvaluationFunction(Class<R> ruleKind, DynamicAtomicConstraintFunction<R> function) {
public <R extends Rule, C extends PolicyContext> Builder dynamicEvaluationFunction(Class<R> ruleKind, DynamicAtomicConstraintRuleFunction<R, C> function) {
validator.dynamicConstraintFunctions.add(new DynamicAtomicConstraintFunctionEntry(ruleKind, function));
return this;
}
Expand All @@ -229,28 +231,13 @@ public PolicyValidator build() {

}

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

private record DynamicAtomicConstraintFunctionEntry<R extends Rule>(
private record DynamicAtomicConstraintFunctionEntry<R extends Rule, C extends PolicyContext>(
Class<R> type,
DynamicAtomicConstraintFunction<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) {
throw new UnsupportedOperationException("Evaluation is not supported");
}

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