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
This commit is contained in:
Artem Bilan
2014-09-12 15:18:59 +03:00
committed by Gary Russell
parent c84b16e2b5
commit a3d8776b5c
9 changed files with 105 additions and 37 deletions

View File

@@ -63,8 +63,14 @@ subprojects { subproject ->
} }
} }
sourceCompatibility=1.6 compileJava {
targetCompatibility=1.6 sourceCompatibility = 1.6
targetCompatibility = 1.6
}
compileTestJava {
sourceCompatibility = 1.8
}
ext { ext {
activeMqVersion = '5.10.0' activeMqVersion = '5.10.0'
@@ -118,7 +124,7 @@ subprojects { subproject ->
springSecurityVersion = '3.2.5.RELEASE' springSecurityVersion = '3.2.5.RELEASE'
springSocialTwitterVersion = '1.1.0.RELEASE' springSocialTwitterVersion = '1.1.0.RELEASE'
springRetryVersion = '1.1.1.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' springWsVersion = '2.2.0.RELEASE'
xmlUnitVersion = '1.5' xmlUnitVersion = '1.5'
xstreamVersion = '1.4.7' xstreamVersion = '1.4.7'

View File

@@ -34,7 +34,7 @@ def customizePom(pom, gradleProject) {
url = linkHomepage url = linkHomepage
organization { organization {
name = 'SpringIO' name = 'SpringIO'
url = 'https://spring.io' url = 'http://spring.io'
} }
licenses { licenses {
license { license {
@@ -59,24 +59,24 @@ def customizePom(pom, gradleProject) {
developer { developer {
id = 'garyrussell' id = 'garyrussell'
name = 'Gary Russell' name = 'Gary Russell'
email = 'grussell@gopivotal.com' email = 'grussell@pivotal.io'
roles = ["project lead"] roles = ["project lead"]
} }
developer { developer {
id = 'markfisher' id = 'markfisher'
name = 'Mark Fisher' name = 'Mark Fisher'
email = 'mfisher@gopivotal.com' email = 'mfisher@pivotal.io'
roles = ["project founder and lead emeritus"] roles = ["project founder and lead emeritus"]
} }
developer { developer {
id = 'ghillert' id = 'ghillert'
name = 'Gunnar Hillert' name = 'Gunnar Hillert'
email = 'ghillert@gopivotal.com' email = 'ghillert@pivotal.io'
} }
developer { developer {
id = 'abilan' id = 'abilan'
name = 'Artem Bilan' name = 'Artem Bilan'
email = 'abilan@gopivotal.com' email = 'abilan@pivotal.io'
} }
} }
} }

View File

@@ -738,7 +738,8 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
+ "disabled or header name is not explicitly provided via @Header annotation."); + "disabled or header name is not explicitly provided via @Header annotation.");
String headerRetrievalExpression = "headers['" + headerName + "']"; String headerRetrievalExpression = "headers['" + headerName + "']";
String fullHeaderExpression = headerRetrievalExpression + relativeExpression; 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: " ? "T(org.springframework.util.Assert).isTrue(false, 'required header not available: "
+ headerName + "')" + headerName + "')"
: "null"; : "null";

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageChannel;
import org.springframework.integration.handler.LoggingHandler.Level; import org.springframework.integration.handler.LoggingHandler.Level;
import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.GenericMessage;
@@ -41,6 +42,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/** /**
* @author Mark Fisher * @author Mark Fisher
* @author Artem Bilan
* @since 2.0 * @since 2.0
*/ */
@ContextConfiguration @ContextConfiguration
@@ -90,12 +92,12 @@ public class LoggingHandlerTests {
expression = spy(expression); expression = spy(expression);
accessor.setPropertyValue("expression", expression); accessor.setPropertyValue("expression", expression);
when(log.isInfoEnabled()).thenReturn(false); when(log.isInfoEnabled()).thenReturn(false);
loggingHandler.handleMessage(new GenericMessage<String>("foo")); loggingHandler.handleMessage(new GenericMessage<>("foo"));
verify(expression, never()).getValue(Mockito.any(EvaluationContext.class), Mockito.any()); verify(expression, never()).getValue(Mockito.any(EvaluationContext.class), Mockito.any(Message.class));
when(log.isInfoEnabled()).thenReturn(true); when(log.isInfoEnabled()).thenReturn(true);
loggingHandler.handleMessage(new GenericMessage<String>("foo")); loggingHandler.handleMessage(new GenericMessage<>("foo"));
verify(expression, times(1)).getValue(Mockito.any(EvaluationContext.class), Mockito.any()); verify(expression, times(1)).getValue(Mockito.any(EvaluationContext.class), Mockito.any(Message.class));
} }
@Test @Test
@@ -106,12 +108,12 @@ public class LoggingHandlerTests {
log = spy(log); log = spy(log);
accessor.setPropertyValue("messageLogger", log); accessor.setPropertyValue("messageLogger", log);
when(log.isInfoEnabled()).thenReturn(true); when(log.isInfoEnabled()).thenReturn(true);
loggingHandler.handleMessage(new GenericMessage<String>("foo")); loggingHandler.handleMessage(new GenericMessage<>("foo"));
verify(log, times(1)).info(Mockito.anyString()); verify(log, times(1)).info(Mockito.anyString());
verify(log, never()).warn(Mockito.anyString()); verify(log, never()).warn(Mockito.anyString());
loggingHandler.setLevel(Level.WARN); loggingHandler.setLevel(Level.WARN);
loggingHandler.handleMessage(new GenericMessage<String>("foo")); loggingHandler.handleMessage(new GenericMessage<>("foo"));
verify(log, times(1)).info(Mockito.anyString()); verify(log, times(1)).info(Mockito.anyString());
verify(log, times(1)).warn(Mockito.anyString()); verify(log, times(1)).warn(Mockito.anyString());
} }
@@ -134,6 +136,7 @@ public class LoggingHandlerTests {
public int getAge() { public int getAge() {
return this.age; return this.age;
} }
} }
} }

