Skip to content

Commit cf6ede6

Browse files
committed
GH-10083: Nullability for json & transformer/support
Related to: #10083 * Add Nullability to JSON components. * Ignore Nullability in deprecated JSON components via `@SuppressWarnings("NullAway")` on the class * Add Nullability to `transformer/support` which has led to the fix in the `XPathExpressionEvaluatingHeaderValueMessageProcessor` * Fix typos in Javadocs of the affected classes
1 parent 0bd825a commit cf6ede6

15 files changed

+83
-65
lines changed

spring-integration-core/src/main/java/org/springframework/integration/json/JacksonIndexAccessor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
* @author Jooyoung Pyoung
3737
*
3838
* @since 7.0
39+
*
3940
* @see JacksonPropertyAccessor
4041
*/
4142
public class JacksonIndexAccessor implements IndexAccessor {
@@ -55,9 +56,9 @@ public boolean canRead(EvaluationContext context, Object target, Object index) {
5556
@Override
5657
public TypedValue read(EvaluationContext context, Object target, Object index) throws AccessException {
5758
ArrayNode arrayNode = (ArrayNode) target;
58-
Integer intIndex = (Integer) index;
59+
int intIndex = (Integer) index;
5960
if (intIndex < 0) {
60-
// negative index: get from the end of array, for compatibility with JacksonPropertyAccessor.ArrayNodeAsList.
61+
// negative index: get from the end of an array, for compatibility with JacksonPropertyAccessor.ArrayNodeAsList.
6162
intIndex = arrayNode.size() + intIndex;
6263
}
6364
return JacksonPropertyAccessor.typedValue(arrayNode.get(intIndex));

spring-integration-core/src/main/java/org/springframework/integration/json/JacksonPropertyAccessor.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
* <p>Uses Jackson {@link JsonNode} API for nested properties access.
4040
*
4141
* @author Jooyoung Pyoung
42+
* @author Artem Bilan
4243
*
4344
* @since 7.0
4445
* @see JacksonIndexAccessor
@@ -70,7 +71,7 @@ public Class<?>[] getSpecificTargetClasses() {
7071
}
7172

7273
@Override
73-
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
74+
public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
7475
JsonNode node;
7576
try {
7677
node = asJson(target);
@@ -85,7 +86,7 @@ public boolean canRead(EvaluationContext context, Object target, String name) th
8586
return true;
8687
}
8788

88-
private JsonNode asJson(Object target) throws AccessException {
89+
private JsonNode asJson(@Nullable Object target) throws AccessException {
8990
if (target instanceof JsonNode jsonNode) {
9091
return jsonNode;
9192
}
@@ -108,7 +109,7 @@ else if (target instanceof String content) {
108109
/**
109110
* Return an integer if the String property name can be parsed as an int, or null otherwise.
110111
*/
111-
private static Integer maybeIndex(String name) {
112+
private static @Nullable Integer maybeIndex(String name) {
112113
if (!isNumeric(name)) {
113114
return null;
114115
}
@@ -133,12 +134,12 @@ public TypedValue read(EvaluationContext context, @Nullable Object target, Strin
133134
}
134135

135136
@Override
136-
public boolean canWrite(EvaluationContext context, Object target, String name) {
137+
public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) {
137138
return false;
138139
}
139140

140141
@Override
141-
public void write(EvaluationContext context, Object target, String name, Object newValue) {
142+
public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue) {
142143
throw new UnsupportedOperationException("Write is not supported");
143144
}
144145

@@ -158,7 +159,7 @@ private static boolean isNumeric(String str) {
158159
return true;
159160
}
160161

161-
static TypedValue typedValue(JsonNode json) throws AccessException {
162+
static TypedValue typedValue(@Nullable JsonNode json) throws AccessException {
162163
if (json == null || json instanceof NullNode) {
163164
return TypedValue.NULL;
164165
}
@@ -168,7 +169,7 @@ else if (json.isValueNode()) {
168169
return new TypedValue(wrap(json));
169170
}
170171

171-
private static Object getValue(JsonNode json) throws AccessException {
172+
private static @Nullable Object getValue(JsonNode json) throws AccessException {
172173
if (json.isString()) {
173174
return json.asString();
174175
}
@@ -193,7 +194,7 @@ else if (json.isBinary()) {
193194
throw new IllegalArgumentException("Json is not ValueNode.");
194195
}
195196

196-
public static Object wrap(JsonNode json) throws AccessException {
197+
public static @Nullable Object wrap(@Nullable JsonNode json) throws AccessException {
197198
if (json == null) {
198199
return null;
199200
}
@@ -262,8 +263,8 @@ public String toString() {
262263
}
263264

264265
@Override
265-
public Object get(int index) {
266-
// negative index - get from the end of list
266+
public @Nullable Object get(int index) {
267+
// negative index - get from the end of a list
267268
int i = index < 0 ? this.delegate.size() + index : index;
268269
try {
269270
return wrap(this.delegate.get(i));
@@ -291,7 +292,7 @@ public boolean hasNext() {
291292
}
292293

293294
@Override
294-
public Object next() {
295+
public @Nullable Object next() {
295296
try {
296297
return wrap(this.it.next());
297298
}

spring-integration-core/src/main/java/org/springframework/integration/json/JsonIndexAccessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
* @deprecated Since 7.0 in favor of {@link JacksonIndexAccessor} for Jackson 3.
4343
*/
4444
@Deprecated(forRemoval = true, since = "7.0")
45-
@SuppressWarnings("removal")
45+
@SuppressWarnings({ "removal", "NullAway"})
4646
public class JsonIndexAccessor implements IndexAccessor {
4747

4848
private static final Class<?>[] SUPPORTED_CLASSES = { ArrayNode.class };

spring-integration-core/src/main/java/org/springframework/integration/json/JsonPropertyAccessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@
4747
* @author Sam Brannen
4848
*
4949
* @since 3.0
50-
* @see JsonIndexAccessor
5150
* @deprecated Since 7.0 in favor of {@link JacksonPropertyAccessor} for Jackson 3.
5251
*/
5352
@Deprecated(forRemoval = true, since = "7.0")
53+
@SuppressWarnings("NullAway")
5454
public class JsonPropertyAccessor implements PropertyAccessor {
5555

5656
/**

spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,15 @@ public class JsonToObjectTransformer extends AbstractTransformer implements Bean
6464

6565
private final JsonObjectMapper<?, ?> jsonObjectMapper;
6666

67+
@SuppressWarnings("NullAway.Init")
6768
private ClassLoader classLoader;
6869

70+
@SuppressWarnings("NullAway") // Does not see Nullability on generics
6971
private Expression valueTypeExpression =
7072
new FunctionExpression<Message<?>>((message) ->
7173
obtainResolvableTypeFromHeadersIfAny(message.getHeaders(), this.classLoader));
7274

75+
@SuppressWarnings("NullAway.Init")
7376
private EvaluationContext evaluationContext;
7477

7578
public JsonToObjectTransformer() {
@@ -112,19 +115,19 @@ public JsonToObjectTransformer(ResolvableType targetType, @Nullable JsonObjectMa
112115
@Override
113116
public void setBeanClassLoader(ClassLoader classLoader) {
114117
this.classLoader = classLoader;
115-
if (this.jsonObjectMapper instanceof BeanClassLoaderAware) {
116-
((BeanClassLoaderAware) this.jsonObjectMapper).setBeanClassLoader(classLoader);
118+
if (this.jsonObjectMapper instanceof BeanClassLoaderAware beanClassLoaderAware) {
119+
beanClassLoaderAware.setBeanClassLoader(classLoader);
117120
}
118121
}
119122

120123
/**
121124
* Configure a SpEL expression to evaluate a {@link ResolvableType}
122125
* to instantiate the payload from the incoming JSON.
123-
* By default this transformer consults {@link JsonHeaders} in the request message.
126+
* By default, this transformer consults {@link JsonHeaders} in the request message.
124127
* If this expression returns {@code null} or {@link ResolvableType} building throws a
125128
* {@link ClassNotFoundException}, this transformer falls back to the provided {@link #targetType}.
126129
* This logic is present as an expression because {@link JsonHeaders} may not have real class values,
127-
* but rather some type ids which have to be mapped to target classes according some external registry.
130+
* but rather some type ids that have to be mapped to target classes according some external registry.
128131
* @param valueTypeExpressionString the SpEL expression to use.
129132
* @since 5.2.6
130133
*/
@@ -135,11 +138,11 @@ public void setValueTypeExpressionString(String valueTypeExpressionString) {
135138
/**
136139
* Configure a SpEL {@link Expression} to evaluate a {@link ResolvableType}
137140
* to instantiate the payload from the incoming JSON.
138-
* By default this transformer consults {@link JsonHeaders} in the request message.
141+
* By default, this transformer consults {@link JsonHeaders} in the request message.
139142
* If this expression returns {@code null} or {@link ResolvableType} building throws a
140143
* {@link ClassNotFoundException}, this transformer falls back to the provided {@link #targetType}.
141144
* This logic is present as an expression because {@link JsonHeaders} may not have real class values,
142-
* but rather some type ids which have to be mapped to target classes according some external registry.
145+
* but rather some type ids that have to be mapped to target classes according some external registry.
143146
* @param valueTypeExpression the SpEL {@link Expression} to use.
144147
* @since 5.2.6
145148
*/
@@ -190,8 +193,7 @@ protected Object doTransform(Message<?> message) {
190193
}
191194
}
192195

193-
@Nullable
194-
private ResolvableType obtainResolvableType(Message<?> message) {
196+
private @Nullable ResolvableType obtainResolvableType(Message<?> message) {
195197
try {
196198
return this.valueTypeExpression.getValue(this.evaluationContext, message, ResolvableType.class);
197199
}
@@ -207,8 +209,7 @@ private ResolvableType obtainResolvableType(Message<?> message) {
207209
}
208210
}
209211

210-
@Nullable
211-
private static ResolvableType obtainResolvableTypeFromHeadersIfAny(MessageHeaders headers,
212+
private static @Nullable ResolvableType obtainResolvableTypeFromHeadersIfAny(MessageHeaders headers,
212213
ClassLoader classLoader) {
213214

214215
Object valueType = headers.get(JsonHeaders.RESOLVABLE_TYPE);
@@ -218,8 +219,8 @@ private static ResolvableType obtainResolvableTypeFromHeadersIfAny(MessageHeader
218219
JsonHeaders.buildResolvableType(classLoader, typeIdHeader,
219220
headers.get(JsonHeaders.CONTENT_TYPE_ID), headers.get(JsonHeaders.KEY_TYPE_ID));
220221
}
221-
return valueType instanceof ResolvableType
222-
? (ResolvableType) valueType
222+
return valueType instanceof ResolvableType resolvableType
223+
? resolvableType
223224
: null;
224225
}
225226

spring-integration-core/src/main/java/org/springframework/integration/json/SimpleJsonSerializer.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.apache.commons.logging.Log;
2727
import org.apache.commons.logging.LogFactory;
28+
import org.jspecify.annotations.Nullable;
2829

2930
import org.springframework.beans.BeanUtils;
3031

@@ -68,18 +69,19 @@ public static String toJson(Object bean, String... propertiesToExclude) {
6869
}
6970
catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
7071
Throwable exception = e;
71-
if (e instanceof InvocationTargetException) { // NOSONAR
72+
if (e instanceof InvocationTargetException) {
7273
exception = e.getCause();
74+
if (exception == null) {
75+
exception = e;
76+
}
7377
}
7478

7579
if (LOGGER.isDebugEnabled()) {
7680
LOGGER.debug("Failed to serialize property " + propertyName, exception);
7781
}
7882

79-
result =
80-
exception.getMessage() != null
81-
? exception.getMessage()
82-
: exception.toString();
83+
String exceptionMessage = exception.getMessage();
84+
result = exceptionMessage != null ? exceptionMessage : exception.toString();
8385
}
8486
stringBuilder.append(toElement(result)).append(",");
8587
}
@@ -94,7 +96,7 @@ public static String toJson(Object bean, String... propertiesToExclude) {
9496
}
9597
}
9698

97-
private static String toElement(Object result) {
99+
private static String toElement(@Nullable Object result) {
98100
if (result instanceof Number || result instanceof Boolean) {
99101
return result.toString();
100102
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/**
22
* Provides classes supporting JSON in Spring Integration.
33
*/
4+
@org.jspecify.annotations.NullMarked
45
package org.springframework.integration.json;

spring-integration-core/src/main/java/org/springframework/integration/transformer/support/AbstractHeaderValueMessageProcessor.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.integration.transformer.support;
1818

19+
import org.jspecify.annotations.Nullable;
20+
1921
/**
2022
* @param <T> inbound payload type.
2123
*
@@ -26,15 +28,14 @@
2628
*/
2729
public abstract class AbstractHeaderValueMessageProcessor<T> implements HeaderValueMessageProcessor<T> {
2830

29-
// null indicates no explicit setting
30-
private Boolean overwrite = null;
31+
private @Nullable Boolean overwrite = null;
3132

32-
public void setOverwrite(Boolean overwrite) {
33+
public void setOverwrite(@Nullable Boolean overwrite) {
3334
this.overwrite = overwrite;
3435
}
3536

3637
@Override
37-
public Boolean isOverwrite() {
38+
public @Nullable Boolean isOverwrite() {
3839
return this.overwrite;
3940
}
4041

spring-integration-core/src/main/java/org/springframework/integration/transformer/support/AvroHeaders.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ private AvroHeaders() {
3535
public static final String PREFIX = "avro_";
3636

3737
/**
38-
* The {@code SpecificRecord} type. By default it's the fully qualified
38+
* The {@code SpecificRecord} type. By default, it's the fully qualified
3939
* SpecificRecord type but can be a key that is mapped to the actual type.
4040
*/
4141
public static final String TYPE = PREFIX + "type";

spring-integration-core/src/main/java/org/springframework/integration/transformer/support/ExpressionEvaluatingHeaderValueMessageProcessor.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.integration.transformer.support;
1818

19+
import org.jspecify.annotations.Nullable;
20+
1921
import org.springframework.beans.factory.BeanFactory;
2022
import org.springframework.beans.factory.BeanFactoryAware;
2123
import org.springframework.expression.Expression;
@@ -26,7 +28,7 @@
2628
import org.springframework.messaging.Message;
2729

2830
/**
29-
* @param <T> ther paylaod type.
31+
* @param <T> the paylaod type.
3032
*
3133
* @author Mark Fisher
3234
* @author Artem Bilan
@@ -48,7 +50,7 @@ public class ExpressionEvaluatingHeaderValueMessageProcessor<T> extends Abstract
4850
* @param expression the {@link Expression} to evaluate.
4951
* @param expectedType the type for return value of {@code expression} evaluation result.
5052
*/
51-
public ExpressionEvaluatingHeaderValueMessageProcessor(Expression expression, Class<T> expectedType) {
53+
public ExpressionEvaluatingHeaderValueMessageProcessor(Expression expression, @Nullable Class<T> expectedType) {
5254
this.targetProcessor = new ExpressionEvaluatingMessageProcessor<T>(expression, expectedType);
5355
}
5456

@@ -59,7 +61,7 @@ public ExpressionEvaluatingHeaderValueMessageProcessor(Expression expression, Cl
5961
* @param expressionString the {@link java.lang.String} expression presentation to evaluate.
6062
* @param expectedType the type for return value of {@code expression} evaluation result.
6163
*/
62-
public ExpressionEvaluatingHeaderValueMessageProcessor(String expressionString, Class<T> expectedType) {
64+
public ExpressionEvaluatingHeaderValueMessageProcessor(String expressionString, @Nullable Class<T> expectedType) {
6365
Expression expression = EXPRESSION_PARSER.parseExpression(expressionString);
6466
this.targetProcessor = new ExpressionEvaluatingMessageProcessor<T>(expression, expectedType);
6567
}
@@ -68,7 +70,7 @@ public void setBeanFactory(BeanFactory beanFactory) {
6870
this.targetProcessor.setBeanFactory(beanFactory);
6971
}
7072

71-
public T processMessage(Message<?> message) {
73+
public @Nullable T processMessage(Message<?> message) {
7274
return this.targetProcessor.processMessage(message);
7375
}
7476

0 commit comments

Comments
 (0)