From bbbbc48f0035c6d8bc07bc421f4b66d2173fdd49 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 24 Jul 2025 18:00:26 -0400 Subject: [PATCH] GH-10083: Apply Nullability to Core `expression` package Related to: https://github.com/spring-projects/spring-integration/issues/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 --- .../outbound/CassandraMessageHandler.java | 1 + .../aop/MessagePublishingInterceptor.java | 4 +- ...eadStatePropagationChannelInterceptor.java | 5 +- .../expression/DynamicExpression.java | 104 ++++++++++-------- .../expression/ExpressionEvalMap.java | 50 ++++----- .../expression/ExpressionUtils.java | 48 ++------ .../expression/FunctionExpression.java | 53 +++++---- ...oadableResourceBundleExpressionSource.java | 33 +++--- .../expression/SupplierExpression.java | 49 +++++---- .../expression/ValueExpression.java | 55 +++++---- .../integration/expression/package-info.java | 2 +- .../outbound/GraphQlMessageHandler.java | 19 +++- .../ws/AbstractWebServiceOutboundGateway.java | 2 +- 13 files changed, 220 insertions(+), 205 deletions(-) diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandler.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandler.java index 55735df835d..1f239ae6fde 100644 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandler.java +++ b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandler.java @@ -145,6 +145,7 @@ protected StandardEvaluationContext getEvaluationContext() { setStatementProcessor((ExpressionEvaluatingMessageProcessor>) expressionEvaluatingMessageProcessor); } + @SuppressWarnings("NullAway") // Cassandra driver uses NullAllowingImmutableMap public void setQuery(String query) { Assert.hasText(query, "'query' must not be empty"); this.sessionMessageCallback = diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java index 44001dd2438..84ff01a8c4b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java @@ -169,7 +169,7 @@ private void publishMessage(Method method, StandardEvaluationContext context) { AbstractIntegrationMessageBuilder builder = (result instanceof Message) ? getMessageBuilderFactory().fromMessage((Message) result) : getMessageBuilderFactory().withPayload(result); - Map headers = evaluateHeaders(method, context); + Map headers = evaluateHeaders(method, context); if (headers != null) { builder.copyHeaders(headers); } @@ -190,7 +190,7 @@ private void publishMessage(Method method, StandardEvaluationContext context) { } } - private @Nullable Map evaluateHeaders(Method method, StandardEvaluationContext context) { + private @Nullable Map evaluateHeaders(Method method, StandardEvaluationContext context) { Map headerExpressionMap = this.metadataSource.getExpressionsForHeaders(method); if (headerExpressionMap != null) { return ExpressionEvalMap.from(headerExpressionMap) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/ThreadStatePropagationChannelInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/ThreadStatePropagationChannelInterceptor.java index ae469494f4a..b9ff9224dea 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/ThreadStatePropagationChannelInterceptor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/ThreadStatePropagationChannelInterceptor.java @@ -19,7 +19,7 @@ import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; -import io.micrometer.common.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.integration.support.MessageDecorator; import org.springframework.messaging.Message; @@ -92,8 +92,7 @@ public final Message beforeHandle(Message message, MessageChannel channel, return postReceive(message, channel); } - @Nullable - protected abstract S obtainPropagatingContext(Message message, MessageChannel channel); + protected abstract @Nullable S obtainPropagatingContext(Message message, MessageChannel channel); protected abstract void populatePropagatedContext(@Nullable S state, Message message, MessageChannel channel); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java index b21c19f7191..8c877db7ddb 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java @@ -18,6 +18,8 @@ import java.util.Locale; +import org.jspecify.annotations.Nullable; + import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationContext; @@ -30,6 +32,8 @@ * for resolving the actual Expression instance per-invocation at runtime. * * @author Mark Fisher + * @author Artem Bilan + * * @since 2.0 */ public class DynamicExpression implements Expression { @@ -45,96 +49,110 @@ public DynamicExpression(String key, ExpressionSource expressionSource) { this.expressionSource = expressionSource; } - public Object getValue() throws EvaluationException { - return this.resolveExpression().getValue(); + public @Nullable Object getValue() throws EvaluationException { + return getValue((Object) null); } - public Object getValue(Object rootObject) throws EvaluationException { - return this.resolveExpression().getValue(rootObject); + public @Nullable Object getValue(@Nullable Object rootObject) throws EvaluationException { + return getValue(rootObject, null); } - public T getValue(Class desiredResultType) throws EvaluationException { - return this.resolveExpression().getValue(desiredResultType); + public @Nullable T getValue(@Nullable Class desiredResultType) throws EvaluationException { + return getValue((Object) null, desiredResultType); } - public T getValue(Object rootObject, Class desiredResultType) throws EvaluationException { - return this.resolveExpression().getValue(rootObject, desiredResultType); + public @Nullable T getValue(@Nullable Object rootObject, @Nullable Class desiredResultType) + throws EvaluationException { + + return resolveExpression().getValue(rootObject, desiredResultType); } - public Object getValue(EvaluationContext context) throws EvaluationException { - return this.resolveExpression().getValue(context); + public @Nullable Object getValue(EvaluationContext context) throws EvaluationException { + return resolveExpression().getValue(context); } - public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException { - return this.resolveExpression().getValue(context, rootObject); + public @Nullable Object getValue(EvaluationContext context, @Nullable Object rootObject) + throws EvaluationException { + + return getValue(context, rootObject, null); } - public T getValue(EvaluationContext context, Class desiredResultType) throws EvaluationException { - return this.resolveExpression().getValue(context, desiredResultType); + public @Nullable T getValue(EvaluationContext context, @Nullable Class desiredResultType) + throws EvaluationException { + + return resolveExpression().getValue(context, desiredResultType); } - public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { - return this.resolveExpression().getValue(context, rootObject, desiredResultType); + public @Nullable T getValue(EvaluationContext context, @Nullable Object rootObject, + @Nullable Class desiredResultType) throws EvaluationException { + + return resolveExpression().getValue(context, rootObject, desiredResultType); } - public Class getValueType() throws EvaluationException { - return this.resolveExpression().getValueType(); + public @Nullable Class getValueType() throws EvaluationException { + return getValueType((Object) null); } - public Class getValueType(Object rootObject) throws EvaluationException { - return this.resolveExpression().getValueType(rootObject); + public @Nullable Class getValueType(@Nullable Object rootObject) throws EvaluationException { + return resolveExpression().getValueType(rootObject); } - public Class getValueType(EvaluationContext context) throws EvaluationException { - return this.resolveExpression().getValueType(context); + public @Nullable Class getValueType(EvaluationContext context) throws EvaluationException { + return resolveExpression().getValueType(context); } - public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { - return this.resolveExpression().getValueType(context, rootObject); + public @Nullable Class getValueType(EvaluationContext context, @Nullable Object rootObject) + throws EvaluationException { + + return resolveExpression().getValueType(context, rootObject); } - public TypeDescriptor getValueTypeDescriptor() throws EvaluationException { - return this.resolveExpression().getValueTypeDescriptor(); + public @Nullable TypeDescriptor getValueTypeDescriptor() throws EvaluationException { + return getValueTypeDescriptor((Object) null); } - public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { + public @Nullable TypeDescriptor getValueTypeDescriptor(@Nullable Object rootObject) throws EvaluationException { return this.resolveExpression().getValueTypeDescriptor(rootObject); } - public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException { - return this.resolveExpression().getValueTypeDescriptor(context); + public @Nullable TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException { + return resolveExpression().getValueTypeDescriptor(context); } - public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException { - return this.resolveExpression().getValueTypeDescriptor(context, rootObject); + public @Nullable TypeDescriptor getValueTypeDescriptor(EvaluationContext context, @Nullable Object rootObject) + throws EvaluationException { + + return resolveExpression().getValueTypeDescriptor(context, rootObject); } public boolean isWritable(EvaluationContext context) throws EvaluationException { - return this.resolveExpression().isWritable(context); + return resolveExpression().isWritable(context); } - public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { - return this.resolveExpression().isWritable(context, rootObject); + public boolean isWritable(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { + return resolveExpression().isWritable(context, rootObject); } - public boolean isWritable(Object rootObject) throws EvaluationException { - return this.resolveExpression().isWritable(rootObject); + public boolean isWritable(@Nullable Object rootObject) throws EvaluationException { + return resolveExpression().isWritable(rootObject); } - public void setValue(EvaluationContext context, Object value) throws EvaluationException { - this.resolveExpression().setValue(context, value); + public void setValue(EvaluationContext context, @Nullable Object value) throws EvaluationException { + resolveExpression().setValue(context, value); } - public void setValue(Object rootObject, Object value) throws EvaluationException { - this.resolveExpression().setValue(rootObject, value); + public void setValue(@Nullable Object rootObject, @Nullable Object value) throws EvaluationException { + resolveExpression().setValue(rootObject, value); } - public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { - this.resolveExpression().setValue(context, rootObject, value); + public void setValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Object value) + throws EvaluationException { + + resolveExpression().setValue(context, rootObject, value); } public String getExpressionString() { - return this.resolveExpression().getExpressionString(); + return resolveExpression().getExpressionString(); } private Expression resolveExpression() { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java index 2621f25763a..234df2bc06f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java @@ -17,6 +17,7 @@ package org.springframework.integration.expression; import java.util.AbstractMap; +import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Set; @@ -67,7 +68,7 @@ * * @since 3.0 */ -public final class ExpressionEvalMap extends AbstractMap { +public final class ExpressionEvalMap extends AbstractMap { public static final EvaluationCallback SIMPLE_CALLBACK = Expression::getValue; @@ -85,8 +86,7 @@ private ExpressionEvalMap(Map original, EvaluationCallback evaluation * from {@link #original} and returns the result of evaluation using {@link #evaluationCallback}. */ @Override - @Nullable - public Object get(Object key) { + public @Nullable Object get(Object key) { Object value = this.original.get(key); if (value != null) { Expression expression; @@ -107,7 +107,7 @@ else if (value instanceof String) { } @Override - public Set> entrySet() { + public Set> entrySet() { return this.original.keySet() .stream() .map((key) -> new SimpleImmutableEntry<>(key, get(key))) @@ -115,11 +115,11 @@ public Set> entrySet() { } @Override - public Collection values() { + public Collection<@Nullable Object> values() { return this.original.values() .stream() .map(this::get) - .collect(Collectors.toList()); + .collect(Collectors.toCollection(ArrayList<@Nullable Object>::new)); } @Override @@ -205,16 +205,13 @@ public interface EvaluationCallback { */ public static class ComponentsEvaluationCallback implements EvaluationCallback { - @Nullable - private final EvaluationContext context; + private final @Nullable EvaluationContext context; - @Nullable - private final Object root; + private final @Nullable Object root; private final boolean rootExplicitlySet; - @Nullable - private final Class returnType; + private final @Nullable Class returnType; public ComponentsEvaluationCallback(@Nullable EvaluationContext context, @Nullable Object root, boolean rootExplicitlySet, @Nullable Class returnType) { @@ -226,7 +223,7 @@ public ComponentsEvaluationCallback(@Nullable EvaluationContext context, @Nullab } @Override - public Object evaluate(Expression expression) { + public @Nullable Object evaluate(Expression expression) { if (this.context != null) { if (this.rootExplicitlySet) { return expression.getValue(this.context, this.root, this.returnType); @@ -247,18 +244,15 @@ public static final class ExpressionEvalMapBuilder { private final Map expressions; - private EvaluationCallback evaluationCallback; + private @Nullable EvaluationCallback evaluationCallback; - @Nullable - private EvaluationContext context; + private @Nullable EvaluationContext context; - @Nullable - private Object root; + private @Nullable Object root; private boolean rootExplicitlySet; - @Nullable - private Class returnType; + private @Nullable Class returnType; private final ExpressionEvalMapComponentsBuilder evalMapComponentsBuilder = new ExpressionEvalMapComponentsBuilderImpl(); @@ -303,16 +297,16 @@ private class ExpressionEvalMapFinalBuilderImpl implements ExpressionEvalMapFina @Override public ExpressionEvalMap build() { - if (ExpressionEvalMapBuilder.this.evaluationCallback != null) { - return new ExpressionEvalMap(ExpressionEvalMapBuilder.this.expressions, - ExpressionEvalMapBuilder.this.evaluationCallback); - } - else { - return new ExpressionEvalMap(ExpressionEvalMapBuilder.this.expressions, + EvaluationCallback evaluationCallbackToUse = ExpressionEvalMapBuilder.this.evaluationCallback; + if (evaluationCallbackToUse == null) { + evaluationCallbackToUse = new ComponentsEvaluationCallback(ExpressionEvalMapBuilder.this.context, - ExpressionEvalMapBuilder.this.root, ExpressionEvalMapBuilder.this.rootExplicitlySet, - ExpressionEvalMapBuilder.this.returnType)); + ExpressionEvalMapBuilder.this.root, + ExpressionEvalMapBuilder.this.rootExplicitlySet, + ExpressionEvalMapBuilder.this.returnType); } + + return new ExpressionEvalMap(ExpressionEvalMapBuilder.this.expressions, evaluationCallbackToUse); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionUtils.java index 5ab987bf596..48f8d5f04c9 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionUtils.java @@ -25,7 +25,6 @@ import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; -import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.context.expression.MapAccessor; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.Resource; @@ -35,11 +34,8 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.DataBindingPropertyAccessor; import org.springframework.expression.spel.support.SimpleEvaluationContext; -import org.springframework.expression.spel.support.SimpleEvaluationContext.Builder; import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.expression.spel.support.StandardTypeConverter; import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.messaging.Message; import org.springframework.util.Assert; import org.springframework.util.ResourceUtils; @@ -107,53 +103,32 @@ public static SimpleEvaluationContext createSimpleEvaluationContext(@Nullable Be } private static EvaluationContext doCreateContext(@Nullable BeanFactory beanFactory, boolean simple) { - ConversionService conversionService = null; - EvaluationContext evaluationContext = null; if (beanFactory != null) { - evaluationContext = - simple - ? IntegrationContextUtils.getSimpleEvaluationContext(beanFactory) - : IntegrationContextUtils.getEvaluationContext(beanFactory); + return simple + ? IntegrationContextUtils.getSimpleEvaluationContext(beanFactory) + : IntegrationContextUtils.getEvaluationContext(beanFactory); } - if (evaluationContext == null) { - if (beanFactory != null) { - conversionService = IntegrationUtils.getConversionService(beanFactory); - } - evaluationContext = createEvaluationContext(conversionService, beanFactory, simple); - } - return evaluationContext; + return createEvaluationContext(simple); } /** * Create a {@link StandardEvaluationContext} with a {@link MapAccessor} in its * property accessor property and the supplied {@link ConversionService} in its * conversionService property. - * @param conversionService the conversion service. - * @param beanFactory the bean factory. * @param simple true if simple. * @return the evaluation context. */ - private static EvaluationContext createEvaluationContext(@Nullable ConversionService conversionService, - @Nullable BeanFactory beanFactory, boolean simple) { - + private static EvaluationContext createEvaluationContext(boolean simple) { if (simple) { - Builder ecBuilder = SimpleEvaluationContext.forPropertyAccessors( - new MapAccessor(), DataBindingPropertyAccessor.forReadOnlyAccess()) - .withInstanceMethods(); - if (conversionService != null) { - ecBuilder.withConversionService(conversionService); - } - return ecBuilder.build(); + return SimpleEvaluationContext.forPropertyAccessors( + new MapAccessor(), + DataBindingPropertyAccessor.forReadOnlyAccess()) + .withInstanceMethods() + .build(); } else { StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); evaluationContext.addPropertyAccessor(new MapAccessor()); - if (conversionService != null) { - evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService)); - } - if (beanFactory != null) { - evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory)); - } return evaluationContext; } } @@ -183,8 +158,7 @@ public static File expressionToFile(Expression expression, EvaluationContext eva if (value instanceof File) { return (File) value; } - else if (value instanceof String) { - String path = (String) value; + else if (value instanceof String path) { Assert.hasText(path, String.format("Unable to resolve %s for the provided Expression '%s'.", propertyName, expression.getExpressionString())); try { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/FunctionExpression.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/FunctionExpression.java index 116a326c727..06ef91e9635 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/expression/FunctionExpression.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/FunctionExpression.java @@ -54,63 +54,63 @@ */ public class FunctionExpression implements Expression { - private final Function function; + private final Function function; private final EvaluationContext defaultContext = new StandardEvaluationContext(); - public FunctionExpression(Function function) { + public FunctionExpression(Function function) { Assert.notNull(function, "'function' must not be null."); this.function = function; } @Override - @Nullable - public Object getValue() throws EvaluationException { - return this.function.apply(null); + public @Nullable Object getValue() throws EvaluationException { + throw noRootObjectException(); } @Override - @Nullable @SuppressWarnings("unchecked") - public Object getValue(@Nullable Object rootObject) throws EvaluationException { + public @Nullable Object getValue(@Nullable Object rootObject) throws EvaluationException { + if (rootObject == null) { + throw noRootObjectException(); + } return this.function.apply((S) rootObject); } @Override - @Nullable - public T getValue(@Nullable Class desiredResultType) throws EvaluationException { + public @Nullable T getValue(@Nullable Class desiredResultType) throws EvaluationException { return getValue(this.defaultContext, desiredResultType); } @Override - @Nullable - public T getValue(@Nullable Object rootObject, @Nullable Class desiredResultType) + public @Nullable T getValue(@Nullable Object rootObject, @Nullable Class desiredResultType) throws EvaluationException { return getValue(this.defaultContext, rootObject, desiredResultType); } @Override - @Nullable - public Object getValue(EvaluationContext context) throws EvaluationException { + public @Nullable Object getValue(EvaluationContext context) throws EvaluationException { return getValue(context.getRootObject().getValue()); } @Override - @Nullable - public Object getValue(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { + public @Nullable Object getValue(EvaluationContext context, @Nullable Object rootObject) + throws EvaluationException { + return getValue(rootObject); } @Override - @Nullable - public T getValue(EvaluationContext context, @Nullable Class desiredResultType) throws EvaluationException { + public @Nullable T getValue(EvaluationContext context, @Nullable Class desiredResultType) + throws EvaluationException { + return ExpressionUtils.convertTypedValue(context, new TypedValue(getValue(context)), desiredResultType); } @Override - @Nullable - public T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class desiredResultType) + public @Nullable T getValue(EvaluationContext context, @Nullable Object rootObject, + @Nullable Class desiredResultType) throws EvaluationException { return ExpressionUtils.convertTypedValue(context, @@ -176,11 +176,6 @@ public void setValue(EvaluationContext context, @Nullable Object rootObject, @Nu throw readOnlyException(); } - private EvaluationException readOnlyException() { - return new EvaluationException(getExpressionString(), - "FunctionExpression is a 'read only' Expression implementation"); - } - @Override public boolean isWritable(EvaluationContext context) throws EvaluationException { return false; @@ -201,4 +196,14 @@ public final String getExpressionString() { return this.function.toString(); } + private EvaluationException readOnlyException() { + return new EvaluationException(getExpressionString(), + "FunctionExpression is a 'read-only' Expression implementation"); + } + + private EvaluationException noRootObjectException() { + return new EvaluationException(getExpressionString(), + "FunctionExpression does not support 'getValue()' contract without 'rootObject'"); + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/ReloadableResourceBundleExpressionSource.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ReloadableResourceBundleExpressionSource.java index 4b87d9a30de..d71a37a2c9c 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/expression/ReloadableResourceBundleExpressionSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ReloadableResourceBundleExpressionSource.java @@ -102,9 +102,9 @@ public class ReloadableResourceBundleExpressionSource implements ExpressionSourc private String[] basenames = {}; - private String defaultEncoding; + private @Nullable String defaultEncoding; - private Properties fileEncodings; + private @Nullable Properties fileEncodings; private boolean fallbackToSystemLocale = true; @@ -144,7 +144,7 @@ public void setBasename(String basename) { * @see #setBasename * @see java.util.ResourceBundle */ - public void setBasenames(@Nullable String[] basenames) { + public void setBasenames(String @Nullable [] basenames) { if (basenames != null) { this.basenames = new String[basenames.length]; for (int i = 0; i < basenames.length; i++) { @@ -218,7 +218,7 @@ public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) { * @param cacheSeconds The cache seconds. */ public void setCacheSeconds(int cacheSeconds) { - this.cacheMillis = (cacheSeconds * 1000); // NOSONAR + this.cacheMillis = (cacheSeconds * 1000L); } /** @@ -237,7 +237,7 @@ public void setPropertiesPersister(@Nullable PropertiesPersister propertiesPersi *

