diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/condition/package-info.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/condition/package-info.java new file mode 100644 index 00000000000..310d3b98a8d --- /dev/null +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/condition/package-info.java @@ -0,0 +1,5 @@ +/** + * JUnit Jupiter conditions. + */ +@org.jspecify.annotations.NullMarked +package org.springframework.integration.test.condition; diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/context/TestApplicationContextAware.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/context/TestApplicationContextAware.java index 1547cd3152f..544377352a9 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/context/TestApplicationContextAware.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/context/TestApplicationContextAware.java @@ -16,6 +16,8 @@ package org.springframework.integration.test.context; +import java.util.Objects; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -54,7 +56,7 @@ static void beforeAll() { TEST_INTEGRATION_CONTEXT.refresh(); } catch (IllegalStateException ex) { - if (!ex.getMessage().contains("just call 'refresh' once")) { + if (!Objects.requireNonNull(ex.getMessage()).contains("just call 'refresh' once")) { throw ex; } } diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/context/package-info.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/context/package-info.java new file mode 100644 index 00000000000..2822bd1a56a --- /dev/null +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/context/package-info.java @@ -0,0 +1,5 @@ +/** + * Test context support classes. + */ +@org.jspecify.annotations.NullMarked +package org.springframework.integration.test.context; diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/mail/package-info.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/mail/package-info.java deleted file mode 100644 index 703ec873443..00000000000 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/mail/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Provides testing utils for mail support. - */ -package org.springframework.integration.test.mail; diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/matcher/PayloadAndHeaderMatcher.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/matcher/PayloadAndHeaderMatcher.java index 034e22678ad..2d77461227f 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/matcher/PayloadAndHeaderMatcher.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/matcher/PayloadAndHeaderMatcher.java @@ -22,6 +22,7 @@ import org.hamcrest.BaseMatcher; import org.hamcrest.Description; +import org.jspecify.annotations.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; @@ -56,13 +57,13 @@ public final class PayloadAndHeaderMatcher extends BaseMatcher> { private final Map headers; - private final String[] ignoreKeys; + private final String @Nullable [] ignoreKeys; public static

PayloadAndHeaderMatcher

sameExceptIgnorableHeaders(Message

expected, String... ignoreKeys) { return new PayloadAndHeaderMatcher<>(expected, ignoreKeys); } - private PayloadAndHeaderMatcher(Message expected, String... ignoreKeys) { + private PayloadAndHeaderMatcher(Message expected, String @Nullable ... ignoreKeys) { this.ignoreKeys = ignoreKeys != null ? Arrays.copyOf(ignoreKeys, ignoreKeys.length) : null; this.payload = expected.getPayload(); this.headers = extractHeadersToAssert(expected); diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/matcher/package-info.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/matcher/package-info.java index e8147cf290a..57666e02895 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/matcher/package-info.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/matcher/package-info.java @@ -1,4 +1,5 @@ /** * Provides several {@link org.hamcrest.BaseMatcher} implementations. */ +@org.jspecify.annotations.NullMarked package org.springframework.integration.test.matcher; diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/predicate/package-info.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/predicate/package-info.java new file mode 100644 index 00000000000..0a6ba3ba42b --- /dev/null +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/predicate/package-info.java @@ -0,0 +1,5 @@ +/** + * AssertJ predicates. + */ +@org.jspecify.annotations.NullMarked +package org.springframework.integration.test.predicate; diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractRequestResponseScenarioTest.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractRequestResponseScenarioTest.java index eb620bbbb11..24f6748150e 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractRequestResponseScenarioTest.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractRequestResponseScenarioTest.java @@ -17,6 +17,7 @@ package org.springframework.integration.test.support; import java.util.List; +import java.util.Objects; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -54,14 +55,15 @@ @DirtiesContext public abstract class AbstractRequestResponseScenarioTest { - private List scenarios = null; + @SuppressWarnings("NullAway.Init") + private List scenarios; @Autowired private ApplicationContext applicationContext; @BeforeEach public void setUp() { - scenarios = defineRequestResponseScenarios(); + this.scenarios = defineRequestResponseScenarios(); } /** @@ -79,8 +81,9 @@ public void testRequestResponseScenarios() { MessageChannel.class); MessageChannel outputChannel = applicationContext.getBean(scenario.getOutputChannelName(), MessageChannel.class); - if (outputChannel instanceof SubscribableChannel) { - ((SubscribableChannel) outputChannel).subscribe(scenario.getResponseValidator()); + AbstractResponseValidator responseValidator = scenario.getResponseValidator(); + if (outputChannel instanceof SubscribableChannel subscribableChannel) { + subscribableChannel.subscribe(responseValidator); } assertThat(inputChannel.send(scenario.getMessage())) @@ -89,14 +92,14 @@ public void testRequestResponseScenarios() { if (outputChannel instanceof PollableChannel) { Message response = ((PollableChannel) outputChannel).receive(10000); // NOSONAR magic number assertThat(response).as(name + ": receive timeout on " + scenario.getOutputChannelName()).isNotNull(); - scenario.getResponseValidator().handleMessage(response); + responseValidator.handleMessage(Objects.requireNonNull(response)); } - assertThat(scenario.getResponseValidator().getLastMessage()) + assertThat(responseValidator.getLastMessage()) .as("message was not handled on " + outputChannel + " for scenario '" + name + "'.").isNotNull(); if (outputChannel instanceof SubscribableChannel) { - ((SubscribableChannel) outputChannel).unsubscribe(scenario.getResponseValidator()); + ((SubscribableChannel) outputChannel).unsubscribe(responseValidator); } } } diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractRequestResponseScenarioTests.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractRequestResponseScenarioTests.java index e332adbede6..8ceb18e518c 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractRequestResponseScenarioTests.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractRequestResponseScenarioTests.java @@ -17,6 +17,7 @@ package org.springframework.integration.test.support; import java.util.List; +import java.util.Objects; import org.junit.Before; import org.junit.Test; @@ -56,14 +57,15 @@ @Deprecated(since = "7.0", forRemoval = true) public abstract class AbstractRequestResponseScenarioTests { - private List scenarios = null; + @SuppressWarnings("NullAway.Init") + private List scenarios; @Autowired private ApplicationContext applicationContext; @Before public void setUp() { - scenarios = defineRequestResponseScenarios(); + this.scenarios = defineRequestResponseScenarios(); } /** @@ -91,7 +93,7 @@ public void testRequestResponseScenarios() { if (outputChannel instanceof PollableChannel) { Message response = ((PollableChannel) outputChannel).receive(10000); // NOSONAR magic number assertThat(response).as(name + ": receive timeout on " + scenario.getOutputChannelName()).isNotNull(); - scenario.getResponseValidator().handleMessage(response); + scenario.getResponseValidator().handleMessage(Objects.requireNonNull(response)); } assertThat(scenario.getResponseValidator().getLastMessage()) diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractResponseValidator.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractResponseValidator.java index 5d6c1413b22..4300b446abf 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractResponseValidator.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/AbstractResponseValidator.java @@ -16,18 +16,22 @@ package org.springframework.integration.test.support; +import org.jspecify.annotations.Nullable; + import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; /** * The base class for response validators used for {@link RequestResponseScenario}s + * * @author David Turanski + * @author Artem Bilan * */ public abstract class AbstractResponseValidator implements MessageHandler { - private Message lastMessage; + private @Nullable Message lastMessage; /** * handle the message @@ -39,6 +43,13 @@ public void handleMessage(Message message) throws MessagingException { validateResponse((T) (extractPayload() ? message.getPayload() : message)); } + /** + * @return the lastMessage + */ + public @Nullable Message getLastMessage() { + return this.lastMessage; + } + /** * Implement this method to validate the response (Message or Payload) * @param response The response. @@ -51,11 +62,4 @@ public void handleMessage(Message message) throws MessagingException { */ protected abstract boolean extractPayload(); - /** - * @return the lastMessage - */ - public Message getLastMessage() { - return lastMessage; - } - } diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/RequestResponseScenario.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/RequestResponseScenario.java index e17bd73bfce..1ffd88db8f2 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/RequestResponseScenario.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/RequestResponseScenario.java @@ -16,6 +16,10 @@ package org.springframework.integration.test.support; +import java.util.Objects; + +import org.jspecify.annotations.Nullable; + import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; import org.springframework.util.Assert; @@ -23,7 +27,9 @@ /** * Defines a Spring Integration request response test scenario. All setter methods may * be chained. + * * @author David Turanski + * @author Artem Bilan * */ public class RequestResponseScenario { @@ -32,20 +38,21 @@ public class RequestResponseScenario { private final String outputChannelName; - private Object payload; + private @Nullable Object payload; - private Message message; + private @Nullable Message message; + @SuppressWarnings("NullAway.Init") private AbstractResponseValidator responseValidator; - private String name; + private @Nullable String name; - protected Message getMessage() { - if (message == null) { - return new GenericMessage(this.payload); + protected Message getMessage() { + if (this.message == null) { + return new GenericMessage<>(Objects.requireNonNull(this.payload)); } else { - return message; + return this.message; } } @@ -64,7 +71,7 @@ public RequestResponseScenario(String inputChannelName, String outputChannelName * @return the input channel name */ public String getInputChannelName() { - return inputChannelName; + return this.inputChannelName; } /** @@ -72,15 +79,15 @@ public String getInputChannelName() { * @return the output channel name */ public String getOutputChannelName() { - return outputChannelName; + return this.outputChannelName; } /** * * @return the request message payload */ - public Object getPayload() { - return payload; + public @Nullable Object getPayload() { + return this.payload; } /** @@ -97,8 +104,8 @@ public RequestResponseScenario setPayload(Object payload) { * * @return the scenario name */ - public String getName() { - return name; + public @Nullable String getName() { + return this.name; } /** @@ -117,7 +124,7 @@ public RequestResponseScenario setName(String name) { * @see AbstractResponseValidator */ public AbstractResponseValidator getResponseValidator() { - return responseValidator; + return this.responseValidator; } /** @@ -142,7 +149,10 @@ public RequestResponseScenario setMessage(Message message) { } protected void init() { - Assert.state(message == null || payload == null, "cannot set both message and payload"); + Assert.state(this.message == null || this.payload == null, "cannot set both message and payload"); + Assert.state(this.responseValidator != null, + "A 'responseValidator' must be provided for the 'SubscribableChannel' scenario."); + } } diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/package-info.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/package-info.java index 8b280c81c8f..d9ae13545f2 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/package-info.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/support/package-info.java @@ -2,4 +2,5 @@ * Provides several test support classes including for testing Spring Integration * request-response message scenarios. */ +@org.jspecify.annotations.NullMarked package org.springframework.integration.test.support; diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/OnlyOnceTrigger.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/OnlyOnceTrigger.java index f97d3a48843..819dc2782c1 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/OnlyOnceTrigger.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/OnlyOnceTrigger.java @@ -21,6 +21,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.jspecify.annotations.Nullable; + import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; @@ -52,7 +54,7 @@ public OnlyOnceTrigger() { } @Override - public Instant nextExecution(TriggerContext triggerContext) { + public @Nullable Instant nextExecution(TriggerContext triggerContext) { if (this.hasRun.getAndSet(true)) { this.latch.countDown(); return null; @@ -61,6 +63,23 @@ public Instant nextExecution(TriggerContext triggerContext) { return this.executionTime; } + public void reset() { + this.latch = new CountDownLatch(1); + this.hasRun.set(false); + } + + public void await() { + try { + if (!this.latch.await(10000, TimeUnit.MILLISECONDS)) { + throw new IllegalStateException("test latch.await() did not count down"); + } + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("test latch.await() interrupted", ex); + } + } + @Override public int hashCode() { final int prime = 31; @@ -84,21 +103,4 @@ public boolean equals(Object obj) { return this.executionTime.equals(other.executionTime); } - public void reset() { - this.latch = new CountDownLatch(1); - this.hasRun.set(false); - } - - public void await() { - try { - if (!this.latch.await(10000, TimeUnit.MILLISECONDS)) { // NOSONAR magic number - throw new IllegalStateException("test latch.await() did not count down"); - } - } - catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("test latch.await() interrupted", ex); - } - } - } diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/TestUtils.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/TestUtils.java index a6a7ed0698c..bea69ea7ac6 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/TestUtils.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/TestUtils.java @@ -33,6 +33,7 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; +import org.jspecify.annotations.Nullable; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.BeanFactory; @@ -41,7 +42,6 @@ import org.springframework.context.support.GenericApplicationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.format.support.DefaultFormattingConversionService; -import org.springframework.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessagingException; @@ -77,7 +77,7 @@ public abstract class TestUtils { * @see DirectFieldAccessor */ @SuppressWarnings("unchecked") - public static T getPropertyValue(Object root, String propertyPath, Class type) { + public static @Nullable T getPropertyValue(Object root, String propertyPath, Class type) { Object value = getPropertyValue(root, propertyPath); if (value != null) { Assert.isAssignable(type, value.getClass()); @@ -94,7 +94,7 @@ public static T getPropertyValue(Object root, String propertyPath, Class * @return the value of the property or null * @see DirectFieldAccessor */ - public static Object getPropertyValue(Object root, String propertyPath) { + public static @Nullable Object getPropertyValue(Object root, String propertyPath) { Object value = null; DirectFieldAccessor accessor = new DirectFieldAccessor(root); String[] tokens = propertyPath.split("\\."); @@ -170,7 +170,7 @@ public static class TestApplicationContext extends GenericApplicationContext { TestApplicationContext() { } - public void registerChannel(@Nullable String channelNameArg, final MessageChannel channel) { + public void registerChannel(@Nullable String channelNameArg, MessageChannel channel) { String channelName = channelNameArg; String componentName = getComponentNameIfNamed(channel); if (componentName != null) { @@ -182,6 +182,7 @@ public void registerChannel(@Nullable String channelNameArg, final MessageChanne "channel name has already been set with a conflicting value"); } } + Assert.notNull(channelName, "The 'channelName' must be provided, or 'MessageChannel' must be named already"); TestUtils.registerBean(channelName, channel, this); } @@ -193,9 +194,9 @@ public void registerBean(String beanName, Object bean) { TestUtils.registerBean(beanName, bean, this); } - private String getComponentNameIfNamed(final MessageChannel channel) { + private @Nullable String getComponentNameIfNamed(MessageChannel channel) { Set> interfaces = ClassUtils.getAllInterfacesAsSet(channel); - final AtomicReference componentName = new AtomicReference<>(); + final AtomicReference<@Nullable String> componentName = new AtomicReference<>(); for (Class intface : interfaces) { if ("org.springframework.integration.support.context.NamedComponent".equals(intface.getName())) { ReflectionUtils.doWithMethods(channel.getClass(), method -> { @@ -220,7 +221,7 @@ private String getComponentNameIfNamed(final MessageChannel channel) { * @param startingIndex the index to start scanning * @return the properties provided by the named component or null if none available */ - public static Properties locateComponentInHistory(List history, String componentName, + public static @Nullable Properties locateComponentInHistory(List history, String componentName, int startingIndex) { Assert.notNull(history, "'history' must not be null"); @@ -288,7 +289,7 @@ public void handleError(Throwable throwable) { } - private MessageChannel resolveErrorChannel(Throwable t) { + private @Nullable MessageChannel resolveErrorChannel(Throwable t) { if (t instanceof MessagingException) { Message failedMessage = ((MessagingException) t).getFailedMessage(); if (failedMessage == null) { diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/package-info.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/package-info.java index 0b0fd5535e4..6b618911689 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/package-info.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/package-info.java @@ -3,4 +3,5 @@ * {@link org.springframework.integration.test.util.TestUtils} provides convenience * helpers to easily retrieve private bean properties. */ +@org.jspecify.annotations.NullMarked package org.springframework.integration.test.util;