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
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'

View File

@@ -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'
}
}
}

View File

@@ -738,7 +738,8 @@ public class MessagingMethodInvokerHelper<T> 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";

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");
* 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<String>("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<String>("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<String>("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<String>("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;
}
}
}

View File

@@ -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<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 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");
* 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<MimeMessage> sentMimeMessages = new ArrayList<MimeMessage>();
@@ -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));
}

View File

@@ -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.<WebServiceMessageExtractor<Object>>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.<WebServiceMessageExtractor<Object>>any());
}
@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
implementation could be a simple counter/monitor of messages deposited on the input channel.
</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
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:

View File

@@ -230,5 +230,12 @@
See <xref linkend="resequencer"/>.
</para>
</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>
</chapter>