View File

@@ -26,7 +26,9 @@ import static org.mockito.Mockito.mock;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Properties; import java.util.Properties;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@@ -40,7 +42,6 @@ import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.gateway.GatewayProxyFactoryBean; import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.integration.gateway.RequestReplyExchanger; 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.integration.util.MessagingMethodInvokerHelper;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.GenericMessage;
@@ -58,6 +60,7 @@ import org.springframework.messaging.support.GenericMessage;
* @author Dave Syer * @author Dave Syer
* @author Gary Russell * @author Gary Russell
* @author Gunnar Hillert * @author Gunnar Hillert
* @author Artem Bilan
*/ */
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
public class MethodInvokingMessageProcessorTests { public class MethodInvokingMessageProcessorTests {
@@ -517,6 +520,36 @@ public class MethodInvokingMessageProcessorTests {
assertEquals("true", bean.lastArg); assertEquals("true", bean.lastArg);
} }
@Test
public void testOptionalArgs() throws Exception {
class Foo {
private final Map<String, Object> arguments = new LinkedHashMap<String, Object>();
public void optionalHeaders(Optional<String> foo, @Header(value="foo", required=false) String foo1,
@Header(value="foo") Optional<String> 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<Exception> { private static class ExceptionCauseMatcher extends TypeSafeMatcher<Exception> {
private Throwable cause; private Throwable cause;

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 Marius Bogoevici
* @author Gary Russell
*/ */
public class StubJavaMailSender implements JavaMailSender { public class StubJavaMailSender implements JavaMailSender {
private final MimeMessage uniqueMessage; private MimeMessage uniqueMessage;
private final List<MimeMessage> sentMimeMessages = new ArrayList<MimeMessage>(); private final List<MimeMessage> sentMimeMessages = new ArrayList<MimeMessage>();
@@ -54,43 +53,35 @@ public class StubJavaMailSender implements JavaMailSender {
return this.sentSimpleMailMessages; return this.sentSimpleMailMessages;
} }
@Override
public MimeMessage createMimeMessage() { public MimeMessage createMimeMessage() {
return this.uniqueMessage; return this.uniqueMessage;
} }
@Override
public MimeMessage createMimeMessage(InputStream contentStream) throws MailException { public MimeMessage createMimeMessage(InputStream contentStream) throws MailException {
return this.uniqueMessage; return this.uniqueMessage;
} }
@Override
public void send(MimeMessage mimeMessage) throws MailException { public void send(MimeMessage mimeMessage) throws MailException {
this.sentMimeMessages.add(mimeMessage); 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)); this.sentMimeMessages.addAll(Arrays.asList(mimeMessages));
} }
@Override
public void send(MimeMessagePreparator mimeMessagePreparator) throws MailException { public void send(MimeMessagePreparator mimeMessagePreparator) throws MailException {
throw new UnsupportedOperationException("MimeMessagePreparator not supported"); 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"); throw new UnsupportedOperationException("MimeMessagePreparator not supported");
} }
@Override
public void send(SimpleMailMessage simpleMessage) throws MailException { public void send(SimpleMailMessage simpleMessage) throws MailException {
this.sentSimpleMailMessages.add(simpleMessage); 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)); this.sentSimpleMailMessages.addAll(Arrays.asList(simpleMessages));
} }

View File

@@ -16,12 +16,20 @@
package org.springframework.integration.ws.config; package org.springframework.integration.ws.config;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*; 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.Assert;
import org.junit.Test; import org.junit.Test;
import org.mockito.Matchers;
import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
@@ -412,7 +420,7 @@ public class WebServiceOutboundGatewayParserTests {
doReturn(null).when(webServiceTemplate).sendAndReceive(anyString(), doReturn(null).when(webServiceTemplate).sendAndReceive(anyString(),
any(WebServiceMessageCallback.class), any(WebServiceMessageCallback.class),
any(WebServiceMessageExtractor.class)); Matchers.<WebServiceMessageExtractor<Object>>any());
new DirectFieldAccessor(handler).setPropertyValue("webServiceTemplate", webServiceTemplate); new DirectFieldAccessor(handler).setPropertyValue("webServiceTemplate", webServiceTemplate);
@@ -420,7 +428,7 @@ public class WebServiceOutboundGatewayParserTests {
verify(webServiceTemplate).sendAndReceive(eq("jms:wsQueue"), verify(webServiceTemplate).sendAndReceive(eq("jms:wsQueue"),
any(WebServiceMessageCallback.class), any(WebServiceMessageCallback.class),
any(WebServiceMessageExtractor.class)); Matchers.<WebServiceMessageExtractor<Object>>any());
} }
@Test(expected = BeanDefinitionParsingException.class) @Test(expected = BeanDefinitionParsingException.class)

View File

@@ -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 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. implementation could be a simple counter/monitor of messages deposited on the input channel.
</note> </note>
<para> <para>
Starting with <emphasis>version 4.1</emphasis> the framework correct converts Message properties
(<code>payload</code> and <code>headers</code>) to the Java 8 <classname>Optional</classname> POJO method
parameters:
<programlisting language="java"><![CDATA[public class MyBean {
public String computeValue(Optional<String> payload,
@Header(value="foo", required=false) String foo1,
@Header(value="foo") Optional<String> foo2) {
if (payload.isPresent()) {
String value = payload.get();
...
}
else {
...
}
}
}]]></programlisting>
</para>
<para>
Using a "ref" attribute is generally recommended if the custom Service Activator handler implementation can be reused Using a "ref" attribute is generally recommended if the custom Service Activator handler implementation can be reused
in other <code>&lt;service-activator&gt;</code> definitions. However if the custom Service Activator handler implementation in other <code>&lt;service-activator&gt;</code> definitions. However if the custom Service Activator handler implementation
is only used within a single definition of the <code>&lt;service-activator&gt;</code>, you can provide an inner bean definition: is only used within a single definition of the <code>&lt;service-activator&gt;</code>, you can provide an inner bean definition:

View File

@@ -230,5 +230,12 @@
See <xref linkend="resequencer"/>. See <xref linkend="resequencer"/>.
</para> </para>
</section> </section>
<section id="4.1-Optional-Parameter">
<title>Optional POJO method parameter</title>
<para>
Now Spring Integration consistently handles the Java 8's <classname>Optional</classname> type.
See <xref linkend="service-activator-namespace"/>.
</para>
</section>
</section> </section>
</chapter> </chapter>