Skip to content

Commit 33afdbe

Browse files
committed
[Fix #461] alternative approach
Signed-off-by: Francisco Javier Tirado Sarti <ftirados@redhat.com>
1 parent c72cfe5 commit 33afdbe

File tree

6 files changed

+161
-61
lines changed

6 files changed

+161
-61
lines changed

api/src/main/java/io/serverlessworkflow/api/OneOfValueProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
*/
1616
package io.serverlessworkflow.api;
1717

18-
public interface OneOfValueProvider<T> {
19-
T get();
18+
public interface OneOfValueProvider {
19+
Object get();
2020
}

api/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,67 @@
2121
import com.fasterxml.jackson.databind.JsonMappingException;
2222
import jakarta.validation.ConstraintViolationException;
2323
import java.io.IOException;
24+
import java.lang.reflect.Method;
25+
import java.util.ArrayList;
2426
import java.util.Collection;
2527

2628
public class DeserializeHelper {
2729

2830
public static <T> T deserializeOneOf(
29-
JsonParser p, Class<T> targetClass, Collection<Class<?>> unionTypes) throws IOException {
31+
JsonParser p,
32+
Class<T> targetClass,
33+
Collection<Class<?>> oneOfTypes,
34+
Collection<Class<?>> allOfTypes)
35+
throws IOException {
3036
TreeNode node = p.readValueAsTree();
31-
JsonProcessingException ex =
32-
new JsonMappingException(p, "Problem deserializing " + targetClass);
33-
for (Class<?> unionType : unionTypes) {
34-
try {
35-
Object object = p.getCodec().treeToValue(node, unionType);
36-
return targetClass.getConstructor(unionType).newInstance(object);
37-
} catch (IOException | ReflectiveOperationException | ConstraintViolationException io) {
38-
ex.addSuppressed(io);
37+
38+
try {
39+
T result = targetClass.getDeclaredConstructor().newInstance();
40+
41+
Collection<Exception> exceptions = new ArrayList<>();
42+
for (Class<?> oneOfType : oneOfTypes) {
43+
try {
44+
getOf(p, result, node, targetClass, oneOfType);
45+
break;
46+
} catch (IOException | ConstraintViolationException | ReflectiveOperationException ex) {
47+
exceptions.add(ex);
48+
}
49+
}
50+
if (exceptions.size() == oneOfTypes.size()) {
51+
JsonMappingException ex =
52+
new JsonMappingException(p, "Error deserializing oneOf, all alternatives failed");
53+
exceptions.forEach(ex::addSuppressed);
54+
throw ex;
55+
}
56+
57+
for (Class<?> allOfType : allOfTypes) {
58+
try {
59+
getOf(p, result, node, targetClass, allOfType);
60+
} catch (IOException | ConstraintViolationException ex) {
61+
throw new JsonMappingException(p, "Error deserializing allOf", ex);
62+
}
63+
}
64+
65+
return result;
66+
} catch (ReflectiveOperationException ex) {
67+
throw new IllegalStateException(ex);
68+
}
69+
}
70+
71+
private static <T> void getOf(
72+
JsonParser p, T result, TreeNode node, Class<T> targetClass, Class<?> type)
73+
throws JsonProcessingException, ReflectiveOperationException {
74+
findSetMethod(targetClass, type).invoke(result, p.getCodec().treeToValue(node, type));
75+
}
76+
77+
private static Method findSetMethod(Class<?> targetClass, Class<?> type) {
78+
for (Method method : targetClass.getMethods()) {
79+
OneOfSetter oneOfSetter = method.getAnnotation(OneOfSetter.class);
80+
if (oneOfSetter != null && type.equals(oneOfSetter.value())) {
81+
return method;
3982
}
4083
}
41-
throw ex;
84+
throw new IllegalStateException("Cannot find a setter for type " + type);
4285
}
4386

4487
public static <T> T deserializeItem(JsonParser p, Class<T> targetClass, Class<?> valueClass)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
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 io.serverlessworkflow.serialization;
17+
18+
import static java.lang.annotation.ElementType.METHOD;
19+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
20+
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.Target;
23+
24+
@Retention(RUNTIME)
25+
@Target(METHOD)
26+
public @interface OneOfSetter {
27+
Class<?> value();
28+
}

custom-generator/src/main/java/io/serverlessworkflow/generator/AllAnyOneOfSchemaRule.java

Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -136,21 +136,25 @@ public JType apply(
136136
Schema schema) {
137137

138138
Optional<JType> refType = refType(nodeName, schemaNode, parent, generatableType, schema);
139-
List<JTypeWrapper> unionTypes = new ArrayList<>();
139+
List<JTypeWrapper> oneOfTypes = new ArrayList<>();
140+
List<JTypeWrapper> allOfTypes = new ArrayList<>();
140141

141-
unionType("oneOf", nodeName, schemaNode, parent, generatableType, schema, unionTypes);
142-
unionType("anyOf", nodeName, schemaNode, parent, generatableType, schema, unionTypes);
143-
unionType("allOf", nodeName, schemaNode, parent, generatableType, schema, unionTypes);
142+
unionType("oneOf", nodeName, schemaNode, parent, generatableType, schema, oneOfTypes);
143+
unionType("anyOf", nodeName, schemaNode, parent, generatableType, schema, oneOfTypes);
144+
unionType("allOf", nodeName, schemaNode, parent, generatableType, schema, allOfTypes);
144145

145-
Collections.sort(unionTypes);
146+
Collections.sort(oneOfTypes);
146147

147148
JType javaType;
148149
if (schemaNode.has("enum")) {
149150
javaType =
150151
ruleFactory.getEnumRule().apply(nodeName, schemaNode, parent, generatableType, schema);
151-
} else if (!schemaNode.has("properties") && unionTypes.isEmpty() && refType.isPresent()) {
152+
} else if (!schemaNode.has("properties")
153+
&& oneOfTypes.isEmpty()
154+
&& allOfTypes.isEmpty()
155+
&& refType.isPresent()) {
152156
javaType = refType.get();
153-
} else if (!unionTypes.isEmpty()) {
157+
} else if (!oneOfTypes.isEmpty() || !allOfTypes.isEmpty()) {
154158
JPackage container = generatableType.getPackage();
155159
JType generatedType =
156160
ruleFactory.getTypeRule().apply(nodeName, schemaNode, parent, container, schema);
@@ -166,15 +170,17 @@ public JType apply(
166170
unionClass = container._class(clazz.name() + "Union");
167171
commonType = Optional.of(clazz);
168172
}
169-
170173
} else {
171174
unionClass =
172175
container._class(
173176
ruleFactory.getNameHelper().getUniqueClassName(nodeName, schemaNode, container));
174177
commonType = Optional.empty();
175178
}
176179
javaType =
177-
populateRef(populateClass(schema, unionClass, commonType, unionTypes), refType, schema);
180+
populateRef(
181+
populateClass(schema, unionClass, commonType, oneOfTypes, allOfTypes),
182+
refType,
183+
schema);
178184
schema.setJavaTypeIfEmpty(javaType);
179185
} catch (JClassAlreadyExistsException ex) {
180186
throw new IllegalStateException(ex);
@@ -197,36 +203,37 @@ private JDefinedClass populateClass(
197203
Schema parentSchema,
198204
JDefinedClass definedClass,
199205
Optional<JType> commonType,
200-
Collection<JTypeWrapper> unionTypes) {
206+
Collection<JTypeWrapper> oneOfTypes,
207+
Collection<JTypeWrapper> allOfTypes) {
201208
JFieldVar valueField =
202209
definedClass.field(
203210
JMod.PRIVATE,
204-
commonType.orElse(definedClass.owner().ref(Object.class)),
211+
definedClass.owner().ref(Object.class),
205212
ruleFactory.getNameHelper().getPropertyName("value", null),
206213
null);
207214

208-
definedClass._implements(
209-
definedClass
210-
.owner()
211-
.ref(GeneratorUtils.ONE_OF_VALUE_PROVIDER_INTERFACE_NAME)
212-
.narrow(valueField.type()));
213-
214-
GeneratorUtils.implementInterface(definedClass, valueField);
215-
216-
try {
217-
JDefinedClass serializer = generateSerializer(definedClass);
218-
definedClass.annotate(JsonSerialize.class).param("using", serializer);
219-
} catch (JClassAlreadyExistsException ex) {
220-
// already serialized aware
215+
if (!oneOfTypes.isEmpty()) {
216+
definedClass._implements(
217+
definedClass.owner().ref(GeneratorUtils.ONE_OF_VALUE_PROVIDER_INTERFACE_NAME));
218+
GeneratorUtils.implementInterface(definedClass, valueField);
219+
try {
220+
JDefinedClass serializer = generateSerializer(definedClass);
221+
definedClass.annotate(JsonSerialize.class).param("using", serializer);
222+
} catch (JClassAlreadyExistsException ex) {
223+
// already serialized aware
224+
}
221225
}
222226

223227
try {
224-
JDefinedClass deserializer = generateDeserializer(definedClass, unionTypes);
228+
JDefinedClass deserializer = generateDeserializer(definedClass, oneOfTypes, allOfTypes);
225229
definedClass.annotate(JsonDeserialize.class).param("using", deserializer);
226230
} catch (JClassAlreadyExistsException ex) {
227231
// already deserialized aware
228232
}
229233

234+
Collection<JTypeWrapper> unionTypes = new ArrayList<>();
235+
unionTypes.addAll(oneOfTypes);
236+
unionTypes.addAll(allOfTypes);
230237
Collection<JTypeWrapper> stringTypes = new ArrayList<>();
231238
for (JTypeWrapper unionType : unionTypes) {
232239
if (isStringType(unionType.getType())) {
@@ -291,40 +298,61 @@ private JDefinedClass generateSerializer(JDefinedClass relatedClass)
291298
}
292299

293300
private JDefinedClass generateDeserializer(
294-
JDefinedClass relatedClass, Collection<JTypeWrapper> unionTypes)
301+
JDefinedClass relatedClass,
302+
Collection<JTypeWrapper> oneOfTypes,
303+
Collection<JTypeWrapper> allOfTypes)
295304
throws JClassAlreadyExistsException {
296305
JDefinedClass definedClass = GeneratorUtils.deserializerClass(relatedClass);
297306
GeneratorUtils.fillDeserializer(
298307
definedClass,
299308
relatedClass,
300309
(method, parserParam) -> {
301310
JBlock body = method.body();
302-
JInvocation list = definedClass.owner().ref(List.class).staticInvoke("of");
303-
unionTypes.forEach(c -> list.arg(((JClass) c.getType()).dotclass()));
311+
304312
body._return(
305313
definedClass
306314
.owner()
307315
.ref(GeneratorUtils.DESERIALIZE_HELPER_NAME)
308316
.staticInvoke("deserializeOneOf")
309317
.arg(parserParam)
310318
.arg(relatedClass.dotclass())
311-
.arg(list));
319+
.arg(list(definedClass, oneOfTypes))
320+
.arg(list(definedClass, allOfTypes)));
312321
});
313322
return definedClass;
314323
}
315324

325+
private JInvocation list(JDefinedClass definedClass, Collection<JTypeWrapper> list) {
326+
JInvocation result = definedClass.owner().ref(List.class).staticInvoke("of");
327+
list.forEach(c -> result.arg(((JClass) c.getType()).dotclass()));
328+
return result;
329+
}
330+
316331
private void wrapIt(
317332
Schema parentSchema,
318333
JDefinedClass definedClass,
319334
Optional<JFieldVar> valueField,
320335
JType unionType,
321336
JsonNode node) {
322337
JFieldVar instanceField = getInstanceField(parentSchema, definedClass, unionType, node);
323-
JMethod constructor = definedClass.constructor(JMod.PUBLIC);
324-
JVar instanceParam = constructor.param(unionType, instanceField.name());
325-
JBlock body = constructor.body();
326-
valueField.ifPresent(v -> body.assign(JExpr._this().ref(v), instanceParam));
327-
body.assign(JExpr._this().ref(instanceField), instanceParam);
338+
JMethod method = getSetterMethod(definedClass, instanceField, node);
339+
JBlock body = method.body();
340+
JVar methodParam = method.param(instanceField.type(), instanceField.name());
341+
valueField.ifPresent(v -> body.assign(JExpr._this().ref(v), methodParam));
342+
body.assign(JExpr._this().ref(instanceField), methodParam);
343+
}
344+
345+
private JMethod getSetterMethod(
346+
JDefinedClass definedClass, JFieldVar instanceField, JsonNode node) {
347+
JMethod method =
348+
definedClass.method(
349+
JMod.PUBLIC,
350+
definedClass.owner().VOID,
351+
ruleFactory.getNameHelper().getSetterName(instanceField.name(), node));
352+
method
353+
.annotate(definedClass.owner().ref(GeneratorUtils.SETTER_ANNOTATION_NAME))
354+
.param("value", instanceField.type());
355+
return method;
328356
}
329357

330358
private void wrapStrings(
@@ -334,21 +362,20 @@ private void wrapStrings(
334362
Collection<JTypeWrapper> stringTypes) {
335363
Iterator<JTypeWrapper> iter = stringTypes.iterator();
336364
JTypeWrapper first = iter.next();
337-
JMethod constructor = definedClass.constructor(JMod.PUBLIC);
338-
339-
JBlock body = constructor.body();
340365
String pattern = pattern(first.getNode(), parentSchema);
341366
if (pattern == null && iter.hasNext()) {
342367
pattern = ".*";
343368
}
344369
JFieldVar instanceField =
345370
getInstanceField(parentSchema, definedClass, first.getType(), first.getNode());
346-
JVar instanceParam = constructor.param(first.type, instanceField.name());
347-
body.assign(JExpr._this().ref(valueField), instanceParam);
371+
JMethod setterMethod = getSetterMethod(definedClass, instanceField, first.getNode());
372+
JBlock body = setterMethod.body();
373+
JVar methodParam = setterMethod.param(instanceField.type(), instanceField.name());
374+
body.assign(JExpr._this().ref(valueField), methodParam);
348375
if (pattern != null) {
349376
JConditional condition =
350-
body._if(getPatternCondition(pattern, body, instanceField, instanceParam, definedClass));
351-
condition._then().assign(JExpr._this().ref(instanceField), instanceParam);
377+
body._if(getPatternCondition(pattern, body, instanceField, methodParam, definedClass));
378+
condition._then().assign(JExpr._this().ref(instanceField), methodParam);
352379
while (iter.hasNext()) {
353380
JTypeWrapper item = iter.next();
354381
instanceField =
@@ -359,8 +386,8 @@ private void wrapStrings(
359386
}
360387
condition =
361388
condition._elseif(
362-
getPatternCondition(pattern, body, instanceField, instanceParam, definedClass));
363-
condition._then().assign(JExpr._this().ref(instanceField), instanceParam);
389+
getPatternCondition(pattern, body, instanceField, methodParam, definedClass));
390+
condition._then().assign(JExpr._this().ref(instanceField), methodParam);
364391
}
365392
condition
366393
._else()
@@ -372,10 +399,10 @@ private void wrapStrings(
372399
.ref(String.class)
373400
.staticInvoke("format")
374401
.arg("%s does not match any pattern")
375-
.arg(instanceParam))
402+
.arg(methodParam))
376403
.arg(JExpr._null()));
377404
} else {
378-
body.assign(JExpr._this().ref(instanceField), instanceParam);
405+
body.assign(JExpr._this().ref(instanceField), methodParam);
379406
}
380407
}
381408

@@ -388,7 +415,7 @@ private JFieldVar getInstanceField(
388415
ruleFactory
389416
.getNameHelper()
390417
.getPropertyName(getTypeName(node, type, parentSchema), node));
391-
GeneratorUtils.buildMethod(
418+
GeneratorUtils.getterMethod(
392419
definedClass, instanceField, ruleFactory.getNameHelper(), instanceField.name());
393420
return instanceField;
394421
}

custom-generator/src/main/java/io/serverlessworkflow/generator/GeneratorUtils.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public class GeneratorUtils {
3838
"io.serverlessworkflow.serialization.DeserializeHelper";
3939
public static final String ONE_OF_VALUE_PROVIDER_INTERFACE_NAME =
4040
"io.serverlessworkflow.api.OneOfValueProvider";
41+
public static final String SETTER_ANNOTATION_NAME =
42+
"io.serverlessworkflow.serialization.OneOfSetter";
4143

4244
@FunctionalInterface
4345
public interface SerializerFiller {
@@ -66,7 +68,7 @@ public static JMethod implementInterface(JDefinedClass definedClass, JFieldVar v
6668
return method;
6769
}
6870

69-
public static JMethod buildMethod(
71+
public static JMethod getterMethod(
7072
JDefinedClass definedClass, JFieldVar instanceField, NameHelper nameHelper, String name) {
7173
JMethod method =
7274
definedClass.method(

custom-generator/src/main/java/io/serverlessworkflow/generator/UnevaluatedPropertiesRule.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private JDefinedClass addKeyValueFields(
7070
JType stringClass = jclass.owner()._ref(String.class);
7171
JFieldVar nameField =
7272
jclass.field(JMod.PRIVATE, stringClass, nameHelper.getPropertyName("name", null));
73-
JMethod nameMethod = GeneratorUtils.buildMethod(jclass, nameField, nameHelper, "name");
73+
JMethod nameMethod = GeneratorUtils.getterMethod(jclass, nameField, nameHelper, "name");
7474
JType propertyType;
7575
if (node != null && node.size() != 0) {
7676
String pathToAdditionalProperties;
@@ -98,7 +98,7 @@ private JDefinedClass addKeyValueFields(
9898
jclass.field(
9999
JMod.PRIVATE, propertyType, nameHelper.getPropertyName(propertyType.name(), null));
100100
JMethod valueMethod =
101-
GeneratorUtils.buildMethod(jclass, valueField, nameHelper, propertyType.name());
101+
GeneratorUtils.getterMethod(jclass, valueField, nameHelper, propertyType.name());
102102
jclass
103103
.annotate(JsonSerialize.class)
104104
.param("using", generateSerializer(jclass, nameMethod, valueMethod));

0 commit comments

Comments
 (0)