All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.edc.policy.engine.plan.PolicyEvaluationPlanner Maven / Gradle / Ivy

/*
 *  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, 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;
    }

    @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 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;
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy