diff --git a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/PolicyEngineImpl.java b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/PolicyEngineImpl.java index 0578250739c..d35d1aa2c46 100644 --- a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/PolicyEngineImpl.java +++ b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/PolicyEngineImpl.java @@ -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; @@ -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>> constraintFunctions = new TreeMap<>(); @@ -148,6 +150,29 @@ public Result validate(Policy policy) { return validatorBuilder.build().validate(policy); } + @Override + public PolicyEvaluationPlan evaluationPlan(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 planner.build().evaluationPlan(policy); + } + @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public void registerFunction(String scope, Class type, String key, AtomicConstraintFunction function) { diff --git a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/plan/PolicyEvaluationPlanner.java b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/plan/PolicyEvaluationPlanner.java new file mode 100644 index 00000000000..9800f2bcd8a --- /dev/null +++ b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/plan/PolicyEvaluationPlanner.java @@ -0,0 +1,324 @@ +/* + * 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.ALL_SCOPES_DELIMITED; + +public class PolicyEvaluationPlanner implements Policy.Visitor, Rule.Visitor>, Constraint.Visitor { + + private final Stack ruleContext = new Stack<>(); + private final List> preValidators = new ArrayList<>(); + private final List> postValidators = new ArrayList<>(); + private final Map>> constraintFunctions = new TreeMap<>(); + private final List> dynamicConstraintFunctions = new ArrayList<>(); + private final List> ruleFunctions = new ArrayList<>(); + private final String delimitedScope; + + private RuleValidator ruleValidator; + + private PolicyEvaluationPlanner(String delimitedScope) { + this.delimitedScope = delimitedScope; + } + + public PolicyEvaluationPlan evaluationPlan(Policy policy) { + + var planBuilder = policy.accept(this); + + preValidators.stream().map(ValidatorStep::new).forEach(planBuilder::preValidator); + postValidators.stream().map(ValidatorStep::new).forEach(planBuilder::postValidator); + + return planBuilder.build(); + } + + @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.Builder visitPolicy(Policy policy) { + + var builder = PolicyEvaluationPlan.Builder.newInstance(); + + 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; + } + + @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 getFunctions(String key, Class 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 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 validateMultiplicityConstraint(MultiplicityConstraint multiplicityConstraint) { + return multiplicityConstraint.getConstraints() + .stream() + .map(c -> c.accept(this)) + .collect(Collectors.toList()); + } + + private AtomicConstraintFunction wrapDynamicFunction(String key, DynamicAtomicConstraintFunction function) { + return new AtomicConstraintFunctionWrapper<>(key, function); + } + + private record ConstraintFunctionEntry( + Class type, + AtomicConstraintFunction function) { + } + + private record DynamicAtomicConstraintFunctionEntry( + Class type, + DynamicAtomicConstraintFunction function) { + } + + private record RuleFunctionFunctionEntry( + Class type, + RuleFunction function) { + } + + private record AtomicConstraintFunctionWrapper( + String leftOperand, + DynamicAtomicConstraintFunction inner) implements AtomicConstraintFunction { + + @Override + public boolean evaluate(Operator operator, Object rightValue, R rule, PolicyContext context) { + return inner.evaluate(leftOperand, operator, rightValue, rule, context); + } + + @Override + public Result 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 validator) { + + if (scopeFilter(scope, planner.delimitedScope)) { + planner.preValidators.add(validator); + } + + return this; + } + + public Builder preValidators(String scope, List> validators) { + validators.forEach(validator -> preValidator(scope, validator)); + return this; + } + + public Builder postValidator(String scope, BiFunction validator) { + if (scopeFilter(scope, planner.delimitedScope)) { + planner.postValidators.add(validator); + } + return this; + } + + public Builder postValidators(String scope, List> validators) { + validators.forEach(validator -> postValidator(scope, validator)); + return this; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Builder evaluationFunction(String scope, String key, Class ruleKind, AtomicConstraintFunction function) { + + if (scopeFilter(scope, planner.delimitedScope)) { + planner.constraintFunctions.computeIfAbsent(key, k -> new ArrayList<>()) + .add(new ConstraintFunctionEntry(ruleKind, function)); + } + return this; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Builder evaluationFunction(String scope, Class ruleKind, DynamicAtomicConstraintFunction function) { + if (scopeFilter(scope, planner.delimitedScope)) { + planner.dynamicConstraintFunctions.add(new DynamicAtomicConstraintFunctionEntry(ruleKind, function)); + } + return this; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Builder evaluationFunction(String scope, Class ruleKind, RuleFunction 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; + } + + private boolean scopeFilter(String entry, String scope) { + return ALL_SCOPES_DELIMITED.equals(entry) || scope.startsWith(entry); + } + } +} diff --git a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/validation/RuleValidator.java b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/validation/RuleValidator.java index 91d3238c51d..b30974c0395 100644 --- a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/validation/RuleValidator.java +++ b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/validation/RuleValidator.java @@ -30,7 +30,14 @@ public RuleValidator(RuleBindingRegistry registry) { /** * Checks if the input ruleType is bound to any scope */ - boolean isBounded(String ruleType) { + public boolean isBounded(String ruleType) { return !registry.bindings(ruleType).isEmpty(); } + + /** + * Checks if the input ruleType is bound within the input scope + */ + public boolean isInScope(String ruleType, String scope) { + return registry.isInScope(ruleType, scope); + } } diff --git a/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplPlannerTest.java b/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplPlannerTest.java new file mode 100644 index 00000000000..ddc26bf87a3 --- /dev/null +++ b/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplPlannerTest.java @@ -0,0 +1,457 @@ +/* + * 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; + +import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintFunction; +import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; +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.AtomicConstraintStep; +import org.eclipse.edc.policy.engine.spi.plan.step.MultiplicityConstraintStep; +import org.eclipse.edc.policy.engine.spi.plan.step.RuleStep; +import org.eclipse.edc.policy.engine.validation.RuleValidator; +import org.eclipse.edc.policy.model.Action; +import org.eclipse.edc.policy.model.AndConstraint; +import org.eclipse.edc.policy.model.AtomicConstraint; +import org.eclipse.edc.policy.model.Duty; +import org.eclipse.edc.policy.model.LiteralExpression; +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.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.policy.engine.spi.PolicyEngine.ALL_SCOPES; +import static org.eclipse.edc.policy.model.Operator.EQ; +import static org.junit.jupiter.params.provider.Arguments.of; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +class PolicyEngineImplPlannerTest { + + private static final String TEST_SCOPE = "test"; + + private final RuleBindingRegistry bindingRegistry = new RuleBindingRegistryImpl(); + private PolicyEngine policyEngine; + + private static AtomicConstraint atomicConstraint(String key, String value) { + var left = new LiteralExpression(key); + var right = new LiteralExpression(value); + return AtomicConstraint.Builder.newInstance() + .leftExpression(left) + .operator(EQ) + .rightExpression(right) + .build(); + } + + @BeforeEach + void setUp() { + policyEngine = new PolicyEngineImpl(new ScopeFilter(bindingRegistry), new RuleValidator(bindingRegistry)); + } + + @Nested + class EvaluationPlan { + + @ParameterizedTest + @ArgumentsSource(SimplePolicyProvider.class) + void withRule(Policy policy, Class ruleClass, String action, String key, Function>> stepsProvider) { + + bindingRegistry.bind(action, TEST_SCOPE); + bindingRegistry.bind(key, TEST_SCOPE); + + policyEngine.registerFunction(TEST_SCOPE, ruleClass, key, (op, rv, r, ctx) -> true); + + var plan = policyEngine.evaluationPlan(TEST_SCOPE, policy); + + assertThat(stepsProvider.apply(plan)).hasSize(1) + .first() + .satisfies(ruleStep -> { + assertThat(ruleStep.isFiltered()).isFalse(); + assertThat(ruleStep.getRuleFunctions()).hasSize(0); + assertThat(ruleStep.getConstraintSteps()).hasSize(1) + .first() + .isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> { + assertThat(constraintStep.isFiltered()).isFalse(); + assertThat(constraintStep.function()).isNotNull(); + assertThat(constraintStep.constraint()).isNotNull(); + assertThat(constraintStep.rule()).isInstanceOf(ruleClass); + }); + }); + } + + @ParameterizedTest + @ArgumentsSource(SimplePolicyProvider.class) + void withRuleAndDynFunction(Policy policy, Class ruleClass, String action, String key, Function>> stepsProvider) { + + DynamicAtomicConstraintFunction function = mock(); + + when(function.canHandle(key)).thenReturn(true); + + bindingRegistry.bind(action, TEST_SCOPE); + bindingRegistry.bind(key, TEST_SCOPE); + + policyEngine.registerFunction(TEST_SCOPE, ruleClass, function); + + var plan = policyEngine.evaluationPlan(TEST_SCOPE, policy); + + assertThat(stepsProvider.apply(plan)).hasSize(1) + .first() + .satisfies(ruleStep -> { + assertThat(ruleStep.isFiltered()).isFalse(); + assertThat(ruleStep.getRuleFunctions()).hasSize(0); + assertThat(ruleStep.getConstraintSteps()).hasSize(1) + .first() + .isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> { + assertThat(constraintStep.isFiltered()).isFalse(); + assertThat(constraintStep.function()).isNotNull(); + assertThat(constraintStep.constraint()).isNotNull(); + assertThat(constraintStep.rule()).isInstanceOf(ruleClass); + }); + }); + } + + + @ParameterizedTest + @ArgumentsSource(SimplePolicyProvider.class) + void withRuleAndRuleFunction(Policy policy, Class ruleClass, String action, String key, Function>> stepsProvider) { + + RuleFunction function = mock(); + + bindingRegistry.bind(action, TEST_SCOPE); + bindingRegistry.bind(key, TEST_SCOPE); + + policyEngine.registerFunction(ALL_SCOPES, ruleClass, function); + policyEngine.registerFunction(TEST_SCOPE, ruleClass, function); + + var plan = policyEngine.evaluationPlan(TEST_SCOPE, policy); + + assertThat(stepsProvider.apply(plan)).hasSize(1) + .first() + .satisfies(ruleStep -> { + assertThat(ruleStep.isFiltered()).isFalse(); + assertThat(ruleStep.getRuleFunctions()).hasSize(2); + assertThat(ruleStep.getConstraintSteps()).hasSize(1) + .first() + .isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> { + assertThat(constraintStep.isFiltered()).isTrue(); + assertThat(constraintStep.function()).isNull(); + assertThat(constraintStep.constraint()).isNotNull(); + assertThat(constraintStep.rule()).isInstanceOf(ruleClass); + }); + }); + } + + @ParameterizedTest + @ArgumentsSource(SimplePolicyProvider.class) + void withRuleAndRuleFunctionNotBound(Policy policy, Class ruleClass, String action, String key, Function>> stepsProvider) { + + RuleFunction function = mock(); + + bindingRegistry.bind(action, TEST_SCOPE); + bindingRegistry.bind(key, TEST_SCOPE); + + policyEngine.registerFunction("invalidScope", ruleClass, function); + + var plan = policyEngine.evaluationPlan(TEST_SCOPE, policy); + + assertThat(stepsProvider.apply(plan)).hasSize(1) + .first() + .satisfies(ruleStep -> { + assertThat(ruleStep.isFiltered()).isFalse(); + assertThat(ruleStep.getRuleFunctions()).hasSize(0); + }); + } + + @Test + void withPermissionContainingDuty() { + + var key = "foo"; + var actionType = "action"; + var constraint = atomicConstraint(key, "bar"); + var action = Action.Builder.newInstance().type(actionType).build(); + var duty = Duty.Builder.newInstance().constraint(constraint).action(action).build(); + var permission = Permission.Builder.newInstance().constraint(constraint).duty(duty).action(action).build(); + var policy = Policy.Builder.newInstance().permission(permission).build(); + + bindingRegistry.bind(actionType, TEST_SCOPE); + bindingRegistry.bind(key, TEST_SCOPE); + + policyEngine.registerFunction(ALL_SCOPES, Duty.class, key, (op, rv, r, ctx) -> true); + + var plan = policyEngine.evaluationPlan(TEST_SCOPE, policy); + + assertThat(plan.getPermissionSteps()).hasSize(1) + .first() + .satisfies(ruleStep -> { + assertThat(ruleStep.isFiltered()).isFalse(); + assertThat(ruleStep.getDutySteps()).hasSize(1); + assertThat(ruleStep.getRuleFunctions()).hasSize(0); + assertThat(ruleStep.getConstraintSteps()).hasSize(1) + .first() + .isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> { + assertThat(constraintStep.isFiltered()).isTrue(); + assertThat(constraintStep.function()).isNull(); + assertThat(constraintStep.constraint()).isNotNull(); + assertThat(constraintStep.rule()).isInstanceOf(Permission.class); + }); + }); + } + + + private static class SimplePolicyProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + + var leftOperand = "foo"; + var actionType = "action"; + + var action = Action.Builder.newInstance().type(actionType).build(); + var constraint = atomicConstraint(leftOperand, "bar"); + + var prohibition = Prohibition.Builder.newInstance().constraint(constraint).action(action).build(); + + Function>> permissionSteps = PolicyEvaluationPlan::getPermissionSteps; + Function>> dutySteps = PolicyEvaluationPlan::getDutySteps; + Function>> prohibitionSteps = PolicyEvaluationPlan::getProhibitionSteps; + + var permission = Permission.Builder.newInstance().constraint(constraint).action(action).build(); + var duty = Duty.Builder.newInstance().constraint(constraint).action(action).build(); + + return Stream.of( + of(Policy.Builder.newInstance().permission(permission).build(), Permission.class, actionType, leftOperand, permissionSteps), + of(Policy.Builder.newInstance().duty(duty).build(), Duty.class, actionType, leftOperand, dutySteps), + of(Policy.Builder.newInstance().prohibition(prohibition).build(), Prohibition.class, actionType, leftOperand, prohibitionSteps) + ); + } + } + } + + @Nested + class IgnoredStep { + + @Test + void shouldIgnorePermissionStep_whenActionNotBound() { + + bindingRegistry.bind("foo", TEST_SCOPE); + + var constraint = atomicConstraint("foo", "bar"); + + var permission = Permission.Builder.newInstance().action(Action.Builder.newInstance().type("action").build()).constraint(constraint).build(); + var policy = Policy.Builder.newInstance().permission(permission).build(); + policyEngine.registerFunction(ALL_SCOPES, Permission.class, "foo", (op, rv, r, ctx) -> true); + + var result = policyEngine.evaluationPlan(TEST_SCOPE, policy); + + assertThat(result.getPermissionSteps()).hasSize(1) + .first() + .satisfies(permissionStep -> { + assertThat(permissionStep.isFiltered()).isTrue(); + assertThat(permissionStep.getConstraintSteps()).hasSize(1) + .first() + .isInstanceOfSatisfying(AtomicConstraintStep.class, constraintStep -> { + assertThat(constraintStep.isFiltered()).isFalse(); + }); + }); + } + + @Test + void shouldIgnoreAtomicConstraintStep_whenLeftExpressionNotScopeBound() { + + bindingRegistry.bind("action", TEST_SCOPE); + + var constraint = atomicConstraint("foo", "bar"); + var permission = Permission.Builder.newInstance().action(Action.Builder.newInstance().type("action").build()).constraint(constraint).build(); + var policy = Policy.Builder.newInstance().permission(permission).build(); + + var plan = policyEngine.evaluationPlan(TEST_SCOPE, policy); + + assertThat(plan.getPermissionSteps()).hasSize(1) + .first() + .satisfies(permissionStep -> { + assertThat(permissionStep.isFiltered()).isFalse(); + assertThat(permissionStep.getConstraintSteps()).hasSize(1) + .first() + .isInstanceOfSatisfying(AtomicConstraintStep.class, constraintStep -> { + assertThat(constraintStep.isFiltered()).isTrue(); + }); + }); + } + + @Test + void shouldIgnoreAtomicConstraintStep_whenLeftExpressionNotFunctionBound() { + + bindingRegistry.bind("action", TEST_SCOPE); + bindingRegistry.bind("foo", TEST_SCOPE); + + var constraint = atomicConstraint("foo", "bar"); + var permission = Permission.Builder.newInstance().action(Action.Builder.newInstance().type("action").build()).constraint(constraint).build(); + var policy = Policy.Builder.newInstance().permission(permission).build(); + + var result = policyEngine.evaluationPlan(TEST_SCOPE, policy); + + assertThat(result.getPermissionSteps()).hasSize(1) + .first() + .satisfies(permissionStep -> { + assertThat(permissionStep.isFiltered()).isFalse(); + assertThat(permissionStep.getConstraintSteps()).hasSize(1) + .first() + .isInstanceOfSatisfying(AtomicConstraintStep.class, constraintStep -> { + assertThat(constraintStep.isFiltered()).isTrue(); + }); + }); + } + + @Test + void shouldIgnoreAtomicConstraintStep_whenLeftExpressionNotDynFunctionBound() { + + + DynamicAtomicConstraintFunction function = mock(); + + when(function.canHandle(any())).thenReturn(true); + + bindingRegistry.bind("action", TEST_SCOPE); + bindingRegistry.bind("foo", TEST_SCOPE); + + var constraint = atomicConstraint("foo", "bar"); + var permission = Permission.Builder.newInstance().action(Action.Builder.newInstance().type("action").build()).constraint(constraint).build(); + var policy = Policy.Builder.newInstance().permission(permission).build(); + policyEngine.registerFunction(ALL_SCOPES, Duty.class, function); + + var result = policyEngine.evaluationPlan(TEST_SCOPE, policy); + + assertThat(result.getPermissionSteps()).hasSize(1) + .first() + .satisfies(permissionStep -> { + assertThat(permissionStep.isFiltered()).isFalse(); + assertThat(permissionStep.getConstraintSteps()).hasSize(1) + .first() + .isInstanceOfSatisfying(AtomicConstraintStep.class, constraintStep -> { + assertThat(constraintStep.isFiltered()).isTrue(); + assertThat(constraintStep.function()).isNull(); + }); + }); + } + + } + + @Nested + class MultiplicityConstraints { + + @ParameterizedTest + @ArgumentsSource(MultiplicityPolicyProvider.class) + void shouldEvaluate_withMultiplicityConstraint(Policy policy, Class ruleClass, String action, String key, Function>> stepsProvider) { + + bindingRegistry.bind(key, TEST_SCOPE); + bindingRegistry.bind(action, TEST_SCOPE); + + policyEngine.registerFunction(ALL_SCOPES, ruleClass, key, (op, rv, r, ctx) -> true); + + var result = policyEngine.evaluationPlan(TEST_SCOPE, policy); + + assertThat(result.getPreValidators()).isEmpty(); + assertThat(result.getPostValidators()).isEmpty(); + + + assertThat(stepsProvider.apply(result)).hasSize(1) + .first() + .satisfies((ruleStep -> { + assertThat(ruleStep.isFiltered()).isFalse(); + assertThat(ruleStep.getConstraintSteps()).hasSize(1) + .first() + .isInstanceOfSatisfying(MultiplicityConstraintStep.class, constraintStep -> { + assertThat(constraintStep.getSteps()).hasSize(2); + assertThat(constraintStep.getConstraint()).isNotNull(); + }); + })); + + } + + private static class MultiplicityPolicyProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + + var leftOperand = "foo"; + var actionType = "action"; + + var firstConstraint = atomicConstraint("foo", "bar"); + var secondConstraint = atomicConstraint("baz", "bar"); + + var orConstraints = OrConstraint.Builder.newInstance().constraint(firstConstraint).constraint(secondConstraint).build(); + var andConstraints = AndConstraint.Builder.newInstance().constraint(firstConstraint).constraint(secondConstraint).build(); + var xoneConstraint = XoneConstraint.Builder.newInstance().constraint(firstConstraint).constraint(secondConstraint).build(); + + var permission = Permission.Builder.newInstance().constraint(andConstraints).build(); + var prohibition = Prohibition.Builder.newInstance().constraint(orConstraints).build(); + var duty = Duty.Builder.newInstance().constraint(xoneConstraint).build(); + + Function>> permissionSteps = PolicyEvaluationPlan::getPermissionSteps; + Function>> dutySteps = PolicyEvaluationPlan::getDutySteps; + Function>> prohibitionSteps = PolicyEvaluationPlan::getProhibitionSteps; + + return Stream.of( + of(Policy.Builder.newInstance().permission(permission).build(), Permission.class, actionType, leftOperand, permissionSteps), + of(Policy.Builder.newInstance().duty(duty).build(), Duty.class, actionType, leftOperand, dutySteps), + of(Policy.Builder.newInstance().prohibition(prohibition).build(), Prohibition.class, actionType, leftOperand, prohibitionSteps) + ); + } + } + } + + @Nested + class Validator { + + @Test + void shouldEvaluate_withNoValidators() { + var emptyPolicy = Policy.Builder.newInstance().build(); + policyEngine.registerPreValidator("foo", (policy, policyContext) -> true); + + var result = policyEngine.evaluationPlan(TEST_SCOPE, emptyPolicy); + + assertThat(result.getPostValidators()).isEmpty(); + assertThat(result.getPreValidators()).isEmpty(); + } + + @Test + void shouldEvaluate_withValidators() { + var emptyPolicy = Policy.Builder.newInstance().build(); + policyEngine.registerPreValidator(TEST_SCOPE, (policy, policyContext) -> true); + policyEngine.registerPostValidator(TEST_SCOPE, (policy, policyContext) -> true); + + var result = policyEngine.evaluationPlan(TEST_SCOPE, emptyPolicy); + + assertThat(result.getPreValidators()).hasSize(1); + assertThat(result.getPostValidators()).hasSize(1); + + } + } + +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyEngine.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyEngine.java index d1fd26e034d..a112154bda6 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyEngine.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyEngine.java @@ -14,6 +14,7 @@ package org.eclipse.edc.policy.engine.spi; +import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; import org.eclipse.edc.policy.model.Action; import org.eclipse.edc.policy.model.AtomicConstraint; import org.eclipse.edc.policy.model.Policy; @@ -63,6 +64,11 @@ public interface PolicyEngine { */ Result validate(Policy policy); + /** + * Returns the {@link PolicyEvaluationPlan} of the given policy within the given scope. + */ + PolicyEvaluationPlan evaluationPlan(String scope, Policy policy); + /** * Registers a function that is invoked when a policy contains an atomic constraint whose left operator expression evaluates to the given key for the specified scope. * diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/PolicyEvaluationPlan.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/PolicyEvaluationPlan.java new file mode 100644 index 00000000000..1045ea8a3cf --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/PolicyEvaluationPlan.java @@ -0,0 +1,100 @@ +/* + * 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.spi.plan; + +import org.eclipse.edc.policy.engine.spi.plan.step.DutyStep; +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.ValidatorStep; +import org.eclipse.edc.policy.model.Policy; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link PolicyEvaluationPlan} contains information about the evaluation process of a {@link Policy} + * withing a scope without executing it. + */ +public class PolicyEvaluationPlan { + + private final List preValidators = new ArrayList<>(); + private final List postValidators = new ArrayList<>(); + private final List permissionSteps = new ArrayList<>(); + private final List prohibitionSteps = new ArrayList<>(); + private final List dutySteps = new ArrayList<>(); + + public List getPostValidators() { + return postValidators; + } + + public List getPreValidators() { + return preValidators; + } + + public List getPermissionSteps() { + return permissionSteps; + } + + public List getDutySteps() { + return dutySteps; + } + + public List getProhibitionSteps() { + return prohibitionSteps; + } + + public static class Builder { + + private final PolicyEvaluationPlan plan = new PolicyEvaluationPlan(); + + private Builder() { + + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder preValidator(ValidatorStep validatorStep) { + plan.preValidators.add(validatorStep); + return this; + } + + + public Builder permission(PermissionStep permissionStep) { + plan.permissionSteps.add(permissionStep); + return this; + } + + public Builder prohibition(ProhibitionStep prohibitionStep) { + plan.prohibitionSteps.add(prohibitionStep); + return this; + } + + public Builder obligation(DutyStep dutyStep) { + plan.dutySteps.add(dutyStep); + return this; + } + + public Builder postValidator(ValidatorStep validatorStep) { + plan.postValidators.add(validatorStep); + return this; + } + + public PolicyEvaluationPlan build() { + return plan; + } + } +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AndConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AndConstraintStep.java new file mode 100644 index 00000000000..e5de75614ab --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AndConstraintStep.java @@ -0,0 +1,29 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.model.AndConstraint; + +import java.util.List; + +/** + * An evaluation step for {@link AndConstraint} + */ +public final class AndConstraintStep extends MultiplicityConstraintStep { + + public AndConstraintStep(List steps, AndConstraint constraint) { + super(steps, constraint); + } +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AtomicConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AtomicConstraintStep.java new file mode 100644 index 00000000000..80e722769f0 --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AtomicConstraintStep.java @@ -0,0 +1,37 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction; +import org.eclipse.edc.policy.model.AtomicConstraint; +import org.eclipse.edc.policy.model.Rule; +import org.jetbrains.annotations.Nullable; + +/** + * An evaluation step for {@link AtomicConstraint}. + *

+ * The {@link AtomicConstraintStep} should be considered filtered when the left expression is not bound to a + * scope or an evaluation function {@link AtomicConstraintFunction} + */ +public record AtomicConstraintStep(AtomicConstraint constraint, boolean isFiltered, Rule rule, + AtomicConstraintFunction function) implements ConstraintStep { + + public AtomicConstraintStep(AtomicConstraint constraint, boolean isFiltered, Rule rule, @Nullable AtomicConstraintFunction function) { + this.constraint = constraint; + this.isFiltered = isFiltered; + this.rule = rule; + this.function = function; + } +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ConstraintStep.java new file mode 100644 index 00000000000..b5d15c1281c --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ConstraintStep.java @@ -0,0 +1,24 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.model.Constraint; + +/** + * Base interface for {@link Constraint} step + */ +public sealed interface ConstraintStep permits MultiplicityConstraintStep, AtomicConstraintStep { + +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/DutyStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/DutyStep.java new file mode 100644 index 00000000000..5394485a79d --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/DutyStep.java @@ -0,0 +1,35 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.model.Duty; + +/** + * An evaluation step for {@link Duty} rule; + */ +public class DutyStep extends RuleStep { + + public static class Builder extends RuleStep.Builder { + + private Builder() { + ruleStep = new DutyStep(); + } + + public static Builder newInstance() { + return new Builder(); + } + + } +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/MultiplicityConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/MultiplicityConstraintStep.java new file mode 100644 index 00000000000..feea5b76318 --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/MultiplicityConstraintStep.java @@ -0,0 +1,42 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.model.MultiplicityConstraint; + +import java.util.List; + +/** + * Base evaluation step for {@link MultiplicityConstraint}. It carries the {@link MultiplicityConstraint} + * and the collection of child {@link ConstraintStep}. + */ +public abstract sealed class MultiplicityConstraintStep implements ConstraintStep permits AndConstraintStep, OrConstraintStep, XoneConstraintStep { + + private final List steps; + private final T constraint; + + public MultiplicityConstraintStep(List steps, T constraint) { + this.constraint = constraint; + this.steps = steps; + } + + public List getSteps() { + return steps; + } + + public T getConstraint() { + return constraint; + } +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/OrConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/OrConstraintStep.java new file mode 100644 index 00000000000..7f24a18653e --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/OrConstraintStep.java @@ -0,0 +1,29 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.model.OrConstraint; + +import java.util.List; + +/** + * An evaluation step for {@link OrConstraint} + */ +public final class OrConstraintStep extends MultiplicityConstraintStep { + + public OrConstraintStep(List steps, OrConstraint constraint) { + super(steps, constraint); + } +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/PermissionStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/PermissionStep.java new file mode 100644 index 00000000000..bc8cdb3dd0e --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/PermissionStep.java @@ -0,0 +1,50 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.model.Permission; + +import java.util.ArrayList; +import java.util.List; + +/** + * An evaluation step for {@link Permission} rule; + */ +public class PermissionStep extends RuleStep { + + private final List dutySteps = new ArrayList<>(); + + public List getDutySteps() { + return dutySteps; + } + + public static class Builder extends RuleStep.Builder { + + private Builder() { + ruleStep = new PermissionStep(); + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder dutyStep(DutyStep dutyStep) { + ruleStep.dutySteps.add(dutyStep); + return this; + } + } + + +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ProhibitionStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ProhibitionStep.java new file mode 100644 index 00000000000..bdb5c53ad80 --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ProhibitionStep.java @@ -0,0 +1,37 @@ +/* + * 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.spi.plan.step; + +import com.fasterxml.jackson.annotation.JsonCreator; +import org.eclipse.edc.policy.model.Prohibition; + +/** + * An evaluation step for {@link Prohibition} rule; + */ +public class ProhibitionStep extends RuleStep { + + public static class Builder extends RuleStep.Builder { + + private Builder() { + ruleStep = new ProhibitionStep(); + } + + @JsonCreator + public static Builder newInstance() { + return new Builder(); + } + + } +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleFunctionStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleFunctionStep.java new file mode 100644 index 00000000000..5ebfe0140e9 --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleFunctionStep.java @@ -0,0 +1,25 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.engine.spi.RuleFunction; +import org.eclipse.edc.policy.model.Rule; + +/** + * An evaluation step for {@link RuleFunction} associated to a {@link Rule} + */ +public record RuleFunctionStep(RuleFunction function, Rule rule) { + +} \ No newline at end of file diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleStep.java new file mode 100644 index 00000000000..e1e1df7b844 --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleStep.java @@ -0,0 +1,80 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.model.Rule; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Base step class for {@link Rule}s evaluation. A rule can have multiple {@link ConstraintStep} + * and {@link RuleFunctionStep} associated during the evaluation process. + * A rule step is considered filtered (not evaluated) when the rule is not bound to a scope. + */ +public abstract class RuleStep { + + protected R rule; + protected boolean isFiltered = false; + + protected List constraintSteps = new ArrayList<>(); + + protected List> ruleFunctions = new ArrayList<>(); + + public List getConstraintSteps() { + return constraintSteps; + } + + public List> getRuleFunctions() { + return ruleFunctions; + } + + public boolean isFiltered() { + return isFiltered; + } + + @SuppressWarnings("unchecked") + public abstract static class Builder, B extends Builder> { + protected T ruleStep; + + + public B rule(R rule) { + ruleStep.rule = rule; + return (B) this; + } + + public B constraint(ConstraintStep constraint) { + ruleStep.constraintSteps.add(constraint); + return (B) this; + } + + public B ruleFunction(RuleFunctionStep function) { + ruleStep.ruleFunctions.add(function); + return (B) this; + } + + public B filtered(boolean isFiltered) { + ruleStep.isFiltered = isFiltered; + return (B) this; + } + + public T build() { + Objects.requireNonNull(ruleStep.rule, "Rule should not be null"); + return ruleStep; + } + + } +} \ No newline at end of file diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ValidatorStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ValidatorStep.java new file mode 100644 index 00000000000..c7a4bdae7aa --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ValidatorStep.java @@ -0,0 +1,27 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Policy; + +import java.util.function.BiFunction; + +/** + * An evaluation step for pre- and post-validators invoked during the evaluation process. + */ +public record ValidatorStep(BiFunction validator) { + +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/XoneConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/XoneConstraintStep.java new file mode 100644 index 00000000000..087d1d9a2b4 --- /dev/null +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/XoneConstraintStep.java @@ -0,0 +1,29 @@ +/* + * 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.spi.plan.step; + +import org.eclipse.edc.policy.model.XoneConstraint; + +import java.util.List; + +/** + * An evaluation step for {@link XoneConstraint} + */ +public final class XoneConstraintStep extends MultiplicityConstraintStep { + + public XoneConstraintStep(List steps, XoneConstraint constraint) { + super(steps, constraint); + } +}