From a3d8776b5cea4e9b3db0f2f14829f4064bce759f Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Fri, 12 Sep 2014 15:18:59 +0300 Subject: [PATCH] INT-3516: Allow `Optional<>` in POJO Method Args JIRA: https://jira.spring.io/browse/INT-3516 * Add `Optional<>` support for `@Header` in the `MessagingMethodInvokerHelper` * Add `Optional<>` test-case * Change `sourceCompatibility` for test to the Java 8 INT-3516: Revert SF version to 4.1.1 Add Docs on the matter Fix WS Tests; Revert StubJavaMailSender --- build.gradle | 12 +++++-- publish-maven.gradle | 10 +++--- .../util/MessagingMethodInvokerHelper.java | 3 +- .../handler/LoggingHandlerTests.java | 17 +++++---- .../MethodInvokingMessageProcessorTests.java | 35 ++++++++++++++++++- .../integration/mail/StubJavaMailSender.java | 19 +++------- .../WebServiceOutboundGatewayParserTests.java | 18 +++++++--- src/reference/docbook/service-activator.xml | 21 ++++++++++- src/reference/docbook/whats-new.xml | 7 ++++ 9 files changed, 105 insertions(+), 37 deletions(-) diff --git a/build.gradle b/build.gradle index d6b8def74e..b716a2e451 100644 --- a/build.gradle +++ b/build.gradle @@ -63,8 +63,14 @@ subprojects { subproject -> } } - sourceCompatibility=1.6 - targetCompatibility=1.6 + compileJava { + sourceCompatibility = 1.6 + targetCompatibility = 1.6 + } + + compileTestJava { + sourceCompatibility = 1.8 + } ext { activeMqVersion = '5.10.0' @@ -118,7 +124,7 @@ subprojects { subproject -> springSecurityVersion = '3.2.5.RELEASE' springSocialTwitterVersion = '1.1.0.RELEASE' springRetryVersion = '1.1.1.RELEASE' - springVersion = project.hasProperty('springVersion') ? project.springVersion : '4.1.2.BUILD-SNAPSHOT' + springVersion = project.hasProperty('springVersion') ? project.springVersion : '4.1.1.RELEASE' springWsVersion = '2.2.0.RELEASE' xmlUnitVersion = '1.5' xstreamVersion = '1.4.7' diff --git a/publish-maven.gradle b/publish-maven.gradle index 14317e1110..6304e2dd46 100644 --- a/publish-maven.gradle +++ b/publish-maven.gradle @@ -34,7 +34,7 @@ def customizePom(pom, gradleProject) { url = linkHomepage organization { name = 'SpringIO' - url = 'https://spring.io' + url = 'http://spring.io' } licenses { license { @@ -59,24 +59,24 @@ def customizePom(pom, gradleProject) { developer { id = 'garyrussell' name = 'Gary Russell' - email = 'grussell@gopivotal.com' + email = 'grussell@pivotal.io' roles = ["project lead"] } developer { id = 'markfisher' name = 'Mark Fisher' - email = 'mfisher@gopivotal.com' + email = 'mfisher@pivotal.io' roles = ["project founder and lead emeritus"] } developer { id = 'ghillert' name = 'Gunnar Hillert' - email = 'ghillert@gopivotal.com' + email = 'ghillert@pivotal.io' } developer { id = 'abilan' name = 'Artem Bilan' - email = 'abilan@gopivotal.com' + email = 'abilan@pivotal.io' } } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java index 54e1b68010..1ab69b22db 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java @@ -738,7 +738,8 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator + "disabled or header name is not explicitly provided via @Header annotation."); String headerRetrievalExpression = "headers['" + headerName + "']"; String fullHeaderExpression = headerRetrievalExpression + relativeExpression; - String fallbackExpression = (annotationAttributes.getBoolean("required")) + String fallbackExpression = (annotationAttributes.getBoolean("required") + && !methodParameter.getParameterType().getName().equals("java.util.Optional")) ? "T(org.springframework.util.Assert).isTrue(false, 'required header not available: " + headerName + "')" : "null"; diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/LoggingHandlerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/LoggingHandlerTests.java index e50b05ea8f..2aabc4edf3 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/LoggingHandlerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/LoggingHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; +import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.integration.handler.LoggingHandler.Level; import org.springframework.messaging.support.GenericMessage; @@ -41,6 +42,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Mark Fisher + * @author Artem Bilan * @since 2.0 */ @ContextConfiguration @@ -90,12 +92,12 @@ public class LoggingHandlerTests { expression = spy(expression); accessor.setPropertyValue("expression", expression); when(log.isInfoEnabled()).thenReturn(false); - loggingHandler.handleMessage(new GenericMessage("foo")); - verify(expression, never()).getValue(Mockito.any(EvaluationContext.class), Mockito.any()); + loggingHandler.handleMessage(new GenericMessage<>("foo")); + verify(expression, never()).getValue(Mockito.any(EvaluationContext.class), Mockito.any(Message.class)); when(log.isInfoEnabled()).thenReturn(true); - loggingHandler.handleMessage(new GenericMessage("foo")); - verify(expression, times(1)).getValue(Mockito.any(EvaluationContext.class), Mockito.any()); + loggingHandler.handleMessage(new GenericMessage<>("foo")); + verify(expression, times(1)).getValue(Mockito.any(EvaluationContext.class), Mockito.any(Message.class)); } @Test @@ -106,12 +108,12 @@ public class LoggingHandlerTests { log = spy(log); accessor.setPropertyValue("messageLogger", log); when(log.isInfoEnabled()).thenReturn(true); - loggingHandler.handleMessage(new GenericMessage("foo")); + loggingHandler.handleMessage(new GenericMessage<>("foo")); verify(log, times(1)).info(Mockito.anyString()); verify(log, never()).warn(Mockito.anyString()); loggingHandler.setLevel(Level.WARN); - loggingHandler.handleMessage(new GenericMessage("foo")); + loggingHandler.handleMessage(new GenericMessage<>("foo")); verify(log, times(1)).info(Mockito.anyString()); verify(log, times(1)).warn(Mockito.anyString()); } @@ -134,6 +136,7 @@ public class LoggingHandlerTests { public int getAge() { return this.age; } + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java index c72a672e21..63cfb09c49 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java @@ -26,7 +26,9 @@ import static org.mockito.Mockito.mock; import java.lang.reflect.Method; import java.util.Date; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.Optional; import java.util.Properties; import org.apache.commons.logging.Log; @@ -40,7 +42,6 @@ import org.junit.rules.ExpectedException; import org.springframework.beans.factory.BeanFactory; import org.springframework.expression.spel.SpelEvaluationException; -import org.springframework.messaging.handler.annotation.Header; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.gateway.GatewayProxyFactoryBean; import org.springframework.integration.gateway.RequestReplyExchanger; @@ -48,6 +49,7 @@ import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.util.MessagingMethodInvokerHelper; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandlingException; +import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.support.GenericMessage; @@ -58,6 +60,7 @@ import org.springframework.messaging.support.GenericMessage; * @author Dave Syer * @author Gary Russell * @author Gunnar Hillert + * @author Artem Bilan */ @SuppressWarnings({"rawtypes", "unchecked"}) public class MethodInvokingMessageProcessorTests { @@ -517,6 +520,36 @@ public class MethodInvokingMessageProcessorTests { assertEquals("true", bean.lastArg); } + @Test + public void testOptionalArgs() throws Exception { + class Foo { + + private final Map arguments = new LinkedHashMap(); + + public void optionalHeaders(Optional foo, @Header(value="foo", required=false) String foo1, + @Header(value="foo") Optional foo2) { + this.arguments.put("foo", (foo.isPresent() ? foo.get() : null)); + this.arguments.put("foo1", foo1); + this.arguments.put("foo2", (foo2.isPresent() ? foo2.get() : null)); + } + + } + + Foo targetObject = new Foo(); + + MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false); + + helper.process(new GenericMessage<>(Optional.empty())); + assertNull(targetObject.arguments.get("foo")); + assertNull(targetObject.arguments.get("foo1")); + assertNull(targetObject.arguments.get("foo2")); + + helper.process(MessageBuilder.withPayload("foo").setHeader("foo", "FOO").build()); + assertEquals("foo", targetObject.arguments.get("foo")); + assertEquals("FOO", targetObject.arguments.get("foo1")); + assertEquals("FOO", targetObject.arguments.get("foo2")); + } + private static class ExceptionCauseMatcher extends TypeSafeMatcher { private Throwable cause; diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/StubJavaMailSender.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/StubJavaMailSender.java index 5e10768f9a..b77da51ed9 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/StubJavaMailSender.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/StubJavaMailSender.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2007 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,11 +30,10 @@ import org.springframework.mail.javamail.MimeMessagePreparator; /** * @author Marius Bogoevici - * @author Gary Russell */ public class StubJavaMailSender implements JavaMailSender { - private final MimeMessage uniqueMessage; + private MimeMessage uniqueMessage; private final List sentMimeMessages = new ArrayList(); @@ -54,43 +53,35 @@ public class StubJavaMailSender implements JavaMailSender { return this.sentSimpleMailMessages; } - @Override public MimeMessage createMimeMessage() { return this.uniqueMessage; } - @Override public MimeMessage createMimeMessage(InputStream contentStream) throws MailException { return this.uniqueMessage; } - @Override public void send(MimeMessage mimeMessage) throws MailException { this.sentMimeMessages.add(mimeMessage); } - @Override - public void send(MimeMessage... mimeMessages) throws MailException { + public void send(MimeMessage[] mimeMessages) throws MailException { this.sentMimeMessages.addAll(Arrays.asList(mimeMessages)); } - @Override public void send(MimeMessagePreparator mimeMessagePreparator) throws MailException { throw new UnsupportedOperationException("MimeMessagePreparator not supported"); } - @Override - public void send(MimeMessagePreparator... mimeMessagePreparators) throws MailException { + public void send(MimeMessagePreparator[] mimeMessagePreparators) throws MailException { throw new UnsupportedOperationException("MimeMessagePreparator not supported"); } - @Override public void send(SimpleMailMessage simpleMessage) throws MailException { this.sentSimpleMailMessages.add(simpleMessage); } - @Override - public void send(SimpleMailMessage... simpleMessages) throws MailException { + public void send(SimpleMailMessage[] simpleMessages) throws MailException { this.sentSimpleMailMessages.addAll(Arrays.asList(simpleMessages)); } diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java index 5edf01d9d9..7be4f4ed08 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java @@ -16,12 +16,20 @@ package org.springframework.integration.ws.config; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import org.junit.Assert; import org.junit.Test; +import org.mockito.Matchers; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; @@ -412,7 +420,7 @@ public class WebServiceOutboundGatewayParserTests { doReturn(null).when(webServiceTemplate).sendAndReceive(anyString(), any(WebServiceMessageCallback.class), - any(WebServiceMessageExtractor.class)); + Matchers.>any()); new DirectFieldAccessor(handler).setPropertyValue("webServiceTemplate", webServiceTemplate); @@ -420,7 +428,7 @@ public class WebServiceOutboundGatewayParserTests { verify(webServiceTemplate).sendAndReceive(eq("jms:wsQueue"), any(WebServiceMessageCallback.class), - any(WebServiceMessageExtractor.class)); + Matchers.>any()); } @Test(expected = BeanDefinitionParsingException.class) diff --git a/src/reference/docbook/service-activator.xml b/src/reference/docbook/service-activator.xml index 1a423a85fc..a29c48808c 100644 --- a/src/reference/docbook/service-activator.xml +++ b/src/reference/docbook/service-activator.xml @@ -53,7 +53,26 @@ not worrying about the contents of the message. Think of it as a NULL JMS message. An example use-case for such an implementation could be a simple counter/monitor of messages deposited on the input channel. - + + Starting with version 4.1 the framework correct converts Message properties + (payload and headers) to the Java 8 Optional POJO method + parameters: + payload, + @Header(value="foo", required=false) String foo1, + @Header(value="foo") Optional foo2) { + if (payload.isPresent()) { + String value = payload.get(); + ... + } + else { + ... + } + } + +}]]> + + Using a "ref" attribute is generally recommended if the custom Service Activator handler implementation can be reused in other <service-activator> definitions. However if the custom Service Activator handler implementation is only used within a single definition of the <service-activator>, you can provide an inner bean definition: diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 21fcb4f525..fca87c7418 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -230,5 +230,12 @@ See . +
+ Optional POJO method parameter + + Now Spring Integration consistently handles the Java 8's Optional type. + See . + +