Skip to content

Commit 32ca0b5

Browse files
committed
Support Jackson 3 in addition to Jackson 2
The code is very much a copy of the code for Jackson 2, adapted in a few places to Jackson 3: * Adapt to Jackson 3 type renames, especially for the `SerializationProvider` -> `SerializationContext` rename * Adapt to the Jackson 3 mapper builder pattern and getting the required `SerializationContext` to inspect types in `Jackson3Registry`. The public API type `org.projectnessie.cel.types.jackson3.Jackson3Registry` is in a different package and uses a different type name, although it results in a repetition of the Jackson major version in the type name. The dependency-free `cel-standalone` artifact includes both Jackson 3 and Jackson 2 now.
1 parent f07420c commit 32ca0b5

30 files changed

+1941
-12
lines changed

README.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,19 +122,21 @@ public class MyClass {
122122

123123
### Jackson example
124124

125+
The following example refers to Jackson 3. Support for Jackson 2 is, see [below](#jackson-2-example).
126+
125127
It is also possible to use plain Java and Jackson objects as arguments by using the
126-
`org.projectnessie.cel.types.jackson.JacksonRegistry` in `org.projectnessie.cel:cel-jackson`.
128+
`org.projectnessie.cel.types.jackson3.Jackson3Registry` in `org.projectnessie.cel:cel-jackson3`.
127129

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

132134
public class MyClass {
133135
public Boolean evalWithJacksonObject(MyInput input, String checkName) {
134136
// Build the script factory
135137
ScriptHost scriptHost = ScriptHost.newBuilder()
136138
// IMPORTANT: use the Jackson registry
137-
.registry(JacksonRegistry.newRegistry())
139+
.registry(Jackson3Registry.newRegistry())
138140
.build();
139141

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

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

170172
```xml
@@ -183,7 +185,7 @@ addition to `cel-core` or `cel-tools`.
183185
<dependencies>
184186
<dependency>
185187
<groupId>org.projectnessie.cel</groupId>
186-
<artifactId>cel-jackson</artifactId>
188+
<artifactId>cel-jackson3</artifactId>
187189
</dependency>
188190
<dependency>
189191
<groupId>org.projectnessie.cel</groupId>
@@ -196,10 +198,17 @@ or Gradle project.
196198
dependencies {
197199
implementation(enforcedPlatform("org.projectnessie.cel:cel-bom:0.5.3"))
198200
implementation("org.projectnessie.cel:cel-tools")
199-
implementation("org.projectnessie.cel:cel-jackson")
201+
implementation("org.projectnessie.cel:cel-jackson3")
200202
}
201203
```
202204

205+
### Jackson 2 example
206+
207+
Support for Jackson 2 is similar to Jackson 3, with a few differences:
208+
209+
* Use `JacksonRegistry` from `org.projectnessie.cel.types.jackson.JacksonRegistry`
210+
* Use `org.projectnessie.cel:cel-jackson` dependency instead of `org.projectnessie.cel:cel-jackson3`
211+
203212
## Dependency-free artifact
204213

205214
The `org.projectnessie.cel:cel-standalone` contains everything from CEL-Java and has no dependencies.

gradle/libs.versions.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ guava = { module = "com.google.guava:guava", version = "33.5.0-jre" }
4848
idea-ext = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version = "1.3" }
4949
immutables-value-annotations = { module = "org.immutables:value-annotations", version.ref = "immutables" }
5050
immutables-value-processor = { module = "org.immutables:value-processor", version.ref = "immutables" }
51-
jackson-bom = { module = "com.fasterxml.jackson:jackson-bom", version = "2.20.1" }
51+
jackson2-bom = { module = "com.fasterxml.jackson:jackson-bom", version = "2.20.1" }
52+
jackson3-bom = { module = "tools.jackson:jackson-bom", version = "3.0.3" }
5253
jacoco-maven-plugin = { module = "org.jacoco:jacoco-maven-plugin", version.ref = "jacoco" }
5354
jandex-plugin = { module = "com.github.vlsi.gradle:jandex-plugin", version.ref = "jandexPlugin" }
5455
jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }

jackson/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ plugins {
2323
`cel-conventions`
2424
}
2525

26+
description = "CEL Jackson 2 support"
27+
2628
dependencies {
2729
api(project(":cel-core"))
2830

29-
implementation(platform(libs.jackson.bom))
31+
implementation(platform(libs.jackson2.bom))
3032
implementation("com.fasterxml.jackson.core:jackson-databind")
3133
implementation("com.fasterxml.jackson.core:jackson-core")
3234
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-protobuf")

jackson/src/main/java/org/projectnessie/cel/types/jackson/JacksonRegistry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import org.projectnessie.cel.common.types.ref.Val;
3434

3535
/**
36-
* CEL-Java {@link TypeRegistry} to use Jackson objects as input values for CEL scripts.
36+
* CEL-Java {@link TypeRegistry} to use Jackson 2 objects as input values for CEL scripts.
3737
*
3838
* <p>The implementation does not support the construction of Jackson objects in CEL expressions and
3939
* therefore returning Jackson objects from CEL expressions is not possible/implemented and results

jackson/src/test/java/org/projectnessie/cel/types/jackson/JacksonRegistryTest.java renamed to jackson/src/test/java/org/projectnessie/cel/types/jackson/Jackson2RegistryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
import org.projectnessie.cel.types.jackson.types.MetaTest;
4040
import org.projectnessie.cel.types.jackson.types.RefVariantB;
4141

42-
class JacksonRegistryTest {
42+
class Jackson2RegistryTest {
4343
@Test
4444
void nessieBranch() {
4545
TypeRegistry reg = JacksonRegistry.newRegistry();

jackson/src/test/java/org/projectnessie/cel/types/jackson/JacksonScriptHostTest.java renamed to jackson/src/test/java/org/projectnessie/cel/types/jackson/Jackson2ScriptHostTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import org.projectnessie.cel.types.jackson.types.MyPojo;
3232
import org.projectnessie.cel.types.jackson.types.ObjectListEnum;
3333

34-
public class JacksonScriptHostTest {
34+
public class Jackson2ScriptHostTest {
3535

3636
@Test
3737
void simple() throws Exception {

jackson/src/test/java/org/projectnessie/cel/types/jackson/JacksonTypeDescriptionTest.java renamed to jackson/src/test/java/org/projectnessie/cel/types/jackson/Jackson2TypeDescriptionTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
import org.projectnessie.cel.types.jackson.types.CollectionsObject;
5555
import org.projectnessie.cel.types.jackson.types.InnerType;
5656

57-
class JacksonTypeDescriptionTest {
57+
class Jackson2TypeDescriptionTest {
5858

5959
@Test
6060
void basics() {

jackson3/build.gradle.kts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (C) 2021 The Authors of CEL-Java
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
plugins {
18+
`java-library`
19+
`maven-publish`
20+
signing
21+
id("org.caffinitas.gradle.testsummary")
22+
id("org.caffinitas.gradle.testrerun")
23+
`cel-conventions`
24+
}
25+
26+
description = "CEL Jackson 3 support"
27+
28+
dependencies {
29+
api(project(":cel-core"))
30+
31+
implementation(platform(libs.jackson3.bom))
32+
implementation("tools.jackson.core:jackson-databind")
33+
implementation("tools.jackson.core:jackson-core")
34+
implementation("tools.jackson.dataformat:jackson-dataformat-protobuf")
35+
implementation("tools.jackson.dataformat:jackson-dataformat-yaml")
36+
37+
testImplementation(project(":cel-tools"))
38+
testAnnotationProcessor(libs.immutables.value.processor)
39+
testCompileOnly(libs.immutables.value.annotations)
40+
testImplementation(libs.findbugs.jsr305)
41+
42+
testImplementation(platform(libs.junit.bom))
43+
testImplementation(libs.bundles.junit.testing)
44+
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
45+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
46+
}
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
* Copyright (C) 2021 The Authors of CEL-Java
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.projectnessie.cel.types.jackson3;
17+
18+
import static org.projectnessie.cel.common.types.Err.newErr;
19+
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
import org.projectnessie.cel.common.types.ref.FieldType;
23+
import org.projectnessie.cel.common.types.ref.Type;
24+
import org.projectnessie.cel.common.types.ref.TypeAdapterSupport;
25+
import org.projectnessie.cel.common.types.ref.TypeRegistry;
26+
import org.projectnessie.cel.common.types.ref.Val;
27+
import tools.jackson.databind.JavaType;
28+
import tools.jackson.databind.ObjectMapper;
29+
import tools.jackson.databind.ValueSerializer;
30+
import tools.jackson.databind.cfg.GeneratorSettings;
31+
import tools.jackson.databind.cfg.SerializationContexts;
32+
import tools.jackson.databind.json.JsonMapper;
33+
import tools.jackson.databind.ser.SerializationContextExt;
34+
import tools.jackson.databind.ser.jdk.EnumSerializer;
35+
import tools.jackson.databind.type.TypeFactory;
36+
37+
/**
38+
* CEL-Java {@link TypeRegistry} to use Jackson 3 objects as input values for CEL scripts.
39+
*
40+
* <p>The implementation does not support the construction of Jackson objects in CEL expressions and
41+
* therefore returning Jackson objects from CEL expressions is not possible/implemented and results
42+
* in {@link UnsupportedOperationException}s.
43+
*/
44+
public final class Jackson3Registry implements TypeRegistry {
45+
final ObjectMapper objectMapper;
46+
private final SerializationContextExt serializationContextExt;
47+
private final TypeFactory typeFactory;
48+
private final Map<Class<?>, JacksonTypeDescription> knownTypes = new HashMap<>();
49+
private final Map<String, JacksonTypeDescription> knownTypesByName = new HashMap<>();
50+
51+
private final Map<Class<?>, JacksonEnumDescription> enumMap = new HashMap<>();
52+
private final Map<String, JacksonEnumValue> enumValues = new HashMap<>();
53+
54+
private Jackson3Registry() {
55+
JsonMapper.Builder b = JsonMapper.builder();
56+
SerializationContexts serializationContexts = b.serializationContexts();
57+
this.objectMapper = b.build();
58+
SerializationContexts forMapper =
59+
serializationContexts.forMapper(
60+
objectMapper,
61+
objectMapper.serializationConfig(),
62+
objectMapper.tokenStreamFactory(),
63+
b.serializerFactory());
64+
this.serializationContextExt =
65+
forMapper.createContext(objectMapper.serializationConfig(), GeneratorSettings.empty());
66+
this.typeFactory = objectMapper.getTypeFactory();
67+
}
68+
69+
public static TypeRegistry newRegistry() {
70+
return new Jackson3Registry();
71+
}
72+
73+
@Override
74+
public TypeRegistry copy() {
75+
return this;
76+
}
77+
78+
@Override
79+
public void register(Object t) {
80+
Class<?> cls = t instanceof Class ? (Class<?>) t : t.getClass();
81+
typeDescription(cls);
82+
}
83+
84+
@Override
85+
public void registerType(Type... types) {
86+
throw new UnsupportedOperationException();
87+
}
88+
89+
@Override
90+
public Val enumValue(String enumName) {
91+
JacksonEnumValue enumVal = enumValues.get(enumName);
92+
if (enumVal == null) {
93+
return newErr("unknown enum name '%s'", enumName);
94+
}
95+
return enumVal.ordinalValue();
96+
}
97+
98+
@Override
99+
public Val findIdent(String identName) {
100+
JacksonTypeDescription td = knownTypesByName.get(identName);
101+
if (td != null) {
102+
return td.type();
103+
}
104+
105+
JacksonEnumValue enumVal = enumValues.get(identName);
106+
if (enumVal != null) {
107+
return enumVal.ordinalValue();
108+
}
109+
return null;
110+
}
111+
112+
@Override
113+
public com.google.api.expr.v1alpha1.Type findType(String typeName) {
114+
JacksonTypeDescription td = knownTypesByName.get(typeName);
115+
if (td == null) {
116+
return null;
117+
}
118+
return td.pbType();
119+
}
120+
121+
@Override
122+
public FieldType findFieldType(String messageType, String fieldName) {
123+
JacksonTypeDescription td = knownTypesByName.get(messageType);
124+
if (td == null) {
125+
return null;
126+
}
127+
return td.fieldType(fieldName);
128+
}
129+
130+
@Override
131+
public Val newValue(String typeName, Map<String, Val> fields) {
132+
throw new UnsupportedOperationException();
133+
}
134+
135+
@Override
136+
public Val nativeToValue(Object value) {
137+
if (value instanceof Val) {
138+
return (Val) value;
139+
}
140+
Val maybe = TypeAdapterSupport.maybeNativeToValue(this, value);
141+
if (maybe != null) {
142+
return maybe;
143+
}
144+
145+
if (value instanceof Enum) {
146+
String fq = JacksonEnumValue.fullyQualifiedName((Enum<?>) value);
147+
JacksonEnumValue v = enumValues.get(fq);
148+
if (v == null) {
149+
return newErr("unknown enum name '%s'", fq);
150+
}
151+
return v.ordinalValue();
152+
}
153+
154+
try {
155+
return JacksonObjectT.newObject(this, value, typeDescription(value.getClass()));
156+
} catch (Exception e) {
157+
throw new RuntimeException("oops", e);
158+
}
159+
}
160+
161+
JacksonEnumDescription enumDescription(Class<?> clazz) {
162+
if (!Enum.class.isAssignableFrom(clazz)) {
163+
throw new IllegalArgumentException("only enum allowed here");
164+
}
165+
166+
JacksonEnumDescription ed = enumMap.get(clazz);
167+
if (ed != null) {
168+
return ed;
169+
}
170+
ed = computeEnumDescription(clazz);
171+
enumMap.put(clazz, ed);
172+
return ed;
173+
}
174+
175+
private JacksonEnumDescription computeEnumDescription(Class<?> clazz) {
176+
ValueSerializer<?> ser = serializationContextExt.findValueSerializer(clazz);
177+
JavaType javaType = typeFactory.constructType(clazz);
178+
179+
JacksonEnumDescription enumDesc = new JacksonEnumDescription(javaType, (EnumSerializer) ser);
180+
enumMap.put(clazz, enumDesc);
181+
182+
enumDesc.buildValues().forEach(v -> enumValues.put(v.fullyQualifiedName(), v));
183+
184+
return enumDesc;
185+
}
186+
187+
JacksonTypeDescription typeDescription(Class<?> clazz) {
188+
if (Enum.class.isAssignableFrom(clazz)) {
189+
throw new IllegalArgumentException("enum not allowed here");
190+
}
191+
192+
JacksonTypeDescription td = knownTypes.get(clazz);
193+
if (td != null) {
194+
return td;
195+
}
196+
td = computeTypeDescription(clazz);
197+
knownTypes.put(clazz, td);
198+
return td;
199+
}
200+
201+
private JacksonTypeDescription computeTypeDescription(Class<?> clazz) {
202+
ValueSerializer<Object> ser = serializationContextExt.findValueSerializer(clazz);
203+
JavaType javaType = typeFactory.constructType(clazz);
204+
205+
JacksonTypeDescription typeDesc = new JacksonTypeDescription(javaType, ser, this::typeQuery);
206+
knownTypesByName.put(clazz.getName(), typeDesc);
207+
208+
return typeDesc;
209+
}
210+
211+
private com.google.api.expr.v1alpha1.Type typeQuery(JavaType javaType) {
212+
if (javaType.isEnumType()) {
213+
return enumDescription(javaType.getRawClass()).pbType();
214+
}
215+
return typeDescription(javaType.getRawClass()).pbType();
216+
}
217+
}

0 commit comments

Comments
 (0)