The default is a DefaultResourceLoader. Will get overridden by the * ApplicationContext if running in a context, as it implements the * ResourceLoaderAware interface. Can be manually overridden when - * running outside of an ApplicationContext. + * running outside an ApplicationContext. * @see org.springframework.core.io.DefaultResourceLoader * @see org.springframework.context.ResourceLoaderAware */ @@ -250,7 +250,7 @@ public void setResourceLoader(@Nullable ResourceLoader resourceLoader) { * Resolves the given key in the retrieved bundle files to an Expression. */ @Override - public Expression getExpression(String key, Locale locale) { + public @Nullable Expression getExpression(String key, Locale locale) { String expressionString = getExpressionString(key, locale); if (expressionString != null) { return this.parser.parseExpression(expressionString); @@ -258,8 +258,7 @@ public Expression getExpression(String key, Locale locale) { return null; } - @Nullable - private String getExpressionString(String key, Locale locale) { + private @Nullable String getExpressionString(String key, Locale locale) { if (this.cacheMillis < 0) { PropertiesHolder propHolder = getMergedProperties(locale); return propHolder.getProperty(key); @@ -379,18 +378,18 @@ private List calculateFilenamesForLocale(String basename, Locale locale) StringBuilder temp = new StringBuilder(basename); temp.append('_'); - if (language.length() > 0) { + if (!language.isEmpty()) { temp.append(language); result.add(0, temp.toString()); } temp.append('_'); - if (country.length() > 0) { + if (!country.isEmpty()) { temp.append(country); result.add(0, temp.toString()); } - if (variant.length() > 0 && (language.length() > 0 || country.length() > 0)) { + if (!variant.isEmpty() && (!language.isEmpty() || !country.isEmpty())) { temp.append('_').append(variant); result.add(0, temp.toString()); } @@ -454,7 +453,6 @@ private PropertiesHolder refreshProperties(String filename, @Nullable Properties LOGGER.debug(resource + " could not be resolved in the file system - assuming that is hasn't changed", ex); } - fileTimestamp = -1; } } propHolder = load(filename, resource, fileTimestamp); @@ -542,8 +540,7 @@ private void loadFromProperties(Resource resource, String filename, InputStream } else { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Loading properties [" + (resourceFilename == null ? resource : resourceFilename) - + "]"); + LOGGER.debug("Loading properties [" + (resourceFilename == null ? resource : resourceFilename) + "]"); } this.propertiesPersister.load(props, is); } @@ -585,7 +582,7 @@ public String toString() { */ private static final class PropertiesHolder { - private Properties properties; + private @Nullable Properties properties; private long fileTimestamp = -1; @@ -599,8 +596,7 @@ private static final class PropertiesHolder { this.fileTimestamp = fileTimestamp; } - @Nullable - public Properties getProperties() { + public @Nullable Properties getProperties() { return this.properties; } @@ -616,8 +612,7 @@ public long getRefreshTimestamp() { return this.refreshTimestamp; } - @Nullable - public String getProperty(String code) { + public @Nullable String getProperty(String code) { if (this.properties == null) { return null; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/SupplierExpression.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/SupplierExpression.java index 865a6efebcc..9728b5ddf45 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/expression/SupplierExpression.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/SupplierExpression.java @@ -16,8 +16,11 @@ package org.springframework.integration.expression; +import java.util.Objects; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; @@ -67,40 +70,43 @@ public Object getValue() throws EvaluationException { } @Override - public Object getValue(Object rootObject) throws EvaluationException { + public Object getValue(@Nullable Object rootObject) throws EvaluationException { return getValue(); } @Override - public C getValue(Class desiredResultType) throws EvaluationException { + public C getValue(@Nullable Class desiredResultType) throws EvaluationException { return getValue(this.defaultContext, desiredResultType); } @Override - public C getValue(Object rootObject, Class desiredResultType) throws EvaluationException { + public C getValue(@Nullable Object rootObject, @Nullable Class desiredResultType) + throws EvaluationException { + return getValue(this.defaultContext, rootObject, desiredResultType); } @Override public Object getValue(EvaluationContext context) throws EvaluationException { - Object root = context.getRootObject().getValue(); - return root == null ? getValue() : getValue(root); + return getValue(); } @Override - public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException { - return getValue(rootObject); + public Object getValue(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { + return getValue(); } @Override - public C getValue(EvaluationContext context, Class desiredResultType) throws EvaluationException { - return ExpressionUtils.convertTypedValue(context, new TypedValue(getValue(context)), desiredResultType); + public C getValue(EvaluationContext context, @Nullable Class desiredResultType) throws EvaluationException { + C value = ExpressionUtils.convertTypedValue(context, new TypedValue(getValue()), desiredResultType); + return Objects.requireNonNull(value); } @Override - public C getValue(EvaluationContext context, Object rootObject, Class desiredResultType) + public C getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class desiredResultType) throws EvaluationException { - return ExpressionUtils.convertTypedValue(context, new TypedValue(getValue(rootObject)), desiredResultType); + + return getValue(context, desiredResultType); } @Override @@ -109,7 +115,7 @@ public Class getValueType() throws EvaluationException { } @Override - public Class getValueType(Object rootObject) throws EvaluationException { + public Class getValueType(@Nullable Object rootObject) throws EvaluationException { throw readOnlyException(); } @@ -119,7 +125,7 @@ public Class getValueType(EvaluationContext context) throws EvaluationExcepti } @Override - public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { + public Class getValueType(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { throw readOnlyException(); } @@ -129,7 +135,7 @@ public TypeDescriptor getValueTypeDescriptor() throws EvaluationException { } @Override - public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { + public TypeDescriptor getValueTypeDescriptor(@Nullable Object rootObject) throws EvaluationException { throw readOnlyException(); } @@ -139,23 +145,26 @@ public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws E } @Override - public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { + throw readOnlyException(); } @Override - public void setValue(EvaluationContext context, Object value) throws EvaluationException { + public void setValue(EvaluationContext context, @Nullable Object value) throws EvaluationException { throw readOnlyException(); } @Override - public void setValue(Object rootObject, Object value) throws EvaluationException { + public void setValue(@Nullable Object rootObject, @Nullable Object value) throws EvaluationException { throw readOnlyException(); } @Override - public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { + public void setValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Object value) + throws EvaluationException { + throw readOnlyException(); } @@ -170,12 +179,12 @@ public boolean isWritable(EvaluationContext context) throws EvaluationException } @Override - public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { + public boolean isWritable(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { return false; } @Override - public boolean isWritable(Object rootObject) throws EvaluationException { + public boolean isWritable(@Nullable Object rootObject) throws EvaluationException { return false; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/ValueExpression.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ValueExpression.java index 899ca52ac78..21f10844be0 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/expression/ValueExpression.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ValueExpression.java @@ -16,11 +16,16 @@ package org.springframework.integration.expression; +import java.util.Objects; + +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.TypedValue; +import org.springframework.expression.common.ExpressionUtils; import org.springframework.util.Assert; /** @@ -47,7 +52,7 @@ public class ValueExpression implements Expression { private final TypeDescriptor typeDescriptor; - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "NullAway"}) public ValueExpression(V value) { Assert.notNull(value, "'value' must not be null"); this.value = value; @@ -62,7 +67,7 @@ public V getValue() throws EvaluationException { } @Override - public V getValue(Object rootObject) throws EvaluationException { + public V getValue(@Nullable Object rootObject) throws EvaluationException { return this.value; } @@ -72,30 +77,34 @@ public V getValue(EvaluationContext context) throws EvaluationException { } @Override - public V getValue(EvaluationContext context, Object rootObject) throws EvaluationException { + public V getValue(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { return this.value; } @Override - public T getValue(Object rootObject, Class desiredResultType) throws EvaluationException { + public T getValue(@Nullable Object rootObject, @Nullable Class desiredResultType) + throws EvaluationException { + return getValue(desiredResultType); } @Override - public T getValue(Class desiredResultType) throws EvaluationException { - return org.springframework.expression.common.ExpressionUtils - .convertTypedValue(null, this.typedResultValue, desiredResultType); + public T getValue(@Nullable Class desiredResultType) throws EvaluationException { + T aValue = ExpressionUtils.convertTypedValue(null, this.typedResultValue, desiredResultType); + return Objects.requireNonNull(aValue); } @Override - public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { + public T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class desiredResultType) + throws EvaluationException { + return getValue(context, desiredResultType); } @Override - public T getValue(EvaluationContext context, Class desiredResultType) throws EvaluationException { - return org.springframework.expression.common.ExpressionUtils - .convertTypedValue(context, this.typedResultValue, desiredResultType); + public T getValue(EvaluationContext context, @Nullable Class desiredResultType) throws EvaluationException { + T aValue = ExpressionUtils.convertTypedValue(context, this.typedResultValue, desiredResultType); + return Objects.requireNonNull(aValue); } @Override @@ -104,7 +113,7 @@ public Class getValueType() throws EvaluationException { } @Override - public Class getValueType(Object rootObject) throws EvaluationException { + public Class getValueType(@Nullable Object rootObject) throws EvaluationException { return this.aClass; } @@ -114,7 +123,7 @@ public Class getValueType(EvaluationContext context) throws EvaluationExcepti } @Override - public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { + public Class getValueType(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { return this.aClass; } @@ -124,7 +133,7 @@ public TypeDescriptor getValueTypeDescriptor() throws EvaluationException { } @Override - public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { + public TypeDescriptor getValueTypeDescriptor(@Nullable Object rootObject) throws EvaluationException { return this.typeDescriptor; } @@ -134,7 +143,9 @@ public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws E } @Override - public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException { + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, @Nullable Object rootObject) + throws EvaluationException { + return this.typeDescriptor; } @@ -144,27 +155,29 @@ public boolean isWritable(EvaluationContext context) throws EvaluationException } @Override - public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { + public boolean isWritable(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { return false; } @Override - public boolean isWritable(Object rootObject) throws EvaluationException { + public boolean isWritable(@Nullable Object rootObject) throws EvaluationException { return false; } @Override - public void setValue(EvaluationContext context, Object value) throws EvaluationException { + public void setValue(EvaluationContext context, @Nullable Object value) throws EvaluationException { setValue(context, null, value); } @Override - public void setValue(Object rootObject, Object value) throws EvaluationException { - setValue(null, rootObject, value); + public void setValue(@Nullable Object rootObject, @Nullable Object value) throws EvaluationException { + throw new EvaluationException(this.value.toString(), "Cannot call setValue() on a ValueExpression"); } @Override - public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { + public void setValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Object value) + throws EvaluationException { + throw new EvaluationException(this.value.toString(), "Cannot call setValue() on a ValueExpression"); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/package-info.java index 06cd283d618..ed253249a03 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/expression/package-info.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/package-info.java @@ -1,5 +1,5 @@ /** * Provides classes supporting SpEL expressions. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.integration.expression; diff --git a/spring-integration-graphql/src/main/java/org/springframework/integration/graphql/outbound/GraphQlMessageHandler.java b/spring-integration-graphql/src/main/java/org/springframework/integration/graphql/outbound/GraphQlMessageHandler.java index 3db278a5ebb..8c0487b654c 100644 --- a/spring-integration-graphql/src/main/java/org/springframework/integration/graphql/outbound/GraphQlMessageHandler.java +++ b/spring-integration-graphql/src/main/java/org/springframework/integration/graphql/outbound/GraphQlMessageHandler.java @@ -30,7 +30,6 @@ import org.springframework.graphql.support.DefaultExecutionGraphQlRequest; import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.expression.SupplierExpression; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.messaging.Message; import org.springframework.util.Assert; @@ -52,13 +51,13 @@ public class GraphQlMessageHandler extends AbstractReplyProducingMessageHandler private @Nullable Expression operationExpression; - private Expression operationNameExpression = new SupplierExpression<>(() -> null); + private @Nullable Expression operationNameExpression = null; - private Expression variablesExpression = new SupplierExpression<>(() -> null); + private @Nullable Expression variablesExpression = null; private @Nullable Locale locale; - @SuppressWarnings("NullAway") // In most cases the message really comes with an ID + @SuppressWarnings("NullAway") // Cannot determine that function result could be null private Expression executionIdExpression = new FunctionExpression>(message -> message.getHeaders().getId()); @@ -164,12 +163,20 @@ private String evaluateOperationExpression(Message message) { } private @Nullable String evaluateOperationNameExpression(Message message) { - return this.operationNameExpression.getValue(this.evaluationContext, message, String.class); + if (this.operationNameExpression != null) { + return this.operationNameExpression.getValue(this.evaluationContext, message, String.class); + } + + return null; } @SuppressWarnings("unchecked") private @Nullable Map evaluateVariablesExpression(Message message) { - return this.variablesExpression.getValue(this.evaluationContext, message, Map.class); + if (this.variablesExpression != null) { + return this.variablesExpression.getValue(this.evaluationContext, message, Map.class); + } + + return null; } private @Nullable String evaluateExecutionIdExpression(Message message) { diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java index 9232f645e98..34d2cad663d 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java @@ -226,7 +226,7 @@ protected WebServiceTemplate getWebServiceTemplate() { return this.destinationProvider.getDestination(); } - Map uriVariables = + Map uriVariables = ExpressionEvalMap.from(this.uriVariableExpressions) .usingEvaluationContext(this.evaluationContext) .withRoot(requestMessage)