Skip to content

Commit a92ca7d

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 a92ca7d

30 files changed

+1937
-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: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
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 =forMapper.createContext(objectMapper.serializationConfig(), GeneratorSettings.empty());
65+
this.typeFactory = objectMapper.getTypeFactory();
66+
}
67+
68+
public static TypeRegistry newRegistry() {
69+
return new Jackson3Registry();
70+
}
71+
72+
@Override
73+
public TypeRegistry copy() {
74+
return this;
75+
}
76+
77+
@Override
78+
public void register(Object t) {
79+
Class<?> cls = t instanceof Class ? (Class<?>) t : t.getClass();
80+
typeDescription(cls);
81+
}
82+
83+
@Override
84+
public void registerType(Type... types) {
85+
throw new UnsupportedOperationException();
86+
}
87+
88+
@Override
89+
public Val enumValue(String enumName) {
90+
JacksonEnumValue enumVal = enumValues.get(enumName);
91+
if (enumVal == null) {
92+
return newErr("unknown enum name '%s'", enumName);
93+
}
94+
return enumVal.ordinalValue();
95+
}
96+
97+
@Override
98+
public Val findIdent(String identName) {
99+
JacksonTypeDescription td = knownTypesByName.get(identName);
100+
if (td != null) {
101+
return td.type();
102+
}
103+
104+
JacksonEnumValue enumVal = enumValues.get(identName);
105+
if (enumVal != null) {
106+
return enumVal.ordinalValue();
107+
}
108+
return null;
109+
}
110+
111+
@Override
112+
public com.google.api.expr.v1alpha1.Type findType(String typeName) {
113+
JacksonTypeDescription td = knownTypesByName.get(typeName);
114+
if (td == null) {
115+
return null;
116+
}
117+
return td.pbType();
118+
}
119+
120+
@Override
121+
public FieldType findFieldType(String messageType, String fieldName) {
122+
JacksonTypeDescription td = knownTypesByName.get(messageType);
123+
if (td == null) {
124+
return null;
125+
}
126+
return td.fieldType(fieldName);
127+
}
128+
129+
@Override
130+
public Val newValue(String typeName, Map<String, Val> fields) {
131+
throw new UnsupportedOperationException();
132+
}
133+
134+
@Override
135+
public Val nativeToValue(Object value) {
136+
if (value instanceof Val) {
137+
return (Val) value;
138+
}
139+
Val maybe = TypeAdapterSupport.maybeNativeToValue(this, value);
140+
if (maybe != null) {
141+
return maybe;
142+
}
143+
144+
if (value instanceof Enum) {
145+
String fq = JacksonEnumValue.fullyQualifiedName((Enum<?>) value);
146+
JacksonEnumValue v = enumValues.get(fq);
147+
if (v == null) {
148+
return newErr("unknown enum name '%s'", fq);
149+
}
150+
return v.ordinalValue();
151+
}
152+
153+
try {
154+
return JacksonObjectT.newObject(this, value, typeDescription(value.getClass()));
155+
} catch (Exception e) {
156+
throw new RuntimeException("oops", e);
157+
}
158+
}
159+
160+
JacksonEnumDescription enumDescription(Class<?> clazz) {
161+
if (!Enum.class.isAssignableFrom(clazz)) {
162+
throw new IllegalArgumentException("only enum allowed here");
163+
}
164+
165+
JacksonEnumDescription ed = enumMap.get(clazz);
166+
if (ed != null) {
167+
return ed;
168+
}
169+
ed = computeEnumDescription(clazz);
170+
enumMap.put(clazz, ed);
171+
return ed;
172+
}
173+
174+
private JacksonEnumDescription computeEnumDescription(Class<?> clazz) {
175+
ValueSerializer<?> ser = serializationContextExt.findValueSerializer(clazz);
176+
JavaType javaType = typeFactory.constructType(clazz);
177+
178+
JacksonEnumDescription enumDesc = new JacksonEnumDescription(javaType, (EnumSerializer) ser);
179+
enumMap.put(clazz, enumDesc);
180+
181+
enumDesc.buildValues().forEach(v -> enumValues.put(v.fullyQualifiedName(), v));
182+
183+
return enumDesc;
184+
}
185+
186+
JacksonTypeDescription typeDescription(Class<?> clazz) {
187+
if (Enum.class.isAssignableFrom(clazz)) {
188+
throw new IllegalArgumentException("enum not allowed here");
189+
}
190+
191+
JacksonTypeDescription td = knownTypes.get(clazz);
192+
if (td != null) {
193+
return td;
194+
}
195+
td = computeTypeDescription(clazz);
196+
knownTypes.put(clazz, td);
197+
return td;
198+
}
199+
200+
private JacksonTypeDescription computeTypeDescription(Class<?> clazz) {
201+
ValueSerializer<Object> ser = serializationContextExt.findValueSerializer(clazz);
202+
JavaType javaType = typeFactory.constructType(clazz);
203+
204+
JacksonTypeDescription typeDesc = new JacksonTypeDescription(javaType, ser, this::typeQuery);
205+
knownTypesByName.put(clazz.getName(), typeDesc);
206+
207+
return typeDesc;
208+
}
209+
210+
private com.google.api.expr.v1alpha1.Type typeQuery(JavaType javaType) {
211+
if (javaType.isEnumType()) {
212+
return enumDescription(javaType.getRawClass()).pbType();
213+
}
214+
return typeDescription(javaType.getRawClass()).pbType();
215+
}
216+
}

0 commit comments

Comments
 (0)