Skip to content

Commit cdda0e6

Browse files
Mitigation for incompatible victools version (#639)
Fix #624
1 parent b96d02f commit cdda0e6

4 files changed

Lines changed: 131 additions & 80 deletions

File tree

sdk-serde-jackson/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ dependencies {
1212

1313
implementation(project(":common"))
1414

15+
implementation(libs.log4j.api)
16+
1517
api(libs.jackson.databind)
1618
implementation(libs.jackson.jsr310)
1719
implementation(libs.jackson.core)

sdk-serde-jackson/src/main/java/dev/restate/sdk/serde/jackson/JacksonSerdes.java

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
import dev.restate.serde.jackson.JacksonSerdeFactory;
2121
import java.io.IOException;
2222
import java.util.stream.StreamSupport;
23+
import org.apache.logging.log4j.LogManager;
24+
import org.apache.logging.log4j.Logger;
2325
import org.jspecify.annotations.NonNull;
26+
import org.jspecify.annotations.Nullable;
2427

2528
/**
2629
* @deprecated This will be removed in the next release, please check the individual methods for
@@ -31,52 +34,68 @@ public final class JacksonSerdes {
3134

3235
private JacksonSerdes() {}
3336

37+
private static final Logger LOG = LogManager.getLogger(JacksonSerdes.class);
38+
3439
private static final ObjectMapper defaultMapper;
35-
private static final SchemaGenerator schemaGenerator;
40+
41+
private static final @Nullable SchemaGenerator schemaGenerator;
3642

3743
static {
3844
defaultMapper = new ObjectMapper();
3945
// Find modules through SPI (e.g. jackson-datatype-jsr310)
4046
defaultMapper.findAndRegisterModules();
4147

42-
JacksonModule module =
43-
new JacksonModule(
44-
JacksonOption.RESPECT_JSONPROPERTY_REQUIRED, JacksonOption.INLINE_TRANSFORMED_SUBTYPES);
45-
SchemaGeneratorConfigBuilder configBuilder =
46-
new SchemaGeneratorConfigBuilder(
47-
defaultMapper, SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON)
48-
.with(module);
49-
50-
// Make sure we use `title` for types
51-
configBuilder
52-
.forTypesInGeneral()
53-
.withTypeAttributeOverride(
54-
(schema, scope, context) -> {
55-
if (schema.isObject()
56-
&& !schema.hasNonNull(
57-
SchemaKeyword.TAG_TITLE.forVersion(
58-
context.getGeneratorConfig().getSchemaVersion()))) {
59-
JsonNode typeKeyword =
60-
schema.get(
61-
SchemaKeyword.TAG_TYPE.forVersion(
62-
context.getGeneratorConfig().getSchemaVersion()));
63-
boolean isObjectSchema =
64-
typeKeyword != null
65-
&& ((typeKeyword.isTextual() && "object".equals(typeKeyword.textValue()))
66-
|| (typeKeyword.isArray()
67-
&& StreamSupport.stream(typeKeyword.spliterator(), false)
68-
.anyMatch(
69-
el -> el.isTextual() && "object".equals(el.textValue()))));
70-
if (isObjectSchema) {
71-
schema.put(
72-
SchemaKeyword.TAG_TITLE.forVersion(
73-
context.getGeneratorConfig().getSchemaVersion()),
74-
scope.getSimpleTypeDescription());
75-
}
76-
}
77-
});
48+
schemaGenerator = buildSchemaGenerator(defaultMapper);
49+
}
7850

79-
schemaGenerator = new SchemaGenerator(configBuilder.build());
51+
private static @Nullable SchemaGenerator buildSchemaGenerator(ObjectMapper mapper) {
52+
try {
53+
JacksonModule module =
54+
new JacksonModule(
55+
JacksonOption.RESPECT_JSONPROPERTY_REQUIRED,
56+
JacksonOption.INLINE_TRANSFORMED_SUBTYPES);
57+
SchemaGeneratorConfigBuilder configBuilder =
58+
new SchemaGeneratorConfigBuilder(
59+
mapper, SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON)
60+
.with(module);
61+
62+
// Make sure we use `title` for types
63+
configBuilder
64+
.forTypesInGeneral()
65+
.withTypeAttributeOverride(
66+
(schema, scope, context) -> {
67+
if (schema.isObject()
68+
&& !schema.hasNonNull(
69+
SchemaKeyword.TAG_TITLE.forVersion(
70+
context.getGeneratorConfig().getSchemaVersion()))) {
71+
JsonNode typeKeyword =
72+
schema.get(
73+
SchemaKeyword.TAG_TYPE.forVersion(
74+
context.getGeneratorConfig().getSchemaVersion()));
75+
boolean isObjectSchema =
76+
typeKeyword != null
77+
&& ((typeKeyword.isTextual() && "object".equals(typeKeyword.textValue()))
78+
|| (typeKeyword.isArray()
79+
&& StreamSupport.stream(typeKeyword.spliterator(), false)
80+
.anyMatch(
81+
el ->
82+
el.isTextual() && "object".equals(el.textValue()))));
83+
if (isObjectSchema) {
84+
schema.put(
85+
SchemaKeyword.TAG_TITLE.forVersion(
86+
context.getGeneratorConfig().getSchemaVersion()),
87+
scope.getSimpleTypeDescription());
88+
}
89+
}
90+
});
91+
92+
return new SchemaGenerator(configBuilder.build());
93+
} catch (LinkageError t) {
94+
LOG.warn(
95+
"Cannot initialize the Jackson JSON Schema generator due to an incompatible version of com.github.victools:jsonschema-generator.",
96+
t);
97+
return null;
98+
}
8099
}
81100

82101
/**
@@ -99,7 +118,12 @@ public static <T> Serde<T> of(Class<T> clazz) {
99118
public static <T> Serde<T> of(ObjectMapper mapper, Class<T> clazz) {
100119
return new Serde<>() {
101120
@Override
102-
public Schema jsonSchema() {
121+
public @Nullable Schema jsonSchema() {
122+
// schemaGenerator is null when an incompatible victools version is on the classpath,
123+
// see #buildSchemaGenerator.
124+
if (schemaGenerator == null) {
125+
return null;
126+
}
103127
return new JsonSchema(schemaGenerator.generateSchema(clazz));
104128
}
105129

sdk-serde-jackson/src/main/java/dev/restate/serde/jackson/JacksonSerdeFactory.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.IOException;
2323
import java.lang.reflect.Type;
2424
import org.jspecify.annotations.NonNull;
25+
import org.jspecify.annotations.Nullable;
2526

2627
/**
2728
* This class implements {@link SerdeFactory} using Jackson's {@link ObjectMapper}.
@@ -35,7 +36,7 @@ public class JacksonSerdeFactory implements SerdeFactory {
3536
public static final JacksonSerdeFactory DEFAULT = new JacksonSerdeFactory();
3637

3738
private final ObjectMapper mapper;
38-
private final SchemaGenerator schemaGenerator;
39+
private final @Nullable SchemaGenerator schemaGenerator;
3940

4041
public JacksonSerdeFactory() {
4142
this(JacksonSerdes.defaultMapper);
@@ -45,7 +46,7 @@ public JacksonSerdeFactory(ObjectMapper mapper) {
4546
this(mapper, JacksonSerdes.schemaGenerator);
4647
}
4748

48-
public JacksonSerdeFactory(ObjectMapper mapper, SchemaGenerator schemaGenerator) {
49+
public JacksonSerdeFactory(ObjectMapper mapper, @Nullable SchemaGenerator schemaGenerator) {
4950
this.mapper = mapper;
5051
this.schemaGenerator = schemaGenerator;
5152
}
@@ -64,11 +65,16 @@ public <T> Serde<T> create(Class<T> clazz) {
6465
static <T> Serde<T> create(
6566
JavaType constructedType,
6667
Type originalType,
67-
SchemaGenerator schemaGenerator,
68+
@Nullable SchemaGenerator schemaGenerator,
6869
ObjectMapper mapper) {
6970
return new Serde<>() {
7071
@Override
71-
public Schema jsonSchema() {
72+
public @Nullable Schema jsonSchema() {
73+
// schemaGenerator is null when an incompatible victools version is on the classpath,
74+
// see JacksonSerdes#buildSchemaGenerator.
75+
if (schemaGenerator == null) {
76+
return null;
77+
}
7278
return new Serde.JsonSchema(schemaGenerator.generateSchema(originalType));
7379
}
7480

sdk-serde-jackson/src/main/java/dev/restate/serde/jackson/JacksonSerdes.java

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import com.github.victools.jsonschema.module.jackson.JacksonOption;
1717
import dev.restate.serde.Serde;
1818
import java.util.stream.StreamSupport;
19+
import org.apache.logging.log4j.LogManager;
20+
import org.apache.logging.log4j.Logger;
21+
import org.jspecify.annotations.Nullable;
1922

2023
/**
2124
* {@link Serde} implementations for Jackson.
@@ -42,52 +45,68 @@ public final class JacksonSerdes {
4245

4346
private JacksonSerdes() {}
4447

48+
private static final Logger LOG = LogManager.getLogger(JacksonSerdes.class);
49+
4550
static final ObjectMapper defaultMapper;
46-
static final SchemaGenerator schemaGenerator;
51+
52+
static final @Nullable SchemaGenerator schemaGenerator;
4753

4854
static {
4955
defaultMapper = new ObjectMapper();
5056
// Find modules through SPI (e.g. jackson-datatype-jsr310)
5157
defaultMapper.findAndRegisterModules();
5258

53-
JacksonModule module =
54-
new JacksonModule(
55-
JacksonOption.RESPECT_JSONPROPERTY_REQUIRED, JacksonOption.INLINE_TRANSFORMED_SUBTYPES);
56-
SchemaGeneratorConfigBuilder configBuilder =
57-
new SchemaGeneratorConfigBuilder(
58-
defaultMapper, SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON)
59-
.with(module);
60-
61-
// Make sure we use `title` for types
62-
configBuilder
63-
.forTypesInGeneral()
64-
.withTypeAttributeOverride(
65-
(schema, scope, context) -> {
66-
if (schema.isObject()
67-
&& !schema.hasNonNull(
68-
SchemaKeyword.TAG_TITLE.forVersion(
69-
context.getGeneratorConfig().getSchemaVersion()))) {
70-
JsonNode typeKeyword =
71-
schema.get(
72-
SchemaKeyword.TAG_TYPE.forVersion(
73-
context.getGeneratorConfig().getSchemaVersion()));
74-
boolean isObjectSchema =
75-
typeKeyword != null
76-
&& ((typeKeyword.isTextual() && "object".equals(typeKeyword.textValue()))
77-
|| (typeKeyword.isArray()
78-
&& StreamSupport.stream(typeKeyword.spliterator(), false)
79-
.anyMatch(
80-
el -> el.isTextual() && "object".equals(el.textValue()))));
81-
if (isObjectSchema) {
82-
schema.put(
83-
SchemaKeyword.TAG_TITLE.forVersion(
84-
context.getGeneratorConfig().getSchemaVersion()),
85-
scope.getSimpleTypeDescription());
59+
schemaGenerator = buildSchemaGenerator(defaultMapper);
60+
}
61+
62+
private static @Nullable SchemaGenerator buildSchemaGenerator(ObjectMapper mapper) {
63+
try {
64+
JacksonModule module =
65+
new JacksonModule(
66+
JacksonOption.RESPECT_JSONPROPERTY_REQUIRED,
67+
JacksonOption.INLINE_TRANSFORMED_SUBTYPES);
68+
SchemaGeneratorConfigBuilder configBuilder =
69+
new SchemaGeneratorConfigBuilder(
70+
mapper, SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON)
71+
.with(module);
72+
73+
// Make sure we use `title` for types
74+
configBuilder
75+
.forTypesInGeneral()
76+
.withTypeAttributeOverride(
77+
(schema, scope, context) -> {
78+
if (schema.isObject()
79+
&& !schema.hasNonNull(
80+
SchemaKeyword.TAG_TITLE.forVersion(
81+
context.getGeneratorConfig().getSchemaVersion()))) {
82+
JsonNode typeKeyword =
83+
schema.get(
84+
SchemaKeyword.TAG_TYPE.forVersion(
85+
context.getGeneratorConfig().getSchemaVersion()));
86+
boolean isObjectSchema =
87+
typeKeyword != null
88+
&& ((typeKeyword.isTextual() && "object".equals(typeKeyword.textValue()))
89+
|| (typeKeyword.isArray()
90+
&& StreamSupport.stream(typeKeyword.spliterator(), false)
91+
.anyMatch(
92+
el ->
93+
el.isTextual() && "object".equals(el.textValue()))));
94+
if (isObjectSchema) {
95+
schema.put(
96+
SchemaKeyword.TAG_TITLE.forVersion(
97+
context.getGeneratorConfig().getSchemaVersion()),
98+
scope.getSimpleTypeDescription());
99+
}
86100
}
87-
}
88-
});
101+
});
89102

90-
schemaGenerator = new SchemaGenerator(configBuilder.build());
103+
return new SchemaGenerator(configBuilder.build());
104+
} catch (LinkageError t) {
105+
LOG.warn(
106+
"Cannot initialize the Jackson JSON Schema generator due to an incompatible version of com.github.victools:jsonschema-generator.",
107+
t);
108+
return null;
109+
}
91110
}
92111

93112
/** Serialize/Deserialize class using the default object mapper. */

0 commit comments

Comments
 (0)