Skip to content

Commit 86364cc

Browse files
authored
GH-10083: Apply Nullability to Core expression package (#10265)
Related to: #10083 * Make `SupplierExpression` as to not return null for result of the provided `Supplier`. And fix Nullability from the super contract * Make `ValueExpression` compatible with super contract, but still ensure not null for return value * Make `DynamicExpression` compatible with super contract. Provide usability and code style refactoring * Make `FunctionExpression` to be compatible with super contract. At the same time ensure that input for the function is not null, but result of function could be null. Some other code style and usability refactoring * Remove redundant logic in the `ExpressionUtils` which is deliberately not reachable due to Nullability * Fix Nullability for the `ReloadableResourceBundleExpressionSource` and some simple refactoring according to IDE report. * Make `ExpressionEvalMap` to be aware of null values after expressions evaluation. * Fix usage of the `ExpressionEvalMap` in the project. * Mark `CassandraMessageHandler.setQuery()` with `NullAway` since `ExpressionEvalMap` may produce nulls, and Cassandra driver can deal with nulls, but that is not expressed on the API with Nullability * Fix `ThreadStatePropagationChannelInterceptor` for wrong import for the `@Nullable` * Slight change in the `GraphQlMessageHandler` according to the mentioned above `expression` package fixes
1 parent 5046d53 commit 86364cc

File tree

13 files changed

+220
-205
lines changed

13 files changed

+220
-205
lines changed

spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandler.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ protected StandardEvaluationContext getEvaluationContext() {
145145
setStatementProcessor((ExpressionEvaluatingMessageProcessor<Statement<?>>) expressionEvaluatingMessageProcessor);
146146
}
147147

148+
@SuppressWarnings("NullAway") // Cassandra driver uses NullAllowingImmutableMap
148149
public void setQuery(String query) {
149150
Assert.hasText(query, "'query' must not be empty");
150151
this.sessionMessageCallback =

spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ private void publishMessage(Method method, StandardEvaluationContext context) {
169169
AbstractIntegrationMessageBuilder<?> builder = (result instanceof Message<?>)
170170
? getMessageBuilderFactory().fromMessage((Message<?>) result)
171171
: getMessageBuilderFactory().withPayload(result);
172-
Map<String, Object> headers = evaluateHeaders(method, context);
172+
Map<String, @Nullable Object> headers = evaluateHeaders(method, context);
173173
if (headers != null) {
174174
builder.copyHeaders(headers);
175175
}
@@ -190,7 +190,7 @@ private void publishMessage(Method method, StandardEvaluationContext context) {
190190
}
191191
}
192192

193-
private @Nullable Map<String, Object> evaluateHeaders(Method method, StandardEvaluationContext context) {
193+
private @Nullable Map<String, @Nullable Object> evaluateHeaders(Method method, StandardEvaluationContext context) {
194194
Map<String, Expression> headerExpressionMap = this.metadataSource.getExpressionsForHeaders(method);
195195
if (headerExpressionMap != null) {
196196
return ExpressionEvalMap.from(headerExpressionMap)

spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/ThreadStatePropagationChannelInterceptor.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import java.util.Queue;
2020
import java.util.concurrent.LinkedBlockingQueue;
2121

22-
import io.micrometer.common.lang.Nullable;
22+
import org.jspecify.annotations.Nullable;
2323

2424
import org.springframework.integration.support.MessageDecorator;
2525
import org.springframework.messaging.Message;
@@ -92,8 +92,7 @@ public final Message<?> beforeHandle(Message<?> message, MessageChannel channel,
9292
return postReceive(message, channel);
9393
}
9494

95-
@Nullable
96-
protected abstract S obtainPropagatingContext(Message<?> message, MessageChannel channel);
95+
protected abstract @Nullable S obtainPropagatingContext(Message<?> message, MessageChannel channel);
9796

9897
protected abstract void populatePropagatedContext(@Nullable S state, Message<?> message, MessageChannel channel);
9998

spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.util.Locale;
2020

21+
import org.jspecify.annotations.Nullable;
22+
2123
import org.springframework.context.i18n.LocaleContextHolder;
2224
import org.springframework.core.convert.TypeDescriptor;
2325
import org.springframework.expression.EvaluationContext;
@@ -30,6 +32,8 @@
3032
* for resolving the actual Expression instance per-invocation at runtime.
3133
*
3234
* @author Mark Fisher
35+
* @author Artem Bilan
36+
*
3337
* @since 2.0
3438
*/
3539
public class DynamicExpression implements Expression {
@@ -45,96 +49,110 @@ public DynamicExpression(String key, ExpressionSource expressionSource) {
4549
this.expressionSource = expressionSource;
4650
}
4751

48-
public Object getValue() throws EvaluationException {
49-
return this.resolveExpression().getValue();
52+
public @Nullable Object getValue() throws EvaluationException {
53+
return getValue((Object) null);
5054
}
5155

52-
public Object getValue(Object rootObject) throws EvaluationException {
53-
return this.resolveExpression().getValue(rootObject);
56+
public @Nullable Object getValue(@Nullable Object rootObject) throws EvaluationException {
57+
return getValue(rootObject, null);
5458
}
5559

56-
public <T> T getValue(Class<T> desiredResultType) throws EvaluationException {
57-
return this.resolveExpression().getValue(desiredResultType);
60+
public <T> @Nullable T getValue(@Nullable Class<T> desiredResultType) throws EvaluationException {
61+
return getValue((Object) null, desiredResultType);
5862
}
5963

60-
public <T> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException {
61-
return this.resolveExpression().getValue(rootObject, desiredResultType);
64+
public <T> @Nullable T getValue(@Nullable Object rootObject, @Nullable Class<T> desiredResultType)
65+
throws EvaluationException {
66+
67+
return resolveExpression().getValue(rootObject, desiredResultType);
6268
}
6369

64-
public Object getValue(EvaluationContext context) throws EvaluationException {
65-
return this.resolveExpression().getValue(context);
70+
public @Nullable Object getValue(EvaluationContext context) throws EvaluationException {
71+
return resolveExpression().getValue(context);
6672
}
6773

68-
public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
69-
return this.resolveExpression().getValue(context, rootObject);
74+
public @Nullable Object getValue(EvaluationContext context, @Nullable Object rootObject)
75+
throws EvaluationException {
76+
77+
return getValue(context, rootObject, null);
7078
}
7179

72-
public <T> T getValue(EvaluationContext context, Class<T> desiredResultType) throws EvaluationException {
73-
return this.resolveExpression().getValue(context, desiredResultType);
80+
public <T> @Nullable T getValue(EvaluationContext context, @Nullable Class<T> desiredResultType)
81+
throws EvaluationException {
82+
83+
return resolveExpression().getValue(context, desiredResultType);
7484
}
7585

76-
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType) throws EvaluationException {
77-
return this.resolveExpression().getValue(context, rootObject, desiredResultType);
86+
public <T> @Nullable T getValue(EvaluationContext context, @Nullable Object rootObject,
87+
@Nullable Class<T> desiredResultType) throws EvaluationException {
88+
89+
return resolveExpression().getValue(context, rootObject, desiredResultType);
7890
}
7991

80-
public Class<?> getValueType() throws EvaluationException {
81-
return this.resolveExpression().getValueType();
92+
public @Nullable Class<?> getValueType() throws EvaluationException {
93+
return getValueType((Object) null);
8294
}
8395

84-
public Class<?> getValueType(Object rootObject) throws EvaluationException {
85-
return this.resolveExpression().getValueType(rootObject);
96+
public @Nullable Class<?> getValueType(@Nullable Object rootObject) throws EvaluationException {
97+
return resolveExpression().getValueType(rootObject);
8698
}
8799

88-
public Class<?> getValueType(EvaluationContext context) throws EvaluationException {
89-
return this.resolveExpression().getValueType(context);
100+
public @Nullable Class<?> getValueType(EvaluationContext context) throws EvaluationException {
101+
return resolveExpression().getValueType(context);
90102
}
91103

92-
public Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
93-
return this.resolveExpression().getValueType(context, rootObject);
104+
public @Nullable Class<?> getValueType(EvaluationContext context, @Nullable Object rootObject)
105+
throws EvaluationException {
106+
107+
return resolveExpression().getValueType(context, rootObject);
94108
}
95109

96-
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
97-
return this.resolveExpression().getValueTypeDescriptor();
110+
public @Nullable TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
111+
return getValueTypeDescriptor((Object) null);
98112
}
99113

100-
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
114+
public @Nullable TypeDescriptor getValueTypeDescriptor(@Nullable Object rootObject) throws EvaluationException {
101115
return this.resolveExpression().getValueTypeDescriptor(rootObject);
102116
}
103117

104-
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
105-
return this.resolveExpression().getValueTypeDescriptor(context);
118+
public @Nullable TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
119+
return resolveExpression().getValueTypeDescriptor(context);
106120
}
107121

108-
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException {
109-
return this.resolveExpression().getValueTypeDescriptor(context, rootObject);
122+
public @Nullable TypeDescriptor getValueTypeDescriptor(EvaluationContext context, @Nullable Object rootObject)
123+
throws EvaluationException {
124+
125+
return resolveExpression().getValueTypeDescriptor(context, rootObject);
110126
}
111127

112128
public boolean isWritable(EvaluationContext context) throws EvaluationException {
113-
return this.resolveExpression().isWritable(context);
129+
return resolveExpression().isWritable(context);
114130
}
115131

116-
public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException {
117-
return this.resolveExpression().isWritable(context, rootObject);
132+
public boolean isWritable(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException {
133+
return resolveExpression().isWritable(context, rootObject);
118134
}
119135

120-
public boolean isWritable(Object rootObject) throws EvaluationException {
121-
return this.resolveExpression().isWritable(rootObject);
136+
public boolean isWritable(@Nullable Object rootObject) throws EvaluationException {
137+
return resolveExpression().isWritable(rootObject);
122138
}
123139

124-
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
125-
this.resolveExpression().setValue(context, value);
140+
public void setValue(EvaluationContext context, @Nullable Object value) throws EvaluationException {
141+
resolveExpression().setValue(context, value);
126142
}
127143

128-
public void setValue(Object rootObject, Object value) throws EvaluationException {
129-
this.resolveExpression().setValue(rootObject, value);
144+
public void setValue(@Nullable Object rootObject, @Nullable Object value) throws EvaluationException {
145+
resolveExpression().setValue(rootObject, value);
130146
}
131147

132-
public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException {
133-
this.resolveExpression().setValue(context, rootObject, value);
148+
public void setValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Object value)
149+
throws EvaluationException {
150+
151+
resolveExpression().setValue(context, rootObject, value);
134152
}
135153

136154
public String getExpressionString() {
137-
return this.resolveExpression().getExpressionString();
155+
return resolveExpression().getExpressionString();
138156
}
139157

140158
private Expression resolveExpression() {

spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.integration.expression;
1818

1919
import java.util.AbstractMap;
20+
import java.util.ArrayList;
2021
import java.util.Collection;
2122
import java.util.Map;
2223
import java.util.Set;
@@ -67,7 +68,7 @@
6768
*
6869
* @since 3.0
6970
*/
70-
public final class ExpressionEvalMap extends AbstractMap<String, Object> {
71+
public final class ExpressionEvalMap extends AbstractMap<String, @Nullable Object> {
7172

7273
public static final EvaluationCallback SIMPLE_CALLBACK = Expression::getValue;
7374

@@ -85,8 +86,7 @@ private ExpressionEvalMap(Map<String, ?> original, EvaluationCallback evaluation
8586
* from {@link #original} and returns the result of evaluation using {@link #evaluationCallback}.
8687
*/
8788
@Override
88-
@Nullable
89-
public Object get(Object key) {
89+
public @Nullable Object get(Object key) {
9090
Object value = this.original.get(key);
9191
if (value != null) {
9292
Expression expression;
@@ -107,19 +107,19 @@ else if (value instanceof String) {
107107
}
108108

109109
@Override
110-
public Set<Map.Entry<String, Object>> entrySet() {
110+
public Set<Map.Entry<String, @Nullable Object>> entrySet() {
111111
return this.original.keySet()
112112
.stream()
113113
.map((key) -> new SimpleImmutableEntry<>(key, get(key)))
114114
.collect(Collectors.toSet());
115115
}
116116

117117
@Override
118-
public Collection<Object> values() {
118+
public Collection<@Nullable Object> values() {
119119
return this.original.values()
120120
.stream()
121121
.map(this::get)
122-
.collect(Collectors.toList());
122+
.collect(Collectors.toCollection(ArrayList<@Nullable Object>::new));
123123
}
124124

125125
@Override
@@ -205,16 +205,13 @@ public interface EvaluationCallback {
205205
*/
206206
public static class ComponentsEvaluationCallback implements EvaluationCallback {
207207

208-
@Nullable
209-
private final EvaluationContext context;
208+
private final @Nullable EvaluationContext context;
210209

211-
@Nullable
212-
private final Object root;
210+
private final @Nullable Object root;
213211

214212
private final boolean rootExplicitlySet;
215213

216-
@Nullable
217-
private final Class<?> returnType;
214+
private final @Nullable Class<?> returnType;
218215

219216
public ComponentsEvaluationCallback(@Nullable EvaluationContext context, @Nullable Object root,
220217
boolean rootExplicitlySet, @Nullable Class<?> returnType) {
@@ -226,7 +223,7 @@ public ComponentsEvaluationCallback(@Nullable EvaluationContext context, @Nullab
226223
}
227224

228225
@Override
229-
public Object evaluate(Expression expression) {
226+
public @Nullable Object evaluate(Expression expression) {
230227
if (this.context != null) {
231228
if (this.rootExplicitlySet) {
232229
return expression.getValue(this.context, this.root, this.returnType);
@@ -247,18 +244,15 @@ public static final class ExpressionEvalMapBuilder {
247244

248245
private final Map<String, ?> expressions;
249246

250-
private EvaluationCallback evaluationCallback;
247+
private @Nullable EvaluationCallback evaluationCallback;
251248

252-
@Nullable
253-
private EvaluationContext context;
249+
private @Nullable EvaluationContext context;
254250

255-
@Nullable
256-
private Object root;
251+
private @Nullable Object root;
257252

258253
private boolean rootExplicitlySet;
259254

260-
@Nullable
261-
private Class<?> returnType;
255+
private @Nullable Class<?> returnType;
262256

263257
private final ExpressionEvalMapComponentsBuilder evalMapComponentsBuilder =
264258
new ExpressionEvalMapComponentsBuilderImpl();
@@ -303,16 +297,16 @@ private class ExpressionEvalMapFinalBuilderImpl implements ExpressionEvalMapFina
303297

304298
@Override
305299
public ExpressionEvalMap build() {
306-
if (ExpressionEvalMapBuilder.this.evaluationCallback != null) {
307-
return new ExpressionEvalMap(ExpressionEvalMapBuilder.this.expressions,
308-
ExpressionEvalMapBuilder.this.evaluationCallback);
309-
}
310-
else {
311-
return new ExpressionEvalMap(ExpressionEvalMapBuilder.this.expressions,
300+
EvaluationCallback evaluationCallbackToUse = ExpressionEvalMapBuilder.this.evaluationCallback;
301+
if (evaluationCallbackToUse == null) {
302+
evaluationCallbackToUse =
312303
new ComponentsEvaluationCallback(ExpressionEvalMapBuilder.this.context,
313-
ExpressionEvalMapBuilder.this.root, ExpressionEvalMapBuilder.this.rootExplicitlySet,
314-
ExpressionEvalMapBuilder.this.returnType));
304+
ExpressionEvalMapBuilder.this.root,
305+
ExpressionEvalMapBuilder.this.rootExplicitlySet,
306+
ExpressionEvalMapBuilder.this.returnType);
315307
}
308+
309+
return new ExpressionEvalMap(ExpressionEvalMapBuilder.this.expressions, evaluationCallbackToUse);
316310
}
317311

318312
}

0 commit comments

Comments
 (0)