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
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,19 +122,21 @@ public class MyClass {

### Jackson example

The following example refers to Jackson 3. Support for Jackson 2 is, see [below](#jackson-2-example).

It is also possible to use plain Java and Jackson objects as arguments by using the
`org.projectnessie.cel.types.jackson.JacksonRegistry` in `org.projectnessie.cel:cel-jackson`.
`org.projectnessie.cel.types.jackson3.Jackson3Registry` in `org.projectnessie.cel:cel-jackson3`.

Code sample similar to the one above. It takes a user-provided object type `MyInput`.
```java
import org.projectnessie.cel.types.jackson.JacksonRegistry;
import org.projectnessie.cel.types.jackson3.Jackson3Registry;

public class MyClass {
public Boolean evalWithJacksonObject(MyInput input, String checkName) {
// Build the script factory
ScriptHost scriptHost = ScriptHost.newBuilder()
// IMPORTANT: use the Jackson registry
.registry(JacksonRegistry.newRegistry())
.registry(Jackson3Registry.newRegistry())
.build();

// Create the script, will be parsed and checked.
Expand Down Expand Up @@ -164,7 +166,7 @@ public class MyClass {
Note that the Jackson field-names are used as property names in CEL-Java. It is not necessary to
annotate "plain Java" classes with Jackson annotations.

To use the `JacksonRegistry` in your application code, add the `cel-jackson` dependency in
To use the `Jackson3Registry` in your application code, add the `cel-jackson3` dependency in
addition to `cel-core` or `cel-tools`.

```xml
Expand All @@ -183,7 +185,7 @@ addition to `cel-core` or `cel-tools`.
<dependencies>
<dependency>
<groupId>org.projectnessie.cel</groupId>
<artifactId>cel-jackson</artifactId>
<artifactId>cel-jackson3</artifactId>
</dependency>
<dependency>
<groupId>org.projectnessie.cel</groupId>
Expand All @@ -196,10 +198,17 @@ or Gradle project.
dependencies {
implementation(enforcedPlatform("org.projectnessie.cel:cel-bom:0.5.3"))
implementation("org.projectnessie.cel:cel-tools")
implementation("org.projectnessie.cel:cel-jackson")
implementation("org.projectnessie.cel:cel-jackson3")
}
```

### Jackson 2 example

Support for Jackson 2 is similar to Jackson 3, with a few differences:

* Use `JacksonRegistry` from `org.projectnessie.cel.types.jackson.JacksonRegistry`
* Use `org.projectnessie.cel:cel-jackson` dependency instead of `org.projectnessie.cel:cel-jackson3`

## Dependency-free artifact

The `org.projectnessie.cel:cel-standalone` contains everything from CEL-Java and has no dependencies.
Expand Down
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ guava = { module = "com.google.guava:guava", version = "33.5.0-jre" }
idea-ext = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version = "1.3" }
immutables-value-annotations = { module = "org.immutables:value-annotations", version.ref = "immutables" }
immutables-value-processor = { module = "org.immutables:value-processor", version.ref = "immutables" }
jackson-bom = { module = "com.fasterxml.jackson:jackson-bom", version = "2.20.1" }
jackson2-bom = { module = "com.fasterxml.jackson:jackson-bom", version = "2.20.1" }
jackson3-bom = { module = "tools.jackson:jackson-bom", version = "3.0.3" }
jacoco-maven-plugin = { module = "org.jacoco:jacoco-maven-plugin", version.ref = "jacoco" }
jandex-plugin = { module = "com.github.vlsi.gradle:jandex-plugin", version.ref = "jandexPlugin" }
jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
Expand Down
4 changes: 3 additions & 1 deletion jackson/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ plugins {
`cel-conventions`
}

description = "CEL Jackson 2 support"

dependencies {
api(project(":cel-core"))

implementation(platform(libs.jackson.bom))
implementation(platform(libs.jackson2.bom))
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.core:jackson-core")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-protobuf")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import org.projectnessie.cel.common.types.ref.Val;

/**
* CEL-Java {@link TypeRegistry} to use Jackson objects as input values for CEL scripts.
* CEL-Java {@link TypeRegistry} to use Jackson 2 objects as input values for CEL scripts.
*
* <p>The implementation does not support the construction of Jackson objects in CEL expressions and
* therefore returning Jackson objects from CEL expressions is not possible/implemented and results
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
import org.projectnessie.cel.types.jackson.types.MetaTest;
import org.projectnessie.cel.types.jackson.types.RefVariantB;

class JacksonRegistryTest {
class Jackson2RegistryTest {
@Test
void nessieBranch() {
TypeRegistry reg = JacksonRegistry.newRegistry();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import org.projectnessie.cel.types.jackson.types.MyPojo;
import org.projectnessie.cel.types.jackson.types.ObjectListEnum;

public class JacksonScriptHostTest {
public class Jackson2ScriptHostTest {

@Test
void simple() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
import org.projectnessie.cel.types.jackson.types.CollectionsObject;
import org.projectnessie.cel.types.jackson.types.InnerType;

class JacksonTypeDescriptionTest {
class Jackson2TypeDescriptionTest {

@Test
void basics() {
Expand Down
46 changes: 46 additions & 0 deletions jackson3/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2021 The Authors of CEL-Java
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
`java-library`
`maven-publish`
signing
id("org.caffinitas.gradle.testsummary")
id("org.caffinitas.gradle.testrerun")
`cel-conventions`
}

description = "CEL Jackson 3 support"

dependencies {
api(project(":cel-core"))

implementation(platform(libs.jackson3.bom))
implementation("tools.jackson.core:jackson-databind")
implementation("tools.jackson.core:jackson-core")
implementation("tools.jackson.dataformat:jackson-dataformat-protobuf")
implementation("tools.jackson.dataformat:jackson-dataformat-yaml")

testImplementation(project(":cel-tools"))
testAnnotationProcessor(libs.immutables.value.processor)
testCompileOnly(libs.immutables.value.annotations)
testImplementation(libs.findbugs.jsr305)

testImplementation(platform(libs.junit.bom))
testImplementation(libs.bundles.junit.testing)
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
* Copyright (C) 2021 The Authors of CEL-Java
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.projectnessie.cel.types.jackson3;

import static org.projectnessie.cel.common.types.Err.newErr;

import java.util.HashMap;
import java.util.Map;
import org.projectnessie.cel.common.types.ref.FieldType;
import org.projectnessie.cel.common.types.ref.Type;
import org.projectnessie.cel.common.types.ref.TypeAdapterSupport;
import org.projectnessie.cel.common.types.ref.TypeRegistry;
import org.projectnessie.cel.common.types.ref.Val;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.cfg.GeneratorSettings;
import tools.jackson.databind.cfg.SerializationContexts;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.ser.SerializationContextExt;
import tools.jackson.databind.ser.jdk.EnumSerializer;
import tools.jackson.databind.type.TypeFactory;

/**
* CEL-Java {@link TypeRegistry} to use Jackson 3 objects as input values for CEL scripts.
*
* <p>The implementation does not support the construction of Jackson objects in CEL expressions and
* therefore returning Jackson objects from CEL expressions is not possible/implemented and results
* in {@link UnsupportedOperationException}s.
*/
public final class Jackson3Registry implements TypeRegistry {
final ObjectMapper objectMapper;
private final SerializationContextExt serializationContextExt;
private final TypeFactory typeFactory;
private final Map<Class<?>, JacksonTypeDescription> knownTypes = new HashMap<>();
private final Map<String, JacksonTypeDescription> knownTypesByName = new HashMap<>();

private final Map<Class<?>, JacksonEnumDescription> enumMap = new HashMap<>();
private final Map<String, JacksonEnumValue> enumValues = new HashMap<>();

private Jackson3Registry() {
JsonMapper.Builder b = JsonMapper.builder();
SerializationContexts serializationContexts = b.serializationContexts();
this.objectMapper = b.build();
SerializationContexts forMapper =
serializationContexts.forMapper(
objectMapper,
objectMapper.serializationConfig(),
objectMapper.tokenStreamFactory(),
b.serializerFactory());
this.serializationContextExt =
forMapper.createContext(objectMapper.serializationConfig(), GeneratorSettings.empty());
this.typeFactory = objectMapper.getTypeFactory();
}

public static TypeRegistry newRegistry() {
return new Jackson3Registry();
}

@Override
public TypeRegistry copy() {
return this;
}

@Override
public void register(Object t) {
Class<?> cls = t instanceof Class ? (Class<?>) t : t.getClass();
typeDescription(cls);
}

@Override
public void registerType(Type... types) {
throw new UnsupportedOperationException();
}

@Override
public Val enumValue(String enumName) {
JacksonEnumValue enumVal = enumValues.get(enumName);
if (enumVal == null) {
return newErr("unknown enum name '%s'", enumName);
}
return enumVal.ordinalValue();
}

@Override
public Val findIdent(String identName) {
JacksonTypeDescription td = knownTypesByName.get(identName);
if (td != null) {
return td.type();
}

JacksonEnumValue enumVal = enumValues.get(identName);
if (enumVal != null) {
return enumVal.ordinalValue();
}
return null;
}

@Override
public com.google.api.expr.v1alpha1.Type findType(String typeName) {
JacksonTypeDescription td = knownTypesByName.get(typeName);
if (td == null) {
return null;
}
return td.pbType();
}

@Override
public FieldType findFieldType(String messageType, String fieldName) {
JacksonTypeDescription td = knownTypesByName.get(messageType);
if (td == null) {
return null;
}
return td.fieldType(fieldName);
}

@Override
public Val newValue(String typeName, Map<String, Val> fields) {
throw new UnsupportedOperationException();
}

@Override
public Val nativeToValue(Object value) {
if (value instanceof Val) {
return (Val) value;
}
Val maybe = TypeAdapterSupport.maybeNativeToValue(this, value);
if (maybe != null) {
return maybe;
}

if (value instanceof Enum) {
String fq = JacksonEnumValue.fullyQualifiedName((Enum<?>) value);
JacksonEnumValue v = enumValues.get(fq);
if (v == null) {
return newErr("unknown enum name '%s'", fq);
}
return v.ordinalValue();
}

try {
return JacksonObjectT.newObject(this, value, typeDescription(value.getClass()));
} catch (Exception e) {
throw new RuntimeException("oops", e);
}
}

JacksonEnumDescription enumDescription(Class<?> clazz) {
if (!Enum.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException("only enum allowed here");
}

JacksonEnumDescription ed = enumMap.get(clazz);
if (ed != null) {
return ed;
}
ed = computeEnumDescription(clazz);
enumMap.put(clazz, ed);
return ed;
}

private JacksonEnumDescription computeEnumDescription(Class<?> clazz) {
ValueSerializer<?> ser = serializationContextExt.findValueSerializer(clazz);
JavaType javaType = typeFactory.constructType(clazz);

JacksonEnumDescription enumDesc = new JacksonEnumDescription(javaType, (EnumSerializer) ser);
enumMap.put(clazz, enumDesc);

enumDesc.buildValues().forEach(v -> enumValues.put(v.fullyQualifiedName(), v));

return enumDesc;
}

JacksonTypeDescription typeDescription(Class<?> clazz) {
if (Enum.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException("enum not allowed here");
}

JacksonTypeDescription td = knownTypes.get(clazz);
if (td != null) {
return td;
}
td = computeTypeDescription(clazz);
knownTypes.put(clazz, td);
return td;
}

private JacksonTypeDescription computeTypeDescription(Class<?> clazz) {
ValueSerializer<Object> ser = serializationContextExt.findValueSerializer(clazz);
JavaType javaType = typeFactory.constructType(clazz);

JacksonTypeDescription typeDesc = new JacksonTypeDescription(javaType, ser, this::typeQuery);
knownTypesByName.put(clazz.getName(), typeDesc);

return typeDesc;
}

private com.google.api.expr.v1alpha1.Type typeQuery(JavaType javaType) {
if (javaType.isEnumType()) {
return enumDescription(javaType.getRawClass()).pbType();
}
return typeDescription(javaType.getRawClass()).pbType();
}
}
Loading