From e6ec86c505ae741326708666ed3712ffd59c0ed1 Mon Sep 17 00:00:00 2001 From: Yilin Wei Date: Mon, 15 Jan 2018 20:05:12 +0000 Subject: [PATCH] INT-3945: Add async to the @ServiceActivator JIRA: https://jira.spring.io/browse/INT-3945 Adding test for async annotated ServiceActivator fix whitespace Address comments Change copyright and author * Some code style polishing * Rename `Log4j2LevelAdjuster.level()` to more friendly `forLevel()` factory method name. * Add `@param level` to the `Log4j2LevelAdjuster.forLevel()` to fix JavaDoc warning --- .../annotation/ServiceActivator.java | 16 +++++-- ...AbstractMethodAnnotationPostProcessor.java | 2 +- ...rviceActivatorAnnotationPostProcessor.java | 16 +++++-- ...notatedEndpointActivationTests-context.xml | 10 ++++ .../AnnotatedEndpointActivationTests.java | 47 +++++++++++++++---- .../test/rule/Log4j2LevelAdjuster.java | 9 ++-- 6 files changed, 77 insertions(+), 23 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java index 4750d7e84a..7d5fddabe9 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -41,8 +41,9 @@ import java.lang.annotation.Target; * @author Mark Fisher * @author Gary Russell * @author Artem Bilan + * @author Yilin Wei */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @@ -77,7 +78,7 @@ public @interface ServiceActivator { * Only the handler is advised, not the downstream flow. * @return the advice chain. */ - String[] adviceChain() default {}; + String[] adviceChain() default { }; /** * Specify the maximum amount of time in milliseconds to wait when sending a reply @@ -110,12 +111,19 @@ public @interface ServiceActivator { */ String phase() default ""; + /** + * Specify whether the service method is async. + * This value is {@code false} by default. + * @return the async flag. + */ + String async() default ""; + /** * @return the {@link Poller} options for a polled endpoint * ({@link org.springframework.integration.scheduling.PollerMetadata}). * This attribute is an {@code array} just to allow an empty default (no poller). * Only one {@link Poller} element is allowed. */ - Poller[] poller() default {}; + Poller[] poller() default { }; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java index 3767423f10..6384ffb5c0 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java @@ -257,7 +257,7 @@ public abstract class AbstractMethodAnnotationPostProcessor 0) { - adviceChain = new ArrayList(); + adviceChain = new ArrayList<>(); for (String adviceChainName : adviceChainNames) { Object adviceChainBean = this.beanFactory.getBean(adviceChainName); if (adviceChainBean instanceof Advice) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java index c9fabc7ec8..f08ca4ade4 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -38,12 +38,13 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @author Gary Russell * @author Artem Bilan + * @author Yilin Wei */ public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { public ServiceActivatorAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { super(beanFactory); - this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "requiresReply", "adviceChain")); + this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "requiresReply", "adviceChain")); } @@ -56,9 +57,9 @@ public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnot if (serviceActivator == null) { if (target instanceof MessageHandler) { /* - * Return a reply-producing message handler so that we still get 'produced no reply' messages - * and the super class will inject the advice chain to advise the handler method if needed. - */ + * Return a reply-producing message handler so that we still get 'produced no reply' messages + * and the super class will inject the advice chain to advise the handler method if needed. + */ return new ReplyProducingMessageHandlerWrapper((MessageHandler) target); } else { @@ -79,6 +80,11 @@ public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnot serviceActivator.setRequiresReply(Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(requiresReply))); } + String isAsync = MessagingAnnotationUtils.resolveAttribute(annotations, "async", String.class); + if (StringUtils.hasText(isAsync)) { + serviceActivator.setAsync(Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(isAsync))); + } + this.setOutputChannelIfPresent(annotations, serviceActivator); return serviceActivator; } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedEndpointActivationTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedEndpointActivationTests-context.xml index 2033bcf5ab..8584fc02f7 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedEndpointActivationTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedEndpointActivationTests-context.xml @@ -19,10 +19,20 @@ + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedEndpointActivationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedEndpointActivationTests.java index c8e715a0b9..42b4e5e084 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedEndpointActivationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AnnotatedEndpointActivationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 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. @@ -35,17 +35,18 @@ import org.springframework.messaging.MessageDeliveryException; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.support.GenericMessage; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.SettableListenableFuture; /** * @author Dave Syer * @author Mark Fisher * @author Gary Russell * @author Artem Bilan + * @author Yilin Wei */ -@ContextConfiguration -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @DirtiesContext public class AnnotatedEndpointActivationTests { @@ -57,6 +58,14 @@ public class AnnotatedEndpointActivationTests { @Qualifier("output") private PollableChannel output; + @Autowired + @Qualifier("inputAsync") + private MessageChannel inputAsync; + + @Autowired + @Qualifier("outputAsync") + private PollableChannel outputAsync; + @Autowired private AbstractApplicationContext applicationContext; @@ -72,7 +81,7 @@ public class AnnotatedEndpointActivationTests { @Test public void sendAndReceive() { - this.input.send(new GenericMessage("foo")); + this.input.send(new GenericMessage<>("foo")); Message message = this.output.receive(100); assertNotNull(message); assertEquals("foo: 1", message.getPayload()); @@ -82,10 +91,19 @@ public class AnnotatedEndpointActivationTests { assertTrue(this.applicationContext.containsBean("annotatedEndpoint2.process.serviceActivator")); } + @Test + public void sendAndReceiveAsync() { + this.inputAsync.send(new GenericMessage<>("foo")); + Message message = this.outputAsync.receive(100); + assertNotNull(message); + assertEquals("foo", message.getPayload()); + assertTrue(this.applicationContext.containsBean("annotatedEndpoint3.process.serviceActivator")); + } + @Test public void sendAndReceiveImplicitInputChannel() { MessageChannel input = this.applicationContext.getBean("inputImplicit", MessageChannel.class); - input.send(new GenericMessage("foo")); + input.send(new GenericMessage<>("foo")); Message message = this.output.receive(100); assertNotNull(message); assertEquals("foo: 1", message.getPayload()); @@ -96,7 +114,7 @@ public class AnnotatedEndpointActivationTests { @DirtiesContext public void stopContext() { applicationContext.stop(); - this.input.send(new GenericMessage("foo")); + this.input.send(new GenericMessage<>("foo")); } @Test @@ -104,7 +122,7 @@ public class AnnotatedEndpointActivationTests { public void stopAndRestartContext() { applicationContext.stop(); applicationContext.start(); - this.input.send(new GenericMessage("foo")); + this.input.send(new GenericMessage<>("foo")); Message message = this.output.receive(100); assertNotNull(message); assertEquals("foo: 1", message.getPayload()); @@ -139,5 +157,16 @@ public class AnnotatedEndpointActivationTests { } + @SuppressWarnings("unused") + private static class AnnotatedEndpoint3 { + + @ServiceActivator(inputChannel = "inputAsync", outputChannel = "outputAsync", async = "true") + public ListenableFuture process(String message) { + SettableListenableFuture future = new SettableListenableFuture<>(); + future.set(message); + return future; + } + + } } diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/rule/Log4j2LevelAdjuster.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/rule/Log4j2LevelAdjuster.java index e272132c23..d8766efe15 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/rule/Log4j2LevelAdjuster.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/rule/Log4j2LevelAdjuster.java @@ -183,7 +183,7 @@ public class Log4j2LevelAdjuster implements MethodRule { * @return the Log4j2LevelAdjuster instance */ public static Log4j2LevelAdjuster trace() { - return level(Level.TRACE); + return forLevel(Level.TRACE); } /** @@ -192,7 +192,7 @@ public class Log4j2LevelAdjuster implements MethodRule { * @return the Log4j2LevelAdjuster instance */ public static Log4j2LevelAdjuster debug() { - return level(Level.DEBUG); + return forLevel(Level.DEBUG); } /** @@ -201,15 +201,16 @@ public class Log4j2LevelAdjuster implements MethodRule { * @return the Log4j2LevelAdjuster instance */ public static Log4j2LevelAdjuster info() { - return level(Level.INFO); + return forLevel(Level.INFO); } /** * The factory to produce Log4j2LevelAdjuster instances for arbitrary logging {@link Level} * with the {@code org.springframework.integration} as default category. + * @param level the {@link Level} to use for logging * @return the Log4j2LevelAdjuster instance */ - public static Log4j2LevelAdjuster level(Level level) { + public static Log4j2LevelAdjuster forLevel(Level level) { return new Log4j2LevelAdjuster(level); }