Skip to content
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
34 changes: 34 additions & 0 deletions core/common/cel-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2025 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

plugins {
`java-library`
}

dependencies {
api(project(":spi:common:cel-spi"))
api(project(":spi:common:transaction-spi"))
api(project(":spi:common:verifiable-credentials-spi"))
api(project(":spi:control-plane:control-plane-spi"))
api(project(":spi:policy-monitor:policy-monitor-spi"))

implementation(project(":core:common:lib:store-lib"))
implementation(libs.cel)

testImplementation(project(":core:common:junit"))
testImplementation(testFixtures(project(":spi:common:cel-spi")))
testImplementation(project(":core:common:lib:query-lib"))

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (c) 2026 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.policy.cel;

import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext;
import org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext;
import org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext;
import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorContext;
import org.eclipse.edc.policy.cel.engine.CelExpressionEngine;
import org.eclipse.edc.policy.cel.engine.CelExpressionEngineImpl;
import org.eclipse.edc.policy.cel.function.CelExpressionFunction;
import org.eclipse.edc.policy.cel.function.context.AgreementContextMapper;
import org.eclipse.edc.policy.cel.function.context.ParticipantAgentContextMapper;
import org.eclipse.edc.policy.cel.function.context.PolicyMonitorContextMapper;
import org.eclipse.edc.policy.cel.function.context.TransferProcessContextMapper;
import org.eclipse.edc.policy.cel.service.CelPolicyExpressionService;
import org.eclipse.edc.policy.cel.service.CelPolicyExpressionServiceImpl;
import org.eclipse.edc.policy.cel.store.CelExpressionStore;
import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction;
import org.eclipse.edc.policy.engine.spi.PolicyContext;
import org.eclipse.edc.policy.engine.spi.PolicyEngine;
import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry;
import org.eclipse.edc.policy.model.Duty;
import org.eclipse.edc.policy.model.Permission;
import org.eclipse.edc.policy.model.Prohibition;
import org.eclipse.edc.policy.model.Rule;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.transaction.spi.TransactionContext;

import java.util.List;

import static org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext.CATALOG_SCOPE;
import static org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext.NEGOTIATION_SCOPE;
import static org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext.TRANSFER_SCOPE;
import static org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorContext.POLICY_MONITOR_SCOPE;
import static org.eclipse.edc.policy.cel.CelPolicyCoreExtension.NAME;
import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA;

@Extension(NAME)
public class CelPolicyCoreExtension implements ServiceExtension {

public static final String NAME = "Common Expression Language Policy Core Extension";

@Inject
private PolicyEngine policyEngine;

@Inject
private CelExpressionStore celExpressionStore;

@Inject
private TransactionContext transactionContext;

private CelExpressionEngine celExpressionEngine;

@Inject
private RuleBindingRegistry ruleBindingRegistry;

@Inject
private Monitor monitor;

@Override
public String name() {
return NAME;
}


@Override
public void initialize(ServiceExtensionContext context) {

ruleBindingRegistry.dynamicBind(policyExpressionEngine()::evaluationScopes);

List.of(Permission.class, Duty.class, Prohibition.class).forEach(c -> {
bindFunction(new CelExpressionFunction<>(policyExpressionEngine(), new TransferProcessContextMapper(new AgreementContextMapper(), new ParticipantAgentContextMapper<>())), c, TransferProcessPolicyContext.class);
bindFunction(new CelExpressionFunction<>(policyExpressionEngine(), new ParticipantAgentContextMapper<>()), c, ContractNegotiationPolicyContext.class);
bindFunction(new CelExpressionFunction<>(policyExpressionEngine(), new ParticipantAgentContextMapper<>()), c, CatalogPolicyContext.class);
bindFunction(new CelExpressionFunction<>(policyExpressionEngine(), new PolicyMonitorContextMapper(new AgreementContextMapper())), c, PolicyMonitorContext.class);
});

List.of(CATALOG_SCOPE, TRANSFER_SCOPE, NEGOTIATION_SCOPE, POLICY_MONITOR_SCOPE).forEach(scope -> {
ruleBindingRegistry.bind(ODRL_SCHEMA + "use", scope);
});

}

@Provider
public CelExpressionEngine policyExpressionEngine() {
if (celExpressionEngine == null) {
celExpressionEngine = new CelExpressionEngineImpl(transactionContext, celExpressionStore, monitor);
}
return celExpressionEngine;
}

@Provider
public CelPolicyExpressionService policyExpressionService() {
return new CelPolicyExpressionServiceImpl(celExpressionStore, transactionContext, policyExpressionEngine());
}

private <C extends PolicyContext, R extends Rule> void bindFunction(DynamicAtomicConstraintRuleFunction<R, C> function, Class<R> ruleClass, Class<C> contextClass) {
policyEngine.registerFunction(contextClass, ruleClass, function);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2026 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.policy.cel;

import org.eclipse.edc.policy.cel.store.CelExpressionStore;
import org.eclipse.edc.policy.cel.store.InMemoryCelExpressionStore;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.spi.query.CriterionOperatorRegistry;
import org.eclipse.edc.spi.system.ServiceExtension;

import static org.eclipse.edc.policy.cel.CelPolicyCoreExtension.NAME;

@Extension(NAME)
public class CelPolicyDefaultServicesExtension implements ServiceExtension {

public static final String NAME = "Common Expression Language Policy Default Services Extension";

@Inject
private CriterionOperatorRegistry criterionOperatorRegistry;

@Override
public String name() {
return NAME;
}

@Provider(isDefault = true)
public CelExpressionStore policyExpressionStore() {
return new InMemoryCelExpressionStore(criterionOperatorRegistry);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Copyright (c) 2025 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

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

import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelValidationException;
import dev.cel.common.internal.ProtoTimeUtils;
import dev.cel.common.types.SimpleType;
import dev.cel.compiler.CelCompiler;
import dev.cel.compiler.CelCompilerFactory;
import dev.cel.parser.CelStandardMacro;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelRuntime;
import dev.cel.runtime.CelRuntimeFactory;
import org.eclipse.edc.policy.cel.model.CelExpression;
import org.eclipse.edc.policy.cel.store.CelExpressionStore;
import org.eclipse.edc.policy.model.Operator;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.transaction.spi.TransactionContext;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class CelExpressionEngineImpl implements CelExpressionEngine {

private final TransactionContext ctx;
private final CelExpressionStore store;
private final Monitor monitor;

private final CelCompiler celCompiler = CelCompilerFactory.standardCelCompilerBuilder()
.addVar("this", SimpleType.DYN)
.addVar("ctx", SimpleType.DYN)
.addVar("now", SimpleType.TIMESTAMP)
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.setResultType(SimpleType.BOOL)
.build();


private final CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder().build();

public CelExpressionEngineImpl(TransactionContext ctx, CelExpressionStore store, Monitor monitor) {
this.ctx = ctx;
this.store = store;
this.monitor = monitor;
}

@Override
public ServiceResult<Void> validate(String expression) {
return compile(expression)
.flatMap(r -> ServiceResult.from(r.mapEmpty()));

}

@Override
public boolean canEvaluate(String leftOperand) {
return !fetch(leftOperand).isEmpty();
}

@Override
public Set<String> evaluationScopes(String leftOperand) {
return fetch(leftOperand)
.stream()
.flatMap(expr -> expr.getScopes().stream())
.collect(Collectors.toSet());
}

@Override
public ServiceResult<Boolean> test(String expression, Object leftOperand, Operator operator, Object rightOperand, Map<String, Object> params) {
return compile(expression)
.compose(ast -> evaluateAst(ast, leftOperand, operator, rightOperand, params))
.flatMap(ServiceResult::from);

}

@Override
public ServiceResult<Boolean> evaluateExpression(Object leftOperand, Operator operator, Object rightOperand, Map<String, Object> params) {
var compileResult = fetchAndCompile(leftOperand.toString());
if (compileResult.failed()) {
monitor.severe("Failed to compile expressions for left operand: " + leftOperand + ". Reason: " + compileResult.getFailureDetail());
return ServiceResult.badRequest("Failed to compile expressions for left operand: " + leftOperand + ". Reason: " + compileResult.getFailureDetail());
}
var expressions = compileResult.getContent();
if (expressions.isEmpty()) {
monitor.severe("No expressions registered for left operand: " + leftOperand);
return ServiceResult.badRequest("No expressions registered for left operand: " + leftOperand);
}
var result = true;
for (var ast : expressions) {
var evaluationResult = evaluateAst(ast, leftOperand, operator, rightOperand, params);

if (evaluationResult.failed()) {
monitor.severe("Failed to evaluate expression for left operand: " + leftOperand + ". Reason: " + evaluationResult.getFailureDetail());
return ServiceResult.badRequest("Failed to evaluate expression for left operand: " + leftOperand + ". Reason: " + evaluationResult.getFailureDetail());
}
result = evaluationResult.getContent();
if (!result) {
break;
}
}
return ServiceResult.success(result);
}

private Result<Boolean> evaluateAst(CelAbstractSyntaxTree ast, Object leftOperand, Operator operator, Object rightOperand, Map<String, Object> params) {
try {
var program = celRuntime.createProgram(ast);
Map<String, Object> newParams = new HashMap<>();
newParams.put("now", ProtoTimeUtils.now());
newParams.put("this", Map.of("leftOperand", leftOperand, "operator", operator.name(), "rightOperand", rightOperand));
newParams.put("ctx", params);

return Result.success((Boolean) program.eval(newParams));
} catch (CelEvaluationException e) {
// Report any evaluation errors, if present
return Result.failure("Evaluation error has occurred. Reason: " + e.getMessage());
}
}

private Result<List<CelAbstractSyntaxTree>> fetchAndCompile(String leftOperand) {
return fetch(leftOperand).stream()
.map(expr -> compile(expr.getExpression()))
.collect(Result.collector());
}

private List<CelExpression> fetch(String leftOperand) {
return ctx.execute(() -> store.query(QuerySpec.Builder.newInstance()
.filter(Criterion.criterion("leftOperand", "=", leftOperand))
.build()));

}

private Result<CelAbstractSyntaxTree> compile(String expression) {
CelAbstractSyntaxTree ast;
try {
// Parse the expression
ast = celCompiler.parse(expression).getAst();
// Type-check the expression for correctness
ast = celCompiler.check(ast).getAst();
} catch (CelValidationException e) {
return Result.failure("Failed to validate expression. Reason: " + e.getMessage());
}
return Result.success(ast);
}
}
Loading
Loading