diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a221ac1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+target/
+.vscode/
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..33af0c7
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,46 @@
+
+ 4.0.0
+ com.yetanalytics
+ cel-demo
+ jar
+ 1.0-SNAPSHOT
+ cel-demo
+ http://maven.apache.org
+
+ 17
+ 17
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.11.0-M2
+ test
+
+
+ dev.cel
+ cel
+ 0.11.0
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.16
+
+
+ com.yetanalytics
+ xapi-tools
+ 0.0.3-beta
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.2
+
+
+
+
diff --git a/src/main/java/com/yetanalytics/CelExecutor.java b/src/main/java/com/yetanalytics/CelExecutor.java
new file mode 100644
index 0000000..4a74b41
--- /dev/null
+++ b/src/main/java/com/yetanalytics/CelExecutor.java
@@ -0,0 +1,63 @@
+package com.yetanalytics;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.CelValidationException;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.SimpleType;
+import dev.cel.compiler.CelCompiler;
+import dev.cel.compiler.CelCompilerBuilder;
+import dev.cel.compiler.CelCompilerFactory;
+import dev.cel.parser.CelStandardMacro;
+import dev.cel.runtime.CelRuntime;
+import dev.cel.runtime.CelRuntimeFactory;
+import dev.cel.runtime.CelEvaluationException;
+import dev.cel.runtime.CelLiteRuntime.Program;
+
+public class CelExecutor {
+ private static final Logger log = LoggerFactory.getLogger(CelExecutor.class);
+ private CelCompiler celCompiler = CelCompilerFactory.standardCelCompilerBuilder().build();
+
+ public void runExpression(String expression) {
+ System.out.println(expression);
+ }
+
+ CelAbstractSyntaxTree compile(String expression, Map varTypeMap) {
+ // Compile (parse + type-check) the expression
+ // CelCompiler is immutable and when statically configured can be moved to a
+ // static final
+ CelCompilerBuilder builder = CelCompilerFactory.standardCelCompilerBuilder()
+ .setStandardMacros(CelStandardMacro.STANDARD_MACROS)
+ .setResultType(SimpleType.BOOL);
+
+ varTypeMap.forEach((k, v) -> builder.addVar(k, v));
+
+ CelCompiler celCompiler = builder.build();
+
+ try {
+ return celCompiler.compile(expression).getAst();
+ } catch (CelValidationException e) {
+ throw new IllegalArgumentException(
+ "Failed to compile expression. Reason: " + e.getMessage(), e);
+ }
+ }
+
+ Boolean run(CelAbstractSyntaxTree ast, Map values) {
+ CelRuntime runtime = CelRuntimeFactory.standardCelRuntimeBuilder().build();
+
+ Boolean result = null;
+ try {
+ Program program = runtime.createProgram(ast);
+ result = (Boolean) program.eval(values);
+ } catch (CelEvaluationException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+}
diff --git a/src/test/java/com/yetanalytics/CelExecutorTest.java b/src/test/java/com/yetanalytics/CelExecutorTest.java
new file mode 100644
index 0000000..93dae47
--- /dev/null
+++ b/src/test/java/com/yetanalytics/CelExecutorTest.java
@@ -0,0 +1,127 @@
+package com.yetanalytics;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yetanalytics.model.Container;
+import com.yetanalytics.model.ListElement;
+import com.yetanalytics.util.ReadStatement;
+import com.yetanalytics.xapi.model.Statement;
+
+import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.types.CelType;
+import dev.cel.common.types.SimpleType;
+
+public class CelExecutorTest {
+
+ private static final Logger log = LoggerFactory.getLogger(CelExecutor.class);
+
+ @Test
+ public void testEasyCommand() {
+ CelExecutor ce = new CelExecutor();
+ ce.runExpression("Blargh");
+ }
+
+ @Test
+ public void testMediumCommand() {
+ CelExecutor ce = new CelExecutor();
+ ce.runExpression("Bleeegh");
+ }
+
+
+ private final String EXP_VERB_AND_ACT_ID = "statement.verb.id == '%s' && statement.object.id == '%s'";
+ private final String VAR_VERB_ID_CORRECT = "https://www.yetanalytics.com/profiles/thing/1.0/concepts/verbs/set";
+ private final String VAR_OBJ_ID_CORRECT = "https://www.yetanalytics.com/profiles/thing/1.0/concepts/activities/act1";
+
+
+ @Test
+ public void evaluateStatementConditionCorrect() throws URISyntaxException {
+ Statement statement = ReadStatement.getJsonTestFile("basic");
+
+ Map typeMap = new HashMap();
+ typeMap.put("statement", SimpleType.DYN);
+
+ Map dyn = new ObjectMapper().convertValue(statement, new TypeReference<>() {});
+ Map varMap = new HashMap();
+ varMap.put("statement", dyn);
+
+ CelExecutor ce = new CelExecutor();
+ String expression = String.format(EXP_VERB_AND_ACT_ID, VAR_VERB_ID_CORRECT, VAR_OBJ_ID_CORRECT);
+
+ CelAbstractSyntaxTree ast = ce.compile(expression, typeMap);
+
+ Boolean result = ce.run(ast, varMap);
+
+ assertTrue(result);
+ }
+
+ @Test
+ public void evaluateStatementConditionIncorrect() throws URISyntaxException {
+ Statement statement = ReadStatement.getJsonTestFile("basic");
+
+ Map typeMap = new HashMap();
+ typeMap.put("statement", SimpleType.DYN);
+
+ Map dyn = new ObjectMapper().convertValue(statement, new TypeReference<>() {});
+ Map varMap = new HashMap();
+ varMap.put("statement", dyn);
+
+ CelExecutor ce = new CelExecutor();
+ String expression = String.format(EXP_VERB_AND_ACT_ID, "verbo", VAR_OBJ_ID_CORRECT);
+
+ CelAbstractSyntaxTree ast = ce.compile(expression, typeMap);
+
+ Boolean result = ce.run(ast, varMap);
+
+ assertFalse(result);
+ }
+
+ private final String EXP_ITERATION = "data.elements.filter(x, x.value == '%s').size() > 0";
+
+ @Test
+ public void testIteration() throws URISyntaxException {
+ // make a stupid object with a list inside
+ Container container = new Container();
+ container.setElements(new ArrayList());
+ container.getElements().add(new ListElement("Thing1"));
+ container.getElements().add(new ListElement("Thing2"));
+ container.getElements().add(new ListElement("Thing3"));
+
+ //generic object datatype
+ Map typeMap = new HashMap();
+ typeMap.put("data", SimpleType.DYN);
+
+ //convert to generic maps and lists and put as data map
+ Map containerData = new ObjectMapper().convertValue(container, new TypeReference<>() {});
+ Map varMap = new HashMap();
+ varMap.put("data", containerData);
+
+ CelExecutor ce = new CelExecutor();
+ String expression = String.format(EXP_ITERATION, "Thing2");
+
+ CelAbstractSyntaxTree ast = ce.compile(expression, typeMap);
+
+ Boolean result = ce.run(ast, varMap);
+
+ assertTrue(result);
+
+ String badExpression = String.format(EXP_ITERATION, "Woooo");
+ CelAbstractSyntaxTree badAst = ce.compile(badExpression, typeMap);
+
+ Boolean badResult = ce.run(badAst, varMap);
+
+ assertFalse(badResult);
+ }
+
+}
diff --git a/src/test/java/com/yetanalytics/model/Container.java b/src/test/java/com/yetanalytics/model/Container.java
new file mode 100644
index 0000000..52be626
--- /dev/null
+++ b/src/test/java/com/yetanalytics/model/Container.java
@@ -0,0 +1,17 @@
+package com.yetanalytics.model;
+
+import java.util.List;
+
+public class Container {
+
+ private List elements;
+
+ public List getElements() {
+ return elements;
+ }
+
+ public void setElements(List elements) {
+ this.elements = elements;
+ }
+
+}
diff --git a/src/test/java/com/yetanalytics/model/ListElement.java b/src/test/java/com/yetanalytics/model/ListElement.java
new file mode 100644
index 0000000..00078d1
--- /dev/null
+++ b/src/test/java/com/yetanalytics/model/ListElement.java
@@ -0,0 +1,19 @@
+package com.yetanalytics.model;
+
+public class ListElement {
+
+ public ListElement(String val){
+ this.value = val;
+ }
+
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+}
diff --git a/src/test/java/com/yetanalytics/util/ReadStatement.java b/src/test/java/com/yetanalytics/util/ReadStatement.java
new file mode 100644
index 0000000..5cee8e9
--- /dev/null
+++ b/src/test/java/com/yetanalytics/util/ReadStatement.java
@@ -0,0 +1,28 @@
+package com.yetanalytics.util;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.yetanalytics.CelExecutor;
+import com.yetanalytics.xapi.model.Statement;
+import com.yetanalytics.xapi.util.Mapper;
+
+public class ReadStatement {
+
+ private static final Logger log = LoggerFactory.getLogger(CelExecutor.class);
+
+ public static Statement getJsonTestFile(String filename){
+ String path = String.format("src/test/resources/statements/%s.json", filename);
+ File file = new File(path);
+ Statement stmt = null;
+ try {
+ stmt = Mapper.getMapper().readValue(file, Statement.class);
+ } catch (IOException e) {
+ log.error("failed to read statement at location: {}", path, e);
+ }
+ return stmt;
+ }
+}
diff --git a/src/test/resources/statements/basic.json b/src/test/resources/statements/basic.json
new file mode 100644
index 0000000..96da417
--- /dev/null
+++ b/src/test/resources/statements/basic.json
@@ -0,0 +1,32 @@
+{
+ "id": "6fbd600f-d87c-4c74-801a-2ec2e53231c8",
+ "actor": {
+ "name": "Cliff Casey",
+ "account": {
+ "homePage": "https://users.training.com",
+ "name": "23897525"
+ }
+ },
+ "verb": {
+ "id": "https://www.yetanalytics.com/profiles/thing/1.0/concepts/verbs/set",
+ "display": { "en-us": "Set" }
+ },
+ "object": {
+ "id": "https://www.yetanalytics.com/profiles/thing/1.0/concepts/activities/act1",
+ "definition": {
+ "name": {
+ "en-us": "Activity 1"
+ },
+ "description": {
+ "en-us": "The First Activity"
+ }
+ }
+ },
+ "authority": {
+ "name": "Yet Analytics Inc",
+ "mbox": "mailto:authority@yetanalytics.com"
+ },
+ "version": "1.0.3",
+ "stored": "2023-10-27T09:03:21.722Z",
+ "timestamp": "2023-10-27T09:03:21.723Z"
+}