Skip to content

Commit 315ea30

Browse files
authored
[Fix #1271] Improving CustomObjectMarshaller resolution (#1272)
Signed-off-by: fjtirado <ftirados@redhat.com>
1 parent ae47e0a commit 315ea30

7 files changed

Lines changed: 167 additions & 17 deletions

File tree

impl/core/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractInputBuffer.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020
import java.util.Collection;
2121
import java.util.LinkedHashMap;
2222
import java.util.Map;
23+
import java.util.concurrent.ConcurrentHashMap;
2324

2425
public abstract class AbstractInputBuffer implements WorkflowInputBuffer {
25-
2626
private final Collection<CustomObjectMarshaller> customMarshallers;
27+
private final Map<Class<?>, CustomObjectMarshaller> marshallerMap = new ConcurrentHashMap<>();
2728

2829
protected AbstractInputBuffer(Collection<CustomObjectMarshaller> customMarshallers) {
2930
this.customMarshallers = customMarshallers;
@@ -129,9 +130,12 @@ protected Class<?> loadClass(String className) throws ClassNotFoundException {
129130
return Class.forName(className);
130131
}
131132

133+
@SuppressWarnings("unchecked")
132134
protected Object readCustomObject() {
133135
Class<?> objectClass = readClass();
134-
return MarshallingUtils.getCustomMarshaller(customMarshallers, objectClass)
136+
return marshallerMap
137+
.computeIfAbsent(
138+
objectClass, o -> MarshallingUtils.getCustomMarshaller(customMarshallers, o))
135139
.read(this, objectClass);
136140
}
137141
}

impl/core/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractOutputBuffer.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
import java.time.Instant;
1919
import java.util.Collection;
2020
import java.util.Map;
21+
import java.util.concurrent.ConcurrentHashMap;
2122

2223
public abstract class AbstractOutputBuffer implements WorkflowOutputBuffer {
2324

2425
private final Collection<CustomObjectMarshaller> customMarshallers;
26+
private final Map<Class, CustomObjectMarshaller> marshallerMap = new ConcurrentHashMap<>();
2527

2628
protected AbstractOutputBuffer(Collection<CustomObjectMarshaller> customMarshallers) {
2729
this.customMarshallers = customMarshallers;
@@ -111,9 +113,11 @@ protected void writeClass(Class<?> objectClass) {
111113

112114
@SuppressWarnings({"rawtypes", "unchecked"})
113115
protected void writeCustomObject(Object object) {
116+
Class<?> objectClass = object.getClass();
114117
CustomObjectMarshaller marshaller =
115-
MarshallingUtils.getCustomMarshaller(customMarshallers, object.getClass());
116-
writeClass(object.getClass());
118+
marshallerMap.computeIfAbsent(
119+
objectClass, o -> MarshallingUtils.getCustomMarshaller(customMarshallers, o));
120+
writeClass(objectClass);
117121
marshaller.write(this, marshaller.getObjectClass().cast(object));
118122
}
119123

impl/core/src/main/java/io/serverlessworkflow/impl/marshaller/MarshallingUtils.java

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@
1818
import io.serverlessworkflow.impl.WorkflowModel;
1919
import java.io.ByteArrayInputStream;
2020
import java.io.ByteArrayOutputStream;
21+
import java.lang.reflect.Modifier;
2122
import java.time.Instant;
23+
import java.util.ArrayList;
2224
import java.util.Collection;
25+
import java.util.List;
2326
import java.util.function.BiConsumer;
2427
import java.util.function.Function;
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
2530

2631
public class MarshallingUtils {
2732

33+
private static final Logger logger = LoggerFactory.getLogger(MarshallingUtils.class);
34+
2835
private MarshallingUtils() {}
2936

3037
public static byte[] writeInstant(WorkflowBufferFactory factory, Instant instant) {
@@ -104,24 +111,75 @@ private static <T> T readValue(
104111
* class.
105112
*
106113
* @param marshallers Priority Sorted collection of marshalers available on classpath
107-
* @param clazz The class of the object being marshaled
114+
* @param objectClass The class of the object being marshaled
108115
* @return The most suitable marshaler for that object class
109116
* @throws IllegalArgumentException if no marshaler is found for that object class
110117
*/
111118
@SuppressWarnings({"rawtypes", "unchecked"})
112119
public static CustomObjectMarshaller getCustomMarshaller(
113-
Collection<CustomObjectMarshaller> marshallers, Class clazz) {
114-
CustomObjectMarshaller assignable = null;
120+
Collection<CustomObjectMarshaller> marshallers, Class objectClass) {
121+
List<CustomObjectMarshaller> assignables = new ArrayList<>();
122+
int minorClassDistance = Integer.MAX_VALUE;
123+
int minorPriority = Integer.MAX_VALUE;
115124
for (CustomObjectMarshaller marshaller : marshallers) {
116-
if (marshaller.getObjectClass().equals(clazz)) {
125+
Class marshallerClass = marshaller.getObjectClass();
126+
if (marshallerClass.equals(objectClass)) {
117127
return marshaller;
118-
} else if (marshaller.getObjectClass().isAssignableFrom(clazz) && assignable == null) {
119-
assignable = marshaller;
128+
} else if (marshallerClass.isAssignableFrom(objectClass)) {
129+
int classDistance = 0;
130+
Class childClass = objectClass;
131+
boolean found = false;
132+
do {
133+
classDistance++;
134+
for (Class<?> candidate : childClass.getInterfaces()) {
135+
if (candidate.equals(marshallerClass)) {
136+
found = true;
137+
}
138+
}
139+
if (!found) {
140+
childClass = childClass.getSuperclass();
141+
if (childClass == null) {
142+
break;
143+
} else {
144+
found = childClass.equals(marshallerClass);
145+
}
146+
}
147+
} while (!found && classDistance < minorClassDistance);
148+
if (found) {
149+
if (classDistance < minorClassDistance) {
150+
assignables.clear();
151+
assignables.add(marshaller);
152+
minorClassDistance = classDistance;
153+
minorPriority = marshaller.priority();
154+
} else if (classDistance == minorClassDistance
155+
&& marshaller.priority() == minorPriority) {
156+
assignables.add(marshaller);
157+
}
158+
}
120159
}
121160
}
122-
if (assignable == null) {
123-
throw new IllegalArgumentException("Cannot find proper marshaler for class " + clazz);
161+
if (assignables.isEmpty()) {
162+
throw new IllegalArgumentException("Cannot find proper marshaler for class " + objectClass);
163+
}
164+
return assignables.size() == 1 ? assignables.get(0) : chooseBetweenEquals(assignables);
165+
}
166+
167+
@SuppressWarnings("rawtypes")
168+
private static CustomObjectMarshaller chooseBetweenEquals(
169+
List<CustomObjectMarshaller> marshallers) {
170+
logger.debug("Several marshaling candidates with same priority {}", marshallers);
171+
for (CustomObjectMarshaller marshaller : marshallers) {
172+
Class marshallerClass = marshaller.getObjectClass();
173+
if (!Modifier.isAbstract(marshallerClass.getModifiers())
174+
&& !marshallerClass.equals(Object.class)) {
175+
return marshaller;
176+
}
177+
}
178+
for (CustomObjectMarshaller marshaller : marshallers) {
179+
if (Modifier.isAbstract(marshaller.getObjectClass().getModifiers())) {
180+
return marshaller;
181+
}
124182
}
125-
return assignable;
183+
return marshallers.get(0);
126184
}
127185
}

impl/core/src/test/java/io/serverlessworkflow/impl/marshaller/MarshallingUtilsTest.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
1919

20+
import java.io.Serializable;
2021
import java.util.List;
2122
import java.util.stream.Collectors;
2223
import java.util.stream.Stream;
@@ -34,22 +35,46 @@ void testCustomMarshallers() {
3435
Mockito.when(employeeMarshaller.priority()).thenReturn(2);
3536
Mockito.when(employeeMarshaller.getObjectClass()).thenReturn(Employee.class);
3637
CustomObjectMarshaller objectMarshaller = Mockito.spy(CustomObjectMarshaller.class);
37-
Mockito.when(objectMarshaller.priority()).thenReturn(3);
38+
Mockito.when(objectMarshaller.priority()).thenReturn(1);
3839
Mockito.when(objectMarshaller.getObjectClass()).thenReturn(Object.class);
40+
CustomObjectMarshaller ignoredMarshaller = Mockito.spy(CustomObjectMarshaller.class);
41+
Mockito.when(ignoredMarshaller.priority()).thenReturn(2);
42+
Mockito.when(ignoredMarshaller.getObjectClass()).thenReturn(Employee.class);
43+
CustomObjectMarshaller serializableMarshaller = Mockito.spy(CustomObjectMarshaller.class);
44+
Mockito.when(serializableMarshaller.priority()).thenReturn(1);
45+
Mockito.when(serializableMarshaller.getObjectClass()).thenReturn(Serializable.class);
46+
3947
Object employee = new Employee();
48+
Object student = new Student();
4049
Object person = new Person();
41-
Object other = new byte[2];
50+
Object serializable = new SerializableClass();
51+
Object other = new NonSerializableClass();
4252

4353
List<CustomObjectMarshaller> marshallers =
44-
Stream.of(objectMarshaller, employeeMarshaller, personMarshaller)
54+
Stream.of(
55+
objectMarshaller,
56+
employeeMarshaller,
57+
ignoredMarshaller,
58+
personMarshaller,
59+
serializableMarshaller)
4560
.sorted()
4661
.collect(Collectors.toList());
47-
assertThat(marshallers).containsExactly(personMarshaller, employeeMarshaller, objectMarshaller);
62+
assertThat(marshallers)
63+
.containsExactly(
64+
objectMarshaller,
65+
personMarshaller,
66+
serializableMarshaller,
67+
employeeMarshaller,
68+
ignoredMarshaller);
4869
assertThat(MarshallingUtils.getCustomMarshaller(marshallers, employee.getClass()))
4970
.isEqualTo(employeeMarshaller);
5071
assertThat(MarshallingUtils.getCustomMarshaller(marshallers, person.getClass()))
5172
.isEqualTo(personMarshaller);
5273
assertThat(MarshallingUtils.getCustomMarshaller(marshallers, other.getClass()))
5374
.isEqualTo(objectMarshaller);
75+
assertThat(MarshallingUtils.getCustomMarshaller(marshallers, student.getClass()))
76+
.isEqualTo(employeeMarshaller);
77+
assertThat(MarshallingUtils.getCustomMarshaller(marshallers, serializable.getClass()))
78+
.isEqualTo(serializableMarshaller);
5479
}
5580
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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.impl.marshaller;
17+
18+
class NonSerializableClass {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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.impl.marshaller;
17+
18+
import java.io.Serializable;
19+
20+
class SerializableClass implements Serializable {
21+
22+
private static final long serialVersionUID = 1L;
23+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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.impl.marshaller;
17+
18+
class Student extends Employee {}

0 commit comments

Comments
 (0)