-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Introduce NullMarked to some packages in spring-integration-core module #10097
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very cool!
Let's see if we can agree on my feedback.
Thanks
@@ -117,6 +119,7 @@ protected Map<String, Object> aggregateHeaders(MessageGroup group) { | |||
return getHeadersFunction().apply(group); | |||
} | |||
|
|||
@Nullable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is OK.
The payload
cannot be null
for the message we are going to produce from an aggregator.
If it is potentially null in the specific implementation, let's address there, e.g. with an Assert.state()
!
@@ -641,8 +652,10 @@ protected boolean isExpireGroupsUponCompletion() { | |||
} | |||
|
|||
private void removeEmptyGroupAfterTimeout(UUID groupId, long timeout) { | |||
TaskScheduler taskScheduler = getTaskScheduler(); | |||
Assert.notNull(taskScheduler, "'taskScheduler' must not be null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably need to address this in the IntegrationObjectSupport.getTaskScheduler()
.
Make it really non-null and have this Assert.notNull
over there in the end of method.
@@ -58,6 +60,7 @@ public void setExpectedType(Class<?> expectedType) { | |||
* {@link org.springframework.integration.core.MessagingTemplate} to send downstream. | |||
*/ | |||
@Override | |||
@Nullable | |||
protected Object aggregatePayloads(MessageGroup group, Map<String, Object> headers) { | |||
return this.processor.process(group.getMessages()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's what I mean. The SpEL may return null, indeed.
But that is not what we expect here.
Let's assert its result before returning!
Or do you see some use-case where it is deliberately returned as null?
private Predicate<Message<?>> boundaryTrigger; | ||
|
||
private Function<Message<?>, Integer> windowSizeFunction = FluxAggregatorMessageHandler::sequenceSizeHeader; | ||
@Nullable | ||
public Function<Message<?>, Integer> windowSizeFunction = FluxAggregatorMessageHandler::sequenceSizeHeader; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not correct. The @Nullable
on the property type would mean that FluxAggregatorMessageHandler::sequenceSizeHeader
lambda is null.
However that is not the case.
Here we are talking about result of message.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, Integer.class)
.
Therefore it is null
for function invocation.
Hence, has to be like this:
private Function<Message<?>, @Nullable Integer> windowSizeFunction = FluxAggregatorMessageHandler::sequenceSizeHeader;
I'm not sure why have you made this property as public
, though...
@@ -99,13 +109,15 @@ private Flux<Message<?>> releaseBy(Flux<Message<?>> groupFlux) { | |||
.flatMap((windowFlux) -> windowFlux.transform(this.combineFunction)); | |||
} | |||
|
|||
@NullUnmarked |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you do Function<Message<?>, @Nullable Integer>
, we won't need this anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even after the change has been applied the following line in the method is getting flagged:
https://github.com/spring-projects/spring-integration/blob/main/spring-integration-core/src/main/java/org/springframework/integration/aggregator/FluxAggregatorMessageHandler.java#L109
This is because the R apply(T t);
from the Function.java has not been annotated with @nullable.
Let's discuss to see if their is a smoother resolution than the @NullMarked
@@ -27,6 +29,7 @@ | |||
@FunctionalInterface | |||
public interface MessageListProcessor { | |||
|
|||
@Nullable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this one has to be non-null as well.
@@ -128,6 +131,7 @@ public static MetadataStore getMetadataStore(BeanFactory beanFactory) { | |||
* @param beanFactory BeanFactory for lookup, must not be null. | |||
* @return The {@link MessageChannel} bean whose name is "errorChannel". | |||
*/ | |||
@Nullable | |||
public static MessageChannel getErrorChannel(BeanFactory beanFactory) { | |||
return getBeanOfType(beanFactory, ERROR_CHANNEL_BEAN_NAME, MessageChannel.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This bean is always there in the Integration infrastructure.
So, let's put an Assert.notNull
in the end of this method instead!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was not addressed
@@ -136,6 +140,7 @@ public static MessageChannel getErrorChannel(BeanFactory beanFactory) { | |||
* @param beanFactory BeanFactory for lookup, must not be null. | |||
* @return The {@link TaskScheduler} bean whose name is "taskScheduler" if available. | |||
*/ | |||
@Nullable | |||
public static TaskScheduler getTaskScheduler(BeanFactory beanFactory) { | |||
return getBeanOfType(beanFactory, TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for TaskScheduler
.
See DefaultConfiguringBeanFactoryPostProcessor
for those infra beans we populate when @EnableIntegration
is in charge.
...e/src/main/java/org/springframework/integration/aggregator/FluxAggregatorMessageHandler.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my last commit into your PR for the FluxAggregatorMessageHandler
fix.
At least that what I think about those nulls.
a6ee2ac
to
750cf77
Compare
.../java/org/springframework/integration/syslog/inbound/SyslogReceivingChannelAdapterTests.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is cool!
We can chat on Monday if you wish.
I also hope you do not update Copyright year manually . We have a Gradle task to do that for us 😉
@@ -929,6 +937,7 @@ protected Collection<Message<?>> completeGroup(Message<?> message, Object correl | |||
this.logger.debug(() -> "Completing group with correlationKey [" + correlationKey + "]"); | |||
|
|||
result = this.outputProcessor.processMessageGroup(group); | |||
Assert.notNull(result, "the group returned a null result"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that is processor is responsible for null result, not group. But again: it really can return null. Might be something is off with our logic here?
Not saying to do that right now with such a huge pr: let’s just have a note to come back to this method!
For now, fix, please, this error message .
@@ -102,7 +105,9 @@ public void setExpectedType(Class<?> expectedType) { | |||
*/ | |||
@Override | |||
public Object process(Collection<? extends Message<?>> messages) { | |||
return this.evaluateExpression(this.expression, messages, this.expectedType); | |||
Object object = this.evaluateExpression(this.expression, messages, this.expectedType); | |||
Assert.state(object != null, "Failed to evaluate expression: " + this.expression); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think it is really correct . The expression has been just evaluated to null, but we don’t expect null here. Let’s say exactly that condition: the expression evaluated to null and therefore this exception .
@@ -59,7 +60,9 @@ public void setExpectedType(Class<?> expectedType) { | |||
*/ | |||
@Override | |||
protected Object aggregatePayloads(MessageGroup group, Map<String, Object> headers) { | |||
return this.processor.process(group.getMessages()); | |||
Object object = this.processor.process(group.getMessages()); | |||
Assert.notNull(object, "Result from processor must not be null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use state()
instead. This really not an illegal arg.
@@ -87,7 +88,9 @@ public void setBeanFactory(BeanFactory beanFactory) { | |||
@Override | |||
protected final Object aggregatePayloads(MessageGroup group, Map<String, Object> headers) { | |||
final Collection<Message<?>> messagesUpForProcessing = group.getMessages(); | |||
return this.processor.process(messagesUpForProcessing, headers); | |||
Object object = this.processor.process(messagesUpForProcessing, headers); | |||
Assert.notNull(object, "Result from processor must not be null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here: state()
@@ -128,6 +131,7 @@ public static MetadataStore getMetadataStore(BeanFactory beanFactory) { | |||
* @param beanFactory BeanFactory for lookup, must not be null. | |||
* @return The {@link MessageChannel} bean whose name is "errorChannel". | |||
*/ | |||
@Nullable | |||
public static MessageChannel getErrorChannel(BeanFactory beanFactory) { | |||
return getBeanOfType(beanFactory, ERROR_CHANNEL_BEAN_NAME, MessageChannel.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was not addressed
noopPublisher(scf); | ||
interceptorsGuts(scf); | ||
scf.stop(); | ||
} | ||
|
||
private AbstractServerConnectionFactory getDefaultServerConnectionFactory() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static
and probably move to the end of class
@@ -820,5 +822,4 @@ private static AbstractClientConnectionFactory createFactoryWithMockConnection(T | |||
when(factory.isActive()).thenReturn(true); | |||
return factory; | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See if you can use https://github.com/spring-projects/spring-integration/blob/main/src/idea/spring-framework.xml in your IDEA to avoid this kind of change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was not addressed, please
@@ -698,5 +699,18 @@ private static AbstractClientConnectionFactory createFactoryWithMockConnection(T | |||
return factory; | |||
} | |||
|
|||
private TcpNetServerConnectionFactory getTcpNetServerConnectionFactory(int port) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was not addressed
return result; | ||
} | ||
|
||
private TcpNetClientConnectionFactory getTcpNetServerConnectionFactory(String host, int port) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DITTO
.thenReturn(taskScheduler); | ||
when(beanFactory.containsBean("taskScheduler")).thenReturn(true); | ||
return beanFactory; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty line before the last }
vin the class.
In other words: every member in the class has to be surrounded with blank lines.
I think the editor config for IDEA I suggested before should do the trick when you reformat files.
This is a first pass at the module to make sure it is being converted properly. Related to spring-projects#10083
…rk.acks packages Note in this commit that there cases where methods are called where the parameters passed in may contain a null. In those cases the attribute that contains the potential null is tested with an `Assert.notNull`. This may not be the best approach, let's discuss. Also note that in FluxAggregatorMessageHandler line 112 `@NullUnmarked` is used on the the applyWindowOptions method. This is because the consumer.apply on line 121 can not be null according to JSpecify. But we can't make that assumption for how the user implemented the function. There maybe a better way of handling this besides marking the method as `NullUnmarked`. Let's discuss. Similarly in FluxAggregatorMessageHandler the way in which sequenceSizeHeader is utilized it had to be `@NullUnmarked`. Let's discuss Make sure all annotations are applied consistently
* This PR is not ready for merge. There are failing tests, but wanted to capture where it is at. * Also still having issue with the .apply() in FluxAggregatorMessageHandler * Make `windowSizeFunction` as `Function<Message<?>, @nullable Integer>` because `sequenceSizeHeader()` may return `null` from message headers * Extract local `subscriptionToDispose` in the `stop()` to satisfy null check context * Use `Objects.requireNonNull(signal.get())` to satisfy `Function.apply()` contract. The `if (signal.hasValue()) {` does the trick for us, but currently that is not visible for that `signal.get()` * Remove `@NullUnmarked` since we have just mitigated all the null problems Updated tests so that they work with nullability changes Update the tests so that they will pass with nullify changes
* Remove Nullunmarked from FluxAggregatorMessageHandler * Add TaskScheuler to tests that are failing because of nullability * In the past the TaskScheduler attribute in the testscould be set to null and the tests would succeed. But because of nullability additions these need to be populated
* Added style to IDE to resolve style issues * Resolved the missed remove from IntegrationContextUtils. * Made sure that private test methods were marked static * Updated Assert.NotNulls to Assert.state and updated their messages * Rebased and updated nullification for changes
16e7b45
to
7305d7b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, there are still some review feedback.
But this is great to be merged soon enough.
Thanks
@@ -125,8 +125,10 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageP | |||
|
|||
private boolean releaseStrategySet; | |||
|
|||
@SuppressWarnings("NullAway.Init") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not correct on this property.
It is initialized in the getDiscardChannel()
.
So, it has to be @Nullable
and that getter has to be used instead in other places.
@@ -102,7 +105,9 @@ public void setExpectedType(Class<?> expectedType) { | |||
*/ | |||
@Override | |||
public Object process(Collection<? extends Message<?>> messages) { | |||
return this.evaluateExpression(this.expression, messages, this.expectedType); | |||
Object object = this.evaluateExpression(this.expression, messages, this.expectedType); | |||
Assert.state(object != null, "The evaluation of the expression returned a null. Null result is not expected." + this.expression); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably you mean: Null result is not expected: " + this.expression
?
@@ -106,7 +115,8 @@ private Flux<Flux<Message<?>>> applyWindowOptions(Flux<Message<?>> groupFlux) { | |||
return groupFlux | |||
.switchOnFirst((signal, group) -> { | |||
if (signal.hasValue()) { | |||
Integer maxSize = this.windowSizeFunction.apply(signal.get()); | |||
Assert.notNull(this.windowSizeFunction, "'windowSizeFunction' must not be null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can that be null, please?
return message.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, Integer.class); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blank line in the end of class before its last }
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright 2002-2019 the original author or authors. | |||
* Copyright 2002-2025 the original author or authors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like redundant change for now.
But that's OK.
We will move to present
instead soon enough 😄
@@ -156,6 +163,7 @@ public static TaskScheduler getRequiredTaskScheduler(BeanFactory beanFactory) { | |||
* @return the instance of {@link StandardEvaluationContext} bean whose name is | |||
* {@value #INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME}. | |||
*/ | |||
@Nullable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cannot be null.
Another infrastructure bean always present there by Integration infrastructure.
@@ -166,11 +174,13 @@ public static StandardEvaluationContext getEvaluationContext(BeanFactory beanFac | |||
* {@value #INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME}. | |||
* @since 4.3.15 | |||
*/ | |||
@Nullable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here: never null.
Therefore similar to that:
Assert.state(taskScheduler != null, "No such bean '" + INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME + "'");
And in the previous, too.
@@ -208,6 +224,7 @@ public final void setPrimaryExpression(Expression expression) { | |||
} | |||
|
|||
@Override | |||
@SuppressWarnings("NullAway") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any comments why this is still here, please?
@@ -820,5 +822,4 @@ private static AbstractClientConnectionFactory createFactoryWithMockConnection(T | |||
when(factory.isActive()).thenReturn(true); | |||
return factory; | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was not addressed, please
@@ -698,5 +699,18 @@ private static AbstractClientConnectionFactory createFactoryWithMockConnection(T | |||
return factory; | |||
} | |||
|
|||
private TcpNetServerConnectionFactory getTcpNetServerConnectionFactory(int port) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was not addressed
This is a first pass at the module to make sure it is being converted properly.
There are 3 commits that comprise this PR. Each commit handles a specific series of package(s) on the model.
This was done to simplify the review process so each package could be reviewed indepently.
Note
Highlights information that users should take into account, even when skimming. in this commit that there cases where methods are called where the parameters passed in may contain a null.
In those cases the attribute that contains the potential null is tested with an
Assert.notNull
. This may not be the best approach, let's discuss.Note
Also note that in FluxAggregatorMessageHandler line 112
@NullUnmarked
is used on the the applyWindowOptions method.This is because the consumer.apply on line 121 can not be null according to JSpecify. But we can't make that assumption for how the user implemented the fuction.
There maybe a better way of handling this besides marking the method as
NullUnmarked
. Let's discuss.Similarly in FluxAggregatorMessageHandler the way in which sequenceSizeHeader is utilized it had to be
@NullUnmarked
. Let's discussRelated to #10083