diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/DslIntegrationConfigurationInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/DslIntegrationConfigurationInitializer.java new file mode 100644 index 0000000000..2f572a2c6d --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/DslIntegrationConfigurationInitializer.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.config.dsl; + +import java.beans.Introspector; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.integration.config.IntegrationConfigurationInitializer; +import org.springframework.integration.dsl.IntegrationComponentSpec; +import org.springframework.integration.dsl.context.IntegrationFlowContext; +import org.springframework.util.Assert; + +/** + * The Java DSL Integration infrastructure {@code beanFactory} initializer. + * Registers {@link IntegrationFlowBeanPostProcessor} and checks if all + * {@link IntegrationComponentSpec} are extracted to the target object using + * {@link IntegrationComponentSpec#get()}. + * + * @author Artem Bilan + * @since 5.0 + * + * @see org.springframework.integration.config.IntegrationConfigurationBeanFactoryPostProcessor + */ +public class DslIntegrationConfigurationInitializer implements IntegrationConfigurationInitializer { + + private static final String INTEGRATION_FLOW_BPP_BEAN_NAME = + Introspector.decapitalize(IntegrationFlowBeanPostProcessor.class.getName()); + + private static final String INTEGRATION_FLOW_CONTEXT_BEAN_NAME = + Introspector.decapitalize(IntegrationFlowContext.class.getName()); + + @Override + public void initialize(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { + Assert.isInstanceOf(BeanDefinitionRegistry.class, configurableListableBeanFactory, + "To use Spring Integration Java DSL the 'beanFactory' has to be an instance of " + + "'BeanDefinitionRegistry'. Consider using 'GenericApplicationContext' implementation." + ); + + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) configurableListableBeanFactory; + if (!registry.containsBeanDefinition(INTEGRATION_FLOW_BPP_BEAN_NAME)) { + registry.registerBeanDefinition(INTEGRATION_FLOW_BPP_BEAN_NAME, + new RootBeanDefinition(IntegrationFlowBeanPostProcessor.class)); + registry.registerBeanDefinition(INTEGRATION_FLOW_CONTEXT_BEAN_NAME, + new RootBeanDefinition(IntegrationFlowContext.class)); + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/IntegrationFlowBeanPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/IntegrationFlowBeanPostProcessor.java new file mode 100644 index 0000000000..ac4b5f613b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/IntegrationFlowBeanPostProcessor.java @@ -0,0 +1,303 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.config.dsl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationNotAllowedException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationEventMulticaster; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.channel.FixedSubscriberChannel; +import org.springframework.integration.config.ConsumerEndpointFactoryBean; +import org.springframework.integration.config.IntegrationConfigUtils; +import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.dsl.ComponentsRegistration; +import org.springframework.integration.dsl.ConsumerEndpointSpec; +import org.springframework.integration.dsl.IntegrationComponentSpec; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlowBuilder; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.SourcePollingChannelAdapterSpec; +import org.springframework.integration.dsl.StandardIntegrationFlow; +import org.springframework.integration.dsl.support.MessageChannelReference; +import org.springframework.integration.support.context.NamedComponent; +import org.springframework.messaging.MessageHandler; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * A {@link BeanPostProcessor} to parse {@link IntegrationFlow} beans and + * register their components as beans in the provided {@link BeanFactory}, + * if necessary. + * + * @author Artem Bilan + * @since 5.0 + */ +public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, + SmartInitializingSingleton { + + private final Set> applicationListeners = new HashSet>(); + + private ConfigurableListableBeanFactory beanFactory; + + private AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory, + "To use Spring Integration Java DSL the 'beanFactory' has to be an instance of " + + "'ConfigurableListableBeanFactory'. Consider using 'GenericApplicationContext' implementation." + ); + + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + this.autowiredAnnotationBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor(); + this.autowiredAnnotationBeanPostProcessor.setBeanFactory(this.beanFactory); + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof StandardIntegrationFlow) { + return processStandardIntegrationFlow((StandardIntegrationFlow) bean, beanName); + } + else if (bean instanceof IntegrationFlow) { + return processIntegrationFlowImpl((IntegrationFlow) bean, beanName); + } + if (bean instanceof IntegrationComponentSpec) { + processIntegrationComponentSpec((IntegrationComponentSpec) bean); + } + return bean; + } + + @Override + public void afterSingletonsInstantiated() { + if (this.beanFactory.containsBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { + ApplicationEventMulticaster multicaster = + (ApplicationEventMulticaster) this.beanFactory.getBean( + AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME); + this.applicationListeners.forEach(multicaster::addApplicationListener); + } + + for (String beanName : this.beanFactory.getBeanNamesForType(IntegrationFlow.class)) { + if (this.beanFactory.containsBeanDefinition(beanName)) { + String scope = this.beanFactory.getBeanDefinition(beanName).getScope(); + if (StringUtils.hasText(scope) && !BeanDefinition.SCOPE_SINGLETON.equals(scope)) { + throw new BeanCreationNotAllowedException(beanName, "IntegrationFlows can not be scoped beans. " + + "Any dependant beans are registered as singletons, meanwhile IntegrationFlow is just a " + + "logical container for them. \n" + + "Consider to use [IntegrationFlowContext] for manual registration of IntegrationFlows."); + } + } + } + } + + private Object processStandardIntegrationFlow(StandardIntegrationFlow flow, String beanName) { + String flowNamePrefix = beanName + "."; + int subFlowNameIndex = 0; + int channelNameIndex = 0; + boolean registerSingleton = flow.isRegisterComponents(); + + + List integrationComponents = new ArrayList<>(flow.getIntegrationComponents()); + for (int i = 0; i < integrationComponents.size(); i++) { + Object component = integrationComponents.get(i); + if (component instanceof ConsumerEndpointSpec) { + ConsumerEndpointSpec endpointSpec = (ConsumerEndpointSpec) component; + MessageHandler messageHandler = endpointSpec.get().getT2(); + ConsumerEndpointFactoryBean endpoint = endpointSpec.get().getT1(); + String id = endpointSpec.getId(); + + Collection messageHandlers = this.beanFactory.getBeansOfType(messageHandler.getClass(), false, + false).values(); + + if (!messageHandlers.contains(messageHandler)) { + String handlerBeanName = generateBeanName(messageHandler); + String[] handlerAlias = id != null + ? new String[] { id + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX } + : null; + + registerComponent(messageHandler, handlerBeanName, beanName, registerSingleton); + if (handlerAlias != null) { + for (String alias : handlerAlias) { + this.beanFactory.registerAlias(handlerBeanName, alias); + } + } + } + + String endpointBeanName = id; + if (endpointBeanName == null) { + endpointBeanName = generateBeanName(endpoint); + } + registerComponent(endpoint, endpointBeanName, beanName, registerSingleton); + integrationComponents.set(i, endpoint); + } + else { + Collection values = this.beanFactory.getBeansOfType(component.getClass(), false, false).values(); + if (!values.contains(component)) { + if (component instanceof AbstractMessageChannel) { + String channelBeanName = ((AbstractMessageChannel) component).getComponentName(); + if (channelBeanName == null) { + channelBeanName = flowNamePrefix + "channel" + + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + channelNameIndex++; + } + registerComponent(component, channelBeanName, beanName, registerSingleton); + } + else if (component instanceof MessageChannelReference) { + String channelBeanName = ((MessageChannelReference) component).getName(); + if (!this.beanFactory.containsBean(channelBeanName)) { + DirectChannel directChannel = new DirectChannel(); + registerComponent(directChannel, channelBeanName, beanName, registerSingleton); + integrationComponents.set(i, directChannel); + } + } + else if (component instanceof FixedSubscriberChannel) { + FixedSubscriberChannel fixedSubscriberChannel = (FixedSubscriberChannel) component; + String channelBeanName = fixedSubscriberChannel.getComponentName(); + if ("Unnamed fixed subscriber channel".equals(channelBeanName)) { + channelBeanName = flowNamePrefix + "channel" + + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + channelNameIndex++; + } + registerComponent(component, channelBeanName, beanName, registerSingleton); + } + else if (component instanceof SourcePollingChannelAdapterSpec) { + SourcePollingChannelAdapterSpec spec = (SourcePollingChannelAdapterSpec) component; + Collection componentsToRegister = spec.getComponentsToRegister(); + if (!CollectionUtils.isEmpty(componentsToRegister)) { + componentsToRegister.stream() + .filter(o -> !this.beanFactory.getBeansOfType(o.getClass(), false, false) + .values() + .contains(o)) + .forEach(o -> registerComponent(o, generateBeanName(o)) + ); + } + SourcePollingChannelAdapterFactoryBean pollingChannelAdapterFactoryBean = spec.get().getT1(); + String id = spec.getId(); + if (!StringUtils.hasText(id)) { + id = generateBeanName(pollingChannelAdapterFactoryBean); + } + registerComponent(pollingChannelAdapterFactoryBean, id, beanName, registerSingleton); + integrationComponents.set(i, pollingChannelAdapterFactoryBean); + + MessageSource messageSource = spec.get().getT2(); + if (!this.beanFactory.getBeansOfType(messageSource.getClass(), false, false) + .values() + .contains(messageSource)) { + String messageSourceId = id + ".source"; + if (messageSource instanceof NamedComponent + && ((NamedComponent) messageSource).getComponentName() != null) { + messageSourceId = ((NamedComponent) messageSource).getComponentName(); + } + registerComponent(messageSource, messageSourceId, beanName, registerSingleton); + } + } + else if (component instanceof StandardIntegrationFlow) { + String subFlowBeanName = flowNamePrefix + "subFlow" + + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + subFlowNameIndex++; + registerComponent(component, subFlowBeanName, beanName, registerSingleton); + } + else { + String generateBeanName = generateBeanName(component); + registerComponent(component, generateBeanName, beanName, registerSingleton); + } + } + } + } + flow.setIntegrationComponents(integrationComponents); + return flow; + } + + private Object processIntegrationFlowImpl(IntegrationFlow flow, String beanName) { + IntegrationFlowBuilder flowBuilder = IntegrationFlows.from(beanName + ".input"); + flow.configure(flowBuilder); + Object standardIntegrationFlow = processStandardIntegrationFlow(flowBuilder.get(), beanName); + return isLambda(flow) ? standardIntegrationFlow : flow; + } + + private void processIntegrationComponentSpec(IntegrationComponentSpec bean) { + registerComponent(bean.get(), generateBeanName(bean.get()), null, false); + if (bean instanceof ComponentsRegistration) { + Collection componentsToRegister = ((ComponentsRegistration) bean).getComponentsToRegister(); + if (!CollectionUtils.isEmpty(componentsToRegister)) { + componentsToRegister.stream() + .filter(component -> !this.beanFactory.getBeansOfType(component.getClass(), false, false) + .values() + .contains(component)) + .forEach(component -> registerComponent(component, generateBeanName(component))); + } + } + } + + private void registerComponent(Object component, String beanName) { + registerComponent(component, beanName, null, true); + } + + private void registerComponent(Object component, String beanName, String parentName, boolean registerSingleton) { + if (component instanceof ApplicationListener) { + this.applicationListeners.add((ApplicationListener) component); + } + this.autowiredAnnotationBeanPostProcessor.processInjection(component); + this.beanFactory.initializeBean(component, beanName); + if (registerSingleton) { + this.beanFactory.registerSingleton(beanName, component); + if (parentName != null) { + this.beanFactory.registerDependentBean(parentName, beanName); + } + } + } + + private String generateBeanName(Object instance) { + if (instance instanceof NamedComponent && ((NamedComponent) instance).getComponentName() != null) { + return ((NamedComponent) instance).getComponentName(); + } + String generatedBeanName = instance.getClass().getName(); + String id = generatedBeanName; + int counter = -1; + while (counter == -1 || this.beanFactory.containsBean(id)) { + counter++; + id = generatedBeanName + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + counter; + } + return id; + } + + private static boolean isLambda(Object o) { + Class aClass = o.getClass(); + return aClass.isSynthetic() && !aClass.isAnonymousClass() && !aClass.isLocalClass(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/package-info.java new file mode 100644 index 0000000000..0f141ca242 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides config classes of the Spring Integration Java DSL. + */ +package org.springframework.integration.config.dsl; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/AbstractRouterSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/AbstractRouterSpec.java new file mode 100644 index 0000000000..72e49e42c0 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/AbstractRouterSpec.java @@ -0,0 +1,131 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.router.AbstractMessageRouter; +import org.springframework.messaging.MessageChannel; +import org.springframework.util.Assert; + +/** + * A {@link MessageHandlerSpec} for {@link AbstractMessageRouter}s. + * + * @param the target {@link AbstractRouterSpec} implementation type. + * @param the {@link AbstractMessageRouter} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public class AbstractRouterSpec, R extends AbstractMessageRouter> + extends MessageHandlerSpec implements ComponentsRegistration { + + protected final List subFlows = new ArrayList(); + + private boolean defaultToParentFlow; + + AbstractRouterSpec(R router) { + this.target = router; + } + + /** + * @param ignoreSendFailures the ignoreSendFailures. + * @return the router spec. + * @see AbstractMessageRouter#setIgnoreSendFailures(boolean) + */ + public S ignoreSendFailures(boolean ignoreSendFailures) { + this.target.setIgnoreSendFailures(ignoreSendFailures); + return _this(); + } + + /** + * @param applySequence the applySequence. + * @return the router spec. + * @see AbstractMessageRouter#setApplySequence(boolean) + */ + public S applySequence(boolean applySequence) { + this.target.setApplySequence(applySequence); + return _this(); + } + + /** + * Specify a {@link MessageChannel} bean name as a default output from the router. + * @param channelName the {@link MessageChannel} bean name. + * @return the router spec. + * @since 1.2 + * @see AbstractMessageRouter#setDefaultOutputChannelName(String) + */ + public S defaultOutputChannel(String channelName) { + this.target.setDefaultOutputChannelName(channelName); + return _this(); + } + + /** + * Specify a {@link MessageChannel} as a default output from the router. + * @param channel the {@link MessageChannel} to use. + * @return the router spec. + * @since 1.2 + * @see AbstractMessageRouter#setDefaultOutputChannel(MessageChannel) + */ + public S defaultOutputChannel(MessageChannel channel) { + this.target.setDefaultOutputChannel(channel); + return _this(); + } + + /** + * Specify an {@link IntegrationFlow} as an output from the router when no any other mapping has matched. + * @param subFlow the {@link IntegrationFlow} for default mapping. + * @return the router spec. + * @since 1.2 + */ + public S defaultSubFlowMapping(IntegrationFlow subFlow) { + Assert.notNull(subFlow); + DirectChannel channel = new DirectChannel(); + IntegrationFlowBuilder flowBuilder = IntegrationFlows.from(channel); + subFlow.configure(flowBuilder); + + this.subFlows.add(flowBuilder); + + return defaultOutputChannel(channel); + } + + /** + * Make a default output mapping of the router to the parent flow. + * Use the next, after router, parent flow {@link MessageChannel} as a + * {@link AbstractMessageRouter#setDefaultOutputChannel(MessageChannel)} of this router. + * @return the router spec. + * @since 1.2 + */ + public S defaultOutputToParentFlow() { + this.defaultToParentFlow = true; + return _this(); + } + + boolean isDefaultToParentFlow() { + return this.defaultToParentFlow; + } + + @Override + public Collection getComponentsToRegister() { + return this.subFlows; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/AggregatorSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/AggregatorSpec.java new file mode 100644 index 0000000000..5364ad21f8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/AggregatorSpec.java @@ -0,0 +1,145 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.integration.aggregator.AggregatingMessageHandler; +import org.springframework.integration.aggregator.DefaultAggregatingMessageGroupProcessor; +import org.springframework.integration.aggregator.ExpressionEvaluatingMessageGroupProcessor; +import org.springframework.integration.aggregator.MessageGroupProcessor; +import org.springframework.integration.aggregator.MethodInvokingMessageGroupProcessor; +import org.springframework.integration.store.MessageGroup; +import org.springframework.util.Assert; + +/** + * A {@link CorrelationHandlerSpec} for an {@link AggregatingMessageHandler}. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public class AggregatorSpec extends CorrelationHandlerSpec { + + AggregatorSpec() { + super(new InternalAggregatingMessageHandler()); + } + + /** + * Configure the handler with {@link org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy} + * and {@link org.springframework.integration.aggregator.MethodInvokingReleaseStrategy} using the target + * object which should have methods annotated appropriately for each function. + * Also set the output processor. + * @param target the target object. + * @return the handler spec. + */ + public AggregatorSpec processor(Object target) { + return processor(target, null); + } + + /** + * Configure the handler with {@link org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy} + * and {@link org.springframework.integration.aggregator.MethodInvokingReleaseStrategy} using the target + * object which should have methods annotated appropriately for each function. + * Also set the output processor. + * @param target the target object. + * @param methodName The method name for the output processor (or 'null' in which case, the + * target object must have an {@link org.springframework.integration.annotation.Aggregator} + * annotation). + * @return the handler spec. + */ + public AggregatorSpec processor(Object target, String methodName) { + super.processor(target); + return this.outputProcessor(methodName != null + ? new MethodInvokingMessageGroupProcessor(target, methodName) + : new MethodInvokingMessageGroupProcessor(target)); + } + + /** + * An expression to determine the output message from the released group. Defaults to a message + * with a payload that is a collection of payloads from the input messages. + * @param expression the expression. + * @return the aggregator spec. + */ + public AggregatorSpec outputExpression(String expression) { + return this.outputProcessor(new ExpressionEvaluatingMessageGroupProcessor(expression)); + } + + /** + * A processor to determine the output message from the released group. Defaults to a message + * with a payload that is a collection of payloads from the input messages. + * @param outputProcessor the processor. + * @return the aggregator spec. + */ + public AggregatorSpec outputProcessor(MessageGroupProcessor outputProcessor) { + Assert.notNull(outputProcessor, "'outputProcessor' must not be null."); + ((InternalAggregatingMessageHandler) this.handler).getOutputProcessor().setDelegate(outputProcessor); + return _this(); + } + + /** + * @param expireGroupsUponCompletion the expireGroupsUponCompletion. + * @return the aggregator spec. + * @see AggregatingMessageHandler#setExpireGroupsUponCompletion(boolean) + */ + public AggregatorSpec expireGroupsUponCompletion(boolean expireGroupsUponCompletion) { + this.handler.setExpireGroupsUponCompletion(expireGroupsUponCompletion); + return _this(); + } + + //TODO Move the logic to the AggregatingMessageHandler + private static class InternalAggregatingMessageHandler extends AggregatingMessageHandler { + + InternalAggregatingMessageHandler() { + super(new MessageGroupProcessorWrapper()); + } + + @Override + protected MessageGroupProcessorWrapper getOutputProcessor() { + return (MessageGroupProcessorWrapper) super.getOutputProcessor(); + } + + } + + private static class MessageGroupProcessorWrapper implements MessageGroupProcessor, BeanFactoryAware { + + MessageGroupProcessorWrapper() { + super(); + } + + private MessageGroupProcessor delegate = new DefaultAggregatingMessageGroupProcessor(); + + void setDelegate(MessageGroupProcessor delegate) { + this.delegate = delegate; + } + + @Override + public Object processMessageGroup(MessageGroup group) { + return this.delegate.processMessageGroup(group); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (this.delegate instanceof BeanFactoryAware) { + ((BeanFactoryAware) this.delegate).setBeanFactory(beanFactory); + } + } + + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/BarrierSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/BarrierSpec.java new file mode 100644 index 0000000000..c03738c739 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/BarrierSpec.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.core.Ordered; +import org.springframework.integration.IntegrationMessageHeaderAccessor; +import org.springframework.integration.aggregator.BarrierMessageHandler; +import org.springframework.integration.aggregator.CorrelationStrategy; +import org.springframework.integration.aggregator.DefaultAggregatingMessageGroupProcessor; +import org.springframework.integration.aggregator.HeaderAttributeCorrelationStrategy; +import org.springframework.integration.aggregator.MessageGroupProcessor; +import org.springframework.integration.config.ConsumerEndpointFactoryBean; +import org.springframework.util.Assert; + +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +/** + * A {@link MessageHandlerSpec} for the {@link BarrierMessageHandler}. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public class BarrierSpec extends ConsumerEndpointSpec { + + private final long timeout; + + private MessageGroupProcessor outputProcessor = new DefaultAggregatingMessageGroupProcessor(); + + private CorrelationStrategy correlationStrategy = + new HeaderAttributeCorrelationStrategy(IntegrationMessageHeaderAccessor.CORRELATION_ID); + + private boolean requiresReply; + + private long sendTimeout = -1; + + private int order = Ordered.LOWEST_PRECEDENCE; + + private boolean async; + + BarrierSpec(long timeout) { + super(null); + this.timeout = timeout; + } + + public BarrierSpec outputProcessor(MessageGroupProcessor outputProcessor) { + Assert.notNull(outputProcessor, "'outputProcessor' must not be null."); + this.outputProcessor = outputProcessor; + return this; + } + + public BarrierSpec correlationStrategy(CorrelationStrategy correlationStrategy) { + Assert.notNull(correlationStrategy, "'correlationStrategy' must not be null."); + this.correlationStrategy = correlationStrategy; + return this; + } + + @Override + public BarrierSpec requiresReply(boolean requiresReply) { + this.requiresReply = requiresReply; + return this; + } + + @Override + public BarrierSpec sendTimeout(long sendTimeout) { + this.sendTimeout = sendTimeout; + return this; + } + + @Override + public BarrierSpec order(int order) { + this.order = order; + return this; + } + + @Override + public BarrierSpec async(boolean async) { + this.async = async; + return this; + } + + @Override + public Tuple2 doGet() { + BarrierMessageHandler barrierMessageHandler = + new BarrierMessageHandler(this.timeout, this.outputProcessor, this.correlationStrategy); + barrierMessageHandler.setAdviceChain(this.adviceChain); + barrierMessageHandler.setRequiresReply(this.requiresReply); + barrierMessageHandler.setSendTimeout(this.sendTimeout); + barrierMessageHandler.setAsync(this.async); + barrierMessageHandler.setOrder(this.order); + this.endpointFactoryBean.setHandler(barrierMessageHandler); + return Tuples.of(this.endpointFactoryBean, barrierMessageHandler); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/ComponentsRegistration.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/ComponentsRegistration.java new file mode 100644 index 0000000000..e02cb07f98 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/ComponentsRegistration.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.Collection; + +/** + * The marker interface for the {@link IntegrationComponentSpec} implementation, + * when there is need to register as beans not only the target spec's components, + * but some additional components, e.g. {@code subflows} from + * {@link org.springframework.integration.dsl.RouterSpec}. + *

+ * For internal use only. + * + * @author Artem Bilan + * + * @since 5.0 + */ +@FunctionalInterface +public interface ComponentsRegistration { + + Collection getComponentsToRegister(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/ConsumerEndpointSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/ConsumerEndpointSpec.java new file mode 100644 index 0000000000..98413d017a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/ConsumerEndpointSpec.java @@ -0,0 +1,217 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import org.aopalliance.aop.Advice; + +import org.springframework.integration.config.ConsumerEndpointFactoryBean; +import org.springframework.integration.handler.AbstractMessageHandler; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.scheduling.PollerMetadata; +import org.springframework.integration.transaction.TransactionInterceptorBuilder; +import org.springframework.messaging.MessageHandler; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.interceptor.DefaultTransactionAttribute; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +/** + * A {@link EndpointSpec} for consumer endpoints. + * + * @param the target {@link ConsumerEndpointSpec} implementation type. + * @param the target {@link MessageHandler} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class ConsumerEndpointSpec, H extends MessageHandler> + extends EndpointSpec { + + protected final List adviceChain = new LinkedList<>(); + + protected ConsumerEndpointSpec(H messageHandler) { + super(messageHandler); + if (messageHandler != null) { + this.endpointFactoryBean.setHandler(messageHandler); + } + this.endpointFactoryBean.setAdviceChain(this.adviceChain); + if (messageHandler instanceof AbstractReplyProducingMessageHandler) { + ((AbstractReplyProducingMessageHandler) messageHandler).setAdviceChain(this.adviceChain); + } + } + + @Override + public S phase(int phase) { + this.endpointFactoryBean.setPhase(phase); + return _this(); + } + + @Override + public S autoStartup(boolean autoStartup) { + this.endpointFactoryBean.setAutoStartup(autoStartup); + return _this(); + } + + @Override + public S poller(PollerMetadata pollerMetadata) { + this.endpointFactoryBean.setPollerMetadata(pollerMetadata); + return _this(); + } + + /** + * Configure a list of {@link Advice} objects to be applied, in nested order, to the endpoint's handler. + * The advice objects are applied only to the handler. + * @param advice the advice chain. + * @return the endpoint spec. + */ + public S advice(Advice... advice) { + this.adviceChain.addAll(Arrays.asList(advice)); + return _this(); + } + + /** + * Specify a {@link TransactionInterceptor} {@link Advice} with the + * provided {@code PlatformTransactionManager} and default {@link DefaultTransactionAttribute} + * for the {@code pollingTask}. + * @param transactionManager the {@link PlatformTransactionManager} to use. + * @return the spec. + */ + public S transactional(PlatformTransactionManager transactionManager) { + return transactional(transactionManager, false); + } + + /** + * Specify a {@link TransactionInterceptor} {@link Advice} with the + * provided {@code PlatformTransactionManager} and default {@link DefaultTransactionAttribute} + * for the {@code pollingTask}. + * @param transactionManager the {@link PlatformTransactionManager} to use. + * @param handleMessageAdvice the flag to indicate the target {@link Advice} type: + * {@code false} - regular {@link TransactionInterceptor}; + * {@code true} - {@link org.springframework.integration.transaction.TransactionHandleMessageAdvice} extension. + * @return the spec. + */ + public S transactional(PlatformTransactionManager transactionManager, boolean handleMessageAdvice) { + return transactional(new TransactionInterceptorBuilder(handleMessageAdvice) + .transactionManager(transactionManager) + .build()); + } + + /** + * Specify a {@link TransactionInterceptor} {@link Advice} for the {@code pollingTask}. + * @param transactionInterceptor the {@link TransactionInterceptor} to use. + * @return the spec. + * @see TransactionInterceptorBuilder + */ + public S transactional(TransactionInterceptor transactionInterceptor) { + return advice(transactionInterceptor); + } + + /** + * Specify a {@link TransactionInterceptor} {@link Advice} with default {@code PlatformTransactionManager} + * and {@link DefaultTransactionAttribute} for the {@code pollingTask}. + * @return the spec. + */ + public S transactional() { + return transactional(false); + } + + /** + * Specify a {@link TransactionInterceptor} {@link Advice} with default {@code PlatformTransactionManager} + * and {@link DefaultTransactionAttribute} for the {@code pollingTask}. + * @param handleMessageAdvice the flag to indicate the target {@link Advice} type: + * {@code false} - regular {@link TransactionInterceptor}; + * {@code true} - {@link org.springframework.integration.transaction.TransactionHandleMessageAdvice} extension. + * @return the spec. + */ + public S transactional(boolean handleMessageAdvice) { + TransactionInterceptor transactionInterceptor = new TransactionInterceptorBuilder(handleMessageAdvice).build(); + this.componentToRegister.add(transactionInterceptor); + return transactional(transactionInterceptor); + } + + /** + * @param requiresReply the requiresReply. + * @return the endpoint spec. + * @see AbstractReplyProducingMessageHandler#setRequiresReply(boolean) + */ + public S requiresReply(boolean requiresReply) { + assertHandler(); + if (this.handler instanceof AbstractReplyProducingMessageHandler) { + ((AbstractReplyProducingMessageHandler) this.handler).setRequiresReply(requiresReply); + } + else { + logger.warn("'requiresReply' can be applied only for AbstractReplyProducingMessageHandler"); + } + return _this(); + } + + /** + * @param sendTimeout the send timeout. + * @return the endpoint spec. + * @see AbstractReplyProducingMessageHandler#setSendTimeout(long) + */ + public S sendTimeout(long sendTimeout) { + assertHandler(); + if (this.handler instanceof AbstractReplyProducingMessageHandler) { + ((AbstractReplyProducingMessageHandler) this.handler).setSendTimeout(sendTimeout); + } + else { + logger.warn("'sendTimeout' can be applied only for AbstractReplyProducingMessageHandler"); + } + return _this(); + } + + /** + * @param order the order. + * @return the endpoint spec. + * @see AbstractMessageHandler#setOrder(int) + */ + public S order(int order) { + assertHandler(); + if (this.handler instanceof AbstractMessageHandler) { + ((AbstractMessageHandler) this.handler).setOrder(order); + } + else { + logger.warn("'order' can be applied only for AbstractMessageHandler"); + } + return _this(); + } + + /** + * Allow async replies. If the handler reply is a {@code ListenableFuture} send + * the output when it is satisfied rather than sending the future as the result. + * Only subclasses that support this feature should set it. + * @param async true to allow. + * @return the endpoint spec. + * @see AbstractReplyProducingMessageHandler#setAsync(boolean) + */ + public S async(boolean async) { + assertHandler(); + if (this.handler instanceof AbstractReplyProducingMessageHandler) { + ((AbstractReplyProducingMessageHandler) this.handler).setAsync(async); + } + else { + logger.warn("'async' can be applied only for AbstractReplyProducingMessageHandler"); + } + return _this(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/CorrelationHandlerSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/CorrelationHandlerSpec.java new file mode 100644 index 0000000000..6d0d3a153f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/CorrelationHandlerSpec.java @@ -0,0 +1,314 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + +import org.aopalliance.aop.Advice; + +import org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler; +import org.springframework.integration.aggregator.CorrelationStrategy; +import org.springframework.integration.aggregator.ExpressionEvaluatingCorrelationStrategy; +import org.springframework.integration.aggregator.ExpressionEvaluatingReleaseStrategy; +import org.springframework.integration.aggregator.ReleaseStrategy; +import org.springframework.integration.config.CorrelationStrategyFactoryBean; +import org.springframework.integration.config.ReleaseStrategyFactoryBean; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.expression.ValueExpression; +import org.springframework.integration.store.MessageGroup; +import org.springframework.integration.store.MessageGroupStore; +import org.springframework.integration.support.locks.LockRegistry; +import org.springframework.messaging.MessageChannel; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.util.Assert; + +/** + * A {@link MessageHandlerSpec} for an {@link AbstractCorrelatingMessageHandler}. + * + * @param the target {@link CorrelationHandlerSpec} implementation type. + * @param the {@link AbstractCorrelatingMessageHandler} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class + CorrelationHandlerSpec, H extends AbstractCorrelatingMessageHandler> + extends ConsumerEndpointSpec { + + private final List forceReleaseAdviceChain = new LinkedList(); + + protected CorrelationHandlerSpec(H messageHandler) { + super(messageHandler); + messageHandler.setForceReleaseAdviceChain(this.forceReleaseAdviceChain); + } + + /** + * @param messageStore the message group store. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setMessageStore(MessageGroupStore) + */ + public S messageStore(MessageGroupStore messageStore) { + Assert.notNull(messageStore, "'messageStore' must not be null."); + this.handler.setMessageStore(messageStore); + return _this(); + } + + /** + * @param sendPartialResultOnExpiry the sendPartialResultOnExpiry. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setSendPartialResultOnExpiry(boolean) + */ + public S sendPartialResultOnExpiry(boolean sendPartialResultOnExpiry) { + this.handler.setSendPartialResultOnExpiry(sendPartialResultOnExpiry); + return _this(); + } + + /** + * @param minimumTimeoutForEmptyGroups the minimumTimeoutForEmptyGroups + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setMinimumTimeoutForEmptyGroups(long) + */ + public S minimumTimeoutForEmptyGroups(long minimumTimeoutForEmptyGroups) { + this.handler.setMinimumTimeoutForEmptyGroups(minimumTimeoutForEmptyGroups); + return _this(); + } + + /** + * Configure the handler with a group timeout expression that evaluates to + * this constant value. + * @param groupTimeout the group timeout in milliseconds. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setGroupTimeoutExpression + * @see ValueExpression + */ + public S groupTimeout(long groupTimeout) { + this.handler.setGroupTimeoutExpression(new ValueExpression<>(groupTimeout)); + return _this(); + } + + /** + * @param groupTimeoutExpression the group timeout expression string. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setGroupTimeoutExpression + */ + public S groupTimeoutExpression(String groupTimeoutExpression) { + Assert.hasText(groupTimeoutExpression, "'groupTimeoutExpression' must not be empty string."); + this.handler.setGroupTimeoutExpression(PARSER.parseExpression(groupTimeoutExpression)); + return _this(); + } + + /** + * Configure the handler with a function that will be invoked to resolve the group timeout, + * based on the message group. + * Usually used with a JDK8 lambda: + *

{@code .groupTimeout(g -> g.size() * 2000L)}. + * @param groupTimeoutFunction a function invoked to resolve the group timeout in milliseconds. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setGroupTimeoutExpression + */ + public S groupTimeout(Function groupTimeoutFunction) { + this.handler.setGroupTimeoutExpression(new FunctionExpression<>(groupTimeoutFunction)); + return _this(); + } + + /** + * @param taskScheduler the task scheduler. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setTaskScheduler(TaskScheduler) + */ + public S taskScheduler(TaskScheduler taskScheduler) { + Assert.notNull(taskScheduler, "'taskScheduler' must not be null."); + this.handler.setTaskScheduler(taskScheduler); + return _this(); + } + + /** + * @param discardChannel the discard channel. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setDiscardChannel(MessageChannel) + */ + public S discardChannel(MessageChannel discardChannel) { + Assert.notNull(discardChannel, "'discardChannel' must not be null."); + this.handler.setDiscardChannel(discardChannel); + return _this(); + } + + /** + * @param discardChannelName the discard channel. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setDiscardChannelName(String) + */ + public S discardChannel(String discardChannelName) { + Assert.hasText(discardChannelName, "'discardChannelName' must not be empty."); + this.handler.setDiscardChannelName(discardChannelName); + return _this(); + } + + /** + * Configure the handler with {@link org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy} + * and {@link org.springframework.integration.aggregator.MethodInvokingReleaseStrategy} using the target + * object which should have methods annotated appropriately for each function. + * @param target the target object, + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setCorrelationStrategy(CorrelationStrategy) + * @see AbstractCorrelatingMessageHandler#setReleaseStrategy(ReleaseStrategy) + */ + public S processor(Object target) { + try { + CorrelationStrategyFactoryBean correlationStrategyFactoryBean = new CorrelationStrategyFactoryBean(); + correlationStrategyFactoryBean.setTarget(target); + correlationStrategyFactoryBean.afterPropertiesSet(); + ReleaseStrategyFactoryBean releaseStrategyFactoryBean = new ReleaseStrategyFactoryBean(); + releaseStrategyFactoryBean.setTarget(target); + releaseStrategyFactoryBean.afterPropertiesSet(); + return correlationStrategy(correlationStrategyFactoryBean.getObject()) + .releaseStrategy(releaseStrategyFactoryBean.getObject()); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** + * Configure the handler with an {@link ExpressionEvaluatingCorrelationStrategy} for the + * given expression. + * @param correlationExpression the correlation expression. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setCorrelationStrategy(CorrelationStrategy) + */ + public S correlationExpression(String correlationExpression) { + return correlationStrategy(new ExpressionEvaluatingCorrelationStrategy(correlationExpression)); + } + + /** + * Configure the handler with an + * {@link org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy} + * for the target object and method name. + * @param target the target object. + * @param methodName the method name. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setCorrelationStrategy(CorrelationStrategy) + */ + public S correlationStrategy(Object target, String methodName) { + try { + CorrelationStrategyFactoryBean correlationStrategyFactoryBean = new CorrelationStrategyFactoryBean(); + correlationStrategyFactoryBean.setTarget(target); + correlationStrategyFactoryBean.setMethodName(methodName); + correlationStrategyFactoryBean.afterPropertiesSet(); + return correlationStrategy(correlationStrategyFactoryBean.getObject()); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** + * @param correlationStrategy the correlation strategy. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setCorrelationStrategy(CorrelationStrategy) + */ + public S correlationStrategy(CorrelationStrategy correlationStrategy) { + this.handler.setCorrelationStrategy(correlationStrategy); + return _this(); + } + + /** + * Configure the handler with an {@link ExpressionEvaluatingReleaseStrategy} for the + * given expression. + * @param releaseExpression the correlation expression. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setReleaseStrategy(ReleaseStrategy) + */ + public S releaseExpression(String releaseExpression) { + return releaseStrategy(new ExpressionEvaluatingReleaseStrategy(releaseExpression)); + } + + /** + * Configure the handler with an + * {@link org.springframework.integration.aggregator.MethodInvokingReleaseStrategy} + * for the target object and method name. + * @param target the target object. + * @param methodName the method name. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setReleaseStrategy(ReleaseStrategy) + */ + public S releaseStrategy(Object target, String methodName) { + try { + ReleaseStrategyFactoryBean releaseStrategyFactoryBean = new ReleaseStrategyFactoryBean(); + releaseStrategyFactoryBean.setTarget(target); + releaseStrategyFactoryBean.setMethodName(methodName); + releaseStrategyFactoryBean.afterPropertiesSet(); + return releaseStrategy(releaseStrategyFactoryBean.getObject()); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** + * @param releaseStrategy the release strategy. + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setReleaseStrategy(ReleaseStrategy) + */ + public S releaseStrategy(ReleaseStrategy releaseStrategy) { + this.handler.setReleaseStrategy(releaseStrategy); + return _this(); + } + + /** + * Expire (completely remove) a group if it is completed due to timeout. + * Default {@code true} for aggregator and {@code false} for resequencer. + * @param expireGroupsUponTimeout the expireGroupsUponTimeout to set + * @return the handler spec. + * @see AbstractCorrelatingMessageHandler#setExpireGroupsUponTimeout + */ + public S expireGroupsUponTimeout(boolean expireGroupsUponTimeout) { + this.handler.setExpireGroupsUponTimeout(expireGroupsUponTimeout); + return _this(); + } + + /** + * Configure a list of {@link Advice} objects to be applied to the + * {@code forceComplete()} operation. + * @param advice the advice chain. + * @return the endpoint spec. + */ + public S forceReleaseAdvice(Advice... advice) { + this.forceReleaseAdviceChain.addAll(Arrays.asList(advice)); + return _this(); + } + + /** + * Used to obtain a {@code Lock} based on the {@code groupId} for concurrent operations + * on the {@code MessageGroup}. + * By default, an internal {@code DefaultLockRegistry} is used. + * Use of a distributed {@link LockRegistry}, such as the {@code RedisLockRegistry}, + * ensures only one instance of the aggregator will operate on a group concurrently. + * @param lockRegistry the {@link LockRegistry} to use. + * @return the endpoint spec. + */ + public S lockRegistry(LockRegistry lockRegistry) { + Assert.notNull(lockRegistry, "'lockRegistry' must not be null."); + this.handler.setLockRegistry(lockRegistry); + return _this(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/DelayerEndpointSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/DelayerEndpointSpec.java new file mode 100644 index 0000000000..9cc7fb4166 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/DelayerEndpointSpec.java @@ -0,0 +1,118 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + +import org.aopalliance.aop.Advice; + +import org.springframework.expression.Expression; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.handler.DelayHandler; +import org.springframework.integration.store.MessageGroupStore; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; + +/** + * A {@link ConsumerEndpointSpec} for a {@link DelayHandler}. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public final class DelayerEndpointSpec extends ConsumerEndpointSpec { + + private final List delayedAdvice = new LinkedList(); + + DelayerEndpointSpec(DelayHandler delayHandler) { + super(delayHandler); + Assert.notNull(delayHandler, "'delayHandler' must not be null."); + this.handler.setDelayedAdviceChain(this.delayedAdvice); + } + + /** + * @param defaultDelay the defaultDelay. + * @return the endpoint spec. + * @see DelayHandler#setDefaultDelay(long) + */ + public DelayerEndpointSpec defaultDelay(long defaultDelay) { + this.handler.setDefaultDelay(defaultDelay); + return _this(); + } + + /** + * @param ignoreExpressionFailures the ignoreExpressionFailures. + * @return the endpoint spec. + * @see DelayHandler#setIgnoreExpressionFailures(boolean) + */ + public DelayerEndpointSpec ignoreExpressionFailures(boolean ignoreExpressionFailures) { + this.handler.setIgnoreExpressionFailures(ignoreExpressionFailures); + return _this(); + } + + /** + * @param messageStore the message store. + * @return the endpoint spec. + */ + public DelayerEndpointSpec messageStore(MessageGroupStore messageStore) { + this.handler.setMessageStore(messageStore); + return _this(); + } + + /** + * Configure a list of {@link Advice} objects that will be applied, in nested order, + * when delayed messages are sent. + * @param advice the advice chain. + * @return the endpoint spec. + */ + public DelayerEndpointSpec delayedAdvice(Advice... advice) { + this.delayedAdvice.addAll(Arrays.asList(advice)); + return _this(); + } + + public DelayerEndpointSpec delayExpression(Expression delayExpression) { + this.handler.setDelayExpression(delayExpression); + return this; + } + + public DelayerEndpointSpec delayExpression(String delayExpression) { + this.handler.setDelayExpression(PARSER.parseExpression(delayExpression)); + return this; + } + + /** + * Specify the function to determine delay value against {@link Message}. + * Typically used with a Java 8 Lambda expression: + *

+	 * {@code
+	 *  .delay("delayer", m -> m.getPayload().getDate(),
+	 *            c -> c.advice(this.delayedAdvice).messageStore(this.messageStore()))
+	 * }
+	 * 
+ * @param delayFunction the {@link Function} to determine delay. + * @param

the payload type. + * @return the endpoint spec. + */ + public

DelayerEndpointSpec delayFunction(Function, Object> delayFunction) { + this.handler.setDelayExpression(new FunctionExpression<>(delayFunction)); + return this; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/DslRecipientListRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/DslRecipientListRouter.java new file mode 100644 index 0000000000..a174409231 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/DslRecipientListRouter.java @@ -0,0 +1,138 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.expression.Expression; +import org.springframework.integration.core.GenericSelector; +import org.springframework.integration.core.MessageSelector; +import org.springframework.integration.dsl.support.MessageChannelReference; +import org.springframework.integration.filter.ExpressionEvaluatingSelector; +import org.springframework.integration.filter.MethodInvokingSelector; +import org.springframework.integration.router.RecipientListRouter; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.core.DestinationResolutionException; +import org.springframework.util.StringUtils; + +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +/** + * @author Artem Bilan + * + * TODO Move the logic to the RecipientListRouter + */ +class DslRecipientListRouter extends RecipientListRouter { + + private final List> recipients = new ArrayList<>(); + + void add(String channelName, Expression expression) { + this.recipients.add(Tuples.of(channelName, expression)); + } + + void add(String channelName, GenericSelector selector) { + this.recipients.add(Tuples.of(channelName, selector)); + } + + void add(MessageChannel channel, Expression expression) { + this.recipients.add(Tuples.of(channel, expression)); + } + + void add(MessageChannel channel, GenericSelector selector) { + this.recipients.add(Tuples.of(channel, selector)); + } + + @Override + public void onInit() throws Exception { + List recipients = new ArrayList(this.recipients.size()); + + for (Tuple2 recipient : this.recipients) { + if (recipient.getT1() instanceof String) { + recipients.add(new DslRecipient(new MessageChannelReference((String) recipient.getT1()), + populateRecipientSelector(recipient.getT2()))); + } + else { + recipients.add(new Recipient((MessageChannel) recipient.getT1(), + populateRecipientSelector(recipient.getT2()))); + } + } + + setRecipients(recipients); + this.recipients.clear(); + super.onInit(); + } + + private MessageSelector populateRecipientSelector(final Object recipientSelector) { + if (recipientSelector instanceof String) { + String expression = (String) recipientSelector; + if (StringUtils.hasText(expression)) { + ExpressionEvaluatingSelector selector = new ExpressionEvaluatingSelector(expression); + selector.setBeanFactory(getBeanFactory()); + return selector; + } + } + else if (recipientSelector instanceof Expression) { + ExpressionEvaluatingSelector selector = new ExpressionEvaluatingSelector((Expression) recipientSelector); + selector.setBeanFactory(getBeanFactory()); + return selector; + } + else if (recipientSelector instanceof MessageSelector) { + return (MessageSelector) recipientSelector; + } + else if (recipientSelector instanceof GenericSelector) { + return new MethodInvokingSelector(new LambdaMessageProcessor(recipientSelector, null)); + } + return null; + } + + private class DslRecipient extends Recipient { + + private volatile MessageChannel channel; + + DslRecipient(MessageChannelReference channel, MessageSelector selector) { + super(channel, selector); + } + + @Override + public MessageChannel getChannel() { + if (this.channel == null) { + synchronized (this) { + if (this.channel == null) { + this.channel = resolveChannelName((MessageChannelReference) super.getChannel()); + } + } + } + return this.channel; + } + + private MessageChannel resolveChannelName(MessageChannelReference channelReference) { + String channelName = channelReference.getName(); + try { + return DslRecipientListRouter.this.getBeanFactory().getBean(channelName, MessageChannel.class); + } + catch (BeansException e) { + throw new DestinationResolutionException("Failed to look up MessageChannel with name '" + + channelName + "' in the BeanFactory."); + } + } + + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/EndpointSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/EndpointSpec.java new file mode 100644 index 0000000000..e58ef57f8d --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/EndpointSpec.java @@ -0,0 +1,134 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Function; + +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.ResolvableType; +import org.springframework.integration.endpoint.AbstractPollingEndpoint; +import org.springframework.integration.scheduling.PollerMetadata; +import org.springframework.messaging.MessageHandler; +import org.springframework.util.Assert; + +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +/** + * An {@link IntegrationComponentSpec} for endpoints. + * + * @param the target {@link ConsumerEndpointSpec} implementation type. + * @param the target {@link BeanNameAware} implementation type. + * @param the target {@link MessageHandler} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class EndpointSpec, F extends BeanNameAware, H> + extends IntegrationComponentSpec> + implements ComponentsRegistration { + + protected final Collection componentToRegister = new ArrayList(); + + protected H handler; + + protected F endpointFactoryBean; + + @SuppressWarnings("unchecked") + protected EndpointSpec(H handler) { + try { + Class fClass = ResolvableType.forClass(this.getClass()).as(EndpointSpec.class).resolveGenerics()[1]; + this.endpointFactoryBean = (F) fClass.newInstance(); + this.handler = handler; + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public S id(String id) { + this.endpointFactoryBean.setBeanName(id); + return super.id(id); + } + + /** + * @param pollers the pollers + * @return the endpoint spec. + * @see AbstractPollingEndpoint + * @see PollerFactory + */ + public S poller(Function pollers) { + return poller(pollers.apply(new PollerFactory())); + } + + /** + * @param pollerMetadataSpec the pollerMetadataSpec + * @return the endpoint spec. + * @see AbstractPollingEndpoint + * @see PollerSpec + */ + public S poller(PollerSpec pollerMetadataSpec) { + Collection componentsToRegister = pollerMetadataSpec.getComponentsToRegister(); + if (componentsToRegister != null) { + this.componentToRegister.addAll(componentsToRegister); + } + return poller(pollerMetadataSpec.get()); + } + + /** + * @param pollerMetadata the pollerMetadata + * @return the endpoint spec. + * @see AbstractPollingEndpoint + */ + public abstract S poller(PollerMetadata pollerMetadata); + + /** + * @param phase the phase. + * @return the endpoint spec. + * @see SmartLifecycle + */ + public abstract S phase(int phase); + + /** + * @param autoStartup the autoStartup. + * @return the endpoint spec + * @see SmartLifecycle + */ + public abstract S autoStartup(boolean autoStartup); + + @Override + public Collection getComponentsToRegister() { + return this.componentToRegister.isEmpty() + ? null + : this.componentToRegister; + } + + @Override + protected Tuple2 doGet() { + return Tuples.of(this.endpointFactoryBean, this.handler); + } + + protected void assertHandler() { + Assert.state(this.handler != null, "'this.handler' must not be null."); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/EnricherSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/EnricherSpec.java new file mode 100644 index 0000000000..1a0aeefc5b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/EnricherSpec.java @@ -0,0 +1,293 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.springframework.expression.Expression; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.expression.ValueExpression; +import org.springframework.integration.transformer.ContentEnricher; +import org.springframework.integration.transformer.support.AbstractHeaderValueMessageProcessor; +import org.springframework.integration.transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor; +import org.springframework.integration.transformer.support.HeaderValueMessageProcessor; +import org.springframework.integration.transformer.support.StaticHeaderValueMessageProcessor; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.util.Assert; + +/** + * The {@link MessageHandlerSpec} implementation for the {@link ContentEnricher}. + * + * @author Artem Bilan + * @author Tim Ysewyn + * + * @since 5.0 + */ +public class EnricherSpec extends MessageHandlerSpec { + + private final ContentEnricher enricher = new ContentEnricher(); + + private final Map propertyExpressions = new HashMap(); + + private final Map> headerExpressions = + new HashMap>(); + + EnricherSpec() { + super(); + } + + /** + * @param requestChannel the request channel. + * @return the enricher spec. + * @see ContentEnricher#setRequestChannel(MessageChannel) + */ + public EnricherSpec requestChannel(MessageChannel requestChannel) { + this.enricher.setRequestChannel(requestChannel); + return _this(); + } + + /** + * @param requestChannel the request channel. + * @return the enricher spec. + * @see ContentEnricher#setRequestChannelName(String) + */ + public EnricherSpec requestChannel(String requestChannel) { + this.enricher.setRequestChannelName(requestChannel); + return _this(); + } + + /** + * @param replyChannel the reply channel. + * @return the enricher spec. + * @see ContentEnricher#setReplyChannel(MessageChannel) + */ + public EnricherSpec replyChannel(MessageChannel replyChannel) { + this.enricher.setReplyChannel(replyChannel); + return _this(); + } + + /** + * @param replyChannel the reply channel. + * @return the enricher spec. + * @see ContentEnricher#setReplyChannelName(String) + */ + public EnricherSpec replyChannel(String replyChannel) { + this.enricher.setReplyChannelName(replyChannel); + return _this(); + } + + /** + * @param requestTimeout the requestTimeout + * @return the enricher spec. + * @see ContentEnricher#setRequestTimeout(Long) + */ + public EnricherSpec requestTimeout(Long requestTimeout) { + this.enricher.setRequestTimeout(requestTimeout); + return _this(); + } + + /** + * @param replyTimeout the replyTimeout + * @return the enricher spec. + * @see ContentEnricher#setReplyTimeout(Long) + */ + public EnricherSpec replyTimeout(Long replyTimeout) { + this.enricher.setReplyTimeout(replyTimeout); + return _this(); + } + + /** + * @param requestPayloadExpression the requestPayloadExpression. + * @return the enricher spec. + * @see ContentEnricher#setRequestPayloadExpression(Expression) + */ + public EnricherSpec requestPayloadExpression(String requestPayloadExpression) { + this.enricher.setRequestPayloadExpression(PARSER.parseExpression(requestPayloadExpression)); + return _this(); + } + + /** + * @param requestPayloadFunction the requestPayloadFunction. + * @param

the payload type. + * @return the enricher spec. + * @see ContentEnricher#setRequestPayloadExpression(Expression) + * @see FunctionExpression + */ + public

EnricherSpec requestPayload(Function, ?> requestPayloadFunction) { + this.enricher.setRequestPayloadExpression(new FunctionExpression<>(requestPayloadFunction)); + return _this(); + } + + /** + * @param shouldClonePayload the shouldClonePayload. + * @return the enricher spec. + * @see ContentEnricher#setShouldClonePayload(boolean) + */ + public EnricherSpec shouldClonePayload(boolean shouldClonePayload) { + this.enricher.setShouldClonePayload(shouldClonePayload); + return _this(); + } + + /** + * @param key the key. + * @param value the value. + * @param the value type. + * @return the enricher spec. + * @see ContentEnricher#setPropertyExpressions(Map) + */ + public EnricherSpec property(String key, V value) { + this.propertyExpressions.put(key, new ValueExpression(value)); + return _this(); + } + + /** + * @param key the key. + * @param expression the expression. + * @return the enricher spec. + * @see ContentEnricher#setPropertyExpressions(Map) + */ + public EnricherSpec propertyExpression(String key, String expression) { + Assert.notNull(key); + this.propertyExpressions.put(key, PARSER.parseExpression(expression)); + return _this(); + } + + /** + * @param key the key. + * @param function the function (usually a JDK8 lambda). + * @param

the payload type. + * @return the enricher spec. + * @see ContentEnricher#setPropertyExpressions(Map) + * @see FunctionExpression + */ + public

EnricherSpec propertyFunction(String key, Function, Object> function) { + this.propertyExpressions.put(key, new FunctionExpression<>(function)); + return _this(); + } + + /** + * Set a header with the value if it is not already present. + * @param name the header name. + * @param value the value. + * @param the value type. + * @return the enricher spec. + * @see ContentEnricher#setHeaderExpressions(Map) + */ + public EnricherSpec header(String name, V value) { + return this.header(name, value, null); + } + + /** + * @param name the header name. + * @param value the value. + * @param overwrite true to overwrite the header if already present. + * @param the value type. + * @return the enricher spec. + * @see ContentEnricher#setHeaderExpressions(Map) + */ + public EnricherSpec header(String name, V value, Boolean overwrite) { + AbstractHeaderValueMessageProcessor headerValueMessageProcessor = + new StaticHeaderValueMessageProcessor(value); + headerValueMessageProcessor.setOverwrite(overwrite); + return header(name, headerValueMessageProcessor); + } + + /** + * Set a header with the expression evaluation if the header is not already present. + * @param name the header name. + * @param expression the expression to be evaluated against the reply message to obtain the value. + * @return the enricher spec. + * @see ContentEnricher#setHeaderExpressions(Map) + */ + public EnricherSpec headerExpression(String name, String expression) { + return headerExpression(name, expression, null); + } + + /** + * @param name the header name. + * @param expression the expression to be evaluated against the reply message to obtain the value. + * @param overwrite true to overwrite the header if already present. + * @return the enricher spec. + * @see ContentEnricher#setHeaderExpressions(Map) + */ + public EnricherSpec headerExpression(String name, String expression, Boolean overwrite) { + Assert.hasText(expression); + return headerExpression(name, PARSER.parseExpression(expression), overwrite); + } + + /** + * Set a header with the function return value if the header is not already present. + * @param name the header name. + * @param function the function (usually a JDK8 lambda). + * @param

the payload type. + * @return the enricher spec. + * @see ContentEnricher#setHeaderExpressions(Map) + * @see FunctionExpression + */ + public

EnricherSpec headerFunction(String name, Function, Object> function) { + return headerFunction(name, function, null); + } + + /** + * @param name the header name. + * @param function the function (usually a JDK8 lambda). + * @param overwrite true to overwrite the header if already present. + * @param

the payload type. + * @return the enricher spec. + * @see ContentEnricher#setHeaderExpressions(Map) + * @see FunctionExpression + */ + public

EnricherSpec headerFunction(String name, Function, Object> function, Boolean overwrite) { + return headerExpression(name, new FunctionExpression<>(function), overwrite); + } + + private EnricherSpec headerExpression(String name, Expression expression, Boolean overwrite) { + AbstractHeaderValueMessageProcessor headerValueMessageProcessor = + new ExpressionEvaluatingHeaderValueMessageProcessor<>(expression, null); + headerValueMessageProcessor.setOverwrite(overwrite); + return header(name, headerValueMessageProcessor); + } + + /** + * Set a header value using an explicit {@link HeaderValueMessageProcessor}. + * @param name the header name. + * @param headerValueMessageProcessor the headerValueMessageProcessor. + * @param the value type. + * @return the enricher spec. + * @see ContentEnricher#setHeaderExpressions(Map) + */ + public EnricherSpec header(String name, HeaderValueMessageProcessor headerValueMessageProcessor) { + Assert.hasText(name); + this.headerExpressions.put(name, headerValueMessageProcessor); + return _this(); + } + + @Override + protected ContentEnricher doGet() { + if (!this.propertyExpressions.isEmpty()) { + this.enricher.setPropertyExpressions(this.propertyExpressions); + } + if (!this.headerExpressions.isEmpty()) { + this.enricher.setHeaderExpressions(this.headerExpressions); + } + return this.enricher; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/FilterEndpointSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/FilterEndpointSpec.java new file mode 100644 index 0000000000..e680c93458 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/FilterEndpointSpec.java @@ -0,0 +1,111 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.filter.MessageFilter; +import org.springframework.messaging.MessageChannel; +import org.springframework.util.Assert; + +/** + * A {@link ConsumerEndpointSpec} implementation for the {@link MessageFilter}. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public final class FilterEndpointSpec extends ConsumerEndpointSpec { + + FilterEndpointSpec(MessageFilter messageFilter) { + super(messageFilter); + } + + /** + * The default value is false meaning that rejected + * Messages will be quietly dropped or sent to the discard channel if + * available. Typically this value would not be true when + * a discard channel is provided, but if so, it will still apply + * (in such a case, the Message will be sent to the discard channel, + * and then the exception will be thrown). + * @param throwExceptionOnRejection the throwExceptionOnRejection. + * @return the endpoint spec. + * @see MessageFilter#setThrowExceptionOnRejection(boolean) + */ + public FilterEndpointSpec throwExceptionOnRejection(boolean throwExceptionOnRejection) { + this.handler.setThrowExceptionOnRejection(throwExceptionOnRejection); + return _this(); + } + + /** + * Specify a channel where rejected Messages should be sent. If the discard + * channel is null (the default), rejected Messages will be dropped. However, + * the 'throwExceptionOnRejection' flag determines whether rejected Messages + * trigger an exception. That value is evaluated regardless of the presence + * of a discard channel. + * @param discardChannel the discardChannel. + * @return the endpoint spec. + * @see MessageFilter#setDiscardChannel(MessageChannel) + */ + public FilterEndpointSpec discardChannel(MessageChannel discardChannel) { + this.handler.setDiscardChannel(discardChannel); + return _this(); + } + + /** + * Specify a channel name where rejected Messages should be sent. If the discard + * channel is null (the default), rejected Messages will be dropped. However, + * the 'throwExceptionOnRejection' flag determines whether rejected Messages + * trigger an exception. That value is evaluated regardless of the presence + * of a discard channel. + * @param discardChannelName the discardChannelName. + * @return the endpoint spec. + * @see MessageFilter#setDiscardChannelName(String) + */ + public FilterEndpointSpec discardChannel(String discardChannelName) { + this.handler.setDiscardChannelName(discardChannelName); + return _this(); + } + + /** + * Configure a subflow to run for discarded messages instead of a + * {@link #discardChannel(MessageChannel)}. + * @param discardFlow the discard flow. + * @return the endpoint spec. + */ + public FilterEndpointSpec discardFlow(IntegrationFlow discardFlow) { + Assert.notNull(discardFlow); + DirectChannel channel = new DirectChannel(); + IntegrationFlowBuilder flowBuilder = IntegrationFlows.from(channel); + discardFlow.configure(flowBuilder); + this.componentToRegister.add(flowBuilder.get()); + return discardChannel(channel); + } + + /** + * Set to 'true' if you wish the discard processing to occur within any + * request handler advice applied to this filter. Also applies to + * throwing an exception on rejection. Default: true. + * @param discardWithinAdvice the discardWithinAdvice. + * @return the endpoint spec. + * @see MessageFilter#setDiscardWithinAdvice(boolean) + */ + public FilterEndpointSpec discardWithinAdvice(boolean discardWithinAdvice) { + this.handler.setDiscardWithinAdvice(discardWithinAdvice); + return _this(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/GatewayEndpointSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/GatewayEndpointSpec.java new file mode 100644 index 0000000000..3a90e73954 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/GatewayEndpointSpec.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.messaging.MessageChannel; + +/** + * A {@link ConsumerEndpointSpec} implementation for a mid-flow {@link GatewayMessageHandler}. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public final class GatewayEndpointSpec extends ConsumerEndpointSpec { + + GatewayEndpointSpec(MessageChannel requestChannel) { + super(new GatewayMessageHandler()); + this.handler.setRequestChannel(requestChannel); + } + + GatewayEndpointSpec(String requestChannel) { + super(new GatewayMessageHandler()); + this.handler.setRequestChannelName(requestChannel); + } + + public GatewayEndpointSpec replyChannel(MessageChannel replyChannel) { + this.handler.setReplyChannel(replyChannel); + return this; + } + + public GatewayEndpointSpec replyChannel(String replyChannel) { + this.handler.setReplyChannelName(replyChannel); + return this; + } + + public GatewayEndpointSpec errorChannel(MessageChannel errorChannel) { + this.handler.setErrorChannel(errorChannel); + return this; + } + + public GatewayEndpointSpec errorChannel(String errorChannel) { + this.handler.setErrorChannelName(errorChannel); + return this; + } + + public GatewayEndpointSpec requestTimeout(Long requestTimeout) { + this.handler.setRequestTimeout(requestTimeout); + return this; + } + + public GatewayEndpointSpec replyTimeout(Long replyTimeout) { + this.handler.setReplyTimeout(replyTimeout); + return this; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/GatewayMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/GatewayMessageHandler.java new file mode 100644 index 0000000000..78e452273e --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/GatewayMessageHandler.java @@ -0,0 +1,127 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.Lifecycle; +import org.springframework.integration.gateway.GatewayProxyFactoryBean; +import org.springframework.integration.gateway.RequestReplyExchanger; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +class GatewayMessageHandler extends AbstractReplyProducingMessageHandler implements Lifecycle { + + private final GatewayProxyFactoryBean gatewayProxyFactoryBean; + + private RequestReplyExchanger exchanger; + + private volatile boolean running; + + GatewayMessageHandler() { + this.gatewayProxyFactoryBean = new GatewayProxyFactoryBean(); + this.gatewayProxyFactoryBean.setServiceInterface(RequestReplyExchanger.class); + } + + void setRequestChannel(MessageChannel requestChannel) { + this.gatewayProxyFactoryBean.setDefaultRequestChannel(requestChannel); + } + + void setRequestChannelName(String requestChannel) { + this.gatewayProxyFactoryBean.setDefaultRequestChannelName(requestChannel); + } + + public void setReplyChannel(MessageChannel replyChannel) { + this.gatewayProxyFactoryBean.setDefaultReplyChannel(replyChannel); + } + + public void setReplyChannelName(String replyChannel) { + this.gatewayProxyFactoryBean.setDefaultReplyChannelName(replyChannel); + } + + public void setErrorChannel(MessageChannel errorChannel) { + this.gatewayProxyFactoryBean.setErrorChannel(errorChannel); + } + + public void setErrorChannelName(String errorChannel) { + this.gatewayProxyFactoryBean.setErrorChannelName(errorChannel); + } + + public void setRequestTimeout(Long requestTimeout) { + this.gatewayProxyFactoryBean.setDefaultRequestTimeout(requestTimeout); + } + + public void setReplyTimeout(Long replyTimeout) { + this.gatewayProxyFactoryBean.setDefaultReplyTimeout(replyTimeout); + } + + @Override + protected Object handleRequestMessage(Message requestMessage) { + if (this.exchanger == null) { + synchronized (this) { + if (this.exchanger == null) { + initialize(); + } + } + } + return this.exchanger.exchange(requestMessage); + } + + private void initialize() { + BeanFactory beanFactory = getBeanFactory(); + + if (beanFactory instanceof ConfigurableListableBeanFactory) { + ((ConfigurableListableBeanFactory) beanFactory).initializeBean(this.gatewayProxyFactoryBean, null); + } + try { + this.exchanger = (RequestReplyExchanger) this.gatewayProxyFactoryBean.getObject(); + } + catch (Exception e) { + throw new BeanCreationException("Can't instantiate the GatewayProxyFactoryBean: " + this, e); + } + if (this.running) { + // We must stop gatewayProxyFactoryBean because after the normal start its "gatewayMap" is still empty + this.gatewayProxyFactoryBean.stop(); + this.gatewayProxyFactoryBean.start(); + } + } + + @Override + public void start() { + this.gatewayProxyFactoryBean.start(); + this.running = true; + } + + @Override + public void stop() { + this.gatewayProxyFactoryBean.stop(); + this.running = false; + } + + @Override + public boolean isRunning() { + return this.running; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/GenericEndpointSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/GenericEndpointSpec.java new file mode 100644 index 0000000000..b3e85d9cfd --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/GenericEndpointSpec.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.messaging.MessageHandler; + +/** + * A {@link ConsumerEndpointSpec} for a {@link MessageHandler} implementations. + * + * @param the {@link MessageHandler} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public final class GenericEndpointSpec + extends ConsumerEndpointSpec, H> { + + GenericEndpointSpec(H messageHandler) { + super(messageHandler); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/HeaderEnricherSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/HeaderEnricherSpec.java new file mode 100644 index 0000000000..585d8d642a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/HeaderEnricherSpec.java @@ -0,0 +1,436 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.springframework.expression.Expression; +import org.springframework.integration.context.IntegrationContextUtils; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.handler.BeanNameMessageProcessor; +import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; +import org.springframework.integration.handler.MessageProcessor; +import org.springframework.integration.support.MapBuilder; +import org.springframework.integration.support.StringStringMapBuilder; +import org.springframework.integration.transformer.HeaderEnricher; +import org.springframework.integration.transformer.support.AbstractHeaderValueMessageProcessor; +import org.springframework.integration.transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor; +import org.springframework.integration.transformer.support.HeaderValueMessageProcessor; +import org.springframework.integration.transformer.support.StaticHeaderValueMessageProcessor; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * An {@link IntegrationComponentSpec} for a {@link HeaderEnricher}. + * + * @author Artem Bilan + * @author Gary Russell + * + * @since 5.0 + */ +public class HeaderEnricherSpec extends IntegrationComponentSpec { + + private final Map> headerToAdd = new HashMap<>(); + + private boolean defaultOverwrite = false; + + private boolean shouldSkipNulls = true; + + private MessageProcessor messageProcessor; + + HeaderEnricherSpec() { + } + + /** + * Determine the default action to take when setting individual header specifications + * without an explicit 'overwrite' argument. + * @param defaultOverwrite the defaultOverwrite. + * @return the header enricher spec. + * @see HeaderEnricher#setDefaultOverwrite(boolean) + */ + public HeaderEnricherSpec defaultOverwrite(boolean defaultOverwrite) { + this.defaultOverwrite = defaultOverwrite; + return _this(); + } + + /** + * @param shouldSkipNulls the shouldSkipNulls. + * @return the header enricher spec. + * @see HeaderEnricher#setShouldSkipNulls(boolean) + */ + public HeaderEnricherSpec shouldSkipNulls(boolean shouldSkipNulls) { + this.shouldSkipNulls = shouldSkipNulls; + return _this(); + } + + /** + * Configure an optional custom {@link MessageProcessor} for the enricher. The + * processor must return a {@link Map} of header names and values. They will be added + * to the inbound message headers before evaluating the individual configured header + * specifications. + * @param messageProcessor the messageProcessor. + * @return the header enricher spec. + * @see HeaderEnricher#setMessageProcessor(MessageProcessor) + */ + public HeaderEnricherSpec messageProcessor(MessageProcessor messageProcessor) { + this.messageProcessor = messageProcessor; + return _this(); + } + + /** + * Configure an {@link ExpressionEvaluatingMessageProcessor} that evaluates to a + * {@link Map} of additional headers. They will be added to the inbound message + * headers before evaluating the individual configured header specifications. + * @param expression the expression. + * @return the header enricher spec. + * @see #messageProcessor(MessageProcessor) + */ + public HeaderEnricherSpec messageProcessor(String expression) { + return messageProcessor(new ExpressionEvaluatingMessageProcessor(PARSER.parseExpression(expression))); + } + + /** + * Configure an + * {@link org.springframework.integration.handler.MethodInvokingMessageProcessor} that + * invokes the method on the bean - the method must return a {@link Map} of headers. + * They will be added to the inbound message headers before evaluating the individual + * configured header specifications. + * @param beanName The bean name. + * @param methodName The method name. + * @return the header enricher spec. + * @see #messageProcessor(MessageProcessor) + */ + public HeaderEnricherSpec messageProcessor(String beanName, String methodName) { + return messageProcessor(new BeanNameMessageProcessor(beanName, methodName)); + } + + /** + * Add header specifications from the {@link MapBuilder}; if a map value is an + * {@link Expression}, it will be evaluated at run time when the message headers are + * enriched. Otherwise the value is simply added to the headers. Headers derived from + * the map will not overwrite existing headers, unless + * {@link #defaultOverwrite(boolean)} is true. + * @param headers the header map builder. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headers(MapBuilder headers) { + return headers(headers, null); + } + + /** + * Add header specifications from the {@link MapBuilder}; if a map value is an + * {@link Expression}, it will be evaluated at run time when the message headers are + * enriched. Otherwise the value is simply added to the headers. + * @param headers the header map builder. + * @param overwrite true to overwrite existing headers. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headers(MapBuilder headers, Boolean overwrite) { + Assert.notNull(headers); + return headers(headers.get(), overwrite); + } + + /** + * Add header specifications from the {@link Map}; if a map value is an + * {@link Expression}, it will be evaluated at run time when the message headers are + * enriched. Otherwise the value is simply added to the headers. Headers derived from + * the map will not overwrite existing headers, unless + * {@link #defaultOverwrite(boolean)} is true. + * @param headers The header builder. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headers(Map headers) { + return headers(headers, null); + } + + /** + * Add header specifications from the {@link Map}; if a map value is an + * {@link Expression}, it will be evaluated at run time when the message headers are + * enriched. Otherwise the value is simply added to the headers. + * @param headers The header builder. + * @param overwrite true to overwrite existing headers. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headers(Map headers, Boolean overwrite) { + Assert.notNull(headers); + for (Entry entry : headers.entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof Expression) { + AbstractHeaderValueMessageProcessor processor = + new ExpressionEvaluatingHeaderValueMessageProcessor((Expression) value, null); + processor.setOverwrite(overwrite); + header(name, processor); + } + else { + header(name, value, overwrite); + } + } + return this; + } + + /** + * Add header specifications from the {@link MapBuilder}; the {@link Map} values must + * be String representations of SpEL expressions that will be evaluated at run time + * when the message headers are enriched. Headers derived from the map will not + * overwrite existing headers, unless {@link #defaultOverwrite(boolean)} is true. + * @param headers the header map builder. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headerExpressions(MapBuilder headers) { + return headerExpressions(headers, null); + } + + /** + * Add header specifications from the {@link MapBuilder}; the {@link Map} values must + * be String representations of SpEL expressions that will be evaluated at run time + * when the message headers are enriched. + * @param headers the header map builder. + * @param overwrite true to overwrite existing headers. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headerExpressions(MapBuilder headers, Boolean overwrite) { + Assert.notNull(headers); + return headerExpressions(headers.get(), overwrite); + } + + /** + * Add header specifications via the consumer callback, which receives a + * {@link StringStringMapBuilder}; the {@link Map} values must be String + * representations of SpEL expressions that will be evaluated at run time when the + * message headers are enriched. Headers derived from the map will not + * overwrite existing headers, unless {@link #defaultOverwrite(boolean)} is true. + * Usually used with a JDK8 lambda: + *
+	 * {@code
+	 * .enrichHeaders(s -> s.headerExpressions(c -> c
+	 * 			.put(MailHeaders.SUBJECT, "payload.subject")
+	 * 			.put(MailHeaders.FROM,    "payload.from[0].toString()")))
+	 * }
+	 * 
+ * @param configurer the configurer. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headerExpressions(Consumer configurer) { + return headerExpressions(configurer, null); + } + + /** + * Add header specifications via the consumer callback, which receives a + * {@link StringStringMapBuilder}; the {@link Map} values must be String + * representations of SpEL expressions that will be evaluated at run time when the + * message headers are enriched. Usually used with a JDK8 lambda: + *
+	 * {@code
+	 * .enrichHeaders(s -> s.headerExpressions(c -> c
+	 * 			.put(MailHeaders.SUBJECT, "payload.subject")
+	 * 			.put(MailHeaders.FROM,    "payload.from[0].toString()"), true))
+	 * }
+	 * 
+ * @param configurer the configurer. + * @param overwrite true to overwrite existing headers. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headerExpressions(Consumer configurer, Boolean overwrite) { + Assert.notNull(configurer); + StringStringMapBuilder builder = new StringStringMapBuilder(); + configurer.accept(builder); + return headerExpressions(builder.get(), overwrite); + } + + /** + * Add header specifications; the {@link Map} values must be String representations + * of SpEL expressions that will be evaluated at run time when the message headers are + * enriched. Headers derived from the map will not overwrite existing headers, + * unless {@link #defaultOverwrite(boolean)} is true. + * @param headers the headers. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headerExpressions(Map headers) { + return headerExpressions(headers, null); + } + + /** + * Add header specifications; the {@link Map} values must be String representations of + * SpEL expressions that will be evaluated at run time when the message headers are + * enriched. + * @param headers the headers. + * @param overwrite true to overwrite existing headers. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headerExpressions(Map headers, Boolean overwrite) { + Assert.notNull(headers); + for (Entry entry : headers.entrySet()) { + AbstractHeaderValueMessageProcessor processor = + new ExpressionEvaluatingHeaderValueMessageProcessor(entry.getValue(), null); + processor.setOverwrite(overwrite); + header(entry.getKey(), processor); + } + return this; + } + + /** + * Add a single header specification. If the header exists, it will not be + * overwritten unless {@link #defaultOverwrite(boolean)} is true. + * @param name the header name. + * @param value the header value (not an {@link Expression}). + * @param the value type. + * @return the header enricher spec. + */ + public HeaderEnricherSpec header(String name, V value) { + return header(name, value, null); + } + + /** + * Add a single header specification. + * @param name the header name. + * @param value the header value (not an {@link Expression}). + * @param overwrite true to overwrite an existing header. + * @param the value type. + * @return the header enricher spec. + */ + public HeaderEnricherSpec header(String name, V value, Boolean overwrite) { + AbstractHeaderValueMessageProcessor headerValueMessageProcessor = + new StaticHeaderValueMessageProcessor(value); + headerValueMessageProcessor.setOverwrite(overwrite); + return header(name, headerValueMessageProcessor); + } + + /** + * Add a single header specification where the value is a String representation of a + * SpEL {@link Expression}. If the header exists, it will not be overwritten + * unless {@link #defaultOverwrite(boolean)} is true. + * @param name the header name. + * @param expression the expression. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headerExpression(String name, String expression) { + return headerExpression(name, expression, null); + } + + /** + * Add a single header specification where the value is a String representation of a + * SpEL {@link Expression}. + * @param name the header name. + * @param expression the expression. + * @param overwrite true to overwrite an existing header. + * @return the header enricher spec. + */ + public HeaderEnricherSpec headerExpression(String name, String expression, Boolean overwrite) { + Assert.hasText(expression); + return headerExpression(name, PARSER.parseExpression(expression), overwrite); + } + + /** + * Add a single header specification where the value is obtained by invoking the + * {@link Function} callback. If the header exists, it will not be overwritten + * unless {@link #defaultOverwrite(boolean)} is true. + * @param name the header name. + * @param function the function. + * @param

the payload type. + * @return the header enricher spec. + * @see FunctionExpression + */ + public

HeaderEnricherSpec headerFunction(String name, Function, Object> function) { + return headerFunction(name, function, null); + } + + /** + * Add a single header specification where the value is obtained by invoking the + * {@link Function} callback. + * @param name the header name. + * @param function the function. + * @param overwrite true to overwrite an existing header. + * @param

the payload type. + * @return the header enricher spec. + * @see FunctionExpression + */ + public

HeaderEnricherSpec headerFunction(String name, Function, Object> function, + Boolean overwrite) { + return headerExpression(name, new FunctionExpression<>(function), overwrite); + } + + private HeaderEnricherSpec headerExpression(String name, Expression expression, Boolean overwrite) { + AbstractHeaderValueMessageProcessor headerValueMessageProcessor = + new ExpressionEvaluatingHeaderValueMessageProcessor(expression, null); + headerValueMessageProcessor.setOverwrite(overwrite); + return header(name, headerValueMessageProcessor); + } + + /** + * Add a single header specification where the value is obtained by calling the + * {@link HeaderValueMessageProcessor}. + * @param name the header name. + * @param headerValueMessageProcessor the message processor. + * @param the value type. + * @return the header enricher spec. + */ + public HeaderEnricherSpec header(String name, HeaderValueMessageProcessor headerValueMessageProcessor) { + Assert.hasText(name); + this.headerToAdd.put(name, headerValueMessageProcessor); + return _this(); + } + + /** + * Add header specifications to automatically convert header channels (reply, error + * channels) to Strings and store them in a header channel registry. Allows + * persistence and serialization of messages without losing these important framework + * headers. + * @return the header enricher spec. + * @see org.springframework.integration.support.channel.HeaderChannelRegistry + */ + public HeaderEnricherSpec headerChannelsToString() { + return headerChannelsToString(null); + } + + /** + * Add header specifications to automatically convert header channels (reply, error + * channels) to Strings and store them in a header channel registry. Allows + * persistence and serialization of messages without losing these important framework + * headers. + * @param timeToLiveExpression the minimum time that the mapping will remain in the registry. + * @return the header enricher spec. + * @see org.springframework.integration.support.channel.HeaderChannelRegistry + */ + public HeaderEnricherSpec headerChannelsToString(String timeToLiveExpression) { + return headerExpression("replyChannel", + "@" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME + + ".channelToChannelName(headers.replyChannel" + + (StringUtils.hasText(timeToLiveExpression) ? ", " + timeToLiveExpression : "") + ")", + true) + .headerExpression("errorChannel", + "@" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME + + ".channelToChannelName(headers.errorChannel" + + (StringUtils.hasText(timeToLiveExpression) ? ", " + timeToLiveExpression : "") + ")", + true); + } + + @Override + protected HeaderEnricher doGet() { + HeaderEnricher headerEnricher = new HeaderEnricher(new HashMap<>(this.headerToAdd)); + headerEnricher.setDefaultOverwrite(this.defaultOverwrite); + headerEnricher.setShouldSkipNulls(this.shouldSkipNulls); + headerEnricher.setMessageProcessor(this.messageProcessor); + return headerEnricher; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationComponentSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationComponentSpec.java new file mode 100644 index 0000000000..08eeb468fb --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationComponentSpec.java @@ -0,0 +1,95 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +/** + * The common Builder abstraction. The {@link #get()} method returns the final component. + * + * @param the target {@link IntegrationComponentSpec} implementation type. + * @param the target type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class IntegrationComponentSpec, T> + implements FactoryBean { + + protected final static SpelExpressionParser PARSER = new SpelExpressionParser(); + + protected final Log logger = LogFactory.getLog(getClass()); + + protected volatile T target; + + private String id; + + /** + * Configure the component identifier. Used as the {@code beanName} to register the + * bean in the application context for this component. + * @param id the id. + * @return the spec. + */ + protected S id(String id) { + this.id = id; + return _this(); + } + + public final String getId() { + return this.id; + } + + /** + * @return the configured component. + */ + public final T get() { + if (this.target == null) { + this.target = doGet(); + } + return this.target; + } + + @Override + public T getObject() throws Exception { + return get(); + } + + @Override + public Class getObjectType() { + return get().getClass(); + } + + @Override + public boolean isSingleton() { + return true; + } + + @SuppressWarnings("unchecked") + protected final S _this() { + return (S) this; + } + + protected T doGet() { + throw new UnsupportedOperationException(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlow.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlow.java new file mode 100644 index 0000000000..cc309c6592 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlow.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +/** + * The main Integration DSL abstraction. + *

+ * The {@link StandardIntegrationFlow} implementation (produced by {@link IntegrationFlowBuilder}) + * represents a container for the integration components, which will be registered + * in the application context. Typically is used as {@code @Bean} definition: + *

+ *  @Bean
+ *  public IntegrationFlow fileReadingFlow() {
+ *      return IntegrationFlows
+ *             .from(Files.inboundAdapter(tmpDir.getRoot()), e -> e.poller(Pollers.fixedDelay(100)))
+ *             .transform(Files.fileToString())
+ *             .channel(MessageChannels.queue("fileReadingResultChannel"))
+ *             .get();
+ *  }
+ * 
+ *

+ * Can be used as a Lambda for top level definition as well as sub-flow definitions: + *

+ * @Bean
+ * public IntegrationFlow routerTwoSubFlows() {
+ *     return f -> f
+ *               .split()
+ *               .<Integer, Boolean>route(p -> p % 2 == 0, m -> m
+ *                              .subFlowMapping(true, sf -> sf.<Integer>handle((p, h) -> p * 2))
+ *                              .subFlowMapping(false, sf -> sf.<Integer>handle((p, h) -> p * 3)))
+ *               .aggregate()
+ *               .channel(MessageChannels..queue("routerTwoSubFlowsOutput"));
+ * }
+ *
+ * 
+ *

+ * Also this interface can be implemented directly to encapsulate the integration logic + * in the target service: + *

+ *  @Component
+ *  public class MyFlow implements IntegrationFlow {
+ *
+ *        @Override
+ *        public void configure(IntegrationFlowDefinition<?> f) {
+ *                f.<String, String>transform(String::toUpperCase);
+ *        }
+ *
+ *  }
+ * 
+ * + * @author Artem Bilan + * + * @since 5.0 + * + * @see IntegrationFlowBuilder + * @see StandardIntegrationFlow + * @see IntegrationFlowAdapter + */ +@FunctionalInterface +public interface IntegrationFlow { + + void configure(IntegrationFlowDefinition flow); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowAdapter.java new file mode 100644 index 0000000000..bcb5ffaf3a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowAdapter.java @@ -0,0 +1,178 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import org.springframework.context.SmartLifecycle; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.dsl.channel.MessageChannelSpec; +import org.springframework.integration.endpoint.MessageProducerSupport; +import org.springframework.integration.gateway.MessagingGatewaySupport; +import org.springframework.messaging.MessageChannel; +import org.springframework.util.Assert; + +/** + * The base {@code Adapter} class for the {@link IntegrationFlow} abstraction. + * Requires the implementation for the {@link #buildFlow()} method to produce + * {@link IntegrationFlowDefinition} using one of {@link #from} support methods. + *

+ * Typically is used for target service implementation: + *

+ *  @Component
+ *  public class MyFlowAdapter extends IntegrationFlowAdapter {
+ *
+ *     @Autowired
+ *     private ConnectionFactory rabbitConnectionFactory;
+ *
+ *     @Override
+ *     protected IntegrationFlowDefinition<?> buildFlow() {
+ *          return from(Amqp.inboundAdapter(this.rabbitConnectionFactory, "myQueue"))
+ *                   .<String, String>transform(String::toLowerCase)
+ *                   .channel(c -> c.queue("myFlowAdapterOutput"));
+ *     }
+ *
+ * }
+ * 
+ * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class IntegrationFlowAdapter implements IntegrationFlow, SmartLifecycle { + + private final AtomicBoolean running = new AtomicBoolean(); + + private StandardIntegrationFlow targetIntegrationFlow; + + @Override + public final void configure(IntegrationFlowDefinition flow) { + IntegrationFlowDefinition targetFlow = buildFlow(); + Assert.state(targetFlow != null, "the 'buildFlow()' must not return null"); + flow.integrationComponents.clear(); + flow.integrationComponents.addAll(targetFlow.integrationComponents); + this.targetIntegrationFlow = flow.get(); + } + + @Override + public void start() { + assertTargetIntegrationFlow(); + if (!this.running.getAndSet(true)) { + this.targetIntegrationFlow.start(); + } + } + + private void assertTargetIntegrationFlow() { + Assert.state(this.targetIntegrationFlow != null, + this + " hasn't been initialized properly via BeanFactory.\n" + + "Missed @EnableIntegration ?"); + } + + @Override + public void stop(Runnable callback) { + assertTargetIntegrationFlow(); + if (this.running.getAndSet(false)) { + this.targetIntegrationFlow.stop(callback); + } + } + + @Override + public void stop() { + assertTargetIntegrationFlow(); + if (this.running.getAndSet(false)) { + this.targetIntegrationFlow.stop(); + } + } + + @Override + public boolean isRunning() { + return this.running.get(); + } + + @Override + public boolean isAutoStartup() { + return false; + } + + @Override + public int getPhase() { + return 0; + } + + protected IntegrationFlowDefinition from(String messageChannelName) { + return IntegrationFlows.from(messageChannelName); + } + + protected IntegrationFlowDefinition from(MessageChannel messageChannel) { + return IntegrationFlows.from(messageChannel); + } + + protected IntegrationFlowDefinition from(String messageChannelName, boolean fixedSubscriber) { + return IntegrationFlows.from(messageChannelName, fixedSubscriber); + } + + protected IntegrationFlowDefinition from(MessageSourceSpec> messageSourceSpec, + Consumer endpointConfigurer) { + return IntegrationFlows.from(messageSourceSpec, endpointConfigurer); + } + + protected IntegrationFlowDefinition from(MessageSource messageSource, + Consumer endpointConfigurer) { + return IntegrationFlows.from(messageSource, endpointConfigurer); + } + + protected IntegrationFlowDefinition from(MessageProducerSupport messageProducer) { + return IntegrationFlows.from(messageProducer); + } + + protected IntegrationFlowDefinition from(MessageSource messageSource) { + return IntegrationFlows.from(messageSource); + } + + protected IntegrationFlowDefinition from(MessagingGatewaySupport inboundGateway) { + return IntegrationFlows.from(inboundGateway); + } + + protected IntegrationFlowDefinition from(MessageChannelSpec messageChannelSpec) { + return IntegrationFlows.from(messageChannelSpec); + } + + protected IntegrationFlowDefinition from(MessageProducerSpec messageProducerSpec) { + return IntegrationFlows.from(messageProducerSpec); + } + + protected IntegrationFlowDefinition from(MessageSourceSpec> messageSourceSpec) { + return IntegrationFlows.from(messageSourceSpec); + } + + protected IntegrationFlowDefinition from(MessagingGatewaySpec inboundGatewaySpec) { + return IntegrationFlows.from(inboundGatewaySpec); + } + + protected IntegrationFlowBuilder from(Object service, String methodName) { + return IntegrationFlows.from(service, methodName); + } + + protected IntegrationFlowBuilder from(Object service, String methodName, + Consumer endpointConfigurer) { + return IntegrationFlows.from(service, methodName, endpointConfigurer); + } + + protected abstract IntegrationFlowDefinition buildFlow(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowBuilder.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowBuilder.java new file mode 100644 index 0000000000..1cb18222fc --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public final class IntegrationFlowBuilder extends IntegrationFlowDefinition { + + IntegrationFlowBuilder() { + super(); + } + + @Override + public StandardIntegrationFlow get() { + return super.get(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java new file mode 100644 index 0000000000..1edde21691 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java @@ -0,0 +1,2937 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.reactivestreams.Publisher; + +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.integration.aggregator.AggregatingMessageHandler; +import org.springframework.integration.aggregator.BarrierMessageHandler; +import org.springframework.integration.aggregator.ResequencingMessageHandler; +import org.springframework.integration.channel.ChannelInterceptorAware; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.channel.FixedSubscriberChannel; +import org.springframework.integration.channel.PublishSubscribeChannel; +import org.springframework.integration.channel.interceptor.WireTap; +import org.springframework.integration.config.ConsumerEndpointFactoryBean; +import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean; +import org.springframework.integration.core.GenericSelector; +import org.springframework.integration.core.MessageSelector; +import org.springframework.integration.dsl.channel.MessageChannelSpec; +import org.springframework.integration.dsl.channel.WireTapSpec; +import org.springframework.integration.dsl.support.FixedSubscriberChannelPrototype; +import org.springframework.integration.dsl.support.MessageChannelReference; +import org.springframework.integration.expression.ControlBusMethodFilter; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.filter.ExpressionEvaluatingSelector; +import org.springframework.integration.filter.MessageFilter; +import org.springframework.integration.filter.MethodInvokingSelector; +import org.springframework.integration.handler.AbstractMessageProducingHandler; +import org.springframework.integration.handler.BeanNameMessageProcessor; +import org.springframework.integration.handler.BridgeHandler; +import org.springframework.integration.handler.DelayHandler; +import org.springframework.integration.handler.ExpressionCommandMessageProcessor; +import org.springframework.integration.handler.GenericHandler; +import org.springframework.integration.handler.LoggingHandler; +import org.springframework.integration.handler.MessageProcessor; +import org.springframework.integration.handler.MessageTriggerAction; +import org.springframework.integration.handler.MethodInvokingMessageProcessor; +import org.springframework.integration.handler.ServiceActivatingHandler; +import org.springframework.integration.router.AbstractMappingMessageRouter; +import org.springframework.integration.router.AbstractMessageRouter; +import org.springframework.integration.router.ExpressionEvaluatingRouter; +import org.springframework.integration.router.MethodInvokingRouter; +import org.springframework.integration.router.RecipientListRouter; +import org.springframework.integration.scattergather.ScatterGatherHandler; +import org.springframework.integration.splitter.AbstractMessageSplitter; +import org.springframework.integration.splitter.DefaultMessageSplitter; +import org.springframework.integration.splitter.ExpressionEvaluatingSplitter; +import org.springframework.integration.splitter.MethodInvokingSplitter; +import org.springframework.integration.store.MessageStore; +import org.springframework.integration.support.MapBuilder; +import org.springframework.integration.transformer.ClaimCheckInTransformer; +import org.springframework.integration.transformer.ClaimCheckOutTransformer; +import org.springframework.integration.transformer.ContentEnricher; +import org.springframework.integration.transformer.ExpressionEvaluatingTransformer; +import org.springframework.integration.transformer.GenericTransformer; +import org.springframework.integration.transformer.HeaderFilter; +import org.springframework.integration.transformer.MessageTransformingHandler; +import org.springframework.integration.transformer.MethodInvokingTransformer; +import org.springframework.integration.transformer.Transformer; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.PollableChannel; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import reactor.util.function.Tuple2; + +/** + * The {@code Builder} pattern implementation for the EIP-method chain. + * Provides a variety of methods to populate Spring Integration components + * to an {@link IntegrationFlow} for the future registration in the + * application context. + * + * @param the {@link IntegrationFlowDefinition} implementation type. + * + * @author Artem Bilan + * @author Gary Russell + * @author Gabriele Del Prete + * + * @since 5.0 + * + * @see org.springframework.integration.config.dsl.IntegrationFlowBeanPostProcessor + */ +public abstract class IntegrationFlowDefinition> { + + private static final SpelExpressionParser PARSER = new SpelExpressionParser(); + + private static final Set REFERENCED_REPLY_PRODUCERS = new HashSet<>(); + + protected final Set integrationComponents = new LinkedHashSet<>(); + + protected MessageChannel currentMessageChannel; + + protected Object currentComponent; + + private StandardIntegrationFlow integrationFlow; + + IntegrationFlowDefinition() { + } + + B addComponent(Object component) { + this.integrationComponents.add(component); + return _this(); + } + + B addComponents(Collection components) { + if (components != null) { + this.integrationComponents.addAll(components); + } + return _this(); + } + + B currentComponent(Object component) { + this.currentComponent = component; + return _this(); + } + + /** + * Populate an {@link org.springframework.integration.channel.FixedSubscriberChannel} instance + * at the current {@link IntegrationFlow} chain position. + * The 'bean name' will be generated during the bean registration phase. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B fixedSubscriberChannel() { + return fixedSubscriberChannel(null); + } + + /** + * Populate an {@link org.springframework.integration.channel.FixedSubscriberChannel} instance + * at the current {@link IntegrationFlow} chain position. + * The provided {@code messageChannelName} is used for the bean registration. + * @param messageChannelName the bean name to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B fixedSubscriberChannel(String messageChannelName) { + return channel(new FixedSubscriberChannelPrototype(messageChannelName)); + } + + /** + * Populate a {@link MessageChannelReference} instance + * at the current {@link IntegrationFlow} chain position. + * The provided {@code messageChannelName} is used for the bean registration + * ({@link org.springframework.integration.channel.DirectChannel}), if there is no such a bean + * in the application context. Otherwise the existing {@link MessageChannel} bean is used + * to wire integration endpoints. + * @param messageChannelName the bean name to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B channel(String messageChannelName) { + return channel(new MessageChannelReference(messageChannelName)); + } + + /** + * Populate a {@link MessageChannel} instance + * at the current {@link IntegrationFlow} chain position using the {@link MessageChannelSpec} + * fluent API. + * @param messageChannelSpec the {@link MessageChannelSpec} to use. + * @return the current {@link IntegrationFlowDefinition}. + * @see org.springframework.integration.dsl.channel.MessageChannels + */ + public B channel(MessageChannelSpec messageChannelSpec) { + Assert.notNull(messageChannelSpec); + return channel(messageChannelSpec.get()); + } + + /** + * Populate the provided {@link MessageChannel} instance + * at the current {@link IntegrationFlow} chain position. + * The {@code messageChannel} can be an existing bean, or fresh instance, in which case + * the {@link org.springframework.integration.config.dsl.IntegrationFlowBeanPostProcessor} + * will populate it as a bean with a generated name. + * @param messageChannel the {@link MessageChannel} to populate. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B channel(MessageChannel messageChannel) { + Assert.notNull(messageChannel); + if (this.currentMessageChannel != null) { + bridge(null); + } + this.currentMessageChannel = messageChannel; + return registerOutputChannelIfCan(this.currentMessageChannel); + } + + /** + * The {@link org.springframework.integration.channel.PublishSubscribeChannel} {@link #channel} + * method specific implementation to allow the use of the 'subflow' subscriber capability. + * @param publishSubscribeChannelConfigurer the {@link Consumer} to specify + * {@link PublishSubscribeSpec} options including 'subflow' definition. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B publishSubscribeChannel(Consumer publishSubscribeChannelConfigurer) { + return publishSubscribeChannel(null, publishSubscribeChannelConfigurer); + } + + /** + * The {@link org.springframework.integration.channel.PublishSubscribeChannel} {@link #channel} + * method specific implementation to allow the use of the 'subflow' subscriber capability. + * Use the provided {@link Executor} for the target subscribers. + * @param executor the {@link Executor} to use. + * @param publishSubscribeChannelConfigurer the {@link Consumer} to specify + * {@link PublishSubscribeSpec} options including 'subflow' definition. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B publishSubscribeChannel(Executor executor, + Consumer publishSubscribeChannelConfigurer) { + Assert.notNull(publishSubscribeChannelConfigurer); + PublishSubscribeSpec spec = new PublishSubscribeSpec(executor); + publishSubscribeChannelConfigurer.accept(spec); + return addComponents(spec.getComponentsToRegister()).channel(spec); + } + + /** + * Populate the {@code Wire Tap} EI Pattern specific + * {@link org.springframework.messaging.support.ChannelInterceptor} implementation + * to the current {@link #currentMessageChannel}. + * It is useful when an implicit {@link MessageChannel} is used between endpoints: + *
+	 * {@code
+	 *  .filter("World"::equals)
+	 *  .wireTap(sf -> sf.transform(String::toUpperCase))
+	 *  .handle(p -> process(p))
+	 * }
+	 * 
+ * This method can be used after any {@link #channel} for explicit {@link MessageChannel}, + * but with the caution do not impact existing {@link org.springframework.messaging.support.ChannelInterceptor}s. + * @param flow the {@link IntegrationFlow} for wire-tap subflow as an alternative to the {@code wireTapChannel}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B wireTap(IntegrationFlow flow) { + return wireTap(flow, null); + } + + /** + * Populate the {@code Wire Tap} EI Pattern specific + * {@link org.springframework.messaging.support.ChannelInterceptor} implementation + * to the current {@link #currentMessageChannel}. + * It is useful when an implicit {@link MessageChannel} is used between endpoints: + *
+	 * {@code
+	 *  f -> f.wireTap("tapChannel")
+	 *    .handle(p -> process(p))
+	 * }
+	 * 
+ * This method can be used after any {@link #channel} for explicit {@link MessageChannel}, + * but with the caution do not impact existing {@link org.springframework.messaging.support.ChannelInterceptor}s. + * @param wireTapChannel the {@link MessageChannel} bean name to wire-tap. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B wireTap(String wireTapChannel) { + return wireTap(wireTapChannel, null); + } + + /** + * Populate the {@code Wire Tap} EI Pattern specific + * {@link org.springframework.messaging.support.ChannelInterceptor} implementation + * to the current {@link #currentMessageChannel}. + * It is useful when an implicit {@link MessageChannel} is used between endpoints: + *
+	 * {@code
+	 *  .transform("payload")
+	 *  .wireTap(tapChannel())
+	 *  .channel("foo")
+	 * }
+	 * 
+ * This method can be used after any {@link #channel} for explicit {@link MessageChannel}, + * but with the caution do not impact existing {@link org.springframework.messaging.support.ChannelInterceptor}s. + * @param wireTapChannel the {@link MessageChannel} to wire-tap. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B wireTap(MessageChannel wireTapChannel) { + return wireTap(wireTapChannel, null); + } + + /** + * Populate the {@code Wire Tap} EI Pattern specific + * {@link org.springframework.messaging.support.ChannelInterceptor} implementation + * to the current {@link #currentMessageChannel}. + * It is useful when an implicit {@link MessageChannel} is used between endpoints: + *
+	 * {@code
+	 *  .transform("payload")
+	 *  .wireTap(sf -> sf.transform(String::toUpperCase), wt -> wt.selector("payload == 'foo'"))
+	 *  .channel("foo")
+	 * }
+	 * 
+ * This method can be used after any {@link #channel} for explicit {@link MessageChannel}, + * but with the caution do not impact existing {@link org.springframework.messaging.support.ChannelInterceptor}s. + * @param flow the {@link IntegrationFlow} for wire-tap subflow as an alternative to the {@code wireTapChannel}. + * @param wireTapConfigurer the {@link Consumer} to accept options for the {@link WireTap}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B wireTap(IntegrationFlow flow, Consumer wireTapConfigurer) { + DirectChannel wireTapChannel = new DirectChannel(); + IntegrationFlowBuilder flowBuilder = IntegrationFlows.from(wireTapChannel); + flow.configure(flowBuilder); + addComponent(flowBuilder.get()); + return wireTap(wireTapChannel, wireTapConfigurer); + } + + /** + * Populate the {@code Wire Tap} EI Pattern specific + * {@link org.springframework.messaging.support.ChannelInterceptor} implementation + * to the current {@link #currentMessageChannel}. + * It is useful when an implicit {@link MessageChannel} is used between endpoints: + *
+	 * {@code
+	 *  .transform("payload")
+	 *  .wireTap("tapChannel", wt -> wt.selector(m -> m.getPayload().equals("foo")))
+	 *  .channel("foo")
+	 * }
+	 * 
+ * This method can be used after any {@link #channel} for explicit {@link MessageChannel}, + * but with the caution do not impact existing {@link org.springframework.messaging.support.ChannelInterceptor}s. + * @param wireTapChannel the {@link MessageChannel} bean name to wire-tap. + * @param wireTapConfigurer the {@link Consumer} to accept options for the {@link WireTap}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B wireTap(String wireTapChannel, Consumer wireTapConfigurer) { + DirectChannel internalWireTapChannel = new DirectChannel(); + addComponent(IntegrationFlows.from(internalWireTapChannel).channel(wireTapChannel).get()); + return wireTap(internalWireTapChannel, wireTapConfigurer); + } + + /** + * Populate the {@code Wire Tap} EI Pattern specific + * {@link org.springframework.messaging.support.ChannelInterceptor} implementation + * to the current {@link #currentMessageChannel}. + * It is useful when an implicit {@link MessageChannel} is used between endpoints: + *
+	 * {@code
+	 *  .transform("payload")
+	 *  .wireTap(tapChannel(), wt -> wt.selector(m -> m.getPayload().equals("foo")))
+	 *  .channel("foo")
+	 * }
+	 * 
+ * This method can be used after any {@link #channel} for explicit {@link MessageChannel}, + * but with the caution do not impact existing {@link org.springframework.messaging.support.ChannelInterceptor}s. + * @param wireTapChannel the {@link MessageChannel} to wire-tap. + * @param wireTapConfigurer the {@link Consumer} to accept options for the {@link WireTap}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B wireTap(MessageChannel wireTapChannel, Consumer wireTapConfigurer) { + WireTapSpec wireTapSpec = new WireTapSpec(wireTapChannel); + if (wireTapConfigurer != null) { + wireTapConfigurer.accept(wireTapSpec); + } + addComponent(wireTapChannel); + return wireTap(wireTapSpec); + } + + /** + * Populate the {@code Wire Tap} EI Pattern specific + * {@link org.springframework.messaging.support.ChannelInterceptor} implementation + * to the current {@link #currentMessageChannel}. + * It is useful when an implicit {@link MessageChannel} is used between endpoints: + *
+	 * {@code
+	 *  .transform("payload")
+	 *  .wireTap(new WireTap(tapChannel().selector(m -> m.getPayload().equals("foo")))
+	 *  .channel("foo")
+	 * }
+	 * 
+ * This method can be used after any {@link #channel} for explicit {@link MessageChannel}, + * but with the caution do not impact existing {@link org.springframework.messaging.support.ChannelInterceptor}s. + * @param wireTapSpec the {@link WireTapSpec} to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B wireTap(WireTapSpec wireTapSpec) { + WireTap interceptor = wireTapSpec.get(); + if (this.currentMessageChannel == null || !(this.currentMessageChannel instanceof ChannelInterceptorAware)) { + channel(new DirectChannel()); + } + addComponents(wireTapSpec.getComponentsToRegister()); + ((ChannelInterceptorAware) this.currentMessageChannel).addInterceptor(interceptor); + return _this(); + } + + /** + * Populate the {@code Control Bus} EI Pattern specific {@link MessageHandler} implementation + * at the current {@link IntegrationFlow} chain position. + * @return the current {@link IntegrationFlowDefinition}. + * @see ExpressionCommandMessageProcessor + */ + public B controlBus() { + return controlBus(null); + } + + /** + * Populate the {@code Control Bus} EI Pattern specific {@link MessageHandler} implementation + * at the current {@link IntegrationFlow} chain position. + * @param endpointConfigurer the {@link Consumer} to accept integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see ExpressionCommandMessageProcessor + * @see GenericEndpointSpec + */ + public B controlBus(Consumer> endpointConfigurer) { + return this.handle(new ServiceActivatingHandler(new ExpressionCommandMessageProcessor( + new ControlBusMethodFilter())), endpointConfigurer); + } + + /** + * Populate the {@code Transformer} EI Pattern specific {@link MessageHandler} implementation + * for the SpEL {@link Expression}. + * @param expression the {@code Transformer} {@link Expression}. + * @return the current {@link IntegrationFlowDefinition}. + * @see ExpressionEvaluatingTransformer + */ + public B transform(String expression) { + return transform(expression, (Consumer>) null); + } + + /** + * Populate the {@code Transformer} EI Pattern specific {@link MessageHandler} implementation + * for the SpEL {@link Expression}. + * @param expression the {@code Transformer} {@link Expression}. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see ExpressionEvaluatingTransformer + */ + public B transform(String expression, Consumer> endpointConfigurer) { + Assert.hasText(expression); + return transform(new ExpressionEvaluatingTransformer(PARSER.parseExpression(expression)), + endpointConfigurer); + } + + /** + * Populate the {@code MessageTransformingHandler} for the {@link MethodInvokingTransformer} + * to invoke the discovered service method at runtime. + * @param service the service to use. + * @return the current {@link IntegrationFlowDefinition}. + * @see ExpressionEvaluatingTransformer + */ + public B transform(Object service) { + return transform(service, null); + } + + /** + * Populate the {@code MessageTransformingHandler} for the {@link MethodInvokingTransformer} + * to invoke the service method at runtime. + * @param service the service to use. + * @param methodName the method to invoke. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingTransformer + */ + public B transform(Object service, String methodName) { + return transform(service, methodName, null); + } + + /** + * Populate the {@code MessageTransformingHandler} for the {@link MethodInvokingTransformer} + * to invoke the service method at runtime. + * @param service the service to use. + * @param methodName the method to invoke. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see ExpressionEvaluatingTransformer + */ + public B transform(Object service, String methodName, + Consumer> endpointConfigurer) { + MethodInvokingTransformer transformer; + if (StringUtils.hasText(methodName)) { + transformer = new MethodInvokingTransformer(service, methodName); + } + else { + transformer = new MethodInvokingTransformer(service); + } + + return transform(transformer, endpointConfigurer); + } + + /** + * Populate the {@link MessageTransformingHandler} instance for the provided {@link GenericTransformer}. + * @param genericTransformer the {@link GenericTransformer} to populate. + * @param the source type - 'transform from'. + * @param the target type - 'transform to'. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingTransformer + * @see LambdaMessageProcessor + */ + public B transform(GenericTransformer genericTransformer) { + return this.transform(null, genericTransformer); + } + + /** + * Populate the {@link MessageTransformingHandler} instance for the + * {@link org.springframework.integration.handler.MessageProcessor} from provided {@link MessageProcessorSpec}. + *
+	 * {@code
+	 *  .transform(Scripts.script("classpath:myScript.py").valiable("foo", bar()))
+	 * }
+	 * 
+ * @param messageProcessorSpec the {@link MessageProcessorSpec} to use. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingTransformer + */ + public B transform(MessageProcessorSpec messageProcessorSpec) { + return transform(messageProcessorSpec, (Consumer>) null); + } + + /** + * Populate the {@link MessageTransformingHandler} instance for the + * {@link org.springframework.integration.handler.MessageProcessor} from provided {@link MessageProcessorSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + *
+	 * {@code
+	 *  .transform(Scripts.script("classpath:myScript.py").valiable("foo", bar()),
+	 *           e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param messageProcessorSpec the {@link MessageProcessorSpec} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingTransformer + */ + public B transform(MessageProcessorSpec messageProcessorSpec, + Consumer> endpointConfigurer) { + Assert.notNull(messageProcessorSpec); + MessageProcessor processor = messageProcessorSpec.get(); + return addComponent(processor) + .transform(new MethodInvokingTransformer(processor), endpointConfigurer); + } + + /** + * Populate the {@link MessageTransformingHandler} instance for the provided {@link GenericTransformer} + * for the specific {@code payloadType} to convert at runtime. + * @param payloadType the {@link Class} for expected payload type. + * @param genericTransformer the {@link GenericTransformer} to populate. + * @param

the payload type - 'transform from'. + * @param the target type - 'transform to'. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingTransformer + * @see LambdaMessageProcessor + */ + public B transform(Class

payloadType, GenericTransformer genericTransformer) { + return this.transform(payloadType, genericTransformer, null); + } + + /** + * Populate the {@link MessageTransformingHandler} instance for the provided {@link GenericTransformer}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param genericTransformer the {@link GenericTransformer} to populate. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param the source type - 'transform from'. + * @param the target type - 'transform to'. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingTransformer + * @see LambdaMessageProcessor + * @see GenericEndpointSpec + */ + public B transform(GenericTransformer genericTransformer, + Consumer> endpointConfigurer) { + return this.transform(null, genericTransformer, endpointConfigurer); + } + + /** + * Populate the {@link MessageTransformingHandler} instance for the provided {@link GenericTransformer} + * for the specific {@code payloadType} to convert at runtime. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param payloadType the {@link Class} for expected payload type. + * @param genericTransformer the {@link GenericTransformer} to populate. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param

the payload type - 'transform from'. + * @param the target type - 'transform to'. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingTransformer + * @see LambdaMessageProcessor + * @see GenericEndpointSpec + */ + public B transform(Class

payloadType, GenericTransformer genericTransformer, + Consumer> endpointConfigurer) { + Assert.notNull(genericTransformer); + Transformer transformer = genericTransformer instanceof Transformer ? (Transformer) genericTransformer : + (isLambda(genericTransformer) + ? new MethodInvokingTransformer(new LambdaMessageProcessor(genericTransformer, payloadType)) + : new MethodInvokingTransformer(genericTransformer)); + return addComponent(transformer) + .handle(new MessageTransformingHandler(transformer), endpointConfigurer); + } + + /** + * Populate a {@link MessageFilter} with {@link MessageSelector} for the provided SpEL expression. + * @param expression the SpEL expression. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B filter(String expression) { + return filter(expression, (Consumer) null); + } + + /** + * Populate a {@link MessageFilter} with {@link MessageSelector} for the provided SpEL expression. + * In addition accept options for the integration endpoint using {@link FilterEndpointSpec}: + *

+	 * {@code
+	 *  .filter("payload.hot"), e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param expression the SpEL expression. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see FilterEndpointSpec + */ + public B filter(String expression, Consumer endpointConfigurer) { + Assert.hasText(expression); + return filter(new ExpressionEvaluatingSelector(expression), endpointConfigurer); + } + + /** + * Populate a {@link MessageFilter} with {@link MethodInvokingSelector} for the + * discovered method of the provided service. + * @param service the service to use. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingSelector + */ + public B filter(Object service) { + return filter(service, null); + } + + /** + * Populate a {@link MessageFilter} with {@link MethodInvokingSelector} for the + * method of the provided service. + * @param service the service to use. + * @param methodName the method to invoke + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingSelector + */ + public B filter(Object service, String methodName) { + return filter(service, methodName, null); + } + + /** + * Populate a {@link MessageFilter} with {@link MethodInvokingSelector} for the + * method of the provided service. + * @param service the service to use. + * @param methodName the method to invoke + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingSelector + */ + public B filter(Object service, String methodName, Consumer endpointConfigurer) { + MethodInvokingSelector selector; + if (StringUtils.hasText(methodName)) { + selector = new MethodInvokingSelector(service, methodName); + } + else { + selector = new MethodInvokingSelector(service); + } + return filter(selector, endpointConfigurer); + } + + /** + * Populate a {@link MessageFilter} with {@link MethodInvokingSelector} + * for the provided {@link GenericSelector}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .filter("World"::equals)
+	 * }
+	 * 
+ * @param genericSelector the {@link GenericSelector} to use. + * @param

the source payload type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public

B filter(GenericSelector

genericSelector) { + return filter(null, genericSelector); + } + + /** + * Populate a {@link MessageFilter} with {@link MethodInvokingSelector} + * for the {@link org.springframework.integration.handler.MessageProcessor} from + * the provided {@link MessageProcessorSpec}. + *

+	 * {@code
+	 *  .filter(Scripts.script(scriptResource).lang("ruby"))
+	 * }
+	 * 
+ * @param messageProcessorSpec the {@link MessageProcessorSpec} to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B filter(MessageProcessorSpec messageProcessorSpec) { + return filter(messageProcessorSpec, (Consumer) null); + } + + /** + * Populate a {@link MessageFilter} with {@link MethodInvokingSelector} + * for the {@link org.springframework.integration.handler.MessageProcessor} from + * the provided {@link MessageProcessorSpec}. + * In addition accept options for the integration endpoint using {@link FilterEndpointSpec}. + *
+	 * {@code
+	 *  .filter(Scripts.script(scriptResource).lang("ruby"),
+	 *        e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param messageProcessorSpec the {@link MessageProcessorSpec} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B filter(MessageProcessorSpec messageProcessorSpec, Consumer endpointConfigurer) { + Assert.notNull(messageProcessorSpec); + MessageProcessor processor = messageProcessorSpec.get(); + return addComponent(processor) + .filter(new MethodInvokingSelector(processor), endpointConfigurer); + } + + /** + * Populate a {@link MessageFilter} with {@link MethodInvokingSelector} + * for the provided {@link GenericSelector}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .filter(Date.class, p -> p.after(new Date()))
+	 * }
+	 * 
+ * @param payloadType the {@link Class} for desired {@code payload} type. + * @param genericSelector the {@link GenericSelector} to use. + * @param

the source payload type. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + */ + public

B filter(Class

payloadType, GenericSelector

genericSelector) { + return this.filter(payloadType, genericSelector, null); + } + + /** + * Populate a {@link MessageFilter} with {@link MethodInvokingSelector} + * for the provided {@link GenericSelector}. + * In addition accept options for the integration endpoint using {@link FilterEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *

+	 * {@code
+	 *  .filter("World"::equals, e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param genericSelector the {@link GenericSelector} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param

the source payload type. + * @return the current {@link IntegrationFlowDefinition}. + * @see FilterEndpointSpec + */ + public

B filter(GenericSelector

genericSelector, Consumer endpointConfigurer) { + return filter(null, genericSelector, endpointConfigurer); + } + + /** + * Populate a {@link MessageFilter} with {@link MethodInvokingSelector} + * for the provided {@link GenericSelector}. + * In addition accept options for the integration endpoint using {@link FilterEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *

+	 * {@code
+	 *  .filter(Date.class, p -> p.after(new Date()), e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param payloadType the {@link Class} for desired {@code payload} type. + * @param genericSelector the {@link GenericSelector} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param

the source payload type. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + * @see FilterEndpointSpec + */ + public

B filter(Class

payloadType, GenericSelector

genericSelector, + Consumer endpointConfigurer) { + Assert.notNull(genericSelector); + MessageSelector selector = genericSelector instanceof MessageSelector ? (MessageSelector) genericSelector : + (isLambda(genericSelector) + ? new MethodInvokingSelector(new LambdaMessageProcessor(genericSelector, payloadType)) + : new MethodInvokingSelector(genericSelector)); + return this.register(new FilterEndpointSpec(new MessageFilter(selector)), endpointConfigurer); + } + + + /** + * Populate a {@link ServiceActivatingHandler} for the selected protocol specific + * {@link MessageHandler} implementation from {@code Namespace Factory}: + *

+	 * {@code
+	 *  .handle(Amqp.outboundAdapter(this.amqpTemplate).routingKeyExpression("headers.routingKey"))
+	 * }
+	 * 
+ * @param messageHandlerSpec the {@link MessageHandlerSpec} to configure protocol specific + * {@link MessageHandler}. + * @param the target {@link MessageHandler} type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(MessageHandlerSpec messageHandlerSpec) { + return handle(messageHandlerSpec, (Consumer>) null); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the provided + * {@link MessageHandler} implementation. + * Can be used as Java 8 Lambda expression: + *
+	 * {@code
+	 *  .handle(m -> logger.info(m.getPayload())
+	 * }
+	 * 
+ * @param messageHandler the {@link MessageHandler} to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(MessageHandler messageHandler) { + return handle(messageHandler, (Consumer>) null); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link org.springframework.integration.handler.MethodInvokingMessageProcessor} + * to invoke the {@code method} for provided {@code bean} at runtime. + * @param beanName the bean name to use. + * @param methodName the method to invoke. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(String beanName, String methodName) { + return this.handle(beanName, methodName, null); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link org.springframework.integration.handler.MethodInvokingMessageProcessor} + * to invoke the {@code method} for provided {@code bean} at runtime. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param beanName the bean name to use. + * @param methodName the method to invoke. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(String beanName, String methodName, + Consumer> endpointConfigurer) { + return handle(new ServiceActivatingHandler(new BeanNameMessageProcessor(beanName, methodName)), + endpointConfigurer); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link MethodInvokingMessageProcessor} + * to invoke the discovered {@code method} for provided {@code service} at runtime. + * @param service the service object to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(Object service) { + return handle(service, null); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link MethodInvokingMessageProcessor} + * to invoke the {@code method} for provided {@code bean} at runtime. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param service the service object to use. + * @param methodName the method to invoke. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(Object service, String methodName) { + return handle(service, methodName, null); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link MethodInvokingMessageProcessor} + * to invoke the {@code method} for provided {@code bean} at runtime. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param service the service object to use. + * @param methodName the method to invoke. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(Object service, String methodName, + Consumer> endpointConfigurer) { + ServiceActivatingHandler handler; + if (StringUtils.hasText(methodName)) { + handler = new ServiceActivatingHandler(service, methodName); + } + else { + handler = new ServiceActivatingHandler(service); + } + return handle(handler, endpointConfigurer); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link org.springframework.integration.handler.MethodInvokingMessageProcessor} + * to invoke the provided {@link GenericHandler} at runtime. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .handle((p, h) -> p / 2)
+	 * }
+	 * 
+ * @param handler the handler to invoke. + * @param

the payload type to expect. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + */ + public

B handle(GenericHandler

handler) { + return handle(null, handler); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link org.springframework.integration.handler.MethodInvokingMessageProcessor} + * to invoke the provided {@link GenericHandler} at runtime. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *

+	 * {@code
+	 *  .handle((p, h) -> p / 2, e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param handler the handler to invoke. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param

the payload type to expect. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + * @see GenericEndpointSpec + */ + public

B handle(GenericHandler

handler, + Consumer> endpointConfigurer) { + return this.handle(null, handler, endpointConfigurer); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link org.springframework.integration.handler.MethodInvokingMessageProcessor} + * to invoke the provided {@link GenericHandler} at runtime. + * Typically used with a Java 8 Lambda expression: + *

+	 * {@code
+	 *  .handle(Integer.class, (p, h) -> p / 2)
+	 * }
+	 * 
+ * @param payloadType the expected payload type. + * The accepted payload can be converted to this one at runtime + * @param handler the handler to invoke. + * @param

the payload type to expect. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + */ + public

B handle(Class

payloadType, GenericHandler

handler) { + return this.handle(payloadType, handler, null); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link org.springframework.integration.handler.MethodInvokingMessageProcessor} + * to invoke the provided {@link GenericHandler} at runtime. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *

+	 * {@code
+	 *  .handle(Integer.class, (p, h) -> p / 2, e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param payloadType the expected payload type. + * The accepted payload can be converted to this one at runtime + * @param handler the handler to invoke. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param

the payload type to expect. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + */ + public

B handle(Class

payloadType, GenericHandler

handler, + Consumer> endpointConfigurer) { + ServiceActivatingHandler serviceActivatingHandler = null; + if (isLambda(handler)) { + serviceActivatingHandler = new ServiceActivatingHandler(new LambdaMessageProcessor(handler, payloadType)); + } + else { + serviceActivatingHandler = new ServiceActivatingHandler(handler, "handle"); + } + return this.handle(serviceActivatingHandler, endpointConfigurer); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link org.springframework.integration.handler.MessageProcessor} from the provided + * {@link MessageProcessorSpec}. + *

+	 * {@code
+	 *  .handle(Scripts.script("classpath:myScript.ruby"))
+	 * }
+	 * 
+ * @param messageProcessorSpec the {@link MessageProcessorSpec} to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(MessageProcessorSpec messageProcessorSpec) { + return handle(messageProcessorSpec, (Consumer>) null); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the + * {@link org.springframework.integration.handler.MessageProcessor} from the provided + * {@link MessageProcessorSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + *
+	 * {@code
+	 *  .handle(Scripts.script("classpath:myScript.ruby"), e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param messageProcessorSpec the {@link MessageProcessorSpec} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(MessageProcessorSpec messageProcessorSpec, + Consumer> endpointConfigurer) { + Assert.notNull(messageProcessorSpec); + MessageProcessor processor = messageProcessorSpec.get(); + return addComponent(processor) + .handle(new ServiceActivatingHandler(processor), endpointConfigurer); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the selected protocol specific + * {@link MessageHandler} implementation from {@code Namespace Factory}: + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .handle(Amqp.outboundAdapter(this.amqpTemplate).routingKeyExpression("headers.routingKey"),
+	 *       e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param messageHandlerSpec the {@link MessageHandlerSpec} to configure protocol specific + * {@link MessageHandler}. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param the {@link MessageHandler} type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(MessageHandlerSpec messageHandlerSpec, + Consumer> endpointConfigurer) { + Assert.notNull(messageHandlerSpec); + if (messageHandlerSpec instanceof ComponentsRegistration) { + addComponents(((ComponentsRegistration) messageHandlerSpec).getComponentsToRegister()); + } + return handle(messageHandlerSpec.get(), endpointConfigurer); + } + + /** + * Populate a {@link ServiceActivatingHandler} for the provided + * {@link MessageHandler} implementation. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Can be used as Java 8 Lambda expression: + *
+	 * {@code
+	 *  .handle(m -> logger.info(m.getPayload()), e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param messageHandler the {@link MessageHandler} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param the {@link MessageHandler} type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B handle(H messageHandler, Consumer> endpointConfigurer) { + Assert.notNull(messageHandler, "'messageHandler' must not be null"); + return this.register(new GenericEndpointSpec(messageHandler), endpointConfigurer); + } + + /** + * Populate a {@link BridgeHandler} to the current integration flow position. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .bridge(s -> s.poller(Pollers.fixedDelay(100))
+	 *                   .autoStartup(false)
+	 *                   .id("priorityChannelBridge"))
+	 * }
+	 * 
+ * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see GenericEndpointSpec + */ + public B bridge(Consumer> endpointConfigurer) { + return this.register(new GenericEndpointSpec(new BridgeHandler()), endpointConfigurer); + } + + /** + * Populate a {@link DelayHandler} to the current integration flow position + * with default options. + * @param groupId the {@code groupId} for delayed messages in the + * {@link org.springframework.integration.store.MessageGroupStore}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B delay(String groupId) { + return this.delay(groupId, null); + } + + /** + * Populate a {@link DelayHandler} to the current integration flow position. + * @param groupId the {@code groupId} for delayed messages in the + * {@link org.springframework.integration.store.MessageGroupStore}. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see DelayerEndpointSpec + */ + public B delay(String groupId, Consumer endpointConfigurer) { + return register(new DelayerEndpointSpec(new DelayHandler(groupId)), endpointConfigurer); + } + + /** + * Populate a {@link ContentEnricher} to the current integration flow position + * with provided options. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .enrich(e -> e.requestChannel("enrichChannel")
+	 *                  .requestPayload(Message::getPayload)
+	 *                  .shouldClonePayload(false)
+	 *                  .>headerFunction("foo", m -> m.getPayload().get("name")))
+	 * }
+	 * 
+ * @param enricherConfigurer the {@link Consumer} to provide {@link ContentEnricher} options. + * @return the current {@link IntegrationFlowDefinition}. + * @see EnricherSpec + */ + public B enrich(Consumer enricherConfigurer) { + return this.enrich(enricherConfigurer, null); + } + + /** + * Populate a {@link ContentEnricher} to the current integration flow position + * with provided options. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .enrich(e -> e.requestChannel("enrichChannel")
+	 *                  .requestPayload(Message::getPayload)
+	 *                  .shouldClonePayload(false)
+	 *                  .>headerFunction("foo", m -> m.getPayload().get("name")),
+	 *           e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param enricherConfigurer the {@link Consumer} to provide {@link ContentEnricher} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see EnricherSpec + * @see GenericEndpointSpec + */ + public B enrich(Consumer enricherConfigurer, + Consumer> endpointConfigurer) { + Assert.notNull(enricherConfigurer); + EnricherSpec enricherSpec = new EnricherSpec(); + enricherConfigurer.accept(enricherSpec); + return this.handle(enricherSpec.get(), endpointConfigurer); + } + + /** + * Populate a {@link MessageTransformingHandler} for + * a {@link org.springframework.integration.transformer.HeaderEnricher} + * using header values from provided {@link MapBuilder}. + * Can be used together with {@code Namespace Factory}: + *
+	 * {@code
+	 *  .enrichHeaders(Mail.headers()
+	 *                    .subjectFunction(m -> "foo")
+	 *                    .from("foo@bar")
+	 *                    .toFunction(m -> new String[] {"bar@baz"}))
+	 * }
+	 * 
+ * @param headers the {@link MapBuilder} to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B enrichHeaders(MapBuilder headers) { + return enrichHeaders(headers, null); + } + + /** + * Populate a {@link MessageTransformingHandler} for + * a {@link org.springframework.integration.transformer.HeaderEnricher} + * using header values from provided {@link MapBuilder}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Can be used together with {@code Namespace Factory}: + *
+	 * {@code
+	 *  .enrichHeaders(Mail.headers()
+	 *                    .subjectFunction(m -> "foo")
+	 *                    .from("foo@bar")
+	 *                    .toFunction(m -> new String[] {"bar@baz"}),
+	 *                 e -> e.autoStartup(false))
+	 * }
+	 * 
+ * @param headers the {@link MapBuilder} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see GenericEndpointSpec + */ + public B enrichHeaders(MapBuilder headers, + Consumer> endpointConfigurer) { + return enrichHeaders(headers.get(), endpointConfigurer); + } + + /** + * Accept a {@link Map} of values to be used for the + * {@link org.springframework.messaging.Message} header enrichment. + * {@code values} can apply an {@link org.springframework.expression.Expression} + * to be evaluated against a request {@link org.springframework.messaging.Message}. + * @param headers the Map of headers to enrich. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B enrichHeaders(Map headers) { + return enrichHeaders(headers, null); + } + + /** + * Accept a {@link Map} of values to be used for the + * {@link org.springframework.messaging.Message} header enrichment. + * {@code values} can apply an {@link org.springframework.expression.Expression} + * to be evaluated against a request {@link org.springframework.messaging.Message}. + * @param headers the Map of headers to enrich. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see GenericEndpointSpec + */ + public B enrichHeaders(final Map headers, + Consumer> endpointConfigurer) { + return enrichHeaders(new Consumer() { + + @Override + public void accept(HeaderEnricherSpec spec) { + spec.headers(headers); + } + + }, endpointConfigurer); + } + + /** + * Populate a {@link MessageTransformingHandler} for + * a {@link org.springframework.integration.transformer.HeaderEnricher} + * as the result of provided {@link Consumer}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .enrichHeaders(h -> h.header(FileHeaders.FILENAME, "foo.sitest")
+	 *                       .header("directory", new File(tmpDir, "fileWritingFlow")))
+	 * }
+	 * 
+ * @param headerEnricherConfigurer the {@link Consumer} to use. + * @return the current {@link IntegrationFlowDefinition}. + * @see HeaderEnricherSpec + */ + public B enrichHeaders(Consumer headerEnricherConfigurer) { + return this.enrichHeaders(headerEnricherConfigurer, null); + } + + /** + * Populate a {@link MessageTransformingHandler} for + * a {@link org.springframework.integration.transformer.HeaderEnricher} + * as the result of provided {@link Consumer}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .enrichHeaders(
+	 *s -> s.header("one", new XPathExpressionEvaluatingHeaderValueMessageProcessor("/root/elementOne"))
+	 *            .header("two", new XPathExpressionEvaluatingHeaderValueMessageProcessor("/root/elementTwo"))
+	 *            .headerChannelsToString(),
+	 *            c -> c.autoStartup(false).id("xpathHeaderEnricher"))
+	 * }
+	 * 
+ * @param headerEnricherConfigurer the {@link Consumer} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see HeaderEnricherSpec + * @see GenericEndpointSpec + */ + public B enrichHeaders(Consumer headerEnricherConfigurer, + Consumer> endpointConfigurer) { + Assert.notNull(headerEnricherConfigurer); + HeaderEnricherSpec headerEnricherSpec = new HeaderEnricherSpec(); + headerEnricherConfigurer.accept(headerEnricherSpec); + return transform(headerEnricherSpec.get(), endpointConfigurer); + } + + /** + * Populate the {@link DefaultMessageSplitter} with default options + * to the current integration flow position. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B split() { + return this.split((Consumer>) null); + } + + /** + * Populate the {@link DefaultMessageSplitter} with provided options + * to the current integration flow position. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .split(s -> s.applySequence(false).get().getT2().setDelimiters(","))
+	 * }
+	 * 
+ * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options + * and for {@link DefaultMessageSplitter}. + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + */ + public B split(Consumer> endpointConfigurer) { + return this.split(new DefaultMessageSplitter(), endpointConfigurer); + } + + /** + * Populate the {@link ExpressionEvaluatingSplitter} with provided + * SpEL expression. + * @param expression the splitter SpEL expression. + * and for {@link ExpressionEvaluatingSplitter}. + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + */ + public B split(String expression) { + return split(expression, (Consumer>) null); + } + + /** + * Populate the {@link ExpressionEvaluatingSplitter} with provided + * SpEL expression. + * @param expression the splitter SpEL expression. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options + * and for {@link ExpressionEvaluatingSplitter}. + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + */ + public B split(String expression, Consumer> endpointConfigurer) { + Assert.hasText(expression); + return split(new ExpressionEvaluatingSplitter(PARSER.parseExpression(expression)), endpointConfigurer); + } + + /** + * Populate the {@link MethodInvokingSplitter} to evaluate the discovered + * {@code method} of the {@code service} at runtime. + * @param service the service to use. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingSplitter + */ + public B split(Object service) { + return split(service, null); + } + + /** + * Populate the {@link MethodInvokingSplitter} to evaluate the provided + * {@code method} of the {@code service} at runtime. + * @param service the service to use. + * @param methodName the method to invoke. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingSplitter + */ + public B split(Object service, String methodName) { + return split(service, methodName, null); + } + + /** + * Populate the {@link MethodInvokingSplitter} to evaluate the provided + * {@code method} of the {@code bean} at runtime. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param service the service to use. + * @param methodName the method to invoke. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options + * and for {@link MethodInvokingSplitter}. + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + * @see MethodInvokingSplitter + */ + public B split(Object service, String methodName, + Consumer> endpointConfigurer) { + MethodInvokingSplitter splitter; + if (StringUtils.hasText(methodName)) { + splitter = new MethodInvokingSplitter(service, methodName); + } + else { + splitter = new MethodInvokingSplitter(service); + } + return split(splitter, endpointConfigurer); + } + + /** + * Populate the {@link MethodInvokingSplitter} to evaluate the provided + * {@code method} of the {@code bean} at runtime. + * @param beanName the bean name to use. + * @param methodName the method to invoke at runtime. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B split(String beanName, String methodName) { + return this.split(beanName, methodName, null); + } + + /** + * Populate the {@link MethodInvokingSplitter} to evaluate the provided + * {@code method} of the {@code bean} at runtime. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param beanName the bean name to use. + * @param methodName the method to invoke at runtime. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options + * and for {@link MethodInvokingSplitter}. + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + */ + public B split(String beanName, String methodName, + Consumer> endpointConfigurer) { + return split(new MethodInvokingSplitter(new BeanNameMessageProcessor(beanName, methodName)), + endpointConfigurer); + } + + /** + * Populate the {@link MethodInvokingSplitter} to evaluate the + * {@link org.springframework.integration.handler.MessageProcessor} at runtime + * from provided {@link MessageProcessorSpec}. + *
+	 * {@code
+	 *  .split(Scripts.script("classpath:myScript.ruby"))
+	 * }
+	 * 
+ * @param messageProcessorSpec the splitter {@link MessageProcessorSpec}. + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + */ + public B split(MessageProcessorSpec messageProcessorSpec) { + return split(messageProcessorSpec, (Consumer>) null); + } + + /** + * Populate the {@link MethodInvokingSplitter} to evaluate the + * {@link org.springframework.integration.handler.MessageProcessor} at runtime + * from provided {@link MessageProcessorSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + *
+	 * {@code
+	 *  .split(Scripts.script(myScriptResource).lang("groovy").refreshCheckDelay(1000),
+	 *  			, e -> e.applySequence(false))
+	 * }
+	 * 
+ * @param messageProcessorSpec the splitter {@link MessageProcessorSpec}. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options + * and for {@link MethodInvokingSplitter}. + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + */ + public B split(MessageProcessorSpec messageProcessorSpec, + Consumer> endpointConfigurer) { + Assert.notNull(messageProcessorSpec); + MessageProcessor processor = messageProcessorSpec.get(); + return addComponent(processor) + .split(new MethodInvokingSplitter(processor), endpointConfigurer); + } + + /** + * Populate the {@link MethodInvokingSplitter} to evaluate the provided + * {@link Function} at runtime. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .split(String.class, p ->
+	 *        jdbcTemplate.execute("SELECT * from FOO",
+	 *            (PreparedStatement ps) ->
+	 *                 new ResultSetIterator(ps.executeQuery(),
+	 *                     (rs, rowNum) ->
+	 *                           new Foo(rs.getInt(1), rs.getString(2)))))
+	 * }
+	 * 
+ * @param payloadType the expected payload type. Used at runtime to convert received payload type to. + * @param splitter the splitter {@link Function}. + * @param

the payload type. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + */ + public

B split(Class

payloadType, Function splitter) { + return split(payloadType, splitter, null); + } + + /** + * Populate the {@link MethodInvokingSplitter} to evaluate the provided + * {@link Function} at runtime. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *

+	 * {@code
+	 *  .split(p ->
+	 *        jdbcTemplate.execute("SELECT * from FOO",
+	 *            (PreparedStatement ps) ->
+	 *                 new ResultSetIterator(ps.executeQuery(),
+	 *                     (rs, rowNum) ->
+	 *                           new Foo(rs.getInt(1), rs.getString(2))))
+	 *       , e -> e.applySequence(false))
+	 * }
+	 * 
+ * @param splitter the splitter {@link Function}. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param

the payload type. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + * @see SplitterEndpointSpec + */ + public

B split(Function splitter, + Consumer> endpointConfigurer) { + return split(null, splitter, endpointConfigurer); + } + + /** + * Populate the {@link MethodInvokingSplitter} to evaluate the provided + * {@link Function} at runtime. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *

+	 * {@code
+	 *  .split(String.class, p ->
+	 *        jdbcTemplate.execute("SELECT * from FOO",
+	 *            (PreparedStatement ps) ->
+	 *                 new ResultSetIterator(ps.executeQuery(),
+	 *                     (rs, rowNum) ->
+	 *                           new Foo(rs.getInt(1), rs.getString(2))))
+	 *       , e -> e.applySequence(false))
+	 * }
+	 * 
+ * @param payloadType the expected payload type. Used at runtime to convert received payload type to. + * @param splitter the splitter {@link Function}. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param

the payload type. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + * @see SplitterEndpointSpec + */ + public

B split(Class

payloadType, Function splitter, + Consumer> endpointConfigurer) { + MethodInvokingSplitter split = isLambda(splitter) + ? new MethodInvokingSplitter(new LambdaMessageProcessor(splitter, payloadType)) + : new MethodInvokingSplitter(splitter); + return this.split(split, endpointConfigurer); + } + + /** + * Populate the provided {@link AbstractMessageSplitter} to the current integration + * flow position. + * @param splitterMessageHandlerSpec the {@link MessageHandlerSpec} to populate. + * @param the {@link AbstractMessageSplitter} + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + */ + public B split(MessageHandlerSpec splitterMessageHandlerSpec) { + return split(splitterMessageHandlerSpec, (Consumer>) null); + } + + /** + * Populate the provided {@link AbstractMessageSplitter} to the current integration + * flow position. + * @param splitterMessageHandlerSpec the {@link MessageHandlerSpec} to populate. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param the {@link AbstractMessageSplitter} + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + */ + public B split(MessageHandlerSpec splitterMessageHandlerSpec, + Consumer> endpointConfigurer) { + Assert.notNull(splitterMessageHandlerSpec); + return split(splitterMessageHandlerSpec.get(), endpointConfigurer); + } + + /** + * Populate the provided {@link AbstractMessageSplitter} to the current integration + * flow position. + * @param splitter the {@link AbstractMessageSplitter} to populate. + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + */ + public B split(AbstractMessageSplitter splitter) { + return split(splitter, (Consumer>) null); + } + + /** + * Populate the provided {@link AbstractMessageSplitter} to the current integration + * flow position. + * @param splitter the {@link AbstractMessageSplitter} to populate. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param the {@link AbstractMessageSplitter} + * @return the current {@link IntegrationFlowDefinition}. + * @see SplitterEndpointSpec + */ + public B split(S splitter, + Consumer> endpointConfigurer) { + Assert.notNull(splitter); + return this.register(new SplitterEndpointSpec(splitter), endpointConfigurer); + } + + /** + * Provide the {@link HeaderFilter} to the current {@link StandardIntegrationFlow}. + * @param headersToRemove the array of headers (or patterns) + * to remove from {@link org.springframework.messaging.MessageHeaders}. + * @return this {@link IntegrationFlowDefinition}. + */ + public B headerFilter(String... headersToRemove) { + return this.headerFilter(new HeaderFilter(headersToRemove), null); + } + + /** + * Provide the {@link HeaderFilter} to the current {@link StandardIntegrationFlow}. + * @param headersToRemove the comma separated headers (or patterns) to remove from + * {@link org.springframework.messaging.MessageHeaders}. + * @param patternMatch the {@code boolean} flag to indicate if {@code headersToRemove} + * should be interpreted as patterns or direct header names. + * @return this {@link IntegrationFlowDefinition}. + */ + public B headerFilter(String headersToRemove, boolean patternMatch) { + HeaderFilter headerFilter = new HeaderFilter(StringUtils.delimitedListToStringArray(headersToRemove, ",", " ")); + headerFilter.setPatternMatch(patternMatch); + return this.headerFilter(headerFilter, null); + } + + /** + * Populate the provided {@link MessageTransformingHandler} for the provided + * {@link HeaderFilter}. + * @param headerFilter the {@link HeaderFilter} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see GenericEndpointSpec + */ + public B headerFilter(HeaderFilter headerFilter, + Consumer> endpointConfigurer) { + return this.transform(headerFilter, endpointConfigurer); + } + + /** + * Populate the {@link MessageTransformingHandler} for the {@link ClaimCheckInTransformer} + * with provided {@link MessageStore}. + * @param messageStore the {@link MessageStore} to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B claimCheckIn(MessageStore messageStore) { + return this.claimCheckIn(messageStore, null); + } + + /** + * Populate the {@link MessageTransformingHandler} for the {@link ClaimCheckInTransformer} + * with provided {@link MessageStore}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param messageStore the {@link MessageStore} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see GenericEndpointSpec + */ + public B claimCheckIn(MessageStore messageStore, + Consumer> endpointConfigurer) { + return this.transform(new ClaimCheckInTransformer(messageStore), endpointConfigurer); + } + + /** + * Populate the {@link MessageTransformingHandler} for the {@link ClaimCheckOutTransformer} + * with provided {@link MessageStore}. + * The {@code removeMessage} option of {@link ClaimCheckOutTransformer} is to {@code false}. + * @param messageStore the {@link MessageStore} to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B claimCheckOut(MessageStore messageStore) { + return this.claimCheckOut(messageStore, false); + } + + /** + * Populate the {@link MessageTransformingHandler} for the {@link ClaimCheckOutTransformer} + * with provided {@link MessageStore} and {@code removeMessage} flag. + * @param messageStore the {@link MessageStore} to use. + * @param removeMessage the removeMessage boolean flag. + * @return the current {@link IntegrationFlowDefinition}. + * @see ClaimCheckOutTransformer#setRemoveMessage(boolean) + */ + public B claimCheckOut(MessageStore messageStore, boolean removeMessage) { + return this.claimCheckOut(messageStore, removeMessage, null); + } + + /** + * Populate the {@link MessageTransformingHandler} for the {@link ClaimCheckOutTransformer} + * with provided {@link MessageStore} and {@code removeMessage} flag. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param messageStore the {@link MessageStore} to use. + * @param removeMessage the removeMessage boolean flag. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see GenericEndpointSpec + * @see ClaimCheckOutTransformer#setRemoveMessage(boolean) + */ + public B claimCheckOut(MessageStore messageStore, boolean removeMessage, + Consumer> endpointConfigurer) { + ClaimCheckOutTransformer claimCheckOutTransformer = new ClaimCheckOutTransformer(messageStore); + claimCheckOutTransformer.setRemoveMessage(removeMessage); + return this.transform(claimCheckOutTransformer, endpointConfigurer); + } + + /** + * Populate the {@link ResequencingMessageHandler} with default options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B resequence() { + return resequence(null); + } + + /** + * Populate the {@link ResequencingMessageHandler} with provided options from {@link ResequencerSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *

+	 * {@code
+	 *  .resequence(r -> r.releasePartialSequences(true).correlationExpression("'foo'"),
+	 *             e -> e.phase(100))
+	 * }
+	 * 
+ * @param resequencerConfigurer the {@link Consumer} to provide {@link ResequencingMessageHandler} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see GenericEndpointSpec + * @deprecated since 1.1 in favor of {@link #resequence(Consumer)} + */ + @Deprecated + public B resequence(Consumer resequencerConfigurer, + Consumer> endpointConfigurer) { + Assert.notNull(resequencerConfigurer); + ResequencerSpec spec = new ResequencerSpec(); + resequencerConfigurer.accept(spec); + return handle(spec.get().getT2(), endpointConfigurer); + } + + /** + * Populate the {@link ResequencingMessageHandler} with provided options from {@link ResequencerSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .resequence(r -> r.releasePartialSequences(true)
+	 *                    .correlationExpression("'foo'")
+	 *                    .phase(100))
+	 * }
+	 * 
+ * @param resequencer the {@link Consumer} to provide {@link ResequencingMessageHandler} options. + * @return the current {@link IntegrationFlowDefinition}. + * @see ResequencerSpec + */ + public B resequence(Consumer resequencer) { + return register(new ResequencerSpec(), resequencer); + } + + /** + * Populate the {@link AggregatingMessageHandler} with default options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B aggregate() { + return aggregate(null); + } + + /** + * Populate the {@link AggregatingMessageHandler} with provided options from {@link AggregatorSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .aggregate(a -> a.correlationExpression("1").releaseStrategy(g -> g.size() == 25),
+	 *            e -> e.applySequence(false))
+	 * }
+	 * 
+ * @param aggregatorConfigurer the {@link Consumer} to provide {@link AggregatingMessageHandler} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see GenericEndpointSpec + * @deprecated since 1.1 in favor of {@link #aggregate(Consumer)} + */ + @Deprecated + public B aggregate(Consumer aggregatorConfigurer, + Consumer> endpointConfigurer) { + Assert.notNull(aggregatorConfigurer); + AggregatorSpec spec = new AggregatorSpec(); + aggregatorConfigurer.accept(spec); + return this.handle(spec.get().getT2(), endpointConfigurer); + } + + /** + * Populate the {@link AggregatingMessageHandler} with provided options from {@link AggregatorSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .aggregate(a -> a.correlationExpression("1")
+	 *                   .releaseStrategy(g -> g.size() == 25)
+	 *                   .phase(100))
+	 * }
+	 * 
+ * @param aggregator the {@link Consumer} to provide {@link AggregatingMessageHandler} options. + * @return the current {@link IntegrationFlowDefinition}. + * @see AggregatorSpec + */ + public B aggregate(Consumer aggregator) { + return register(new AggregatorSpec(), aggregator); + } + + /** + * Populate the {@link MethodInvokingRouter} for provided bean and its method + * with default options. + * @param beanName the bean to use. + * @param method the method to invoke at runtime. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(String beanName, String method) { + return this.route(beanName, method, null); + } + + /** + * Populate the {@link MethodInvokingRouter} for provided bean and its method + * with provided options from {@link RouterSpec}. + * @param beanName the bean to use. + * @param method the method to invoke at runtime. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(String beanName, String method, Consumer> routerConfigurer) { + return route(beanName, method, routerConfigurer, null); + } + + /** + * Populate the {@link MethodInvokingRouter} for provided bean and its method + * with provided options from {@link RouterSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param beanName the bean to use. + * @param method the method to invoke at runtime. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(String beanName, String method, Consumer> routerConfigurer, + Consumer> endpointConfigurer) { + return this.route(new MethodInvokingRouter(new BeanNameMessageProcessor(beanName, method)), + routerConfigurer, endpointConfigurer); + } + + /** + * Populate the {@link MethodInvokingRouter} for the discovered method + * of the provided service and its method with default options. + * @param service the bean to use. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingRouter + */ + public B route(Object service) { + return route(service, null); + } + + /** + * Populate the {@link MethodInvokingRouter} for the method + * of the provided service and its method with default options. + * @param service the service to use. + * @param methodName the method to invoke. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingRouter + */ + public B route(Object service, String methodName) { + return route(service, methodName, null); + } + + /** + * Populate the {@link MethodInvokingRouter} for the method + * of the provided service and its method with provided options from {@link RouterSpec}. + * @param service the service to use. + * @param methodName the method to invoke. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingRouter + */ + public B route(Object service, String methodName, + Consumer> routerConfigurer) { + return route(service, methodName, routerConfigurer, null); + } + + /** + * Populate the {@link MethodInvokingRouter} for the method + * of the provided service and its method with provided options from {@link RouterSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param service the service to use. + * @param methodName the method to invoke. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + * @see MethodInvokingRouter + */ + public B route(Object service, String methodName, + Consumer> routerConfigurer, + Consumer> endpointConfigurer) { + MethodInvokingRouter router; + if (StringUtils.hasText(methodName)) { + router = new MethodInvokingRouter(service, methodName); + } + else { + router = new MethodInvokingRouter(service); + } + return route(router, routerConfigurer, endpointConfigurer); + } + + + /** + * Populate the {@link ExpressionEvaluatingRouter} for provided SpEL expression + * with default options. + * @param expression the expression to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(String expression) { + return this.route(expression, (Consumer>) null); + } + + /** + * Populate the {@link ExpressionEvaluatingRouter} for provided SpEL expression + * with provided options from {@link RouterSpec}. + * @param expression the expression to use. + * @param routerConfigurer the {@link Consumer} to provide {@link ExpressionEvaluatingRouter} options. + * @param the target result type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(String expression, Consumer> routerConfigurer) { + return route(expression, routerConfigurer, null); + } + + /** + * Populate the {@link ExpressionEvaluatingRouter} for provided bean and its method + * with provided options from {@link RouterSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param expression the expression to use. + * @param routerConfigurer the {@link Consumer} to provide {@link ExpressionEvaluatingRouter} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param the target result type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(String expression, Consumer> routerConfigurer, + Consumer> endpointConfigurer) { + return this.route(new ExpressionEvaluatingRouter(PARSER.parseExpression(expression)), routerConfigurer, + endpointConfigurer); + } + + /** + * Populate the {@link MethodInvokingRouter} for provided {@link Function} + * with default options. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .route(p -> p.equals("foo") || p.equals("bar") ? new String[] {"foo", "bar"} : null)
+	 * }
+	 * 
+ * @param router the {@link Function} to use. + * @param the source payload type. + * @param the target result type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(Function router) { + return this.route(null, router); + } + + /** + * Populate the {@link MethodInvokingRouter} for provided {@link Function} + * with provided options from {@link RouterSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .route(p -> p % 2 == 0,
+	 *                 m -> m.channelMapping("true", "evenChannel")
+	 *                       .subFlowMapping("false", f ->
+	 *                                   f.handle((p, h) -> p * 3)))
+	 * }
+	 * 
+ * @param router the {@link Function} to use. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @param the source payload type. + * @param the target result type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(Function router, Consumer> routerConfigurer) { + return this.route(null, router, routerConfigurer); + } + + /** + * Populate the {@link MethodInvokingRouter} for provided {@link Function} + * and payload type with default options. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .route(Integer.class, p -> p % 2 == 0)
+	 * }
+	 * 
+ * @param payloadType the expected payload type. + * @param router the {@link Function} to use. + * @param the source payload type. + * @param the target result type. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + */ + public B route(Class payloadType, Function router) { + return this.route(payloadType, router, null, null); + } + + /** + * Populate the {@link MethodInvokingRouter} for provided {@link Function} + * and payload type and options from {@link RouterSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .route(Integer.class, p -> p % 2 == 0,
+	 *                 m -> m.channelMapping("true", "evenChannel")
+	 *                       .subFlowMapping("false", f ->
+	 *                                   f.handle((p, h) -> p * 3)))
+	 * }
+	 * 
+ * @param payloadType the expected payload type. + * @param router the {@link Function} to use. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @param the source payload type. + * @param the target result type. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + */ + public B route(Class payloadType, Function router, + Consumer> routerConfigurer) { + return this.route(payloadType, router, routerConfigurer, null); + } + + /** + * Populate the {@link MethodInvokingRouter} for provided {@link Function} + * with provided options from {@link RouterSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .route(p -> p % 2 == 0,
+	 *                 m -> m.channelMapping("true", "evenChannel")
+	 *                       .subFlowMapping("false", f ->
+	 *                                   f.handle((p, h) -> p * 3)),
+	 *            e -> e.applySequence(false))
+	 * }
+	 * 
+ * @param router the {@link Function} to use. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param the source payload type. + * @param the target result type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(Function router, Consumer> routerConfigurer, + Consumer> endpointConfigurer) { + return route(null, router, routerConfigurer, endpointConfigurer); + } + + /** + * Populate the {@link MethodInvokingRouter} for provided {@link Function} + * and payload type and options from {@link RouterSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .route(Integer.class, p -> p % 2 == 0,
+	 *					m -> m.channelMapping("true", "evenChannel")
+	 *                       .subFlowMapping("false", f ->
+	 *                                   f.handle((p, h) -> p * 3)),
+	 * 		           e -> e.applySequence(false))
+	 * }
+	 * 
+ * @param payloadType the expected payload type. + * @param router the {@link Function} to use. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param

the source payload type. + * @param the target result type. + * @return the current {@link IntegrationFlowDefinition}. + * @see LambdaMessageProcessor + */ + public B route(Class

payloadType, Function router, + Consumer> routerConfigurer, + Consumer> endpointConfigurer) { + MethodInvokingRouter methodInvokingRouter = isLambda(router) + ? new MethodInvokingRouter(new LambdaMessageProcessor(router, payloadType)) + : new MethodInvokingRouter(router); + return route(methodInvokingRouter, routerConfigurer, endpointConfigurer); + } + + /** + * Populate the {@link MethodInvokingRouter} for the {@link org.springframework.integration.handler.MessageProcessor} + * from the provided {@link MessageProcessorSpec} with default options. + *

+	 * {@code
+	 *  .route(Scripts.script(myScriptResource).lang("groovy").refreshCheckDelay(1000))
+	 * }
+	 * 
+ * @param messageProcessorSpec the {@link MessageProcessorSpec} to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(MessageProcessorSpec messageProcessorSpec) { + return route(messageProcessorSpec, (Consumer>) null); + } + + /** + * Populate the {@link MethodInvokingRouter} for the {@link org.springframework.integration.handler.MessageProcessor} + * from the provided {@link MessageProcessorSpec} with default options. + *
+	 * {@code
+	 *  .route(Scripts.script(myScriptResource).lang("groovy").refreshCheckDelay(1000),
+	 *                 m -> m.channelMapping("true", "evenChannel")
+	 *                       .subFlowMapping("false", f ->
+	 *                                   f.handle((p, h) -> p * 3)))
+	 * }
+	 * 
+ * @param messageProcessorSpec the {@link MessageProcessorSpec} to use. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(MessageProcessorSpec messageProcessorSpec, + Consumer> routerConfigurer) { + return route(messageProcessorSpec, routerConfigurer, null); + } + + /** + * Populate the {@link MethodInvokingRouter} for the {@link org.springframework.integration.handler.MessageProcessor} + * from the provided {@link MessageProcessorSpec} with default options. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + *
+	 * {@code
+	 *  .route(Scripts.script(myScriptResource).lang("groovy").refreshCheckDelay(1000),
+	 *                 m -> m.channelMapping("true", "evenChannel")
+	 *                       .subFlowMapping("false", f ->
+	 *                                   f.handle((p, h) -> p * 3)),
+	 *                 e -> e.applySequence(false))
+	 * }
+	 * 
+ * @param messageProcessorSpec the {@link MessageProcessorSpec} to use. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(MessageProcessorSpec messageProcessorSpec, + Consumer> routerConfigurer, + Consumer> endpointConfigurer) { + Assert.notNull(messageProcessorSpec); + MessageProcessor processor = messageProcessorSpec.get(); + return addComponent(processor) + .route(new MethodInvokingRouter(processor), routerConfigurer, endpointConfigurer); + } + + /** + * Populate the provided {@link AbstractMappingMessageRouter} implementation + * with options from {@link RouterSpec} and endpoint options from {@link GenericEndpointSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param router the {@link AbstractMappingMessageRouter} to populate. + * @param routerConfigurer the {@link Consumer} to provide {@link MethodInvokingRouter} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param the {@code channelKey mapping} type. + * @param the {@link AbstractMappingMessageRouter} type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(R router, Consumer> routerConfigurer, + Consumer> endpointConfigurer) { + + RouterSpec routerSpec = new RouterSpec(router); + if (routerConfigurer != null) { + routerConfigurer.accept(routerSpec); + } + + return route(router, routerSpec, endpointConfigurer); + } + + private > B route(R router, + S routerSpec, Consumer> endpointConfigurer) { + + route(router, endpointConfigurer); + + final BridgeHandler bridgeHandler = new BridgeHandler(); + boolean registerSubflowBridge = false; + Collection componentsToRegister = routerSpec.getComponentsToRegister(); + if (!CollectionUtils.isEmpty(componentsToRegister)) { + for (Object component : componentsToRegister) { + if (component instanceof IntegrationFlowDefinition) { + IntegrationFlowDefinition flowBuilder = (IntegrationFlowDefinition) component; + if (flowBuilder.isOutputChannelRequired()) { + registerSubflowBridge = true; + flowBuilder.channel(new FixedSubscriberChannel(bridgeHandler)); + } + addComponent(flowBuilder.get()); + } + else { + addComponent(component); + } + } + } + if (routerSpec.isDefaultToParentFlow()) { + routerSpec.defaultOutputChannel(new FixedSubscriberChannel(bridgeHandler)); + registerSubflowBridge = true; + } + + if (registerSubflowBridge) { + this.currentComponent = null; + handle(bridgeHandler); + } + return _this(); + } + + /** + * Populate the {@link RecipientListRouter} options from {@link RecipientListRouterSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .routeToRecipients(r -> r
+	 *.recipient("bar-channel", m ->
+	 *            m.getHeaders().containsKey("recipient") && (boolean) m.getHeaders().get("recipient"))
+	 *      .recipientFlow("'foo' == payload or 'bar' == payload or 'baz' == payload",
+	 *                         f -> f.transform(String.class, p -> p.toUpperCase())
+	 *                               .channel(c -> c.queue("recipientListSubFlow1Result"))))
+	 * }
+	 * 
+ * @param routerConfigurer the {@link Consumer} to provide {@link RecipientListRouter} options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B routeToRecipients(Consumer routerConfigurer) { + return routeToRecipients(routerConfigurer, null); + } + + /** + * Populate the {@link RecipientListRouter} options from {@link RecipientListRouterSpec}. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .routeToRecipients(r -> r
+	 *.recipient("bar-channel", m ->
+	 *            m.getHeaders().containsKey("recipient") && (boolean) m.getHeaders().get("recipient"))
+	 *      .recipientFlow("'foo' == payload or 'bar' == payload or 'baz' == payload",
+	 *                         f -> f.transform(String.class, p -> p.toUpperCase())
+	 *                               .channel(c -> c.queue("recipientListSubFlow1Result"))),
+	 *      e -> e.applySequence(false))
+	 * }
+	 * 
+ * @param routerConfigurer the {@link Consumer} to provide {@link RecipientListRouter} options. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B routeToRecipients(Consumer routerConfigurer, + Consumer> endpointConfigurer) { + + RecipientListRouterSpec spec = new RecipientListRouterSpec(); + if (routerConfigurer != null) { + routerConfigurer.accept(spec); + } + + return route(spec.get(), spec, endpointConfigurer); + } + + /** + * Populate the provided {@link AbstractMessageRouter} implementation to the + * current integration flow position. + * @param router the {@link AbstractMessageRouter} to populate. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(AbstractMessageRouter router) { + return route(router, (Consumer>) null); + } + + /** + * Populate the provided {@link AbstractMessageRouter} implementation to the + * current integration flow position. + * In addition accept options for the integration endpoint using {@link GenericEndpointSpec}. + * @param router the {@link AbstractMessageRouter} to populate. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @param the {@link AbstractMessageRouter} type. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B route(R router, Consumer> endpointConfigurer) { + return handle(router, endpointConfigurer); + } + + /** + * Populate the "artificial" {@link GatewayMessageHandler} for the provided + * {@code requestChannel} to send a request with default options. + * Uses {@link org.springframework.integration.gateway.RequestReplyExchanger} Proxy + * on the background. + * @param requestChannel the {@link MessageChannel} bean name. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B gateway(String requestChannel) { + return gateway(requestChannel, null); + } + + /** + * Populate the "artificial" {@link GatewayMessageHandler} for the provided + * {@code requestChannel} to send a request with options from {@link GatewayEndpointSpec}. + * Uses {@link org.springframework.integration.gateway.RequestReplyExchanger} Proxy + * on the background. + * @param requestChannel the {@link MessageChannel} bean name. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B gateway(String requestChannel, Consumer endpointConfigurer) { + return register(new GatewayEndpointSpec(requestChannel), endpointConfigurer); + } + + /** + * Populate the "artificial" {@link GatewayMessageHandler} for the provided + * {@code requestChannel} to send a request with default options. + * Uses {@link org.springframework.integration.gateway.RequestReplyExchanger} Proxy + * on the background. + * @param requestChannel the {@link MessageChannel} to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B gateway(MessageChannel requestChannel) { + return gateway(requestChannel, null); + } + + /** + * Populate the "artificial" {@link GatewayMessageHandler} for the provided + * {@code requestChannel} to send a request with options from {@link GatewayEndpointSpec}. + * Uses {@link org.springframework.integration.gateway.RequestReplyExchanger} Proxy + * on the background. + * @param requestChannel the {@link MessageChannel} to use. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B gateway(MessageChannel requestChannel, Consumer endpointConfigurer) { + return register(new GatewayEndpointSpec(requestChannel), endpointConfigurer); + } + + /** + * Populate the "artificial" {@link GatewayMessageHandler} for the provided + * {@code subflow}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .gateway(f -> f.transform("From Gateway SubFlow: "::concat))
+	 * }
+	 * 
+ * @param flow the {@link IntegrationFlow} to to send a request message and wait for reply. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B gateway(IntegrationFlow flow) { + return gateway(flow, null); + } + + /** + * Populate the "artificial" {@link GatewayMessageHandler} for the provided + * {@code subflow} with options from {@link GatewayEndpointSpec}. + * Typically used with a Java 8 Lambda expression: + *
+	 * {@code
+	 *  .gateway(f -> f.transform("From Gateway SubFlow: "::concat), e -> e.replyTimeout(100L))
+	 * }
+	 * 
+ * @param flow the {@link IntegrationFlow} to to send a request message and wait for reply. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B gateway(IntegrationFlow flow, Consumer endpointConfigurer) { + Assert.notNull(flow); + final DirectChannel requestChannel = new DirectChannel(); + IntegrationFlowBuilder flowBuilder = IntegrationFlows.from(requestChannel); + flow.configure(flowBuilder); + addComponent(flowBuilder.get()); + return gateway(requestChannel, endpointConfigurer); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the {@code INFO} + * logging level and {@code org.springframework.integration.handler.LoggingHandler} + * as a default logging category. + *

The full request {@link Message} will be logged. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B log() { + return log(LoggingHandler.Level.INFO); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for provided {@link LoggingHandler.Level} + * logging level and {@code org.springframework.integration.handler.LoggingHandler} + * as a default logging category. + *

The full request {@link Message} will be logged. + * @param level the {@link LoggingHandler.Level}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B log(LoggingHandler.Level level) { + return log(level, (String) null); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the provided logging category + * and {@code INFO} logging level. + *

The full request {@link Message} will be logged. + * @param category the logging category to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B log(String category) { + return log(LoggingHandler.Level.INFO, category); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the provided + * {@link LoggingHandler.Level} logging level and logging category. + *

The full request {@link Message} will be logged. + * @param level the {@link LoggingHandler.Level}. + * @param category the logging category to use. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B log(LoggingHandler.Level level, String category) { + return log(level, category, (Expression) null); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the provided + * {@link LoggingHandler.Level} logging level, logging category + * and SpEL expression for the log message. + * @param level the {@link LoggingHandler.Level}. + * @param category the logging category. + * @param logExpression the SpEL expression to evaluate logger message at runtime + * against the request {@link Message}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B log(LoggingHandler.Level level, String category, String logExpression) { + Assert.hasText(logExpression); + return log(level, category, PARSER.parseExpression(logExpression)); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the {@code INFO} logging level, + * the {@code org.springframework.integration.handler.LoggingHandler} + * as a default logging category and {@link Function} for the log message. + * @param function the function to evaluate logger message at runtime + * @param

the expected payload type. + * against the request {@link Message}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public

B log(Function, Object> function) { + Assert.notNull(function); + return log(new FunctionExpression>(function)); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the {@code INFO} logging level, + * the {@code org.springframework.integration.handler.LoggingHandler} + * as a default logging category and SpEL expression to evaluate + * logger message at runtime against the request {@link Message}. + * @param logExpression the {@link Expression} to evaluate logger message at runtime + * against the request {@link Message}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B log(Expression logExpression) { + return log(LoggingHandler.Level.INFO, logExpression); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the provided + * {@link LoggingHandler.Level} logging level, + * the {@code org.springframework.integration.handler.LoggingHandler} + * as a default logging category and SpEL expression to evaluate + * logger message at runtime against the request {@link Message}. + * @param level the {@link LoggingHandler.Level}. + * @param logExpression the {@link Expression} to evaluate logger message at runtime + * against the request {@link Message}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B log(LoggingHandler.Level level, Expression logExpression) { + return log(level, null, logExpression); + } + + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the {@code INFO} + * {@link LoggingHandler.Level} logging level, + * the provided logging category and SpEL expression to evaluate + * logger message at runtime against the request {@link Message}. + * @param category the logging category. + * @param logExpression the {@link Expression} to evaluate logger message at runtime + * against the request {@link Message}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B log(String category, Expression logExpression) { + return log(LoggingHandler.Level.INFO, category, logExpression); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the provided + * {@link LoggingHandler.Level} logging level, + * the {@code org.springframework.integration.handler.LoggingHandler} + * as a default logging category and {@link Function} for the log message. + * @param level the {@link LoggingHandler.Level}. + * @param function the function to evaluate logger message at runtime + * @param

the expected payload type. + * against the request {@link Message}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public

B log(LoggingHandler.Level level, Function, Object> function) { + return log(level, null, function); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the provided + * {@link LoggingHandler.Level} logging level, + * the provided logging category and {@link Function} for the log message. + * @param category the logging category. + * @param function the function to evaluate logger message at runtime + * @param

the expected payload type. + * against the request {@link Message}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public

B log(String category, Function, Object> function) { + return log(LoggingHandler.Level.INFO, category, function); + } + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the provided + * {@link LoggingHandler.Level} logging level, logging category + * and {@link Function} for the log message. + * @param level the {@link LoggingHandler.Level}. + * @param category the logging category. + * @param function the function to evaluate logger message at runtime + * @param

the expected payload type. + * against the request {@link Message}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public

B log(LoggingHandler.Level level, String category, Function, Object> function) { + Assert.notNull(function); + return log(level, category, new FunctionExpression>(function)); + } + + + /** + * Populate a {@link WireTap} for the {@link #currentMessageChannel} + * with the {@link LoggingHandler} subscriber for the provided + * {@link LoggingHandler.Level} logging level, logging category + * and SpEL expression for the log message. + * @param level the {@link LoggingHandler.Level}. + * @param category the logging category. + * @param logExpression the {@link Expression} to evaluate logger message at runtime + * against the request {@link Message}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B log(LoggingHandler.Level level, String category, Expression logExpression) { + LoggingHandler loggingHandler = new LoggingHandler(level); + if (StringUtils.hasText(category)) { + loggingHandler.setLoggerName(category); + } + + if (logExpression != null) { + loggingHandler.setLogExpression(logExpression); + } + else { + loggingHandler.setShouldLogFullMessage(true); + } + + addComponent(loggingHandler); + MessageChannel loggerChannel = new FixedSubscriberChannel(loggingHandler); + return wireTap(loggerChannel); + } + + /** + * Populate a {@link ScatterGatherHandler} to the current integration flow position + * based on the provided {@link MessageChannel} for scattering function + * and default {@link AggregatorSpec} for gathering function. + * @param scatterChannel the {@link MessageChannel} for scatting requests. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B scatterGather(MessageChannel scatterChannel) { + return scatterGather(scatterChannel, null); + } + + /** + * Populate a {@link ScatterGatherHandler} to the current integration flow position + * based on the provided {@link MessageChannel} for scattering function + * and {@link AggregatorSpec} for gathering function. + * @param scatterChannel the {@link MessageChannel} for scatting requests. + * @param gatherer the {@link Consumer} for {@link AggregatorSpec} to configure gatherer. + * Can be {@code null}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B scatterGather(MessageChannel scatterChannel, Consumer gatherer) { + return scatterGather(scatterChannel, gatherer, null); + } + + /** + * Populate a {@link ScatterGatherHandler} to the current integration flow position + * based on the provided {@link MessageChannel} for scattering function + * and {@link AggregatorSpec} for gathering function. + * @param scatterChannel the {@link MessageChannel} for scatting requests. + * @param gatherer the {@link Consumer} for {@link AggregatorSpec} to configure gatherer. + * Can be {@code null}. + * @param scatterGather the {@link Consumer} for {@link ScatterGatherSpec} to configure + * {@link ScatterGatherHandler} and its endpoint. Can be {@code null}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B scatterGather(MessageChannel scatterChannel, Consumer gatherer, + Consumer scatterGather) { + AggregatorSpec aggregatorSpec = new AggregatorSpec(); + if (gatherer != null) { + gatherer.accept(aggregatorSpec); + } + + AggregatingMessageHandler aggregatingMessageHandler = aggregatorSpec.get().getT2(); + addComponent(aggregatingMessageHandler); + ScatterGatherHandler messageHandler = new ScatterGatherHandler(scatterChannel, aggregatingMessageHandler); + return register(new ScatterGatherSpec(messageHandler), scatterGather); + } + + /** + * Populate a {@link ScatterGatherHandler} to the current integration flow position + * based on the provided {@link RecipientListRouterSpec} for scattering function + * and default {@link AggregatorSpec} for gathering function. + * @param scatterer the {@link Consumer} for {@link RecipientListRouterSpec} to configure scatterer. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B scatterGather(Consumer scatterer) { + return scatterGather(scatterer, null); + } + + /** + * Populate a {@link ScatterGatherHandler} to the current integration flow position + * based on the provided {@link RecipientListRouterSpec} for scattering function + * and {@link AggregatorSpec} for gathering function. + * @param scatterer the {@link Consumer} for {@link RecipientListRouterSpec} to configure scatterer. + * Can be {@code null}. + * @param gatherer the {@link Consumer} for {@link AggregatorSpec} to configure gatherer. + * Can be {@code null}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B scatterGather(Consumer scatterer, Consumer gatherer) { + return scatterGather(scatterer, gatherer, null); + } + + /** + * Populate a {@link ScatterGatherHandler} to the current integration flow position + * based on the provided {@link RecipientListRouterSpec} for scattering function + * and {@link AggregatorSpec} for gathering function. + * @param scatterer the {@link Consumer} for {@link RecipientListRouterSpec} to configure scatterer. + * @param gatherer the {@link Consumer} for {@link AggregatorSpec} to configure gatherer. + * @param scatterGather the {@link Consumer} for {@link ScatterGatherSpec} to configure + * {@link ScatterGatherHandler} and its endpoint. Can be {@code null}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B scatterGather(Consumer scatterer, Consumer gatherer, + Consumer scatterGather) { + Assert.notNull(scatterer); + RecipientListRouterSpec recipientListRouterSpec = new RecipientListRouterSpec(); + scatterer.accept(recipientListRouterSpec); + AggregatorSpec aggregatorSpec = new AggregatorSpec(); + if (gatherer != null) { + gatherer.accept(aggregatorSpec); + } + + RecipientListRouter recipientListRouter = recipientListRouterSpec.get(); + addComponent(recipientListRouter); + addComponents(recipientListRouterSpec.getComponentsToRegister()); + AggregatingMessageHandler aggregatingMessageHandler = aggregatorSpec.get().getT2(); + addComponent(aggregatingMessageHandler); + ScatterGatherHandler messageHandler = new ScatterGatherHandler(recipientListRouter, aggregatingMessageHandler); + return register(new ScatterGatherSpec(messageHandler), scatterGather); + } + + /** + * Populate a {@link BarrierMessageHandler} instance for provided timeout. + * @param timeout the timeout in milliseconds. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B barrier(long timeout) { + return barrier(timeout, null); + } + + /** + * Populate a {@link BarrierMessageHandler} instance for provided timeout + * and options from {@link BarrierSpec} and endpoint options from {@link GenericEndpointSpec}. + * @param timeout the timeout in milliseconds. + * @param barrierConfigurer the {@link Consumer} to provide {@link BarrierMessageHandler} options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B barrier(long timeout, Consumer barrierConfigurer) { + return register(new BarrierSpec(timeout), barrierConfigurer); + } + + /** + * Populate a {@link ServiceActivatingHandler} instance to perform {@link MessageTriggerAction}. + * @param triggerActionId the {@link MessageTriggerAction} bean id. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B trigger(String triggerActionId) { + return trigger(triggerActionId, null); + } + + /** + * Populate a {@link ServiceActivatingHandler} instance to perform {@link MessageTriggerAction} + * and endpoint options from {@link GenericEndpointSpec}. + * @param triggerActionId the {@link MessageTriggerAction} bean id. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B trigger(String triggerActionId, + Consumer> endpointConfigurer) { + MessageProcessor trigger = new BeanNameMessageProcessor<>(triggerActionId, "trigger"); + return handle(new ServiceActivatingHandler(trigger), endpointConfigurer); + } + + /** + * Populate a {@link ServiceActivatingHandler} instance to perform {@link MessageTriggerAction}. + * @param triggerAction the {@link MessageTriggerAction}. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B trigger(MessageTriggerAction triggerAction) { + return trigger(triggerAction, null); + } + + /** + * Populate a {@link ServiceActivatingHandler} instance to perform {@link MessageTriggerAction} + * and endpoint options from {@link GenericEndpointSpec}. + * @param triggerAction the {@link MessageTriggerAction}. + * @param endpointConfigurer the {@link Consumer} to provide integration endpoint options. + * @return the current {@link IntegrationFlowDefinition}. + */ + public B trigger(MessageTriggerAction triggerAction, + Consumer> endpointConfigurer) { + return handle(new ServiceActivatingHandler(triggerAction, "trigger"), endpointConfigurer); + } + + + /** + * Represent an Integration Flow as a Reactive Streams {@link Publisher} bean. + * @param the {@code payload} type + * @return the Reactive Streams {@link Publisher} + */ + public Publisher> toReactivePublisher() { + return toReactivePublisher(Executors.newSingleThreadExecutor()); + } + + /** + * Represent an Integration Flow as a Reactive Streams {@link Publisher} bean. + * @param executor the managed {@link Executor} to be used for the background task to + * poll messages from the {@link PollableChannel}. + * Defaults to {@link Executors#newSingleThreadExecutor()}. + * @param the {@code payload} type + * @return the Reactive Streams {@link Publisher} + */ + public Publisher> toReactivePublisher(Executor executor) { + Assert.notNull(executor); + MessageChannel channelForPublisher = this.currentMessageChannel; + if (channelForPublisher == null) { + PublishSubscribeChannel publishSubscribeChannel = new PublishSubscribeChannel(); + publishSubscribeChannel.setMinSubscribers(1); + channelForPublisher = publishSubscribeChannel; + channel(channelForPublisher); + } + get(); + return new PublisherIntegrationFlow(this.integrationComponents, channelForPublisher, executor); + } + + @SuppressWarnings("unchecked") + private > B register(S endpointSpec, + Consumer endpointConfigurer) { + if (endpointConfigurer != null) { + endpointConfigurer.accept(endpointSpec); + } + + addComponents(endpointSpec.getComponentsToRegister()); + + MessageChannel inputChannel = this.currentMessageChannel; + this.currentMessageChannel = null; + if (inputChannel == null) { + inputChannel = new DirectChannel(); + this.registerOutputChannelIfCan(inputChannel); + } + + Tuple2 factoryBeanTuple2 = endpointSpec.get(); + if (inputChannel instanceof MessageChannelReference) { + factoryBeanTuple2.getT1().setInputChannelName(((MessageChannelReference) inputChannel).getName()); + } + else { + if (inputChannel instanceof FixedSubscriberChannelPrototype) { + String beanName = ((FixedSubscriberChannelPrototype) inputChannel).getName(); + inputChannel = new FixedSubscriberChannel(factoryBeanTuple2.getT2()); + if (beanName != null) { + ((FixedSubscriberChannel) inputChannel).setBeanName(beanName); + } + registerOutputChannelIfCan(inputChannel); + } + factoryBeanTuple2.getT1().setInputChannel(inputChannel); + } + + return addComponent(endpointSpec).currentComponent(factoryBeanTuple2.getT2()); + } + + private B registerOutputChannelIfCan(MessageChannel outputChannel) { + if (!(outputChannel instanceof FixedSubscriberChannelPrototype)) { + this.integrationComponents.add(outputChannel); + if (this.currentComponent != null) { + String channelName = null; + if (outputChannel instanceof MessageChannelReference) { + channelName = ((MessageChannelReference) outputChannel).getName(); + } + + Object currentComponent = this.currentComponent; + + if (AopUtils.isAopProxy(currentComponent)) { + currentComponent = extractProxyTarget(currentComponent); + } + + if (currentComponent instanceof AbstractMessageProducingHandler) { + AbstractMessageProducingHandler messageProducer = + (AbstractMessageProducingHandler) currentComponent; + checkReuse(messageProducer); + if (channelName != null) { + messageProducer.setOutputChannelName(channelName); + } + else { + messageProducer.setOutputChannel(outputChannel); + } + } + else if (currentComponent instanceof SourcePollingChannelAdapterSpec) { + SourcePollingChannelAdapterFactoryBean pollingChannelAdapterFactoryBean = + ((SourcePollingChannelAdapterSpec) currentComponent).get().getT1(); + if (channelName != null) { + pollingChannelAdapterFactoryBean.setOutputChannelName(channelName); + } + else { + pollingChannelAdapterFactoryBean.setOutputChannel(outputChannel); + } + } + else { + throw new BeanCreationException("The 'currentComponent' (" + currentComponent + + ") is a one-way 'MessageHandler' and it isn't appropriate to configure 'outputChannel'. " + + "This is the end of the integration flow."); + } + this.currentComponent = null; + } + } + return _this(); + } + + private boolean isOutputChannelRequired() { + if (this.currentComponent != null) { + Object currentComponent = this.currentComponent; + + if (AopUtils.isAopProxy(currentComponent)) { + currentComponent = extractProxyTarget(currentComponent); + } + + return currentComponent instanceof AbstractMessageProducingHandler + || currentComponent instanceof SourcePollingChannelAdapterSpec; + } + return false; + } + + @SuppressWarnings("unchecked") + protected final B _this() { + return (B) this; + } + + protected StandardIntegrationFlow get() { + if (this.integrationFlow == null) { + if (this.currentMessageChannel instanceof FixedSubscriberChannelPrototype) { + throw new BeanCreationException("The 'currentMessageChannel' (" + this.currentMessageChannel + + ") is a prototype for FixedSubscriberChannel which can't be created without MessageHandler " + + "constructor argument. That means that '.fixedSubscriberChannel()' can't be the last " + + "EIP-method in the IntegrationFlow definition."); + } + + if (this.integrationComponents.size() == 1) { + if (this.currentComponent != null) { + if (this.currentComponent instanceof SourcePollingChannelAdapterSpec) { + throw new BeanCreationException("The 'SourcePollingChannelAdapter' (" + this.currentComponent + + ") " + "must be configured with at least one 'MessageChannel' or 'MessageHandler'."); + } + } + else if (this.currentMessageChannel != null) { + throw new BeanCreationException("The 'IntegrationFlow' can't consist of only one 'MessageChannel'. " + + "Add at lest '.bridge()' EIP-method before the end of flow."); + } + } + this.integrationFlow = new StandardIntegrationFlow(this.integrationComponents); + } + return this.integrationFlow; + } + + private static boolean isLambda(Object o) { + Class aClass = o.getClass(); + return aClass.isSynthetic() && !aClass.isAnonymousClass() && !aClass.isLocalClass(); + } + + private static Object extractProxyTarget(Object target) { + if (!(target instanceof Advised)) { + return target; + } + Advised advised = (Advised) target; + if (advised.getTargetSource() == null) { + return null; + } + try { + return extractProxyTarget(advised.getTargetSource().getTarget()); + } + catch (Exception e) { + throw new BeanCreationException("Could not extract target", e); + } + } + + private void checkReuse(AbstractMessageProducingHandler replyHandler) { + Assert.isTrue(!REFERENCED_REPLY_PRODUCERS.contains(replyHandler), + "An AbstractMessageProducingHandler may only be referenced once (" + + replyHandler.getComponentName() + + ") - use @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) on @Bean definition."); + REFERENCED_REPLY_PRODUCERS.add(replyHandler); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlows.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlows.java new file mode 100644 index 0000000000..ffaded3166 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlows.java @@ -0,0 +1,291 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.function.Consumer; + +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.dsl.channel.MessageChannelSpec; +import org.springframework.integration.dsl.support.FixedSubscriberChannelPrototype; +import org.springframework.integration.dsl.support.MessageChannelReference; +import org.springframework.integration.endpoint.MessageProducerSupport; +import org.springframework.integration.endpoint.MethodInvokingMessageSource; +import org.springframework.integration.gateway.MessagingGatewaySupport; +import org.springframework.messaging.MessageChannel; +import org.springframework.util.Assert; + +/** + * The central factory for fluent {@link IntegrationFlowBuilder} API. + * + * @author Artem Bilan + * + * @since 5.0 + * + * @see org.springframework.integration.config.dsl.IntegrationFlowBeanPostProcessor + */ +public final class IntegrationFlows { + + /** + * Populate the {@link MessageChannel} name to the new {@link IntegrationFlowBuilder} chain. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code inputChannel}. + * @param messageChannelName the name of existing {@link MessageChannel} bean. + * The new {@link DirectChannel} bean will be created on context startup + * if there is no bean with this name. + * @return new {@link IntegrationFlowBuilder}. + */ + public static IntegrationFlowBuilder from(String messageChannelName) { + return from(new MessageChannelReference(messageChannelName)); + } + + /** + * Populate the {@link MessageChannel} name to the new {@link IntegrationFlowBuilder} chain. + * Typically for the {@link org.springframework.integration.channel.FixedSubscriberChannel} together + * with {@code fixedSubscriber = true}. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code inputChannel}. + * @param messageChannelName the name for {@link DirectChannel} or + * {@link org.springframework.integration.channel.FixedSubscriberChannel} + * to be created on context startup, not reference. + * The {@link MessageChannel} depends on the {@code fixedSubscriber} boolean argument. + * @param fixedSubscriber the boolean flag to determine if result {@link MessageChannel} should + * be {@link DirectChannel}, if {@code false} or + * {@link org.springframework.integration.channel.FixedSubscriberChannel}, if {@code true}. + * @return new {@link IntegrationFlowBuilder}. + * @see DirectChannel + * @see org.springframework.integration.channel.FixedSubscriberChannel + */ + public static IntegrationFlowBuilder from(String messageChannelName, boolean fixedSubscriber) { + return fixedSubscriber + ? from(new FixedSubscriberChannelPrototype(messageChannelName)) + : from(messageChannelName); + } + + /** + * Populate the {@link MessageChannel} object to the + * {@link IntegrationFlowBuilder} chain using the fluent API from {@link MessageChannelSpec}. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code inputChannel}. + * @param messageChannelSpec the MessageChannelSpec to populate {@link MessageChannel} instance. + * @return new {@link IntegrationFlowBuilder}. + * @see org.springframework.integration.dsl.channel.MessageChannels + */ + public static IntegrationFlowBuilder from(MessageChannelSpec messageChannelSpec) { + Assert.notNull(messageChannelSpec); + return from(messageChannelSpec.get()); + } + + /** + * Populate the provided {@link MessageChannel} object to the {@link IntegrationFlowBuilder} chain. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code inputChannel}. + * @param messageChannel the {@link MessageChannel} to populate. + * @return new {@link IntegrationFlowBuilder}. + */ + public static IntegrationFlowBuilder from(MessageChannel messageChannel) { + return new IntegrationFlowBuilder().channel(messageChannel); + } + + /** + * Populate the {@link MessageSource} object to the {@link IntegrationFlowBuilder} chain + * using the fluent API from the provided {@link MessageSourceSpec}. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code startMessageSource}. + * @param messageSourceSpec the {@link MessageSourceSpec} to use. + * @return new {@link IntegrationFlowBuilder}. + * @see MessageSourceSpec and its implementations. + */ + public static IntegrationFlowBuilder from(MessageSourceSpec> messageSourceSpec) { + return from(messageSourceSpec, (Consumer) null); + } + + /** + * Populate the {@link MessageSource} object to the {@link IntegrationFlowBuilder} chain + * using the fluent API from the provided {@link MessageSourceSpec}. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code startMessageSource}. + * @param messageSourceSpec the {@link MessageSourceSpec} to use. + * @param endpointConfigurer the {@link Consumer} to provide more options for the + * {@link org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean}. + * @return new {@link IntegrationFlowBuilder}. + * @see MessageSourceSpec + * @see SourcePollingChannelAdapterSpec + */ + public static IntegrationFlowBuilder from(MessageSourceSpec> messageSourceSpec, + Consumer endpointConfigurer) { + Assert.notNull(messageSourceSpec); + return from(messageSourceSpec.get(), endpointConfigurer, registerComponents(messageSourceSpec)); + } + + /** + * Populate the provided {@link MethodInvokingMessageSource} for the method of the provided service. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code startMessageSource}. + * @param service the service to use. + * @param methodName the method to invoke. + * @return new {@link IntegrationFlowBuilder}. + * @since 1.1 + * @see MethodInvokingMessageSource + */ + public static IntegrationFlowBuilder from(Object service, String methodName) { + return from(service, methodName, null); + } + + /** + * Populate the provided {@link MethodInvokingMessageSource} for the method of the provided service. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code startMessageSource}. + * @param service the service to use. + * @param methodName the method to invoke. + * @param endpointConfigurer the {@link Consumer} to provide more options for the + * {@link org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean}. + * @return new {@link IntegrationFlowBuilder}. + * @since 1.1 + * @see MethodInvokingMessageSource + */ + public static IntegrationFlowBuilder from(Object service, String methodName, + Consumer endpointConfigurer) { + Assert.notNull(service); + Assert.hasText(methodName); + MethodInvokingMessageSource messageSource = new MethodInvokingMessageSource(); + messageSource.setObject(service); + messageSource.setMethodName(methodName); + return from(messageSource, endpointConfigurer); + } + + /** + * Populate the provided {@link MessageSource} object to the {@link IntegrationFlowBuilder} chain. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code startMessageSource}. + * @param messageSource the {@link MessageSource} to populate. + * @return new {@link IntegrationFlowBuilder}. + * @see MessageSource + */ + public static IntegrationFlowBuilder from(MessageSource messageSource) { + return from(messageSource, (Consumer) null); + } + + /** + * Populate the provided {@link MessageSource} object to the {@link IntegrationFlowBuilder} chain. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code startMessageSource}. + * In addition use {@link SourcePollingChannelAdapterSpec} to provide options for the underlying + * {@link org.springframework.integration.endpoint.SourcePollingChannelAdapter} endpoint. + * @param messageSource the {@link MessageSource} to populate. + * @param endpointConfigurer the {@link Consumer} to provide more options for the + * {@link org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean}. + * @return new {@link IntegrationFlowBuilder}. + * @see MessageSource + * @see SourcePollingChannelAdapterSpec + */ + public static IntegrationFlowBuilder from(MessageSource messageSource, + Consumer endpointConfigurer) { + return from(messageSource, endpointConfigurer, null); + } + + private static IntegrationFlowBuilder from(MessageSource messageSource, + Consumer endpointConfigurer, + IntegrationFlowBuilder integrationFlowBuilder) { + SourcePollingChannelAdapterSpec spec = new SourcePollingChannelAdapterSpec(messageSource); + if (endpointConfigurer != null) { + endpointConfigurer.accept(spec); + } + if (integrationFlowBuilder == null) { + integrationFlowBuilder = new IntegrationFlowBuilder(); + } + return integrationFlowBuilder.addComponent(spec) + .currentComponent(spec); + } + + /** + * Populate the {@link MessageProducerSupport} object to the {@link IntegrationFlowBuilder} chain + * using the fluent API from the {@link MessageProducerSpec}. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code startMessageProducer}. + * @param messageProducerSpec the {@link MessageProducerSpec} to use. + * @return new {@link IntegrationFlowBuilder}. + * @see MessageProducerSpec + */ + public static IntegrationFlowBuilder from(MessageProducerSpec messageProducerSpec) { + return from(messageProducerSpec.get(), registerComponents(messageProducerSpec)); + } + + /** + * Populate the provided {@link MessageProducerSupport} object to the {@link IntegrationFlowBuilder} chain. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code startMessageProducer}. + * @param messageProducer the {@link MessageProducerSupport} to populate. + * @return new {@link IntegrationFlowBuilder}. + */ + public static IntegrationFlowBuilder from(MessageProducerSupport messageProducer) { + return from(messageProducer, (IntegrationFlowBuilder) null); + } + + private static IntegrationFlowBuilder from(MessageProducerSupport messageProducer, + IntegrationFlowBuilder integrationFlowBuilder) { + MessageChannel outputChannel = messageProducer.getOutputChannel(); + if (outputChannel == null) { + outputChannel = new DirectChannel(); + messageProducer.setOutputChannel(outputChannel); + } + if (integrationFlowBuilder == null) { + integrationFlowBuilder = from(outputChannel); + } + else { + integrationFlowBuilder.channel(outputChannel); + } + return integrationFlowBuilder.addComponent(messageProducer); + } + + /** + * Populate the {@link MessagingGatewaySupport} object to the {@link IntegrationFlowBuilder} chain + * using the fluent API from the {@link MessagingGatewaySpec}. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code startMessagingGateway}. + * @param inboundGatewaySpec the {@link MessagingGatewaySpec} to use. + * @return new {@link IntegrationFlowBuilder}. + */ + public static IntegrationFlowBuilder from(MessagingGatewaySpec inboundGatewaySpec) { + return from(inboundGatewaySpec.get(), registerComponents(inboundGatewaySpec)); + } + + /** + * Populate the provided {@link MessagingGatewaySupport} object to the {@link IntegrationFlowBuilder} chain. + * The {@link org.springframework.integration.dsl.IntegrationFlow} {@code startMessageProducer}. + * @param inboundGateway the {@link MessagingGatewaySupport} to populate. + * @return new {@link IntegrationFlowBuilder}. + */ + public static IntegrationFlowBuilder from(MessagingGatewaySupport inboundGateway) { + return from(inboundGateway, (IntegrationFlowBuilder) null); + } + + private static IntegrationFlowBuilder from(MessagingGatewaySupport inboundGateway, + IntegrationFlowBuilder integrationFlowBuilder) { + MessageChannel outputChannel = inboundGateway.getRequestChannel(); + if (outputChannel == null) { + outputChannel = new DirectChannel(); + inboundGateway.setRequestChannel(outputChannel); + } + if (integrationFlowBuilder == null) { + integrationFlowBuilder = from(outputChannel); + } + else { + integrationFlowBuilder.channel(outputChannel); + } + return integrationFlowBuilder.addComponent(inboundGateway); + } + + private static IntegrationFlowBuilder registerComponents(Object spec) { + if (spec instanceof ComponentsRegistration) { + return new IntegrationFlowBuilder() + .addComponents(((ComponentsRegistration) spec).getComponentsToRegister()); + } + return null; + } + + private IntegrationFlows() { + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/LambdaMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/LambdaMessageProcessor.java new file mode 100644 index 0000000000..4330493aa0 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/LambdaMessageProcessor.java @@ -0,0 +1,131 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.integration.handler.MessageProcessor; +import org.springframework.integration.support.utils.IntegrationUtils; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandlingException; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +class LambdaMessageProcessor implements MessageProcessor, BeanFactoryAware { + + private final Object target; + + private final Method method; + + private final TypeDescriptor payloadType; + + private final Class[] parameterTypes; + + + private ConversionService conversionService; + + LambdaMessageProcessor(Object target, Class payloadType) { + Assert.notNull(target); + this.target = target; + final AtomicReference methodValue = new AtomicReference<>(); + ReflectionUtils.doWithMethods(target.getClass(), + methodValue::set, + methodCandidate -> + !methodCandidate.isBridge() + && !methodCandidate.isDefault() + && methodCandidate.getDeclaringClass() != Object.class + && Modifier.isPublic(methodCandidate.getModifiers()) + && !Modifier.isStatic(methodCandidate.getModifiers())); + + Assert.notNull(methodValue.get(), "LambdaMessageProcessor is applicable for inline or lambda " + + "classes with single method - functional interface implementations."); + + this.method = methodValue.get(); + this.method.setAccessible(true); + this.parameterTypes = this.method.getParameterTypes(); + this.payloadType = payloadType != null ? TypeDescriptor.valueOf(payloadType) : null; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + ConversionService conversionService = IntegrationUtils.getConversionService(beanFactory); + if (conversionService == null) { + conversionService = new DefaultConversionService(); + } + this.conversionService = conversionService; + } + + @Override + public Object processMessage(Message message) { + Object[] args = new Object[this.parameterTypes.length]; + for (int i = 0; i < this.parameterTypes.length; i++) { + Class parameterType = this.parameterTypes[i]; + if (Message.class.isAssignableFrom(parameterType)) { + args[i] = message; + } + if (Map.class.isAssignableFrom(parameterType)) { + if (message.getPayload() instanceof Map) { + args[i] = message.getPayload(); + } + else { + args[i] = message.getHeaders(); + } + } + else { + if (this.payloadType != null) { + if (Message.class.isAssignableFrom(this.payloadType.getType())) { + args[i] = message; + } + else { + args[i] = this.conversionService.convert(message.getPayload(), + TypeDescriptor.forObject(message.getPayload()), this.payloadType); + } + + } + else { + args[i] = message.getPayload(); + } + } + } + + try { + return this.method.invoke(this.target, args); + } + catch (InvocationTargetException e) { + throw new MessageHandlingException(message, e.getCause()); + } + catch (Exception e) { + throw new MessageHandlingException(message, e); + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageHandlerSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageHandlerSpec.java new file mode 100644 index 0000000000..94ef0c0503 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageHandlerSpec.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.messaging.MessageHandler; + +/** + * An {@link IntegrationComponentSpec} for {@link MessageHandler}s. + * + * @param the target {@link ConsumerEndpointSpec} implementation type. + * @param the target {@link MessageHandler} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class MessageHandlerSpec, H extends MessageHandler> + extends IntegrationComponentSpec { +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageProcessorSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageProcessorSpec.java new file mode 100644 index 0000000000..33c62c9c2c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageProcessorSpec.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.integration.handler.MessageProcessor; + +/** + * The {@link IntegrationComponentSpec} specific base class + * for {@link MessageProcessor}s. + * + * @param the target {@link MessageProcessorSpec} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class MessageProcessorSpec> + extends IntegrationComponentSpec> { + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageProducerSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageProducerSpec.java new file mode 100644 index 0000000000..98d2feb56c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageProducerSpec.java @@ -0,0 +1,116 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.integration.endpoint.MessageProducerSupport; +import org.springframework.messaging.MessageChannel; + +/** + * An {@link IntegrationComponentSpec} for + * {@link org.springframework.integration.core.MessageProducer}s. + * + * @param the target {@link MessageProducerSpec} implementation type. + * @param

the target {@link MessageProducerSupport} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class MessageProducerSpec, P extends MessageProducerSupport> + extends IntegrationComponentSpec { + + public MessageProducerSpec(P producer) { + this.target = producer; + } + + /** + * {@inheritDoc} + * Configure the message producer's bean name. + */ + @Override + public S id(String id) { + this.target.setBeanName(id); + return super.id(id); + } + + /** + * @param phase the phase. + * @return the spec. + * @see org.springframework.context.SmartLifecycle + */ + public S phase(int phase) { + this.target.setPhase(phase); + return _this(); + } + + /** + * @param autoStartup the autoStartup. + * @return the spec. + * @see org.springframework.context.SmartLifecycle + */ + public S autoStartup(boolean autoStartup) { + this.target.setAutoStartup(autoStartup); + return _this(); + } + + /** + * Specify the {@code outputChannel} for the + * {@link org.springframework.integration.core.MessageProducer} + * @param outputChannel the outputChannel. + * @return the spec. + * @see MessageProducerSupport#setOutputChannel(MessageChannel) + */ + public S outputChannel(MessageChannel outputChannel) { + target.setOutputChannel(outputChannel); + return _this(); + } + + /** + * Specify the bean name of the {@code outputChannel} for the + * {@link org.springframework.integration.core.MessageProducer} + * @param outputChannel the outputChannel bean name. + * @return the spec. + * @see MessageProducerSupport#setOutputChannelName(String) + */ + public S outputChannel(String outputChannel) { + target.setOutputChannelName(outputChannel); + return _this(); + } + + /** + * Configure the {@link MessageChannel} to which error messages will be sent. + * @param errorChannel the errorChannel. + * @return the spec. + * @see MessageProducerSupport#setErrorChannel(MessageChannel) + */ + public S errorChannel(MessageChannel errorChannel) { + target.setErrorChannel(errorChannel); + return _this(); + } + + /** + * Configure the bean name of the {@link MessageChannel} to which error messages will be sent. + * @param errorChannel the errorChannel bean name. + * @return the spec. + * @see MessageProducerSupport#setErrorChannelName(String) + */ + public S errorChannel(String errorChannel) { + target.setErrorChannelName(errorChannel); + return _this(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageSourceSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageSourceSpec.java new file mode 100644 index 0000000000..94bb99307b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessageSourceSpec.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.integration.core.MessageSource; + +/** + * An {@link IntegrationComponentSpec} for {@link MessageSource}s. + * + * @param the target {@link MessageSourceSpec} implementation type. + * @param the target {@link MessageSource} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class MessageSourceSpec, H extends MessageSource> + extends IntegrationComponentSpec { +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessagingGatewaySpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessagingGatewaySpec.java new file mode 100644 index 0000000000..8854728756 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/MessagingGatewaySpec.java @@ -0,0 +1,179 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.integration.gateway.MessagingGatewaySupport; +import org.springframework.integration.mapping.InboundMessageMapper; +import org.springframework.integration.mapping.OutboundMessageMapper; +import org.springframework.messaging.MessageChannel; + +/** + * An {@link IntegrationComponentSpec} for {@link MessagingGatewaySupport}s. + * + * @param the target {@link MessagingGatewaySpec} implementation type. + * @param the target {@link MessagingGatewaySupport} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class MessagingGatewaySpec, G extends MessagingGatewaySupport> + extends IntegrationComponentSpec { + + public MessagingGatewaySpec(G gateway) { + this.target = gateway; + } + + @Override + public S id(String id) { + this.target.setBeanName(id); + return super.id(id); + } + + /** + * A lifecycle phase to use. + * @param phase the phase. + * @return the spec. + * @see org.springframework.context.SmartLifecycle + */ + public S phase(int phase) { + this.target.setPhase(phase); + return _this(); + } + + /** + * An auto-startup flag. + * @param autoStartup the autoStartup. + * @return the spec. + * @see org.springframework.context.SmartLifecycle + */ + public S autoStartup(boolean autoStartup) { + this.target.setAutoStartup(autoStartup); + return _this(); + } + + /** + * A reply channel to use. + * @param replyChannel the replyChannel. + * @return the spec. + * @see MessagingGatewaySupport#setReplyChannel(MessageChannel) + */ + public S replyChannel(MessageChannel replyChannel) { + this.target.setReplyChannel(replyChannel); + return _this(); + } + + /** + * A reply channel name to use. + * @param replyChannelName the name of replyChannel. + * @return the spec. + * @see MessagingGatewaySupport#setReplyChannelName(String) + */ + public S replyChannel(String replyChannelName) { + this.target.setReplyChannelName(replyChannelName); + return _this(); + } + + /** + * A request channel to use. + * @param requestChannel the requestChannel. + * @return the spec. + * @see MessagingGatewaySupport#setRequestChannel(MessageChannel) + */ + public S requestChannel(MessageChannel requestChannel) { + this.target.setRequestChannel(requestChannel); + return _this(); + } + + /** + * A request channel name to use. + * @param requestChannelName the name of requestChannel. + * @return the spec. + * @see MessagingGatewaySupport#setRequestChannelName(String) + */ + public S requestChannel(String requestChannelName) { + this.target.setRequestChannelName(requestChannelName); + return _this(); + } + + /** + * An error channel to use. + * @param errorChannel the errorChannel. + * @return the spec. + * @see MessagingGatewaySupport#setErrorChannel(MessageChannel) + */ + public S errorChannel(MessageChannel errorChannel) { + this.target.setErrorChannel(errorChannel); + return _this(); + } + + /** + * An error channel name to use. + * @param errorChannelName the name of errorChannel. + * @return the spec. + * @see MessagingGatewaySupport#setErrorChannelName(String) + */ + public S errorChannel(String errorChannelName) { + this.target.setErrorChannelName(errorChannelName); + return _this(); + } + + /** + * A request timeout to use. + * @param requestTimeout the requestTimeout. + * @return the spec. + * @see MessagingGatewaySupport#setRequestTimeout(long) + */ + public S requestTimeout(long requestTimeout) { + this.target.setRequestTimeout(requestTimeout); + return _this(); + } + + /** + * A reply timeout to use. + * @param replyTimeout the replyTimeout. + * @return the spec. + * @see MessagingGatewaySupport#setReplyTimeout(long) + */ + public S replyTimeout(long replyTimeout) { + this.target.setReplyTimeout(replyTimeout); + return _this(); + } + + /** + * An {@link InboundMessageMapper} to use. + * @param requestMapper the requestMapper. + * @return the spec. + * @see MessagingGatewaySupport#setRequestMapper(InboundMessageMapper) + */ + public S requestMapper(InboundMessageMapper requestMapper) { + this.target.setRequestMapper(requestMapper); + return _this(); + } + + /** + * An {@link OutboundMessageMapper} to use. + * @param replyMapper the replyMapper. + * @return the spec. + * @see MessagingGatewaySupport#setReplyMapper(OutboundMessageMapper) + */ + public S replyMapper(OutboundMessageMapper replyMapper) { + this.target.setReplyMapper(replyMapper); + return _this(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/PollerFactory.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/PollerFactory.java new file mode 100644 index 0000000000..4fa91c6828 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/PollerFactory.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + +import org.springframework.scheduling.Trigger; + +/** + * An {@code Adapter} class for the {@link Pollers} factory. + * Typically used with a Java 8 Lambda expression: + *

+ * {@code
+ *  c -> c.poller(p -> p.fixedRate(100))
+ * }
+ * 
+ * + * @author Artem Bilan + * + * @since 5.0 + */ +public final class PollerFactory { + + public PollerSpec trigger(Trigger trigger) { + return Pollers.trigger(trigger); + } + + public PollerSpec cron(String cronExpression) { + return Pollers.cron(cronExpression); + } + + public PollerSpec cron(String cronExpression, TimeZone timeZone) { + return Pollers.cron(cronExpression, timeZone); + } + + public PollerSpec fixedRate(long period) { + return Pollers.fixedRate(period); + } + + public PollerSpec fixedRate(long period, TimeUnit timeUnit) { + return Pollers.fixedRate(period, timeUnit); + } + + public PollerSpec fixedRate(long period, long initialDelay) { + return Pollers.fixedRate(period, initialDelay); + } + + public PollerSpec fixedDelay(long period, TimeUnit timeUnit, long initialDelay) { + return Pollers.fixedDelay(period, timeUnit, initialDelay); + } + + public PollerSpec fixedRate(long period, TimeUnit timeUnit, long initialDelay) { + return Pollers.fixedRate(period, timeUnit, initialDelay); + } + + public PollerSpec fixedDelay(long period, TimeUnit timeUnit) { + return Pollers.fixedDelay(period, timeUnit); + } + + public PollerSpec fixedDelay(long period, long initialDelay) { + return Pollers.fixedDelay(period, initialDelay); + } + + public PollerSpec fixedDelay(long period) { + return Pollers.fixedDelay(period); + } + + PollerFactory() { + super(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/PollerSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/PollerSpec.java new file mode 100644 index 0000000000..afc0ae1228 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/PollerSpec.java @@ -0,0 +1,200 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Executor; + +import org.aopalliance.aop.Advice; + +import org.springframework.integration.channel.MessagePublishingErrorHandler; +import org.springframework.integration.scheduling.PollerMetadata; +import org.springframework.integration.transaction.TransactionInterceptorBuilder; +import org.springframework.integration.transaction.TransactionSynchronizationFactory; +import org.springframework.messaging.MessageChannel; +import org.springframework.scheduling.Trigger; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.interceptor.DefaultTransactionAttribute; +import org.springframework.transaction.interceptor.TransactionInterceptor; +import org.springframework.util.ErrorHandler; + +/** + * An {@link IntegrationComponentSpec} for {@link PollerMetadata}s. + * + * @author Artem Bilan + * @author Gary Russell + * + * @since 5.0 + */ +public final class PollerSpec extends IntegrationComponentSpec + implements ComponentsRegistration { + + private final List adviceChain = new LinkedList<>(); + + private final Collection componentsToRegister = new ArrayList<>(); + + PollerSpec(Trigger trigger) { + this.target = new PollerMetadata(); + this.target.setAdviceChain(this.adviceChain); + this.target.setTrigger(trigger); + } + + /** + * Specify the {@link TransactionSynchronizationFactory} to attach a + * {@link org.springframework.transaction.support.TransactionSynchronization} + * to the transaction around {@code poll} operation. + * @param transactionSynchronizationFactory the TransactionSynchronizationFactory to use. + * @return the spec. + */ + public PollerSpec transactionSynchronizationFactory( + TransactionSynchronizationFactory transactionSynchronizationFactory) { + this.target.setTransactionSynchronizationFactory(transactionSynchronizationFactory); + return this; + } + + /** + * Specify the {@link ErrorHandler} to wrap a {@code taskExecutor} + * to the {@link org.springframework.integration.util.ErrorHandlingTaskExecutor}. + * @param errorHandler the {@link ErrorHandler} to use. + * @return the spec. + * @see #taskExecutor + */ + public PollerSpec errorHandler(ErrorHandler errorHandler) { + this.target.setErrorHandler(errorHandler); + return this; + } + + /** + * Specify a {@link MessageChannel} to use for sending error message in case + * of polling failures. + * @param errorChannel the {@link MessageChannel} to use. + * @return the spec. + * @see MessagePublishingErrorHandler + */ + public PollerSpec errorChannel(MessageChannel errorChannel) { + MessagePublishingErrorHandler errorHandler = new MessagePublishingErrorHandler(); + errorHandler.setDefaultErrorChannel(errorChannel); + this.componentsToRegister.add(errorHandler); + return errorHandler(errorHandler); + } + + /** + * Specify a bean name for the {@link MessageChannel} to use for sending error message in case + * of polling failures. + * @param errorChannelName the bean name for {@link MessageChannel} to use. + * @return the spec. + * @see MessagePublishingErrorHandler + */ + public PollerSpec errorChannel(String errorChannelName) { + MessagePublishingErrorHandler errorHandler = new MessagePublishingErrorHandler(); + errorHandler.setDefaultErrorChannelName(errorChannelName); + this.componentsToRegister.add(errorHandler); + return errorHandler(errorHandler); + } + + /** + * @param maxMessagesPerPoll the maxMessagesPerPoll to set. + * @return the spec. + * @see PollerMetadata#setMaxMessagesPerPoll + */ + public PollerSpec maxMessagesPerPoll(long maxMessagesPerPoll) { + this.target.setMaxMessagesPerPoll(maxMessagesPerPoll); + return this; + } + + /** + * Specify a timeout in milliseconds to wait for a message in the + * {@link org.springframework.messaging.MessageChannel}. + * Defaults to {@code 1000}. + * @param receiveTimeout the timeout to use. + * @return the spec. + * @see org.springframework.messaging.PollableChannel#receive(long) + */ + public PollerSpec receiveTimeout(long receiveTimeout) { + this.target.setReceiveTimeout(receiveTimeout); + return this; + } + + /** + * Specify AOP {@link Advice}s for the {@code pollingTask}. + * @param advice the {@link Advice}s to use. + * @return the spec. + */ + public PollerSpec advice(Advice... advice) { + this.adviceChain.addAll(Arrays.asList(advice)); + return this; + } + + /** + * Specify a {@link TransactionInterceptor} {@link Advice} with the + * provided {@code PlatformTransactionManager} and default {@link DefaultTransactionAttribute} + * for the {@code pollingTask}. + * @param transactionManager the {@link PlatformTransactionManager} to use. + * @return the spec. + */ + public PollerSpec transactional(PlatformTransactionManager transactionManager) { + return transactional(new TransactionInterceptorBuilder() + .transactionManager(transactionManager) + .build()); + } + + /** + * Specify a {@link TransactionInterceptor} {@link Advice} with default {@code PlatformTransactionManager} + * and {@link DefaultTransactionAttribute} for the {@code pollingTask}. + * @return the spec. + */ + public PollerSpec transactional() { + TransactionInterceptor transactionInterceptor = new TransactionInterceptorBuilder().build(); + this.componentsToRegister.add(transactionInterceptor); + return transactional(transactionInterceptor); + } + + /** + * Specify a {@link TransactionInterceptor} {@link Advice} for the {@code pollingTask}. + * @param transactionInterceptor the {@link TransactionInterceptor} to use. + * @return the spec. + * @see TransactionInterceptorBuilder + */ + public PollerSpec transactional(TransactionInterceptor transactionInterceptor) { + return advice(transactionInterceptor); + } + + /** + * Specify an {@link Executor} to perform the {@code pollingTask}. + * @param taskExecutor the {@link Executor} to use. + * @return the spec. + */ + public PollerSpec taskExecutor(Executor taskExecutor) { + this.target.setTaskExecutor(taskExecutor); + return this; + } + + public PollerSpec sendTimeout(long sendTimeout) { + this.target.setSendTimeout(sendTimeout); + return this; + } + + @Override + public Collection getComponentsToRegister() { + return this.componentsToRegister; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/Pollers.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/Pollers.java new file mode 100644 index 0000000000..40770aa7b2 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/Pollers.java @@ -0,0 +1,90 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + +import org.springframework.scheduling.Trigger; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.scheduling.support.PeriodicTrigger; + +/** + * An utility class to provide {@link PollerSpec}s for + * {@link org.springframework.integration.scheduling.PollerMetadata} configuration + * variants. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public final class Pollers { + + public static PollerSpec trigger(Trigger trigger) { + return new PollerSpec(trigger); + } + + public static PollerSpec fixedRate(long period) { + return fixedRate(period, null); + } + + public static PollerSpec fixedRate(long period, TimeUnit timeUnit) { + return fixedRate(period, timeUnit, 0); + } + public static PollerSpec fixedRate(long period, long initialDelay) { + return periodicTrigger(period, null, true, initialDelay); + } + + public static PollerSpec fixedRate(long period, TimeUnit timeUnit, long initialDelay) { + return periodicTrigger(period, timeUnit, true, initialDelay); + } + + public static PollerSpec fixedDelay(long period) { + return fixedDelay(period, null); + } + + public static PollerSpec fixedDelay(long period, TimeUnit timeUnit) { + return fixedDelay(period, timeUnit, 0); + } + + public static PollerSpec fixedDelay(long period, long initialDelay) { + return periodicTrigger(period, null, false, initialDelay); + } + + public static PollerSpec fixedDelay(long period, TimeUnit timeUnit, long initialDelay) { + return periodicTrigger(period, timeUnit, false, initialDelay); + } + + private static PollerSpec periodicTrigger(long period, TimeUnit timeUnit, boolean fixedRate, long initialDelay) { + PeriodicTrigger periodicTrigger = new PeriodicTrigger(period, timeUnit); + periodicTrigger.setFixedRate(fixedRate); + periodicTrigger.setInitialDelay(initialDelay); + return new PollerSpec(periodicTrigger); + } + + public static PollerSpec cron(String cronExpression) { + return cron(cronExpression, TimeZone.getDefault()); + } + + public static PollerSpec cron(String cronExpression, TimeZone timeZone) { + return new PollerSpec(new CronTrigger(cronExpression, timeZone)); + } + + private Pollers() { + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/PublishSubscribeSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/PublishSubscribeSpec.java new file mode 100644 index 0000000000..0ee44d46bd --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/PublishSubscribeSpec.java @@ -0,0 +1,63 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executor; + +import org.springframework.integration.dsl.channel.PublishSubscribeChannelSpec; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public class PublishSubscribeSpec extends PublishSubscribeChannelSpec { + + private final List subscriberFlows = new ArrayList<>(); + + PublishSubscribeSpec() { + super(); + } + + PublishSubscribeSpec(Executor executor) { + super(executor); + } + + @Override + public PublishSubscribeSpec id(String id) { + return super.id(id); + } + + public PublishSubscribeSpec subscribe(IntegrationFlow flow) { + IntegrationFlowBuilder flowBuilder = IntegrationFlows.from(this.channel); + flow.configure(flowBuilder); + this.subscriberFlows.add(flowBuilder.get()); + return _this(); + } + + @Override + public Collection getComponentsToRegister() { + List objects = new ArrayList(); + objects.addAll(super.getComponentsToRegister()); + objects.addAll(this.subscriberFlows); + return objects; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/PublisherIntegrationFlow.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/PublisherIntegrationFlow.java new file mode 100644 index 0000000000..fe65e766f1 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/PublisherIntegrationFlow.java @@ -0,0 +1,249 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageDeliveryException; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.SubscribableChannel; + +/** + * + * @param the message payload type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +class PublisherIntegrationFlow extends StandardIntegrationFlow implements Publisher> { + + private static final Subscription NO_OP_SUBSCRIPTION = new Subscription() { + + @Override + public void request(long n) { + } + + @Override + public void cancel() { + } + + }; + + private final Queue>> subscribers = new LinkedBlockingQueue<>(); + + private final MessageChannel messageChannel; + + private final Executor executor; + + PublisherIntegrationFlow(Set integrationComponents, MessageChannel messageChannel, Executor executor) { + super(integrationComponents); + this.messageChannel = messageChannel; + this.executor = executor; + start(); + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(Subscriber> subscriber) { + if (!isRunning()) { + //Reactive Streams Specification: https://github.com/reactive-streams/reactive-streams-jvm#1.4 + subscriber.onSubscribe(NO_OP_SUBSCRIPTION); + subscriber.onError( + new IllegalStateException("The Publisher must be started ('Lifecycle.start()') " + + "before accepting subscription.")); + return; + } + + this.subscribers.add(subscriber); + if (this.messageChannel instanceof SubscribableChannel) { + subscriber.onSubscribe(new MessageHandlerSubscription((Subscriber>) subscriber)); + } + else if (this.messageChannel instanceof PollableChannel) { + subscriber.onSubscribe(new PollableSubscription((Subscriber>) subscriber)); + } + else { + //Reactive Streams Specification: https://github.com/reactive-streams/reactive-streams-jvm#1.4 + subscriber.onSubscribe(NO_OP_SUBSCRIPTION); + subscriber.onError( + new IllegalStateException("Unsupported MessageChannel type [" + + this.messageChannel + "]. Must be 'SubscribableChannel' or 'PollableChannel'.")); + } + } + + @Override + public void stop() { + super.stop(); + shutdown(); + } + + public void shutdown() { + Subscriber> subscriber; + while ((subscriber = this.subscribers.poll()) != null) { + subscriber.onComplete(); + } + } + + + private abstract class SubscriberSubscription implements Subscription { + + final Subscriber> subscriber; + + volatile boolean terminated; + + SubscriberSubscription(Subscriber> subscriber) { + this.subscriber = subscriber; + } + + @Override + public void request(long n) { + //Reactive Streams Specification: https://github.com/reactive-streams/reactive-streams-jvm#3.9 + if (n <= 0L) { + this.subscriber.onError( + new IllegalArgumentException("Spec. Rule 3.9 - " + + "Cannot request a non strictly positive number: " + n)); + } + //Reactive Streams Specification: https://github.com/reactive-streams/reactive-streams-jvm#3.6 + else if (!this.terminated && isRunning()) { + onRequest(n); + } + } + + @Override + public void cancel() { + PublisherIntegrationFlow.this.subscribers.remove(this.subscriber); + this.terminated = true; + } + + protected abstract void onRequest(long n); + + } + + private final class MessageHandlerSubscription extends SubscriberSubscription implements MessageHandler { + + private final Queue pendingRequests = new LinkedBlockingQueue<>(); + + private final AtomicReference currentRequest = new AtomicReference<>(); + + private final AtomicLong count = new AtomicLong(); + + private volatile boolean unbounded; + + MessageHandlerSubscription(Subscriber> subscriber) { + super(subscriber); + } + + @Override + public void onRequest(long n) { + if (n == Long.MAX_VALUE) { + this.unbounded = true; + this.pendingRequests.clear(); + this.currentRequest.set(null); + this.count.set(0); + } + else if (!this.unbounded) { + if (this.currentRequest.get() != null) { + this.pendingRequests.offer(n); + } + else { + this.currentRequest.set(n); + this.count.set(0); + } + } + ((SubscribableChannel) PublisherIntegrationFlow.this.messageChannel).subscribe(this); + } + + @Override + public void handleMessage(Message message) throws MessagingException { + if (this.terminated || !PublisherIntegrationFlow.this.isRunning()) { + ((SubscribableChannel) PublisherIntegrationFlow.this.messageChannel).unsubscribe(this); + throw new MessageDeliveryException(message); + } + + if (this.unbounded) { + this.subscriber.onNext(message); + } + else { + if (this.currentRequest.get() == null || this.count.getAndIncrement() == this.currentRequest.get()) { + this.currentRequest.set(this.pendingRequests.poll()); + this.count.set(0); + if (this.currentRequest.get() == null) { + ((SubscribableChannel) PublisherIntegrationFlow.this.messageChannel).unsubscribe(this); + throw new MessageDeliveryException(message); + } + } + this.subscriber.onNext(message); + } + } + + @Override + public void cancel() { + ((SubscribableChannel) PublisherIntegrationFlow.this.messageChannel).unsubscribe(this); + super.cancel(); + } + + } + + + private final class PollableSubscription extends SubscriberSubscription { + + PollableSubscription(Subscriber> subscriber) { + super(subscriber); + } + + @Override + public void onRequest(final long n) { + PublisherIntegrationFlow.this.executor.execute(() -> { + if (n == Long.MAX_VALUE) { + while (!terminated && isRunning()) { + Message receive = + ((PollableChannel) PublisherIntegrationFlow.this.messageChannel).receive(50); + if (receive != null) { + subscriber.onNext(receive); + } + } + } + else { + long i = 0; + while (!terminated && isRunning() && i < n) { + Message receive = + ((PollableChannel) PublisherIntegrationFlow.this.messageChannel).receive(50); + if (receive != null) { + subscriber.onNext(receive); + i++; + } + } + } + }); + } + + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/RecipientListRouterSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/RecipientListRouterSpec.java new file mode 100644 index 0000000000..fa4b54402a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/RecipientListRouterSpec.java @@ -0,0 +1,218 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.expression.Expression; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.core.GenericSelector; +import org.springframework.integration.core.MessageSelector; +import org.springframework.integration.router.RecipientListRouter; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * An {@link AbstractRouterSpec} for a {@link RecipientListRouter}. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public class RecipientListRouterSpec extends AbstractRouterSpec { + + RecipientListRouterSpec() { + super(new DslRecipientListRouter()); + } + + /** + * Adds a recipient channel that always will be selected. + * @param channelName the channel name. + * @return the router spec. + */ + public RecipientListRouterSpec recipient(String channelName) { + return recipient(channelName, (String) null); + } + + /** + * Adds a recipient channel that will be selected if the the expression evaluates to 'true'. + * @param channelName the channel name. + * @param expression the expression. + * @return the router spec. + */ + public RecipientListRouterSpec recipient(String channelName, String expression) { + return recipient(channelName, StringUtils.hasText(expression) ? PARSER.parseExpression(expression) : null); + } + + /** + * Adds a recipient channel that will be selected if the the expression evaluates to 'true'. + * @param channelName the channel name. + * @param expression the expression. + * @return the router spec. + */ + public RecipientListRouterSpec recipient(String channelName, Expression expression) { + Assert.hasText(channelName); + ((DslRecipientListRouter) this.target).add(channelName, expression); + return _this(); + } + + + /** + * Adds a recipient channel that will be selected if the the selector's accept method returns 'true'. + * @param channelName the channel name. + * @param selector the selector. + * @return the router spec. + */ + public RecipientListRouterSpec recipientMessageSelector(String channelName, MessageSelector selector) { + return recipient(channelName, (GenericSelector>) selector); + } + + /** + * Adds a recipient channel that will be selected if the the selector's accept method returns 'true'. + * @param channelName the channel name. + * @param selector the selector. + * @param

the selector source type. + * @return the router spec. + */ + public

RecipientListRouterSpec recipient(String channelName, GenericSelector

selector) { + Assert.hasText(channelName); + ((DslRecipientListRouter) this.target).add(channelName, selector); + return _this(); + } + + /** + * Adds a recipient channel that always will be selected. + * @param channel the recipient channel. + * @return the router spec. + */ + public RecipientListRouterSpec recipient(MessageChannel channel) { + return recipient(channel, (String) null); + } + + /** + * Adds a recipient channel that will be selected if the the expression evaluates to 'true'. + * @param channel the recipient channel. + * @param expression the expression. + * @return the router spec. + */ + public RecipientListRouterSpec recipient(MessageChannel channel, String expression) { + return recipient(channel, StringUtils.hasText(expression) ? PARSER.parseExpression(expression) : null); + } + + /** + * Adds a recipient channel that will be selected if the the expression evaluates to 'true'. + * @param channel the recipient channel. + * @param expression the expression. + * @return the router spec. + */ + public RecipientListRouterSpec recipient(MessageChannel channel, Expression expression) { + Assert.notNull(channel); + ((DslRecipientListRouter) this.target).add(channel, expression); + return _this(); + } + + /** + * Adds a recipient channel that will be selected if the the selector's accept method returns 'true'. + * @param channel the recipient channel. + * @param selector the selector. + * @return the router spec. + */ + public RecipientListRouterSpec recipientMessageSelector(MessageChannel channel, MessageSelector selector) { + return recipient(channel, (GenericSelector>) selector); + } + + /** + * Adds a recipient channel that will be selected if the the selector's accept method returns 'true'. + * @param channel the recipient channel. + * @param selector the selector. + * @param

the selector source type. + * @return the router spec. + */ + public

RecipientListRouterSpec recipient(MessageChannel channel, GenericSelector

selector) { + Assert.notNull(channel); + ((DslRecipientListRouter) this.target).add(channel, selector); + return _this(); + } + + /** + * Adds a subflow that will be invoked if the selector's accept methods returns 'true'. + * @param selector the selector. + * @param subFlow the subflow. + * @return the router spec. + */ + public RecipientListRouterSpec recipientMessageSelectorFlow(MessageSelector selector, IntegrationFlow subFlow) { + return recipientFlow((GenericSelector>) selector, subFlow); + } + + /** + * Adds a subflow that will be invoked if the selector's accept methods returns 'true'. + * @param selector the selector. + * @param subFlow the subflow. + * @param

the selector source type. + * @return the router spec. + */ + public

RecipientListRouterSpec recipientFlow(GenericSelector

selector, IntegrationFlow subFlow) { + Assert.notNull(subFlow); + DirectChannel channel = populateSubFlow(subFlow); + ((DslRecipientListRouter) this.target).add(channel, selector); + return _this(); + } + + /** + * Adds a subflow that will be invoked as a recipient. + * @param subFlow the subflow. + * @return the router spec. + * @since 1.2 + */ + public RecipientListRouterSpec recipientFlow(IntegrationFlow subFlow) { + return recipientFlow((String) null, subFlow); + } + + + /** + * Adds a subflow that will be invoked if the expression evaluates to 'true'. + * @param expression the expression. + * @param subFlow the subflow. + * @return the router spec. + */ + public RecipientListRouterSpec recipientFlow(String expression, IntegrationFlow subFlow) { + return recipientFlow(StringUtils.hasText(expression) ? PARSER.parseExpression(expression) : null, subFlow); + } + + /** + * Adds a subflow that will be invoked if the expression evaluates to 'true'. + * @param expression the expression. + * @param subFlow the subflow. + * @return the router spec. + * @since 1.2 + */ + public RecipientListRouterSpec recipientFlow(Expression expression, IntegrationFlow subFlow) { + Assert.notNull(subFlow); + DirectChannel channel = populateSubFlow(subFlow); + ((DslRecipientListRouter) this.target).add(channel, expression); + return _this(); + } + + private DirectChannel populateSubFlow(IntegrationFlow subFlow) { + DirectChannel channel = new DirectChannel(); + IntegrationFlowBuilder flowBuilder = IntegrationFlows.from(channel); + subFlow.configure(flowBuilder); + this.subFlows.add(flowBuilder.get()); + return channel; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/ResequencerSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/ResequencerSpec.java new file mode 100644 index 0000000000..66360a611f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/ResequencerSpec.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.integration.aggregator.ResequencingMessageGroupProcessor; +import org.springframework.integration.aggregator.ResequencingMessageHandler; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public class ResequencerSpec extends CorrelationHandlerSpec { + + ResequencerSpec() { + super(new ResequencingMessageHandler(new ResequencingMessageGroupProcessor())); + } + + /** + * @param releasePartialSequences the releasePartialSequences + * @return the handler spec. + * @see ResequencingMessageHandler#setReleasePartialSequences(boolean) + */ + public ResequencerSpec releasePartialSequences(boolean releasePartialSequences) { + this.handler.setReleasePartialSequences(releasePartialSequences); + return _this(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/RouterSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/RouterSpec.java new file mode 100644 index 0000000000..815143d160 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/RouterSpec.java @@ -0,0 +1,199 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.context.IntegrationObjectSupport; +import org.springframework.integration.router.AbstractMappingMessageRouter; +import org.springframework.integration.support.context.NamedComponent; +import org.springframework.integration.support.management.MappingMessageRouterManagement; +import org.springframework.messaging.MessagingException; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * The {@link AbstractRouterSpec} for an {@link AbstractMappingMessageRouter}. + * + * @param the key type. + * @param the {@link AbstractMappingMessageRouter} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public final class RouterSpec + extends AbstractRouterSpec, R> + implements ComponentsRegistration { + + private final RouterMappingProvider mappingProvider; + + private String prefix; + + private String suffix; + + RouterSpec(R router) { + super(router); + this.mappingProvider = new RouterMappingProvider(this.target); + } + + /** + * @param resolutionRequired the resolutionRequired. + * @return the router spec. + * @see AbstractMappingMessageRouter#setResolutionRequired(boolean) + */ + public RouterSpec resolutionRequired(boolean resolutionRequired) { + this.target.setResolutionRequired(resolutionRequired); + return _this(); + } + + /** + * Cannot be invoked if {@link #subFlowMapping(Object, IntegrationFlow)} is used. + * @param prefix the prefix. + * @return the router spec. + * @see AbstractMappingMessageRouter#setPrefix(String) + */ + public RouterSpec prefix(String prefix) { + Assert.state(this.subFlows.isEmpty(), "The 'prefix'('suffix') and 'subFlowMapping' are mutually exclusive"); + this.prefix = prefix; + this.target.setPrefix(prefix); + return _this(); + } + + /** + * Cannot be invoked if {@link #subFlowMapping(Object, IntegrationFlow)} is used. + * @param suffix the suffix to set. + * @return the router spec. + * @see AbstractMappingMessageRouter#setSuffix(String) + */ + public RouterSpec suffix(String suffix) { + Assert.state(this.subFlows.isEmpty(), "The 'prefix'('suffix') and 'subFlowMapping' are mutually exclusive"); + this.suffix = suffix; + this.target.setSuffix(suffix); + return _this(); + } + + /** + * @param key the key. + * @param channelName the channelName. + * @return the router spec. + * @see AbstractMappingMessageRouter#setChannelMapping(String, String) + */ + public RouterSpec channelMapping(K key, final String channelName) { + Assert.notNull(key); + Assert.hasText(channelName); + if (key instanceof String) { + this.target.setChannelMapping((String) key, channelName); + } + else { + this.mappingProvider.addMapping(key, new NamedComponent() { + + @Override + public String getComponentName() { + return channelName; + } + + @Override + public String getComponentType() { + return "channel"; + } + + }); + } + return _this(); + } + + /** + * Add a subflow as an alternative to a {@link #channelMapping(Object, String)}. + * {@link #prefix(String)} and {@link #suffix(String)} cannot be used when subflow + * mappings are used. + * @param key the key. + * @param subFlow the subFlow. + * @return the router spec. + */ + public RouterSpec subFlowMapping(K key, IntegrationFlow subFlow) { + Assert.notNull(key); + Assert.notNull(subFlow); + Assert.state(!(StringUtils.hasText(this.prefix) || StringUtils.hasText(this.suffix)), + "The 'prefix'('suffix') and 'subFlowMapping' are mutually exclusive"); + + DirectChannel channel = new DirectChannel(); + IntegrationFlowBuilder flowBuilder = IntegrationFlows.from(channel); + subFlow.configure(flowBuilder); + + this.subFlows.add(flowBuilder); + + this.mappingProvider.addMapping(key, channel); + return _this(); + } + + @Override + public Collection getComponentsToRegister() { + // The 'mappingProvider' must be added to the 'componentToRegister' in the end to + // let all other components to be registered before the 'RouterMappingProvider.onInit()' logic. + this.subFlows.add(this.mappingProvider); + return super.getComponentsToRegister(); + } + + private static class RouterMappingProvider extends IntegrationObjectSupport { + + private final MappingMessageRouterManagement router; + + private final Map mapping = new HashMap(); + + RouterMappingProvider(MappingMessageRouterManagement router) { + this.router = router; + } + + void addMapping(Object key, NamedComponent channel) { + this.mapping.put(key, channel); + } + + @Override + protected void onInit() throws Exception { + ConversionService conversionService = getConversionService(); + if (conversionService == null) { + conversionService = new DefaultConversionService(); + } + for (Map.Entry entry : this.mapping.entrySet()) { + Object key = entry.getKey(); + String channelKey; + if (key instanceof String) { + channelKey = (String) key; + } + else if (key instanceof Class) { + channelKey = ((Class) key).getName(); + } + else if (conversionService.canConvert(key.getClass(), String.class)) { + channelKey = conversionService.convert(key, String.class); + } + else { + throw new MessagingException("unsupported channel mapping type for router [" + key.getClass() + "]"); + } + + this.router.setChannelMapping(channelKey, entry.getValue().getComponentName()); + } + } + + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/ScatterGatherSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/ScatterGatherSpec.java new file mode 100644 index 0000000000..f22f3f0994 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/ScatterGatherSpec.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.integration.scattergather.ScatterGatherHandler; +import org.springframework.messaging.MessageChannel; + +/** + * A {@link GenericEndpointSpec} extension for the {@link ScatterGatherHandler}. + * + * @author Artem Bilan + * + * @since 5.0 + * + * @see ScatterGatherHandler + */ +public class ScatterGatherSpec extends ConsumerEndpointSpec { + + ScatterGatherSpec(ScatterGatherHandler messageHandler) { + super(messageHandler); + } + + /** + * Specify a {@link MessageChannel} (optional) which is used internally + * in the {@link ScatterGatherHandler} for gathering (aggregate) results for scattered requests. + * @param gatherChannel the {@link MessageChannel} for gathering results. + * @return the current {@link ScatterGatherSpec} instance. + */ + public ScatterGatherSpec gatherChannel(MessageChannel gatherChannel) { + this.handler.setGatherChannel(gatherChannel); + return this; + } + + /** + * Specify a timeout (in milliseconds) for the + * {@link org.springframework.messaging.PollableChannel#receive(long)} operation + * to wait for gathering results to output. + * Defaults to {@code -1} - to wait indefinitely. + * @param gatherTimeout the {@link org.springframework.messaging.PollableChannel} receive timeout. + * @return the current {@link ScatterGatherSpec} instance. + */ + public ScatterGatherSpec gatherTimeout(long gatherTimeout) { + this.handler.setGatherTimeout(gatherTimeout); + return this; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/SourcePollingChannelAdapterSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/SourcePollingChannelAdapterSpec.java new file mode 100644 index 0000000000..1cc67d0d7d --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/SourcePollingChannelAdapterSpec.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.scheduling.PollerMetadata; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public final class SourcePollingChannelAdapterSpec extends + EndpointSpec> { + + SourcePollingChannelAdapterSpec(MessageSource messageSource) { + super(messageSource); + this.endpointFactoryBean.setSource(messageSource); + } + + public SourcePollingChannelAdapterSpec phase(int phase) { + this.endpointFactoryBean.setPhase(phase); + return _this(); + } + + public SourcePollingChannelAdapterSpec autoStartup(boolean autoStartup) { + this.endpointFactoryBean.setAutoStartup(autoStartup); + return _this(); + } + + public SourcePollingChannelAdapterSpec poller(PollerMetadata pollerMetadata) { + if (pollerMetadata != null) { + if (PollerMetadata.MAX_MESSAGES_UNBOUNDED == pollerMetadata.getMaxMessagesPerPoll()) { + pollerMetadata.setMaxMessagesPerPoll(1); + } + this.endpointFactoryBean.setPollerMetadata(pollerMetadata); + } + return _this(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/SplitterEndpointSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/SplitterEndpointSpec.java new file mode 100644 index 0000000000..aeaccb0167 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/SplitterEndpointSpec.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import org.springframework.integration.splitter.AbstractMessageSplitter; +import org.springframework.integration.splitter.DefaultMessageSplitter; + +/** + * A {@link ConsumerEndpointSpec} for a {@link AbstractMessageSplitter} implementations. + * + * @param the target {@link SplitterEndpointSpec} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public final class SplitterEndpointSpec + extends ConsumerEndpointSpec, S> { + + SplitterEndpointSpec(S splitter) { + super(splitter); + } + + /** + * Set the applySequence flag to the specified value. Defaults to {@code true}. + * @param applySequence the applySequence. + * @return the endpoint spec. + * @see AbstractMessageSplitter#setApplySequence(boolean) + */ + public SplitterEndpointSpec applySequence(boolean applySequence) { + this.handler.setApplySequence(applySequence); + return _this(); + } + + /** + * Set delimiters to tokenize String values. The default is + * null indicating that no tokenizing should occur. + * If delimiters are provided, they will be applied to any String payload. + * Only applied if provided {@code splitter} is instance of {@link DefaultMessageSplitter}. + * @param delimiters The delimiters. + * @return the endpoint spec. + * @see DefaultMessageSplitter#setDelimiters(String) + */ + public SplitterEndpointSpec delimiters(String delimiters) { + if (this.handler instanceof DefaultMessageSplitter) { + ((DefaultMessageSplitter) this.handler).setDelimiters(delimiters); + } + else { + logger.warn("'delimiters' can be applied only for the DefaultMessageSplitter"); + } + return this; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/StandardIntegrationFlow.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/StandardIntegrationFlow.java new file mode 100644 index 0000000000..1ea637c53c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/StandardIntegrationFlow.java @@ -0,0 +1,150 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.springframework.context.SmartLifecycle; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public class StandardIntegrationFlow implements IntegrationFlow, SmartLifecycle { + + private final List integrationComponents; + + private final List lifecycles = new LinkedList(); + + private final boolean registerComponents = true; + + private boolean running; + + StandardIntegrationFlow(Set integrationComponents) { + this.integrationComponents = new LinkedList(integrationComponents); + } + + //TODO Figure out some custom DestinationResolver when we don't register singletons + /*public void setRegisterComponents(boolean registerComponents) { + this.registerComponents = registerComponents; + }*/ + + public boolean isRegisterComponents() { + return this.registerComponents; + } + + public void setIntegrationComponents(List integrationComponents) { + this.integrationComponents.clear(); + this.integrationComponents.addAll(integrationComponents); + } + + public List getIntegrationComponents() { + return this.integrationComponents; + } + + @Override + public void configure(IntegrationFlowDefinition flow) { + throw new UnsupportedOperationException(); + } + + @Override + public void start() { + if (!this.running) { + ListIterator iterator = this.integrationComponents.listIterator(this.integrationComponents.size()); + this.lifecycles.clear(); + while (iterator.hasPrevious()) { + Object component = iterator.previous(); + if (component instanceof SmartLifecycle) { + this.lifecycles.add((SmartLifecycle) component); + ((SmartLifecycle) component).start(); + } + } + this.running = true; + } + } + + @Override + public void stop(Runnable callback) { + if (this.running) { + AggregatingCallback aggregatingCallback = new AggregatingCallback(this.lifecycles.size(), callback); + ListIterator iterator = this.lifecycles.listIterator(this.lifecycles.size()); + while (iterator.hasPrevious()) { + SmartLifecycle lifecycle = iterator.previous(); + if (lifecycle.isRunning()) { + lifecycle.stop(aggregatingCallback); + } + else { + aggregatingCallback.run(); + } + } + this.running = false; + } + } + + @Override + public void stop() { + if (this.running) { + ListIterator iterator = this.lifecycles.listIterator(this.lifecycles.size()); + while (iterator.hasPrevious()) { + iterator.previous().stop(); + } + this.running = false; + } + } + + @Override + public boolean isRunning() { + return this.running; + } + + @Override + public boolean isAutoStartup() { + return false; + } + + @Override + public int getPhase() { + return 0; + } + + private static final class AggregatingCallback implements Runnable { + + private final AtomicInteger count; + + private final Runnable finishCallback; + + AggregatingCallback(int count, Runnable finishCallback) { + this.count = new AtomicInteger(count); + this.finishCallback = finishCallback; + } + + @Override + public void run() { + if (this.count.decrementAndGet() <= 0) { + this.finishCallback.run(); + } + } + + } + + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java new file mode 100644 index 0000000000..69642a0a3d --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java @@ -0,0 +1,234 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import java.util.function.Function; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.serializer.Deserializer; +import org.springframework.core.serializer.Serializer; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.integration.codec.Codec; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.json.JsonToObjectTransformer; +import org.springframework.integration.json.ObjectToJsonTransformer; +import org.springframework.integration.support.json.JsonObjectMapper; +import org.springframework.integration.transformer.DecodingTransformer; +import org.springframework.integration.transformer.EncodingPayloadTransformer; +import org.springframework.integration.transformer.MapToObjectTransformer; +import org.springframework.integration.transformer.ObjectToMapTransformer; +import org.springframework.integration.transformer.ObjectToStringTransformer; +import org.springframework.integration.transformer.PayloadDeserializingTransformer; +import org.springframework.integration.transformer.PayloadSerializingTransformer; +import org.springframework.integration.transformer.PayloadTypeConvertingTransformer; +import org.springframework.integration.transformer.SyslogToMapTransformer; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; + +/** + * An utility class to provide methods for out-of-the-box + * {@link org.springframework.integration.transformer.Transformer}s. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class Transformers { + + private final static SpelExpressionParser PARSER = new SpelExpressionParser(); + + public static ObjectToStringTransformer objectToString() { + return objectToString(null); + } + + public static ObjectToStringTransformer objectToString(String charset) { + return charset != null ? new ObjectToStringTransformer(charset) : new ObjectToStringTransformer(); + } + + public static ObjectToMapTransformer toMap() { + return new ObjectToMapTransformer(); + } + + public static ObjectToMapTransformer toMap(boolean shouldFlattenKeys) { + ObjectToMapTransformer transformer = new ObjectToMapTransformer(); + transformer.setShouldFlattenKeys(shouldFlattenKeys); + return transformer; + } + + public static MapToObjectTransformer fromMap(Class targetClass) { + return new MapToObjectTransformer(targetClass); + } + + public static MapToObjectTransformer fromMap(String beanName) { + return new MapToObjectTransformer(beanName); + } + + public static ObjectToJsonTransformer toJson() { + return toJson(null, null, null); + } + + public static ObjectToJsonTransformer toJson(JsonObjectMapper jsonObjectMapper) { + return toJson(jsonObjectMapper, null, null); + } + + public static ObjectToJsonTransformer toJson(JsonObjectMapper jsonObjectMapper, + ObjectToJsonTransformer.ResultType resultType) { + return toJson(jsonObjectMapper, resultType, null); + } + + public static ObjectToJsonTransformer toJson(String contentType) { + return toJson(null, null, contentType); + } + + public static ObjectToJsonTransformer toJson(JsonObjectMapper jsonObjectMapper, String contentType) { + return toJson(jsonObjectMapper, null, contentType); + } + + public static ObjectToJsonTransformer toJson(ObjectToJsonTransformer.ResultType resultType, String contentType) { + return toJson(null, resultType, contentType); + } + + public static ObjectToJsonTransformer toJson(JsonObjectMapper jsonObjectMapper, + ObjectToJsonTransformer.ResultType resultType, String contentType) { + ObjectToJsonTransformer transformer; + if (jsonObjectMapper != null) { + if (resultType != null) { + transformer = new ObjectToJsonTransformer(jsonObjectMapper, resultType); + } + else { + transformer = new ObjectToJsonTransformer(jsonObjectMapper); + } + } + else if (resultType != null) { + transformer = new ObjectToJsonTransformer(resultType); + } + else { + transformer = new ObjectToJsonTransformer(); + } + if (contentType != null) { + transformer.setContentType(contentType); + } + return transformer; + } + + public static JsonToObjectTransformer fromJson() { + return fromJson(null, null); + } + + public static JsonToObjectTransformer fromJson(Class targetClass) { + return fromJson(targetClass, null); + } + + public static JsonToObjectTransformer fromJson(JsonObjectMapper jsonObjectMapper) { + return fromJson(null, jsonObjectMapper); + } + + public static JsonToObjectTransformer fromJson(Class targetClass, JsonObjectMapper jsonObjectMapper) { + return new JsonToObjectTransformer(targetClass, jsonObjectMapper); + } + + public static PayloadSerializingTransformer serializer() { + return serializer(null); + } + + public static PayloadSerializingTransformer serializer(Serializer serializer) { + PayloadSerializingTransformer transformer = new PayloadSerializingTransformer(); + if (serializer != null) { + transformer.setSerializer(serializer); + } + return transformer; + } + + public static PayloadDeserializingTransformer deserializer() { + return deserializer(null); + } + + public static PayloadDeserializingTransformer deserializer(Deserializer deserializer) { + PayloadDeserializingTransformer transformer = new PayloadDeserializingTransformer(); + if (deserializer != null) { + transformer.setDeserializer(deserializer); + } + return transformer; + } + + public static PayloadTypeConvertingTransformer converter(Converter converter) { + Assert.notNull(converter, "The Converter is required for the PayloadTypeConvertingTransformer"); + PayloadTypeConvertingTransformer transformer = new PayloadTypeConvertingTransformer<>(); + transformer.setConverter(converter); + return transformer; + } + + public static SyslogToMapTransformer syslogToMap() { + return new SyslogToMapTransformer(); + } + + /** + * The factory method for the {@link EncodingPayloadTransformer}. + * @param codec the {@link Codec} to use. + * @param the {@code payload} type. + * @return the {@link EncodingPayloadTransformer} instance. + */ + public static EncodingPayloadTransformer encoding(Codec codec) { + return new EncodingPayloadTransformer<>(codec); + } + + /** + * The factory method for the {@link DecodingTransformer}. + * @param codec the {@link Codec} to use. + * @param type the target type to transform to. + * @param the target type. + * @return the {@link DecodingTransformer} instance. + */ + public static DecodingTransformer decoding(Codec codec, Class type) { + return new DecodingTransformer<>(codec, type); + } + + /** + * The factory method for the {@link DecodingTransformer}. + * @param codec the {@link Codec} to use. + * @param typeExpression the target type SpEL expression. + * @param the target type. + * @return the {@link DecodingTransformer} instance. + */ + public static DecodingTransformer decoding(Codec codec, String typeExpression) { + return decoding(codec, PARSER.parseExpression(typeExpression)); + } + + /** + * The factory method for the {@link DecodingTransformer}. + * @param codec the {@link Codec} to use. + * @param typeFunction the target type function. + * @param the target type. + * @return the {@link DecodingTransformer} instance. + */ + public static DecodingTransformer decoding(Codec codec, Function, Class> typeFunction) { + return decoding(codec, new FunctionExpression<>(typeFunction)); + } + + /** + * The factory method for the {@link DecodingTransformer}. + * @param codec the {@link Codec} to use. + * @param typeExpression the target type SpEL expression. + * @param the target type. + * @return the {@link DecodingTransformer} instance. + */ + public static DecodingTransformer decoding(Codec codec, Expression typeExpression) { + return new DecodingTransformer<>(codec, typeExpression); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/DirectChannelSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/DirectChannelSpec.java new file mode 100644 index 0000000000..31ba66d923 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/DirectChannelSpec.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.channel; + +import org.springframework.integration.channel.DirectChannel; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public class DirectChannelSpec extends LoadBalancingChannelSpec { + + @Override + protected DirectChannel doGet() { + this.channel = new DirectChannel(this.loadBalancingStrategy); + if (this.failover != null) { + this.channel.setFailover(this.failover); + } + if (this.maxSubscribers != null) { + this.channel.setMaxSubscribers(this.maxSubscribers); + } + return super.doGet(); + } + + DirectChannelSpec() { + super(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/ExecutorChannelSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/ExecutorChannelSpec.java new file mode 100644 index 0000000000..8cf58d308b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/ExecutorChannelSpec.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.channel; + +import java.util.concurrent.Executor; + +import org.springframework.integration.channel.ExecutorChannel; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public class ExecutorChannelSpec extends LoadBalancingChannelSpec { + + private final Executor executor; + + ExecutorChannelSpec(Executor executor) { + this.executor = executor; + } + + @Override + protected ExecutorChannel doGet() { + this.channel = new ExecutorChannel(this.executor, this.loadBalancingStrategy); + if (this.failover != null) { + this.channel.setFailover(this.failover); + } + if (this.maxSubscribers != null) { + this.channel.setMaxSubscribers(this.maxSubscribers); + } + return super.doGet(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/LoadBalancingChannelSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/LoadBalancingChannelSpec.java new file mode 100644 index 0000000000..a9106f1f4f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/LoadBalancingChannelSpec.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.channel; + +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.dispatcher.LoadBalancingStrategy; +import org.springframework.integration.dispatcher.RoundRobinLoadBalancingStrategy; + +/** + * + * @param the target {@link LoadBalancingChannelSpec} implementation type. + * @param the target {@link AbstractMessageChannel} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class LoadBalancingChannelSpec, C extends AbstractMessageChannel> + extends MessageChannelSpec { + + protected LoadBalancingStrategy loadBalancingStrategy = new RoundRobinLoadBalancingStrategy(); + + protected Boolean failover; + + protected Integer maxSubscribers; + + protected LoadBalancingChannelSpec() { + super(); + } + + public S loadBalancer(LoadBalancingStrategy loadBalancingStrategy) { + this.loadBalancingStrategy = loadBalancingStrategy; + return _this(); + } + + public S failover(Boolean failover) { + this.failover = failover; + return _this(); + } + + public S maxSubscribers(Integer maxSubscribers) { + this.maxSubscribers = maxSubscribers; + return _this(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/MessageChannelSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/MessageChannelSpec.java new file mode 100644 index 0000000000..8efc0a38dc --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/MessageChannelSpec.java @@ -0,0 +1,132 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.channel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.channel.interceptor.WireTap; +import org.springframework.integration.dsl.ComponentsRegistration; +import org.springframework.integration.dsl.IntegrationComponentSpec; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.converter.MessageConverter; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.util.Assert; + +/** + * + * @param the target {@link MessageChannelSpec} implementation type. + * @param the target {@link AbstractMessageChannel} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class MessageChannelSpec, C extends AbstractMessageChannel> + extends IntegrationComponentSpec + implements ComponentsRegistration { + + private final List componentsToRegister = new ArrayList<>(); + + private final List> datatypes = new ArrayList<>(); + + private final List interceptors = new LinkedList<>(); + + protected C channel; + + private MessageConverter messageConverter; + + protected MessageChannelSpec() { + super(); + } + + @Override + protected S id(String id) { + return super.id(id); + } + + public S datatype(Class... datatypes) { + Assert.noNullElements(datatypes); + this.datatypes.addAll(Arrays.asList(datatypes)); + return _this(); + } + + public S interceptor(ChannelInterceptor... interceptors) { + Assert.noNullElements(interceptors); + this.interceptors.addAll(Arrays.asList(interceptors)); + return _this(); + } + + /** + * Populate the {@code Wire Tap} EI Pattern specific + * {@link org.springframework.messaging.support.ChannelInterceptor} implementation. + * @param wireTapChannel the {@link MessageChannel} bean name to wire-tap. + * @return the current {@link MessageChannelSpec}. + * @see WireTapSpec + */ + public S wireTap(String wireTapChannel) { + return wireTap(new WireTapSpec(wireTapChannel)); + } + + /** + * Populate the {@code Wire Tap} EI Pattern specific + * {@link org.springframework.messaging.support.ChannelInterceptor} implementation. + * @param wireTapChannel the {@link MessageChannel} instance to wire-tap. + * @return the current {@link MessageChannelSpec}. + * @see WireTapSpec + */ + public S wireTap(MessageChannel wireTapChannel) { + return wireTap(new WireTapSpec(wireTapChannel)); + } + + /** + * Populate the {@code Wire Tap} EI Pattern specific + * {@link org.springframework.messaging.support.ChannelInterceptor} implementation. + * @param wireTapSpec the {@link WireTapSpec} to build {@link WireTap} instance. + * @return the current {@link MessageChannelSpec}. + * @see WireTap + */ + public S wireTap(WireTapSpec wireTapSpec) { + WireTap interceptor = wireTapSpec.get(); + this.componentsToRegister.add(interceptor); + return interceptor(interceptor); + } + + public S messageConverter(MessageConverter messageConverter) { + this.messageConverter = messageConverter; + return _this(); + } + + @Override + public Collection getComponentsToRegister() { + return this.componentsToRegister; + } + + @Override + protected C doGet() { + this.channel.setDatatypes(this.datatypes.toArray(new Class[this.datatypes.size()])); + this.channel.setBeanName(getId()); + this.channel.setInterceptors(this.interceptors); + this.channel.setMessageConverter(this.messageConverter); + return this.channel; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/MessageChannels.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/MessageChannels.java new file mode 100644 index 0000000000..611ac4376f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/MessageChannels.java @@ -0,0 +1,130 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.channel; + +import java.util.Queue; +import java.util.concurrent.Executor; + +import org.springframework.integration.store.ChannelMessageStore; +import org.springframework.integration.store.PriorityCapableChannelMessageStore; +import org.springframework.messaging.Message; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public final class MessageChannels { + + public static DirectChannelSpec direct() { + return new DirectChannelSpec(); + } + + public static DirectChannelSpec direct(String id) { + return direct().id(id); + } + + public static QueueChannelSpec queue() { + return new QueueChannelSpec(); + } + + public static QueueChannelSpec queue(String id) { + return queue().id(id); + } + + public static QueueChannelSpec queue(Queue> queue) { + return new QueueChannelSpec(queue); + } + + public static QueueChannelSpec queue(String id, Queue> queue) { + return queue(queue).id(id); + } + + public static QueueChannelSpec queue(Integer capacity) { + return new QueueChannelSpec(capacity); + } + + public static QueueChannelSpec queue(String id, Integer capacity) { + return queue(capacity).id(id); + } + + public static QueueChannelSpec.MessageStoreSpec queue(ChannelMessageStore messageGroupStore, Object groupId) { + return new QueueChannelSpec.MessageStoreSpec(messageGroupStore, groupId); + } + + public static QueueChannelSpec.MessageStoreSpec queue(String id, ChannelMessageStore messageGroupStore, + Object groupId) { + return queue(messageGroupStore, groupId).id(id); + } + + public static ExecutorChannelSpec executor(Executor executor) { + return new ExecutorChannelSpec(executor); + } + + public static ExecutorChannelSpec executor(String id, Executor executor) { + return executor(executor).id(id); + } + + public static RendezvousChannelSpec rendezvous() { + return new RendezvousChannelSpec(); + } + + public static RendezvousChannelSpec rendezvous(String id) { + return rendezvous().id(id); + } + + public static PriorityChannelSpec priority() { + return new PriorityChannelSpec(); + } + + public static PriorityChannelSpec priority(String id) { + return priority().id(id); + } + + public static QueueChannelSpec.MessageStoreSpec priority(PriorityCapableChannelMessageStore messageGroupStore, + Object groupId) { + return new QueueChannelSpec.MessageStoreSpec(messageGroupStore, groupId); + } + + public static QueueChannelSpec.MessageStoreSpec priority(String id, + PriorityCapableChannelMessageStore messageGroupStore, Object groupId) { + return queue(messageGroupStore, groupId).id(id); + } + + public static > PublishSubscribeChannelSpec publishSubscribe() { + return new PublishSubscribeChannelSpec(); + } + + public static > PublishSubscribeChannelSpec publishSubscribe( + String id) { + return MessageChannels.publishSubscribe().id(id); + } + + public static > PublishSubscribeChannelSpec publishSubscribe( + Executor executor) { + return new PublishSubscribeChannelSpec(executor); + } + + public static > PublishSubscribeChannelSpec publishSubscribe(String id, + Executor executor) { + return MessageChannels.publishSubscribe(executor).id(id); + } + + private MessageChannels() { + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/PriorityChannelSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/PriorityChannelSpec.java new file mode 100644 index 0000000000..5aeb49dea0 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/PriorityChannelSpec.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.channel; + +import java.util.Comparator; + +import org.springframework.integration.channel.PriorityChannel; +import org.springframework.messaging.Message; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public class PriorityChannelSpec extends MessageChannelSpec { + + private int capacity; + + private Comparator> comparator; + + public PriorityChannelSpec setCapacity(int capacity) { + this.capacity = capacity; + return this; + } + + public PriorityChannelSpec setComparator(Comparator> comparator) { + this.comparator = comparator; + return this; + } + + @Override + protected PriorityChannel doGet() { + this.channel = new PriorityChannel(this.capacity, this.comparator); + return super.doGet(); + } + + + PriorityChannelSpec() { + super(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/PublishSubscribeChannelSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/PublishSubscribeChannelSpec.java new file mode 100644 index 0000000000..54ed8acc52 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/PublishSubscribeChannelSpec.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.channel; + +import java.util.concurrent.Executor; + +import org.springframework.integration.channel.PublishSubscribeChannel; +import org.springframework.util.ErrorHandler; + +/** + * + * @param the target {@link PublishSubscribeChannelSpec} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public class PublishSubscribeChannelSpec> + extends MessageChannelSpec { + + protected PublishSubscribeChannelSpec() { + this.channel = new PublishSubscribeChannel(); + } + + protected PublishSubscribeChannelSpec(Executor executor) { + this.channel = new PublishSubscribeChannel(executor); + } + + public S errorHandler(ErrorHandler errorHandler) { + this.channel.setErrorHandler(errorHandler); + return _this(); + } + + public S ignoreFailures(boolean ignoreFailures) { + this.channel.setIgnoreFailures(ignoreFailures); + return _this(); + } + + public S applySequence(boolean applySequence) { + this.channel.setApplySequence(applySequence); + return _this(); + } + + public S maxSubscribers(Integer maxSubscribers) { + this.channel.setMaxSubscribers(maxSubscribers); + return _this(); + } + + public S minSubscribers(int minSubscribers) { + this.channel.setMinSubscribers(minSubscribers); + return _this(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/QueueChannelSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/QueueChannelSpec.java new file mode 100644 index 0000000000..58c3e55475 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/QueueChannelSpec.java @@ -0,0 +1,124 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.channel; + +import java.util.Queue; +import java.util.concurrent.locks.Lock; + +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.store.ChannelMessageStore; +import org.springframework.integration.store.MessageGroupQueue; +import org.springframework.integration.store.PriorityCapableChannelMessageStore; +import org.springframework.messaging.Message; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public class QueueChannelSpec extends MessageChannelSpec { + + protected Queue> queue; + + protected Integer capacity; + + QueueChannelSpec() { + super(); + } + + QueueChannelSpec(Queue> queue) { + this.queue = queue; + } + + QueueChannelSpec(Integer capacity) { + this.capacity = capacity; + } + + @Override + protected QueueChannel doGet() { + if (this.queue != null) { + this.channel = new QueueChannel(this.queue); + } + else if (this.capacity != null) { + this.channel = new QueueChannel(this.capacity); + } + else { + this.channel = new QueueChannel(); + } + return super.doGet(); + } + + /** + * The {@link ChannelMessageStore}-specific {@link QueueChannelSpec} extension. + */ + public static class MessageStoreSpec extends QueueChannelSpec { + + private final ChannelMessageStore messageGroupStore; + + private final Object groupId; + + private Lock storeLock; + + MessageStoreSpec(ChannelMessageStore messageGroupStore, Object groupId) { + super(); + this.messageGroupStore = messageGroupStore; + this.groupId = groupId; + } + + @Override + protected MessageStoreSpec id(String id) { + return (MessageStoreSpec) super.id(id); + } + + + public MessageStoreSpec capacity(Integer capacity) { + this.capacity = capacity; + return this; + } + + public MessageStoreSpec storeLock(Lock storeLock) { + this.storeLock = storeLock; + return this; + } + + @Override + protected QueueChannel doGet() { + if (this.capacity != null) { + if (this.storeLock != null) { + this.queue = new MessageGroupQueue(this.messageGroupStore, this.groupId, this.capacity, + this.storeLock); + } + else { + this.queue = new MessageGroupQueue(this.messageGroupStore, this.groupId, this.capacity); + } + } + else if (this.storeLock != null) { + this.queue = new MessageGroupQueue(this.messageGroupStore, this.groupId, this.storeLock); + } + else { + this.queue = new MessageGroupQueue(this.messageGroupStore, this.groupId); + } + + ((MessageGroupQueue) this.queue).setPriority( + this.messageGroupStore instanceof PriorityCapableChannelMessageStore); + + return super.doGet(); + } + + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/RendezvousChannelSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/RendezvousChannelSpec.java new file mode 100644 index 0000000000..da15764465 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/RendezvousChannelSpec.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.channel; + +import org.springframework.integration.channel.RendezvousChannel; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +public class RendezvousChannelSpec extends MessageChannelSpec { + + RendezvousChannelSpec() { + this.channel = new RendezvousChannel(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/WireTapSpec.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/WireTapSpec.java new file mode 100644 index 0000000000..7d13fda9ba --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/WireTapSpec.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.channel; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.springframework.expression.Expression; +import org.springframework.integration.channel.interceptor.WireTap; +import org.springframework.integration.core.MessageSelector; +import org.springframework.integration.dsl.ComponentsRegistration; +import org.springframework.integration.dsl.IntegrationComponentSpec; +import org.springframework.integration.filter.ExpressionEvaluatingSelector; +import org.springframework.messaging.MessageChannel; +import org.springframework.util.Assert; + +/** + * The {@link IntegrationComponentSpec} implementation for the {@link WireTap} component. + * + * @author Gary Russell + * @author Artem Bilan + * + * @since 5.0 + * + */ +public class WireTapSpec extends IntegrationComponentSpec implements ComponentsRegistration { + + private final MessageChannel channel; + + private final String channelName; + + private MessageSelector selector; + + private Long timeout; + + public WireTapSpec(MessageChannel channel) { + Assert.notNull(channel, "'channel' must not be null"); + this.channel = channel; + this.channelName = null; + } + + public WireTapSpec(String channelName) { + Assert.notNull(channelName, "'channelName' must not be null"); + this.channelName = channelName; + this.channel = null; + } + + public WireTapSpec selector(String selectorExpression) { + return selector(new ExpressionEvaluatingSelector(selectorExpression)); + } + + /** + * Specify an {@link Expression} for selector. + * @param selectorExpression the expression for selector. + * @return the current {@link WireTapSpec} + * @since 1.2 + * @see WireTap#WireTap(MessageChannel, MessageSelector) + */ + public WireTapSpec selector(Expression selectorExpression) { + return selector(new ExpressionEvaluatingSelector(selectorExpression)); + } + + public WireTapSpec selector(MessageSelector selector) { + this.selector = selector; + return this; + } + + public WireTapSpec timeout(long timeout) { + this.timeout = timeout; + return this; + } + + @Override + protected WireTap doGet() { + WireTap wireTap; + if (this.channel != null) { + wireTap = new WireTap(this.channel, this.selector); + } + else { + wireTap = new WireTap(this.channelName, this.selector); + } + + if (this.timeout != null) { + wireTap.setTimeout(this.timeout); + } + return wireTap; + } + + @Override + public Collection getComponentsToRegister() { + if (this.selector != null) { + return Arrays.asList(this.selector, this.target); + } + else { + return Collections.singletonList(this.target); + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/package-info.java new file mode 100644 index 0000000000..4ed59bd904 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/channel/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains MessageChannel Builders DSL. + */ +package org.springframework.integration.dsl.channel; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowContext.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowContext.java new file mode 100644 index 0000000000..8a9d7d695f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowContext.java @@ -0,0 +1,227 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.context; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.support.context.NamedComponent; +import org.springframework.messaging.MessageChannel; +import org.springframework.util.Assert; + +/** + * A public API for dynamic (manual) registration of {@link IntegrationFlow}, + * not via standard bean registration phase. + *

+ * The bean of this component is provided via framework automatically. + * A bean name is based on the decapitalized class name. + * It must be injected to the target service before use. + *

+ * The typical use-case, and, therefore algorithm, is: + *

    + *
  • create {@link IntegrationFlow} depending of the business logic + *
  • register that {@link IntegrationFlow} in this {@link IntegrationFlowContext}, + * with optional {@code id} and {@code autoStartup} flag + *
  • obtain a {@link MessagingTemplate} for that {@link IntegrationFlow} + * (if it is started from the {@link MessageChannel}) and send (or send-and-receive) + * messages to the {@link IntegrationFlow} + *
  • remove the {@link IntegrationFlow} by its {@code id} from this {@link IntegrationFlowContext} + *
+ *

+ * For convenience an associated {@link IntegrationFlowRegistration} is returned after registration. + * It can be used for access to the target {@link IntegrationFlow} or for manipulation with its lifecycle. + * + * @author Artem Bilan + * + * @since 5.0 + * + * @see IntegrationFlowRegistration + */ +public final class IntegrationFlowContext implements BeanFactoryAware { + + private final Map registry = new HashMap<>(); + + private ConfigurableListableBeanFactory beanFactory; + + private AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor; + + private IntegrationFlowContext() { + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory, + "To use Spring Integration Java DSL the 'beanFactory' has to be an instance of " + + "'ConfigurableListableBeanFactory'. " + + "Consider using 'GenericApplicationContext' implementation."); + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + this.autowiredAnnotationBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor(); + this.autowiredAnnotationBeanPostProcessor.setBeanFactory(this.beanFactory); + } + + /** + * Associate provided {@link IntegrationFlow} with an {@link IntegrationFlowRegistrationBuilder} + * for additional options and farther registration in the application context. + * @param integrationFlow the {@link IntegrationFlow} to register + * @return the IntegrationFlowRegistrationBuilder associated with the provided {@link IntegrationFlow} + */ + public IntegrationFlowRegistrationBuilder registration(IntegrationFlow integrationFlow) { + return new IntegrationFlowRegistrationBuilder(integrationFlow); + } + + private void register(IntegrationFlowRegistrationBuilder builder) { + IntegrationFlow integrationFlow = builder.integrationFlowRegistration.getIntegrationFlow(); + String flowId = builder.integrationFlowRegistration.getId(); + if (flowId == null) { + flowId = generateBeanName(integrationFlow, null); + builder.id(flowId); + } + IntegrationFlow theFlow = (IntegrationFlow) registerBean(integrationFlow, flowId, null); + builder.integrationFlowRegistration.setIntegrationFlow(theFlow); + + final String theFlowId = flowId; + builder.additionalBeans.forEach((key, value) -> registerBean(key, value, theFlowId)); + + if (builder.autoStartup) { + builder.integrationFlowRegistration.start(); + } + this.registry.put(flowId, builder.integrationFlowRegistration); + } + + private Object registerBean(Object bean, String beanName, String parentName) { + if (beanName == null) { + beanName = generateBeanName(bean, parentName); + } + + this.autowiredAnnotationBeanPostProcessor.processInjection(bean); + bean = this.beanFactory.initializeBean(bean, beanName); + this.beanFactory.registerSingleton(beanName, bean); + if (parentName != null) { + this.beanFactory.registerDependentBean(parentName, beanName); + } + return bean; + } + + /** + * Obtain an {@link IntegrationFlowRegistration} for the {@link IntegrationFlow} + * associated with the provided {@code flowId}. + * @param flowId the bean name to obtain + * @return the IntegrationFlowRegistration for provided {@code id} or {@code null} + */ + public IntegrationFlowRegistration getRegistrationById(String flowId) { + return this.registry.get(flowId); + } + + /** + * Destroy an {@link IntegrationFlow} bean (as well as all its dependant beans) + * for provided {@code flowId} and clean up all the local cache for it. + * @param flowId the bean name to destroy from + */ + public synchronized void remove(String flowId) { + if (this.registry.containsKey(flowId)) { + IntegrationFlowRegistration flowRegistration = this.registry.remove(flowId); + flowRegistration.stop(); + ((DefaultSingletonBeanRegistry) this.beanFactory).destroySingleton(flowId); + } + else { + throw new IllegalStateException("Only manually registered IntegrationFlows can be removed. " + + "But [" + flowId + "] ins't one of them."); + } + } + + /** + * Obtain a {@link MessagingTemplate} with its default destination set to the input channel + * of the {@link IntegrationFlow} for provided {@code flowId}. + *

Any {@link IntegrationFlow} bean (not only manually registered) can be used for this method. + *

If {@link IntegrationFlow} doesn't start with the {@link MessageChannel}, the + * {@link IllegalStateException} is thrown. + * @param flowId the bean name to obtain the input channel from + * @return the {@link MessagingTemplate} instance + */ + public MessagingTemplate messagingTemplateFor(String flowId) { + return this.registry.get(flowId) + .getMessagingTemplate(); + } + + private String generateBeanName(Object instance, String parentName) { + if (instance instanceof NamedComponent && ((NamedComponent) instance).getComponentName() != null) { + return ((NamedComponent) instance).getComponentName(); + } + String generatedBeanName = (parentName != null ? parentName : "") + instance.getClass().getName(); + String id = generatedBeanName; + int counter = -1; + while (counter == -1 || this.beanFactory.containsBean(id)) { + counter++; + id = generatedBeanName + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + counter; + } + return id; + } + + /** + * A Builder pattern implementation for the options to register {@link IntegrationFlow} + * in the application context. + */ + public final class IntegrationFlowRegistrationBuilder { + + private Map additionalBeans = new HashMap(); + + private final IntegrationFlowRegistration integrationFlowRegistration; + + private boolean autoStartup = true; + + IntegrationFlowRegistrationBuilder(IntegrationFlow integrationFlow) { + this.integrationFlowRegistration = new IntegrationFlowRegistration(integrationFlow); + this.integrationFlowRegistration.setBeanFactory(IntegrationFlowContext.this.beanFactory); + this.integrationFlowRegistration.setIntegrationFlowContext(IntegrationFlowContext.this); + } + + public IntegrationFlowRegistrationBuilder id(String id) { + this.integrationFlowRegistration.setId(id); + return this; + } + + public IntegrationFlowRegistrationBuilder autoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + return this; + } + + public IntegrationFlowRegistrationBuilder addBean(Object bean) { + return addBean(null, bean); + } + + public IntegrationFlowRegistrationBuilder addBean(String name, Object bean) { + this.additionalBeans.put(bean, name); + return this; + } + + public IntegrationFlowRegistration register() { + IntegrationFlowContext.this.register(this); + return this.integrationFlowRegistration; + } + + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowRegistration.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowRegistration.java new file mode 100644 index 0000000000..4a4406a568 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowRegistration.java @@ -0,0 +1,167 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.context; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.Lifecycle; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.StandardIntegrationFlow; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; + +/** + * Instances of this classes are returned as a result of + * {@link IntegrationFlowContext#registration(IntegrationFlow)} invocation + * and provide an API for some useful {@link IntegrationFlow} options and its lifecycle. + * + * @author Artem Bilan + * @since 5.0 + * + * @see IntegrationFlowContext + */ +public class IntegrationFlowRegistration { + + private IntegrationFlow integrationFlow; + + private IntegrationFlowContext integrationFlowContext; + + private String id; + + private MessageChannel inputChannel; + + private MessagingTemplate messagingTemplate; + + private ConfigurableListableBeanFactory beanFactory; + + IntegrationFlowRegistration(IntegrationFlow integrationFlow) { + this.integrationFlow = integrationFlow; + } + + void setBeanFactory(ConfigurableListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + void setIntegrationFlowContext(IntegrationFlowContext integrationFlowContext) { + this.integrationFlowContext = integrationFlowContext; + } + + void setId(String id) { + this.id = id; + } + + void setIntegrationFlow(IntegrationFlow integrationFlow) { + this.integrationFlow = integrationFlow; + } + + public String getId() { + return this.id; + } + + public IntegrationFlow getIntegrationFlow() { + return this.integrationFlow; + } + + public MessageChannel getInputChannel() { + if (this.inputChannel == null) { + synchronized (this) { + if (this.inputChannel == null) { + if (this.integrationFlow instanceof StandardIntegrationFlow) { + StandardIntegrationFlow integrationFlow = (StandardIntegrationFlow) this.integrationFlow; + Object next = integrationFlow.getIntegrationComponents().iterator().next(); + if (next instanceof MessageChannel) { + this.inputChannel = (MessageChannel) next; + } + else { + throw new IllegalStateException("The 'IntegrationFlow' [" + integrationFlow + "] " + + "doesn't start with 'MessageChannel' for direct message sending."); + } + } + else { + throw new IllegalStateException("Only 'StandardIntegrationFlow' instances " + + "(e.g. extracted from 'IntegrationFlow' Lambdas) can be used " + + "for direct 'send' operation. " + + "But [" + this.integrationFlow + "] ins't one of them.\n" + + "Consider 'BeanFactory.getBean()' usage for sending messages " + + "to the required 'MessageChannel'."); + } + } + } + } + return this.inputChannel; + } + + /** + * Obtain a {@link MessagingTemplate} with its default destination set to the input channel + * of the {@link IntegrationFlow}. + *

Any {@link IntegrationFlow} bean (not only manually registered) can be used for this method. + *

If {@link IntegrationFlow} doesn't start with the {@link MessageChannel}, the + * {@link IllegalStateException} is thrown. + * @return the {@link MessagingTemplate} instance + */ + public MessagingTemplate getMessagingTemplate() { + if (this.messagingTemplate == null) { + synchronized (this) { + if (this.messagingTemplate == null) { + this.messagingTemplate = new MessagingTemplate(getInputChannel()) { + + @Override + public Message receive() { + return receiveAndConvert(Message.class); + } + + @Override + public T receiveAndConvert(Class targetClass) { + throw new UnsupportedOperationException("The 'receive()/receiveAndConvert()' " + + "isn't supported on the 'IntegrationFlow' input channel."); + } + + }; + this.messagingTemplate.setBeanFactory(this.beanFactory); + } + } + } + return this.messagingTemplate; + } + + public void start() { + if (this.integrationFlow instanceof Lifecycle) { + ((Lifecycle) this.integrationFlow).start(); + } + else { + throw new IllegalStateException("For 'autoStartup' mode the 'IntegrationFlow' " + + "must be an instance of 'Lifecycle'.\n" + + "Consider to implement it for [" + this.integrationFlow + "]. " + + "Or start dependent components on their own."); + } + } + + public void stop() { + if (this.integrationFlow instanceof Lifecycle) { + ((Lifecycle) this.integrationFlow).stop(); + } + } + + /** + * Destroy the {@link IntegrationFlow} bean (as well as all its dependant beans) + * and clean up all the local cache for it. + */ + public void destroy() { + this.integrationFlowContext.remove(this.id); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/package-info.java new file mode 100644 index 0000000000..22ecaef0c3 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/context/package-info.java @@ -0,0 +1,4 @@ +/** + * The context support classes for Spring Integration Java DSL. + */ +package org.springframework.integration.dsl.context; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/package-info.java new file mode 100644 index 0000000000..bcf137dab3 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/package-info.java @@ -0,0 +1,4 @@ +/** + * Root package of the Spring Integration Java DSL. + */ +package org.springframework.integration.dsl; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/support/FixedSubscriberChannelPrototype.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/support/FixedSubscriberChannelPrototype.java new file mode 100644 index 0000000000..7551ae9ecf --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/support/FixedSubscriberChannelPrototype.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.support; + +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; + +/** + * An "artificial" {@link MessageChannel} implementation which will be unwrapped to the + * {@link org.springframework.integration.channel.FixedSubscriberChannel} on the bean + * registration phase. + * For internal use only. + * + * @author Artem Bilan + * @since 5.0 + * + * @see org.springframework.integration.config.dsl.IntegrationFlowBeanPostProcessor + */ +public class FixedSubscriberChannelPrototype implements MessageChannel { + + private final String name; + + public FixedSubscriberChannelPrototype() { + this(null); + } + + public FixedSubscriberChannelPrototype(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + + @Override + public boolean send(Message message) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean send(Message message, long timeout) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return "FixedSubscriberChannelPrototype{" + + "name='" + this.name + '\'' + + '}'; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/support/MessageChannelReference.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/support/MessageChannelReference.java new file mode 100644 index 0000000000..065b105309 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/support/MessageChannelReference.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.support; + +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.util.Assert; + +/** + * An "artificial" {@link MessageChannel} implementation which will be unwrapped to the + * {@link MessageChannel} bean on the bean registration phase. + * For internal use only. + * + * @author Artem Bilan + * + * @since 5.0 + * + * @see org.springframework.integration.config.dsl.IntegrationFlowBeanPostProcessor + */ +public class MessageChannelReference implements MessageChannel { + + private final String name; + + public MessageChannelReference(String name) { + Assert.notNull(name); + this.name = name; + } + + public String getName() { + return this.name; + } + + @Override + public boolean send(Message message) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean send(Message message, long timeout) { + throw new UnsupportedOperationException(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/support/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/support/package-info.java new file mode 100644 index 0000000000..1affe13084 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/support/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides various support classes used across Spring Integration Java DSL Components. + */ +package org.springframework.integration.dsl.support; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MessageProcessorMessageSource.java b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MessageProcessorMessageSource.java new file mode 100644 index 0000000000..84c48dc80b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MessageProcessorMessageSource.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.endpoint; + +import org.springframework.integration.handler.MessageProcessor; + +/** + * The {@link org.springframework.integration.core.MessageSource} strategy implementation + * to produce a {@link org.springframework.messaging.Message} from underlying + * {@linkplain #messageProcessor} for polling endpoints. + * + * @author Artem Bilan + * @author Gary Russell + * + * @since 5.0 + */ +public class MessageProcessorMessageSource extends AbstractMessageSource { + + private final MessageProcessor messageProcessor; + + public MessageProcessorMessageSource(MessageProcessor messageProcessor) { + this.messageProcessor = messageProcessor; + } + + @Override + public String getComponentType() { + return "inbound-channel-adapter"; + } + + @Override + protected Object doReceive() { + return this.messageProcessor.processMessage(null); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/BeanNameMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/BeanNameMessageProcessor.java new file mode 100644 index 0000000000..984504bd93 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/BeanNameMessageProcessor.java @@ -0,0 +1,64 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.handler; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; + +/** + * An "artificial" {@link MessageProcessor} for lazy-load of target bean by its name. + * For internal use only. + * + * @param the expected {@link #processMessage} result type. + * + * @author Artem Bilan + * @since 5.0 + */ +public class BeanNameMessageProcessor implements MessageProcessor, BeanFactoryAware { + + private final String beanName; + + private final String methodName; + + private MessageProcessor delegate; + + private BeanFactory beanFactory; + + public BeanNameMessageProcessor(String object, String methodName) { + this.beanName = object; + this.methodName = methodName; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.notNull(beanFactory); + this.beanFactory = beanFactory; + } + + @Override + public T processMessage(Message message) { + if (this.delegate == null) { + Object target = this.beanFactory.getBean(this.beanName); + this.delegate = new MethodInvokingMessageProcessor<>(target, this.methodName); + } + return this.delegate.processMessage(message); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/GenericHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/GenericHandler.java new file mode 100644 index 0000000000..f0fb62758c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/GenericHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.handler; + +import java.util.Map; + +/** + * A functional interface to specify {@link org.springframework.messaging.MessageHandler} + * logic with Java 8 Lambda expression: + *
+ * {@code
+ *  .handle((p, h) -> p / 2)
+ * }
+ * 
+ * + * @param

the expected {@code payload} type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +@FunctionalInterface +public interface GenericHandler

{ + + Object handle(P payload, Map headers); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/MapBuilder.java b/spring-integration-core/src/main/java/org/springframework/integration/support/MapBuilder.java new file mode 100644 index 0000000000..b813227ee5 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/MapBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.support; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.expression.spel.standard.SpelExpressionParser; + +/** + * A {@code Builder} pattern implementation for the {@link Map}. + * + * @param The type of target {@link MapBuilder} implementation. + * @param The Map key type. + * @param The Map value type. + * + * @author Artem Bilan + * @since 5.0 + */ +public class MapBuilder, K, V> { + + protected final static SpelExpressionParser PARSER = new SpelExpressionParser(); + + private final Map map = new HashMap(); + + public B put(K key, V value) { + this.map.put(key, value); + return _this(); + } + + public Map get() { + return this.map; + } + + @SuppressWarnings("unchecked") + protected final B _this() { + return (B) this; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/PropertiesBuilder.java b/spring-integration-core/src/main/java/org/springframework/integration/support/PropertiesBuilder.java new file mode 100644 index 0000000000..881557f7f0 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/PropertiesBuilder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.support; + +import java.util.Properties; + + +/** + * A {@code Builder} pattern implementation for the {@link Properties}. + * + * @author Gary Russell + * @author Artem Bilan + */ +public class PropertiesBuilder { + + private final Properties properties = new Properties(); + + public PropertiesBuilder put(Object key, Object value) { + this.properties.put(key, value); + return this; + } + + public Properties get() { + return this.properties; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/StringStringMapBuilder.java b/spring-integration-core/src/main/java/org/springframework/integration/support/StringStringMapBuilder.java new file mode 100644 index 0000000000..051672d571 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/StringStringMapBuilder.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.support; + +/** + * A map builder creating a map with String keys and values. + * + * @author Gary Russell + * + */ +public class StringStringMapBuilder extends MapBuilder { + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transaction/TransactionHandleMessageAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/transaction/TransactionHandleMessageAdvice.java new file mode 100644 index 0000000000..1f28cfea40 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/transaction/TransactionHandleMessageAdvice.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.transaction; + +import java.util.Properties; + +import org.aopalliance.aop.Advice; + +import org.springframework.integration.handler.advice.HandleMessageAdvice; +import org.springframework.messaging.MessageHandler; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.interceptor.TransactionAttributeSource; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +/** + * A {@link TransactionInterceptor} extension with {@link HandleMessageAdvice} marker. + *

+ * When this {@link Advice} is used from the {@code request-handler-advice-chain}, it is applied + * to the {@link MessageHandler#handleMessage} + * (not to the + * {@link org.springframework.integration.handler.AbstractReplyProducingMessageHandler.RequestHandler#handleRequestMessage}), + * therefore the entire downstream process is wrapped to the transaction. + *

+ * In any other cases it is operated as a regular {@link TransactionInterceptor}. + * + * @author Artem Bilan + * + * @since 5.0 + */ +@SuppressWarnings("serial") +public class TransactionHandleMessageAdvice extends TransactionInterceptor implements HandleMessageAdvice { + + public TransactionHandleMessageAdvice() { + } + + public TransactionHandleMessageAdvice(PlatformTransactionManager ptm, Properties attributes) { + super(ptm, attributes); + } + + public TransactionHandleMessageAdvice(PlatformTransactionManager ptm, TransactionAttributeSource tas) { + super(ptm, tas); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transaction/TransactionInterceptorBuilder.java b/spring-integration-core/src/main/java/org/springframework/integration/transaction/TransactionInterceptorBuilder.java new file mode 100644 index 0000000000..538f49a2fa --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/transaction/TransactionInterceptorBuilder.java @@ -0,0 +1,103 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.transaction; + +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.interceptor.DefaultTransactionAttribute; +import org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource; +import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.transaction.interceptor.TransactionInterceptor; +import org.springframework.util.Assert; + +/** + * Provides a fluent API to build a transaction interceptor. See + * {@link TransactionAttribute} for property meanings; if a {@link TransactionAttribute} + * is provided, the individual properties are ignored. If a + * {@link PlatformTransactionManager} is not provided, a single instance of + * {@link PlatformTransactionManager} will be discovered at runtime; if you have more + * than one transaction manager, you must inject the one you want to use here. + *

+ * When the {@code handleMessageAdvice} option is in use, this builder produces + * {@link TransactionHandleMessageAdvice} instance. + * + * @author Gary Russell + * @author Artem Bilan + * + * @since 5.0 + * + */ +public class TransactionInterceptorBuilder { + + private final DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); + + private final TransactionInterceptor transactionInterceptor; + + public TransactionInterceptorBuilder() { + this(false); + } + + public TransactionInterceptorBuilder(boolean handleMessageAdvice) { + if (handleMessageAdvice) { + this.transactionInterceptor = new TransactionHandleMessageAdvice(); + } + else { + this.transactionInterceptor = new TransactionInterceptor(); + } + transactionAttribute(this.transactionAttribute); + } + + public TransactionInterceptorBuilder propagation(Propagation propagation) { + Assert.notNull(propagation, "'propagation' must not be null."); + this.transactionAttribute.setPropagationBehavior(propagation.value()); + return this; + } + + public TransactionInterceptorBuilder isolation(Isolation isolation) { + Assert.notNull(isolation, "'isolation' must not be null."); + this.transactionAttribute.setIsolationLevel(isolation.value()); + return this; + } + + public TransactionInterceptorBuilder timeout(int timeout) { + this.transactionAttribute.setTimeout(timeout); + return this; + } + + public TransactionInterceptorBuilder readOnly(boolean readOnly) { + this.transactionAttribute.setReadOnly(readOnly); + return this; + } + + public final TransactionInterceptorBuilder transactionAttribute(TransactionAttribute transactionAttribute) { + MatchAlwaysTransactionAttributeSource txAttributeSource = new MatchAlwaysTransactionAttributeSource(); + txAttributeSource.setTransactionAttribute(transactionAttribute); + this.transactionInterceptor.setTransactionAttributeSource(txAttributeSource); + return this; + } + + public TransactionInterceptorBuilder transactionManager(PlatformTransactionManager transactionManager) { + this.transactionInterceptor.setTransactionManager(transactionManager); + return this; + } + + public TransactionInterceptor build() { + return this.transactionInterceptor; + } + +} diff --git a/spring-integration-core/src/main/resources/META-INF/spring.factories b/spring-integration-core/src/main/resources/META-INF/spring.factories index 4ac88568a7..fb4b8927fd 100644 --- a/spring-integration-core/src/main/resources/META-INF/spring.factories +++ b/spring-integration-core/src/main/resources/META-INF/spring.factories @@ -1,4 +1,5 @@ org.springframework.integration.config.IntegrationConfigurationInitializer=\ org.springframework.integration.config.GlobalChannelInterceptorInitializer,\ org.springframework.integration.config.IntegrationConverterInitializer,\ -org.springframework.integration.config.IdempotentReceiverAutoProxyCreatorInitializer +org.springframework.integration.config.IdempotentReceiverAutoProxyCreatorInitializer,\ +org.springframework.integration.config.dsl.DslIntegrationConfigurationInitializer diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandlerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandlerTests.java index 8acc7b4d61..e26745c41b 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandlerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandlerTests.java @@ -192,7 +192,7 @@ public class AbstractCorrelatingMessageHandlerTests { .build(); handler.handleMessage(message); - handler.setMinimumTimeoutForEmptyGroups(100); + handler.setMinimumTimeoutForEmptyGroups(10_000); assertEquals(1, outputMessages.size()); @@ -200,6 +200,8 @@ public class AbstractCorrelatingMessageHandlerTests { groupStore.expireMessageGroups(0); assertEquals(1, TestUtils.getPropertyValue(handler, "messageStore.groupIdToMessageGroup", Map.class).size()); + handler.setMinimumTimeoutForEmptyGroups(10); + int n = 0; while (n++ < 200) { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/LambdaMessageProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/LambdaMessageProcessorTests.java new file mode 100644 index 0000000000..e768fc5d4d --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/LambdaMessageProcessorTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import org.junit.Test; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.integration.handler.GenericHandler; +import org.springframework.messaging.support.GenericMessage; + + +/** + * @author Gary Russell + * + * @since 5.0 + */ +public class LambdaMessageProcessorTests { + + @Test + @SuppressWarnings("divzero") + public void testException() { + try { + handle((m, h) -> 1 / 0); + fail("Expected exception"); + } + catch (Exception e) { + assertThat(e.getCause(), instanceOf(ArithmeticException.class)); + } + } + + private void handle(GenericHandler h) { + LambdaMessageProcessor lmp = new LambdaMessageProcessor(h, String.class); + lmp.setBeanFactory(mock(BeanFactory.class)); + lmp.processMessage(new GenericMessage<>("foo")); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/correlation/CorrelationHandlerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/correlation/CorrelationHandlerTests.java new file mode 100644 index 0000000000..3b10bd4b27 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/correlation/CorrelationHandlerTests.java @@ -0,0 +1,281 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.correlation; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.integration.IntegrationMessageHeaderAccessor; +import org.springframework.integration.aggregator.HeaderAttributeCorrelationStrategy; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.integration.handler.MessageTriggerAction; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +@RunWith(SpringRunner.class) +@DirtiesContext +public class CorrelationHandlerTests { + + private static final String BARRIER = "barrier"; + + @Autowired + @Qualifier("splitResequenceFlow.input") + private MessageChannel splitInput; + + + @Autowired + @Qualifier("splitAggregateInput") + private MessageChannel splitAggregateInput; + + @Autowired + @Qualifier("publishSubscribeFlow.input") + private MessageChannel subscriberAggregateFlowInput; + + @Autowired + private PollableChannel subscriberAggregateResult; + + @Autowired + @Qualifier("barrierFlow.input") + private MessageChannel barrierFlowInput; + + @Autowired + private PollableChannel barrierResults; + + @Autowired + private PollableChannel releaseChannel; + + @Test + public void testSplitterResequencer() { + QueueChannel replyChannel = new QueueChannel(); + + this.splitInput.send(MessageBuilder.withPayload("") + .setReplyChannel(replyChannel) + .setHeader("foo", "bar") + .build()); + + for (int i = 0; i < 12; i++) { + Message receive = replyChannel.receive(2000); + assertNotNull(receive); + assertFalse(receive.getHeaders().containsKey("foo")); + assertTrue(receive.getHeaders().containsKey("FOO")); + assertEquals("BAR", receive.getHeaders().get("FOO")); + assertEquals(i + 1, receive.getPayload()); + } + } + + @Test + public void testSplitterAggregator() { + List payload = Arrays.asList('a', 'b', 'c', 'd', 'e'); + + QueueChannel replyChannel = new QueueChannel(); + this.splitAggregateInput.send(MessageBuilder.withPayload(payload) + .setReplyChannel(replyChannel) + .build()); + + Message receive = replyChannel.receive(2000); + assertNotNull(receive); + assertThat(receive.getPayload(), instanceOf(List.class)); + @SuppressWarnings("unchecked") + List result = (List) receive.getPayload(); + for (int i = 0; i < payload.size(); i++) { + assertEquals(payload.get(i), result.get(i)); + } + } + + @Test + public void testSubscriberAggregateFlow() { + this.subscriberAggregateFlowInput.send(new GenericMessage<>("test")); + + Message receive1 = this.subscriberAggregateResult.receive(10000); + assertNotNull(receive1); + assertEquals("Hello World!", receive1.getPayload()); + } + + + @Test + public void testBarrier() { + Message releasing = MessageBuilder.withPayload("bar").setHeader(BARRIER, "foo").build(); + this.releaseChannel.send(releasing); + Message suspending = MessageBuilder.withPayload("foo").setHeader(BARRIER, "foo").build(); + this.barrierFlowInput.send(suspending); + Message out = this.barrierResults.receive(10000); + assertNotNull(out); + assertEquals("bar", out.getPayload()); + } + + @Configuration + @EnableIntegration + public static class ContextConfiguration { + + @Bean + public Executor taskExecutor() { + return Executors.newCachedThreadPool(); + } + + @Bean + public TestSplitterPojo testSplitterData() { + List first = new ArrayList<>(); + first.add("1,2,3"); + first.add("4,5,6"); + + List second = new ArrayList<>(); + second.add("7,8,9"); + second.add("10,11,12"); + + return new TestSplitterPojo(first, second); + } + + @Bean + public IntegrationFlow splitResequenceFlow() { + return f -> f.enrichHeaders(s -> s.header("FOO", "BAR")) + .split("testSplitterData", "buildList", c -> c.applySequence(false)) + .channel(MessageChannels.executor(taskExecutor())) + .split(Message.class, m -> m.getPayload(), c -> c.applySequence(false)) + .channel(MessageChannels.executor(taskExecutor())) + .split(s -> s + .applySequence(false) + .delimiters(",")) + .channel(MessageChannels.executor(taskExecutor())) + .transform(Integer::parseInt) + .enrichHeaders(h -> + h.headerFunction(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, Message::getPayload)) + .resequence(r -> r.releasePartialSequences(true).correlationExpression("'foo'")) + .headerFilter("foo", false); + } + + + @Bean + public IntegrationFlow splitAggregateFlow() { + return IntegrationFlows.from("splitAggregateInput", true) + .split() + .channel(MessageChannels.executor(taskExecutor())) + .resequence() + .aggregate() + .get(); + } + + @Bean + public IntegrationFlow publishSubscribeFlow() { + return flow -> flow + .publishSubscribeChannel(s -> s + .applySequence(true) + .subscribe(f -> f + .handle((p, h) -> "Hello") + .channel("publishSubscribeAggregateFlow.input")) + .subscribe(f -> f + .handle((p, h) -> "World!") + .channel("publishSubscribeAggregateFlow.input")) + ); + } + + @Bean + public IntegrationFlow publishSubscribeAggregateFlow() { + return flow -> flow + .aggregate(a -> a.outputProcessor(g -> g.getMessages() + .stream() + .map(m -> (String) m.getPayload()) + .collect(Collectors.joining(" ")))) + .channel(MessageChannels.queue("subscriberAggregateResult")); + } + + @Bean + public IntegrationFlow barrierFlow() { + return f -> f + .barrier(10000, b -> b + .correlationStrategy(new HeaderAttributeCorrelationStrategy(BARRIER)) + .outputProcessor(g -> + g.getMessages() + .stream() + .skip(1) + .findFirst() + .get())) + .channel(MessageChannels.queue("barrierResults")); + } + + @Bean + @DependsOn("barrierFlow") + public IntegrationFlow releaseBarrierFlow(MessageTriggerAction barrierTriggerAction) { + return IntegrationFlows.from(MessageChannels.queue("releaseChannel")) + .trigger(barrierTriggerAction, + e -> e.poller(p -> p.fixedDelay(100))) + .get(); + } + + } + + private static final class TestSplitterPojo { + + final List first; + + final List second; + + TestSplitterPojo(List first, List second) { + this.first = first; + this.second = second; + } + + @SuppressWarnings("unused") + public List getFirst() { + return first; + } + + @SuppressWarnings("unused") + public List getSecond() { + return second; + } + + @SuppressWarnings("unused") + public List> buildList() { + return Arrays.asList(this.first, this.second); + } + + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java new file mode 100644 index 0000000000..121f140cb9 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java @@ -0,0 +1,720 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.flows; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.MessageDispatchingException; +import org.springframework.integration.MessageRejectedException; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.channel.FixedSubscriberChannel; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.context.IntegrationContextUtils; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.Pollers; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice; +import org.springframework.integration.scheduling.PollerMetadata; +import org.springframework.integration.store.MessageStore; +import org.springframework.integration.store.SimpleMessageStore; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.support.MutableMessageBuilder; +import org.springframework.integration.transformer.PayloadDeserializingTransformer; +import org.springframework.integration.transformer.PayloadSerializingTransformer; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageDeliveryException; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.messaging.support.ErrorMessage; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Artem Bilan + * @author Tim Ysewyn + * @author Gary Russell + * + * @since 5.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext +public class IntegrationFlowTests { + + @Autowired + private ListableBeanFactory beanFactory; + + @Autowired + private ControlBusGateway controlBus; + + @Autowired + @Qualifier("inputChannel") + private MessageChannel inputChannel; + + @Autowired + @Qualifier("discardChannel") + private PollableChannel discardChannel; + + @Autowired + @Qualifier("foo") + private SubscribableChannel foo; + + @Autowired + @Qualifier("successChannel") + private PollableChannel successChannel; + + @Autowired + @Qualifier("bridgeFlowInput") + private PollableChannel bridgeFlowInput; + + @Autowired + @Qualifier("bridgeFlowOutput") + private PollableChannel bridgeFlowOutput; + + @Autowired + @Qualifier("bridgeFlow2Input") + private MessageChannel bridgeFlow2Input; + + @Autowired + @Qualifier("bridgeFlow2Output") + private PollableChannel bridgeFlow2Output; + + @Autowired + @Qualifier("methodInvokingInput") + private MessageChannel methodInvokingInput; + + @Autowired + @Qualifier("delayedAdvice") + private DelayedAdvice delayedAdvice; + + @Autowired + private MessageStore messageStore; + + @Autowired + @Qualifier("claimCheckInput") + private MessageChannel claimCheckInput; + + @Autowired + @Qualifier("lambdasInput") + private MessageChannel lambdasInput; + + @Autowired + @Qualifier("gatewayInput") + private MessageChannel gatewayInput; + + @Autowired + @Qualifier("gatewayError") + private PollableChannel gatewayError; + + @Test + public void testDirectFlow() { + assertTrue(this.beanFactory.containsBean("filter")); + assertTrue(this.beanFactory.containsBean("filter.handler")); + assertTrue(this.beanFactory.containsBean("expressionFilter")); + assertTrue(this.beanFactory.containsBean("expressionFilter.handler")); + QueueChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("100").setReplyChannel(replyChannel).build(); + try { + this.inputChannel.send(message); + fail("Expected MessageDispatchingException"); + } + catch (Exception e) { + assertThat(e, instanceOf(MessageDeliveryException.class)); + assertThat(e.getCause(), instanceOf(MessageDispatchingException.class)); + assertThat(e.getMessage(), containsString("Dispatcher has no subscribers")); + } + this.controlBus.send("@payloadSerializingTransformer.start()"); + + final AtomicBoolean used = new AtomicBoolean(); + + this.foo.subscribe(m -> used.set(true)); + + this.inputChannel.send(message); + Message reply = replyChannel.receive(5000); + assertNotNull(reply); + assertEquals(200, reply.getPayload()); + + Message successMessage = this.successChannel.receive(5000); + assertNotNull(successMessage); + assertEquals(100, successMessage.getPayload()); + + assertTrue(used.get()); + + this.inputChannel.send(new GenericMessage(1000)); + Message discarded = this.discardChannel.receive(5000); + assertNotNull(discarded); + assertEquals("Discarded: 1000", discarded.getPayload()); + } + + @Test + public void testBridge() { + GenericMessage message = new GenericMessage<>("test"); + this.bridgeFlowInput.send(message); + Message reply = this.bridgeFlowOutput.receive(5000); + assertNotNull(reply); + assertEquals("test", reply.getPayload()); + + assertTrue(this.beanFactory.containsBean("bridgeFlow2.channel#0")); + assertThat(this.beanFactory.getBean("bridgeFlow2.channel#0"), instanceOf(FixedSubscriberChannel.class)); + + try { + this.bridgeFlow2Input.send(message); + fail("Expected MessageDispatchingException"); + } + catch (Exception e) { + assertThat(e, instanceOf(MessageDeliveryException.class)); + assertThat(e.getCause(), instanceOf(MessageDispatchingException.class)); + assertThat(e.getMessage(), containsString("Dispatcher has no subscribers")); + } + this.controlBus.send("@bridge.start()"); + this.bridgeFlow2Input.send(message); + reply = this.bridgeFlow2Output.receive(5000); + assertNotNull(reply); + assertEquals("test", reply.getPayload()); + assertTrue(this.delayedAdvice.getInvoked()); + } + + @Test + public void testWrongLastMessageChannel() { + ConfigurableApplicationContext context = null; + try { + context = new AnnotationConfigApplicationContext(InvalidLastMessageChannelFlowContext.class); + fail("BeanCreationException expected"); + } + catch (Exception e) { + assertThat(e, instanceOf(BeanCreationException.class)); + assertThat(e.getMessage(), containsString("'.fixedSubscriberChannel()' " + + "can't be the last EIP-method in the IntegrationFlow definition")); + } + finally { + if (context != null) { + context.close(); + } + } + } + + @Test + public void testMethodInvokingMessageHandler() { + QueueChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("world") + .setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel) + .build(); + this.methodInvokingInput.send(message); + Message receive = replyChannel.receive(5000); + assertNotNull(receive); + assertEquals("Hello World and world", receive.getPayload()); + } + + @Test + public void testLambdas() { + QueueChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("World") + .setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel) + .build(); + this.lambdasInput.send(message); + Message receive = replyChannel.receive(5000); + assertNotNull(receive); + assertEquals("Hello World", receive.getPayload()); + + message = MessageBuilder.withPayload("Spring") + .setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel) + .build(); + + this.lambdasInput.send(message); + assertNull(replyChannel.receive(10)); + + } + + @Test + public void testClaimCheck() { + QueueChannel replyChannel = new QueueChannel(); + + Message message = MutableMessageBuilder.withPayload("foo").setReplyChannel(replyChannel).build(); + + this.claimCheckInput.send(message); + + Message receive = replyChannel.receive(2000); + assertNotNull(receive); + assertSame(message, receive); + + assertEquals(1, this.messageStore.getMessageCount()); + assertSame(message, this.messageStore.getMessage(message.getHeaders().getId())); + } + + @Test + public void testGatewayFlow() throws Exception { + PollableChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("foo").setReplyChannel(replyChannel).build(); + + this.gatewayInput.send(message); + + Message receive = replyChannel.receive(2000); + assertNotNull(receive); + assertEquals("From Gateway SubFlow: FOO", receive.getPayload()); + assertNull(this.gatewayError.receive(1)); + + message = MessageBuilder.withPayload("bar").setReplyChannel(replyChannel).build(); + + this.gatewayInput.send(message); + + receive = replyChannel.receive(1); + assertNull(receive); + + receive = this.gatewayError.receive(2000); + assertNotNull(receive); + assertThat(receive, instanceOf(ErrorMessage.class)); + assertThat(receive.getPayload(), instanceOf(MessageRejectedException.class)); + assertThat(((Exception) receive.getPayload()).getMessage(), containsString("' rejected Message")); + } + + @Autowired + private SubscribableChannel tappedChannel1; + + @Autowired + @Qualifier("wireTapFlow2.input") + private SubscribableChannel tappedChannel2; + + @Autowired + @Qualifier("wireTapFlow3.input") + private SubscribableChannel tappedChannel3; + + @Autowired + private SubscribableChannel tappedChannel4; + + @Autowired + @Qualifier("tapChannel") + private QueueChannel tapChannel; + + @Autowired + @Qualifier("wireTapFlow5.input") + private SubscribableChannel tappedChannel5; + + @Autowired + private PollableChannel wireTapSubflowResult; + + @Test + public void testWireTap() { + this.tappedChannel1.send(new GenericMessage<>("foo")); + this.tappedChannel1.send(new GenericMessage<>("bar")); + Message out = this.tapChannel.receive(10000); + assertNotNull(out); + assertEquals("foo", out.getPayload()); + assertNull(this.tapChannel.receive(0)); + + this.tappedChannel2.send(new GenericMessage<>("foo")); + this.tappedChannel2.send(new GenericMessage<>("bar")); + out = this.tapChannel.receive(10000); + assertNotNull(out); + assertEquals("foo", out.getPayload()); + assertNull(this.tapChannel.receive(0)); + + this.tappedChannel3.send(new GenericMessage<>("foo")); + this.tappedChannel3.send(new GenericMessage<>("bar")); + out = this.tapChannel.receive(10000); + assertNotNull(out); + assertEquals("foo", out.getPayload()); + assertNull(this.tapChannel.receive(0)); + + this.tappedChannel4.send(new GenericMessage<>("foo")); + this.tappedChannel4.send(new GenericMessage<>("bar")); + out = this.tapChannel.receive(10000); + assertNotNull(out); + assertEquals("foo", out.getPayload()); + out = this.tapChannel.receive(10000); + assertNotNull(out); + assertEquals("bar", out.getPayload()); + + this.tappedChannel5.send(new GenericMessage<>("foo")); + out = this.wireTapSubflowResult.receive(10000); + assertNotNull(out); + assertEquals("FOO", out.getPayload()); + } + + @Autowired + @Qualifier("subscribersFlow.input") + private MessageChannel subscribersFlowInput; + + @Autowired + @Qualifier("subscriber1Results") + private PollableChannel subscriber1Results; + + @Autowired + @Qualifier("subscriber2Results") + private PollableChannel subscriber2Results; + + @Autowired + @Qualifier("subscriber3Results") + private PollableChannel subscriber3Results; + + @Test + public void testSubscribersSubFlows() { + this.subscribersFlowInput.send(new GenericMessage<>(2)); + + Message receive1 = this.subscriber1Results.receive(5000); + assertNotNull(receive1); + assertEquals(1, receive1.getPayload()); + + Message receive2 = this.subscriber2Results.receive(5000); + assertNotNull(receive2); + assertEquals(4, receive2.getPayload()); + Message receive3 = this.subscriber3Results.receive(5000); + assertNotNull(receive3); + assertEquals(6, receive3.getPayload()); + } + + + @MessagingGateway(defaultRequestChannel = "controlBus") + private interface ControlBusGateway { + + void send(String command); + } + + @Configuration + @EnableIntegration + @IntegrationComponentScan + public static class ContextConfiguration { + + @Bean + public IntegrationFlow controlBusFlow() { + return IntegrationFlows.from("controlBus").controlBus().get(); + } + + @Bean(name = PollerMetadata.DEFAULT_POLLER) + public PollerMetadata poller() { + return Pollers.fixedRate(500).get(); + } + + @Bean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME) + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); + threadPoolTaskScheduler.setPoolSize(100); + return threadPoolTaskScheduler; + } + + @Bean + public MessageChannel inputChannel() { + return MessageChannels.direct().get(); + } + + @Bean + public MessageChannel foo() { + return MessageChannels.publishSubscribe().get(); + } + + } + + @Configuration + @ComponentScan + public static class ContextConfiguration2 { + + @Autowired + @Qualifier("inputChannel") + private MessageChannel inputChannel; + + @Autowired + @Qualifier("successChannel") + private PollableChannel successChannel; + + + @Bean + public Advice expressionAdvice() { + ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice(); + advice.setOnSuccessExpression("payload"); + advice.setSuccessChannel(this.successChannel); + return advice; + } + + @Bean + public IntegrationFlow flow2() { + return IntegrationFlows.from(this.inputChannel) + .filter(p -> p instanceof String, e -> e + .id("filter") + .discardFlow(df -> df + .transform(String.class, "Discarded: "::concat) + .channel(MessageChannels.queue("discardChannel")))) + .channel("foo") + .fixedSubscriberChannel() + .transform(Integer::parseInt) + .transform(new PayloadSerializingTransformer(), + c -> c.autoStartup(false).id("payloadSerializingTransformer")) + .channel(MessageChannels.queue(new SimpleMessageStore(), "fooQueue")) + .transform(new PayloadDeserializingTransformer()) + .filter("true", e -> e.id("expressionFilter")) + .channel(publishSubscribeChannel()) + .transform((Integer p) -> p * 2, c -> c.advice(this.expressionAdvice())) + .get(); + } + + @Bean + public MessageChannel publishSubscribeChannel() { + return MessageChannels.publishSubscribe().get(); + } + + @Bean + public IntegrationFlow subscribersFlow() { + return flow -> flow + .publishSubscribeChannel(Executors.newCachedThreadPool(), s -> s + .subscribe(f -> f + .handle((p, h) -> p / 2) + .channel(MessageChannels.queue("subscriber1Results"))) + .subscribe(f -> f + .handle((p, h) -> p * 2) + .channel(MessageChannels.queue("subscriber2Results")))) + .handle((p, h) -> p * 3) + .channel(MessageChannels.queue("subscriber3Results")); + } + + @Bean + public IntegrationFlow wireTapFlow1() { + return IntegrationFlows.from("tappedChannel1") + .wireTap("tapChannel", wt -> wt.selector(m -> m.getPayload().equals("foo"))) + .channel("nullChannel") + .get(); + } + + @Bean + public IntegrationFlow wireTapFlow2() { + return f -> f + .wireTap("tapChannel", wt -> wt.selector(m -> m.getPayload().equals("foo"))) + .channel("nullChannel"); + } + + @Bean + public IntegrationFlow wireTapFlow3() { + return f -> f + .transform("payload") + .wireTap("tapChannel", wt -> wt.selector("payload == 'foo'")) + .channel("nullChannel"); + } + + @Bean + public IntegrationFlow wireTapFlow4() { + return IntegrationFlows.from("tappedChannel4") + .wireTap(tapChannel()) + .channel("nullChannel") + .get(); + } + + @Bean + public IntegrationFlow wireTapFlow5() { + return f -> f + .wireTap(sf -> sf + .transform(String::toUpperCase) + .channel(MessageChannels.queue("wireTapSubflowResult"))) + .channel("nullChannel"); + } + + @Bean + public QueueChannel tapChannel() { + return new QueueChannel(); + } + + } + + @MessageEndpoint + public static class AnnotationTestService { + + @ServiceActivator(inputChannel = "publishSubscribeChannel") + public void handle(Object payload) { + assertEquals(100, payload); + } + } + + @Configuration + public static class ContextConfiguration3 { + + @Autowired + @Qualifier("delayedAdvice") + private MethodInterceptor delayedAdvice; + + @Bean + public QueueChannel successChannel() { + return MessageChannels.queue().get(); + } + + @Bean + public IntegrationFlow bridgeFlow() { + return IntegrationFlows.from(MessageChannels.queue("bridgeFlowInput")) + .channel(MessageChannels.queue("bridgeFlowOutput")) + .get(); + } + + @Bean + public IntegrationFlow bridgeFlow2() { + return IntegrationFlows.from("bridgeFlow2Input") + .bridge(c -> c.autoStartup(false).id("bridge")) + .fixedSubscriberChannel() + .delay("delayer", d -> d + .delayExpression("200") + .advice(this.delayedAdvice) + .messageStore(this.messageStore())) + .channel(MessageChannels.queue("bridgeFlow2Output")) + .get(); + } + + @Bean + public SimpleMessageStore messageStore() { + return new SimpleMessageStore(); + } + + @Bean + public IntegrationFlow claimCheckFlow() { + return IntegrationFlows.from("claimCheckInput") + .claimCheckIn(this.messageStore()) + .claimCheckOut(this.messageStore()) + .get(); + } + + } + + @Component("delayedAdvice") + public static class DelayedAdvice implements MethodInterceptor { + + private final AtomicBoolean invoked = new AtomicBoolean(); + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + this.invoked.set(true); + return invocation.proceed(); + } + + public Boolean getInvoked() { + return invoked.get(); + } + + } + + @Configuration + public static class ContextConfiguration4 { + + @Autowired + @Qualifier("integrationFlowTests.GreetingService") + private MessageHandler greetingService; + + @Bean + public IntegrationFlow methodInvokingFlow() { + return IntegrationFlows.from("methodInvokingInput") + .handle(this.greetingService) + .get(); + } + + @Bean + public IntegrationFlow lambdasFlow() { + return IntegrationFlows.from("lambdasInput") + .filter("World"::equals) + .transform("Hello "::concat) + .get(); + } + + @Bean + public IntegrationFlow gatewayFlow() { + return IntegrationFlows.from("gatewayInput") + .gateway("gatewayRequest", g -> g.errorChannel("gatewayError").replyTimeout(10L)) + .gateway(f -> f.transform("From Gateway SubFlow: "::concat)) + .get(); + } + + @Bean + public IntegrationFlow gatewayRequestFlow() { + return IntegrationFlows.from("gatewayRequest") + .filter("foo"::equals, f -> f.throwExceptionOnRejection(true)) + .transform(String::toUpperCase) + .get(); + } + + @Bean + public MessageChannel gatewayError() { + return MessageChannels.queue().get(); + } + + } + + @Service + public static class GreetingService extends AbstractReplyProducingMessageHandler { + + @Autowired + private WorldService worldService; + + @Override + protected Object handleRequestMessage(Message requestMessage) { + return "Hello " + this.worldService.world() + " and " + requestMessage.getPayload(); + } + } + + @Service + public static class WorldService { + + public String world() { + return "World"; + } + } + + + private static class InvalidLastMessageChannelFlowContext { + + @Bean + public IntegrationFlow wrongLastComponent() { + return IntegrationFlows.from(MessageChannels.direct()) + .fixedSubscriberChannel() + .get(); + } + + } + +} + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/flowservices/FlowServiceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/flowservices/FlowServiceTests.java new file mode 100644 index 0000000000..571663b7df --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/flowservices/FlowServiceTests.java @@ -0,0 +1,209 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.flowservices; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.Aggregator; +import org.springframework.integration.annotation.CorrelationStrategy; +import org.springframework.integration.annotation.Filter; +import org.springframework.integration.annotation.ReleaseStrategy; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.annotation.Splitter; +import org.springframework.integration.annotation.Transformer; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlowAdapter; +import org.springframework.integration.dsl.IntegrationFlowDefinition; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.scheduling.TriggerContext; +import org.springframework.stereotype.Component; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.StringUtils; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext +public class FlowServiceTests { + + @Autowired + @Qualifier("flowServiceTests.MyFlow.input") + private MessageChannel input; + + @Autowired(required = false) + private MyFlow myFlow; + + @Autowired + private PollableChannel myFlowAdapterOutput; + + @Test + public void testFlowService() { + assertNotNull(this.myFlow); + QueueChannel replyChannel = new QueueChannel(); + this.input.send(MessageBuilder.withPayload("foo").setReplyChannel(replyChannel).build()); + Message receive = replyChannel.receive(1000); + assertNotNull(receive); + assertEquals("FOO", receive.getPayload()); + } + + @Test + public void testFlowAdapterService() { + Message receive = this.myFlowAdapterOutput.receive(10000); + assertNotNull(receive); + assertEquals("bar:FOO", receive.getPayload()); + } + + + @Autowired + @Qualifier("testGateway.input") + private MessageChannel testGatewayInput; + + @Test + public void testGatewayExplicitReplyChannel() { + QueueChannel replyChannel = new QueueChannel(); + this.testGatewayInput.send(MessageBuilder.withPayload("foo").setReplyChannel(replyChannel).build()); + Message message = replyChannel.receive(10000); + assertNotNull(message); + assertEquals("FOO", message.getPayload()); + } + + @Configuration + @EnableIntegration + @ComponentScan + public static class ContextConfiguration { + + @Bean + public IntegrationFlow testGateway() { + return f -> f.gateway("processChannel", g -> g.replyChannel("replyChannel")); + } + + @Bean + public IntegrationFlow subFlow() { + return IntegrationFlows + .from("processChannel") + .transform(String::toUpperCase) + .channel("replyChannel") + .get(); + } + + } + + @Component + public static class MyFlow implements IntegrationFlow { + + @Override + public void configure(IntegrationFlowDefinition f) { + f.transform(String::toUpperCase); + } + + } + + @Component + public static class MyFlowAdapter extends IntegrationFlowAdapter { + + private final AtomicReference executionDate = new AtomicReference<>(new Date()); + + private Date nextExecutionTime(TriggerContext triggerContext) { + return this.executionDate.getAndSet(null); + } + + @Override + protected IntegrationFlowDefinition buildFlow() { + return from(this, "messageSource", e -> e.poller(p -> p.trigger(this::nextExecutionTime))) + .split(this, null, e -> e.applySequence(false)) + .transform(this) + .aggregate(a -> a.processor(this, null)) + .enrichHeaders(Collections.singletonMap("foo", "FOO")) + .filter(this) + .handle(this) + .channel(MessageChannels.queue("myFlowAdapterOutput")); + } + + public String messageSource() { + return "B,A,R"; + } + + @Splitter + public String[] split(String payload) { + return StringUtils.commaDelimitedListToStringArray(payload); + } + + @Transformer + public String transform(String payload) { + return payload.toLowerCase(); + } + + + @CorrelationStrategy + public Integer correlationKey() { + return 1; + } + + @ReleaseStrategy + public boolean canRelease(Collection> messages) { + return messages.size() == 3; + } + + @Aggregator + public String aggregate(List payloads) { + return payloads.stream().collect(Collectors.joining()); + } + + @Filter + public boolean filter(@Header Optional foo) { + return foo.isPresent(); + } + + @ServiceActivator + public String handle(String payload, @Header String foo) { + return payload + ":" + foo; + } + + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java new file mode 100644 index 0000000000..dc51790efe --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java @@ -0,0 +1,265 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.manualflow; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.util.Date; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.BeanCreationNotAllowedException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlowAdapter; +import org.springframework.integration.dsl.IntegrationFlowDefinition; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.integration.dsl.context.IntegrationFlowContext; +import org.springframework.integration.dsl.context.IntegrationFlowRegistration; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +@ContextConfiguration(classes = ManualFlowTests.RootConfiguration.class) +@RunWith(SpringRunner.class) +@DirtiesContext +public class ManualFlowTests { + + @Autowired + private IntegrationFlowContext integrationFlowContext; + + @Autowired + private BeanFactory beanFactory; + + @Test + public void testManualFlowRegistration() throws InterruptedException { + IntegrationFlow myFlow = f -> f + .transform(String::toUpperCase) + .channel(MessageChannels.queue()) + .transform("Hello, "::concat, e -> e + .poller(p -> p + .fixedDelay(10) + .maxMessagesPerPoll(1) + .receiveTimeout(10))) + .handle(new BeanFactoryHandler()); + + BeanFactoryHandler additionalBean = new BeanFactoryHandler(); + IntegrationFlowRegistration flowRegistration = + this.integrationFlowContext.registration(myFlow) + .addBean(additionalBean) + .register(); + + BeanFactoryHandler bean = + this.beanFactory.getBean(flowRegistration.getId() + BeanFactoryHandler.class.getName() + "#0", + BeanFactoryHandler.class); + assertSame(additionalBean, bean); + assertSame(this.beanFactory, bean.beanFactory); + + MessagingTemplate messagingTemplate = flowRegistration.getMessagingTemplate(); + messagingTemplate.setReceiveTimeout(10000); + + assertEquals("Hello, FOO", messagingTemplate.convertSendAndReceive("foo", String.class)); + + assertEquals("Hello, BAR", messagingTemplate.convertSendAndReceive("bar", String.class)); + + try { + messagingTemplate.receive(); + fail("UnsupportedOperationException expected"); + } + catch (Exception e) { + assertThat(e, instanceOf(UnsupportedOperationException.class)); + assertThat(e.getMessage(), containsString("The 'receive()/receiveAndConvert()' isn't supported")); + } + + flowRegistration.destroy(); + + assertFalse(this.beanFactory.containsBean(flowRegistration.getId())); + assertFalse(this.beanFactory.containsBean(flowRegistration.getId() + ".input")); + assertFalse(this.beanFactory.containsBean(flowRegistration.getId() + BeanFactoryHandler.class.getName() + "#0")); + + ThreadPoolTaskScheduler taskScheduler = this.beanFactory.getBean(ThreadPoolTaskScheduler.class); + Thread.sleep(100); + assertEquals(0, taskScheduler.getActiveCount()); + } + + @Test + public void testWrongLifecycle() { + + class MyIntegrationFlow implements IntegrationFlow { + + @Override + public void configure(IntegrationFlowDefinition flow) { + flow.bridge(null); + } + + } + + IntegrationFlow testFlow = new MyIntegrationFlow(); + + // This is fine because we are not going to start it automatically. + assertNotNull(this.integrationFlowContext.registration(testFlow) + .autoStartup(false) + .register()); + + try { + this.integrationFlowContext.registration(testFlow).register(); + fail("IllegalStateException expected"); + } + catch (Exception e) { + assertThat(e, instanceOf(IllegalStateException.class)); + assertThat(e.getMessage(), containsString("Consider to implement it for [" + testFlow + "].")); + } + + try { + this.integrationFlowContext.remove("foo"); + fail("IllegalStateException expected"); + } + catch (Exception e) { + assertThat(e, instanceOf(IllegalStateException.class)); + assertThat(e.getMessage(), containsString("But [" + "foo" + "] ins't one of them.")); + } + } + + @Test + public void testDynamicSubFlow() { + PollableChannel resultChannel = new QueueChannel(); + + this.integrationFlowContext.registration(flow -> + flow.publishSubscribeChannel(p -> p + .minSubscribers(1) + .subscribe(f -> f.channel(resultChannel)) + )) + .id("dynamicFlow") + .register(); + + this.integrationFlowContext.messagingTemplateFor("dynamicFlow").send(new GenericMessage<>("test")); + + Message receive = resultChannel.receive(1000); + assertNotNull(receive); + assertEquals("test", receive.getPayload()); + } + + @Test + public void testDynamicAdapterFlow() { + this.integrationFlowContext.registration(new MyFlowAdapter()).register(); + PollableChannel resultChannel = this.beanFactory.getBean("flowAdapterOutput", PollableChannel.class); + + Message receive = resultChannel.receive(1000); + assertNotNull(receive); + assertEquals("flowAdapterMessage", receive.getPayload()); + } + + + @Test + public void testWrongIntegrationFlowScope() { + try { + new AnnotationConfigApplicationContext(InvalidIntegrationFlowScopeConfiguration.class).close(); + fail("BeanCreationNotAllowedException expected"); + } + catch (Exception e) { + assertThat(e, instanceOf(BeanCreationNotAllowedException.class)); + assertThat(e.getMessage(), containsString("IntegrationFlows can not be scoped beans.")); + } + } + + @Configuration + @EnableIntegration + public static class RootConfiguration { + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public Date foo() { + return new Date(); + } + + } + + private static class MyFlowAdapter extends IntegrationFlowAdapter { + + private final AtomicReference nextExecutionTime = new AtomicReference<>(new Date()); + + @Override + protected IntegrationFlowDefinition buildFlow() { + return from(() -> new GenericMessage<>("flowAdapterMessage"), + e -> e.poller(p -> p + .trigger(ctx -> this.nextExecutionTime.getAndSet(null)))) + .channel(MessageChannels.queue("flowAdapterOutput")); + + } + + } + + @Configuration + @EnableIntegration + public static class InvalidIntegrationFlowScopeConfiguration { + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public IntegrationFlow wrongScopeFlow() { + return flow -> flow.bridge(null); + } + + } + + private final class BeanFactoryHandler extends AbstractReplyProducingMessageHandler { + + @Autowired + private BeanFactory beanFactory; + + @Override + protected Object handleRequestMessage(Message requestMessage) { + Objects.requireNonNull(this.beanFactory); + return requestMessage; + } + + @Override + protected void doInit() { + this.beanFactory.getClass(); // ensure wiring before afterPropertiesSet() + } + + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/reactivestreams/ReactiveStreamsTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/reactivestreams/ReactiveStreamsTests.java new file mode 100644 index 0000000000..207f49aef5 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/reactivestreams/ReactiveStreamsTests.java @@ -0,0 +1,164 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.reactivestreams; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.reactivestreams.Publisher; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.Lifecycle; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.IntegrationMessageHeaderAccessor; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import reactor.core.publisher.Flux; + + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +@RunWith(SpringRunner.class) +@DirtiesContext +public class ReactiveStreamsTests { + + @Autowired + @Qualifier("reactiveFlow") + private Publisher> publisher; + + @Autowired + @Qualifier("pollableReactiveFlow") + private Publisher> pollablePublisher; + + @Autowired + @Qualifier("reactiveSteamsMessageSource") + private Lifecycle messageSource; + + @Autowired + @Qualifier("inputChannel") + private MessageChannel inputChannel; + + @Test + public void testReactiveFlow() throws InterruptedException { + List results = new ArrayList<>(); + CountDownLatch latch = new CountDownLatch(6); + Flux.from(this.publisher) + .map(m -> m.getPayload().toUpperCase()) + .subscribe(p -> { + results.add(p); + latch.countDown(); + }); + this.messageSource.start(); + assertTrue(latch.await(10, TimeUnit.SECONDS)); + String[] strings = results.toArray(new String[results.size()]); + assertArrayEquals(new String[] { "A", "B", "C", "D", "E", "F" }, strings); + } + + @Test + @Ignore("Until Reactor 3.0.x solution") + public void testPollableReactiveFlow() throws InterruptedException, TimeoutException, ExecutionException { + this.inputChannel.send(new GenericMessage<>("1,2,3,4,5")); + + CountDownLatch latch = new CountDownLatch(6); + + Flux.from(this.pollablePublisher) + .filter(m -> m.getHeaders().containsKey(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER)) + .doOnNext(p -> latch.countDown()) + .subscribe(6); + + Future> future = + Executors.newSingleThreadExecutor().submit(() -> + Flux.fromArray(new String[] { "11,12,13" }) + .map(v -> v.split(",")) + .map(Arrays::asList) + .flatMapIterable(data -> data) + .map(Integer::parseInt) + .>map(GenericMessage::new) + .concatWith(this.pollablePublisher) + .map(Message::getPayload) + .collect(Collectors.toList()) + .block(Duration.ofSeconds(5))); + + this.inputChannel.send(new GenericMessage<>("6,7,8,9,10")); + + assertTrue(latch.await(10, TimeUnit.SECONDS)); + List integers = future.get(10, TimeUnit.SECONDS); + assertNotNull(integers); + assertEquals(7, integers.size()); + } + + @Configuration + @EnableIntegration + public static class ContextConfiguration { + + private final AtomicBoolean invoked = new AtomicBoolean(); + + @Bean + public Publisher> reactiveFlow() { + return IntegrationFlows + .from(() -> new GenericMessage<>("a,b,c,d,e,f"), + e -> e.poller(p -> p.trigger(ctx -> this.invoked.getAndSet(true) ? null : new Date())) + .autoStartup(false) + .id("reactiveSteamsMessageSource")) + .split(String.class, p -> p.split(",")) + .toReactivePublisher(); + } + + @Bean + public Publisher> pollableReactiveFlow() { + return IntegrationFlows + .from("inputChannel") + .split(s -> s.delimiters(",")) + .transform(Integer::parseInt) + .channel(MessageChannels.queue()) + .toReactivePublisher(); + } + + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/routers/RouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/routers/RouterTests.java new file mode 100644 index 0000000000..6fa9795f95 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/routers/RouterTests.java @@ -0,0 +1,727 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.dsl.routers; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.Router; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.config.EnableMessageHistory; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageDeliveryException; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.core.DestinationResolutionException; +import org.springframework.messaging.handler.annotation.Header; +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; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext +public class RouterTests { + + @Autowired + private ListableBeanFactory beanFactory; + + @Autowired + @Qualifier("routerInput") + private MessageChannel routerInput; + + @Autowired + @Qualifier("oddChannel") + private PollableChannel oddChannel; + + @Autowired + @Qualifier("evenChannel") + private PollableChannel evenChannel; + + + @Test + public void testRouter() { + this.beanFactory.containsBean("routeFlow.subFlow#0.channel#0"); + + int[] payloads = new int[] {1, 2, 3, 4, 5, 6}; + + for (int payload : payloads) { + this.routerInput.send(new GenericMessage<>(payload)); + } + + for (int i = 0; i < 3; i++) { + Message receive = this.oddChannel.receive(2000); + assertNotNull(receive); + assertEquals(payloads[i * 2] * 3, receive.getPayload()); + + receive = this.evenChannel.receive(2000); + assertNotNull(receive); + assertEquals(payloads[i * 2 + 1], receive.getPayload()); + } + } + + @Autowired + @Qualifier("routerTwoSubFlows.input") + private MessageChannel routerTwoSubFlowsInput; + + @Autowired + @Qualifier("routerTwoSubFlowsOutput") + private PollableChannel routerTwoSubFlowsOutput; + + @Test + public void testRouterWithTwoSubflows() { + this.routerTwoSubFlowsInput.send(new GenericMessage(Arrays.asList(1, 2, 3, 4, 5, 6))); + Message receive = this.routerTwoSubFlowsOutput.receive(5000); + assertNotNull(receive); + Object payload = receive.getPayload(); + assertThat(payload, instanceOf(List.class)); + @SuppressWarnings("unchecked") + List results = (List) payload; + + assertArrayEquals(new Integer[] {3, 4, 9, 8, 15, 12}, results.toArray(new Integer[results.size()])); + } + + @Autowired + @Qualifier("routeSubflowToReplyChannelFlow.input") + private MessageChannel routeSubflowToReplyChannelFlowInput; + + @Test + public void testRouterSubflowWithReplyChannelHeader() { + PollableChannel replyChannel = new QueueChannel(); + this.routeSubflowToReplyChannelFlowInput.send( + MessageBuilder.withPayload("baz") + .setReplyChannel(replyChannel) + .build()); + + Message receive = replyChannel.receive(10000); + assertNotNull(receive); + assertEquals("BAZ", receive.getPayload()); + } + + + @Autowired + @Qualifier("routeSubflowWithoutReplyToMainFlow.input") + private MessageChannel routeSubflowWithoutReplyToMainFlowInput; + + @Autowired + @Qualifier("routerSubflowResult") + private PollableChannel routerSubflowResult; + + @Test + public void testRouterSubflowWithoutReplyToMainFlow() { + this.routeSubflowWithoutReplyToMainFlowInput.send(new GenericMessage<>("BOO")); + + Message receive = routerSubflowResult.receive(10000); + assertNotNull(receive); + assertEquals("boo", receive.getPayload()); + assertNull(this.defaultOutputChannel.receive(1)); + this.routeSubflowWithoutReplyToMainFlowInput.send(new GenericMessage<>("foo")); + assertNotNull(this.defaultOutputChannel.receive(10000)); + } + + @Autowired + @Qualifier("recipientListInput") + private MessageChannel recipientListInput; + + @Autowired + @Qualifier("foo-channel") + private PollableChannel fooChannel; + + @Autowired + @Qualifier("bar-channel") + private PollableChannel barChannel; + + + @Autowired + @Qualifier("recipientListSubFlow1Result") + private PollableChannel recipientListSubFlow1Result; + + @Autowired + @Qualifier("recipientListSubFlow2Result") + private PollableChannel recipientListSubFlow2Result; + + @Autowired + @Qualifier("recipientListSubFlow3Result") + private PollableChannel recipientListSubFlow3Result; + + @Autowired + @Qualifier("defaultOutputChannel") + private PollableChannel defaultOutputChannel; + + @Test + public void testRecipientListRouter() { + + Message fooMessage = MessageBuilder.withPayload("fooPayload").setHeader("recipient", true).build(); + Message barMessage = MessageBuilder.withPayload("barPayload").setHeader("recipient", true).build(); + Message bazMessage = new GenericMessage<>("baz"); + Message badMessage = new GenericMessage<>("badPayload"); + + this.recipientListInput.send(fooMessage); + Message result1a = this.fooChannel.receive(10000); + assertNotNull(result1a); + assertEquals("foo", result1a.getPayload()); + Message result1b = this.barChannel.receive(10000); + assertNotNull(result1b); + assertEquals("foo", result1b.getPayload()); + Message result1c = this.recipientListSubFlow1Result.receive(10000); + assertNotNull(result1c); + assertEquals("FOO", result1c.getPayload()); + assertNull(this.recipientListSubFlow2Result.receive(0)); + + this.recipientListInput.send(barMessage); + assertNull(this.fooChannel.receive(0)); + assertNull(this.recipientListSubFlow2Result.receive(0)); + Message result2b = this.barChannel.receive(10000); + assertNotNull(result2b); + assertEquals("bar", result2b.getPayload()); + Message result2c = this.recipientListSubFlow1Result.receive(10000); + assertNotNull(result1c); + assertEquals("BAR", result2c.getPayload()); + + this.recipientListInput.send(bazMessage); + assertNull(this.fooChannel.receive(0)); + assertNull(this.barChannel.receive(0)); + Message result3c = this.recipientListSubFlow1Result.receive(10000); + assertNotNull(result3c); + assertEquals("BAZ", result3c.getPayload()); + Message result4c = this.recipientListSubFlow2Result.receive(10000); + assertNotNull(result4c); + assertEquals("Hello baz", result4c.getPayload()); + + this.recipientListInput.send(badMessage); + assertNull(this.fooChannel.receive(0)); + assertNull(this.barChannel.receive(0)); + assertNull(this.recipientListSubFlow1Result.receive(0)); + assertNull(this.recipientListSubFlow2Result.receive(0)); + Message resultD = this.defaultOutputChannel.receive(10000); + assertNotNull(resultD); + assertEquals("bad", resultD.getPayload()); + + this.recipientListInput.send(new GenericMessage<>("bax")); + Message result5c = this.recipientListSubFlow3Result.receive(10000); + assertNotNull(result5c); + assertEquals("bax", result5c.getPayload()); + assertNull(this.fooChannel.receive(0)); + assertNull(this.barChannel.receive(0)); + assertNull(this.recipientListSubFlow1Result.receive(0)); + assertNull(this.recipientListSubFlow2Result.receive(0)); + } + + @Autowired + @Qualifier("routerMethodInput") + private MessageChannel routerMethodInput; + + @Autowired + @Qualifier("routerMethod2Input") + private MessageChannel routerMethod2Input; + + @Autowired + @Qualifier("routeMethodInvocationFlow3.input") + private MessageChannel routerMethod3Input; + + @Autowired + @Qualifier("routerMultiInput") + private MessageChannel routerMultiInput; + + @Test + public void testMethodInvokingRouter() { + Message fooMessage = new GenericMessage<>("foo"); + Message barMessage = new GenericMessage<>("bar"); + Message badMessage = new GenericMessage<>("bad"); + + this.routerMethodInput.send(fooMessage); + + Message result1a = this.fooChannel.receive(2000); + assertNotNull(result1a); + assertEquals("foo", result1a.getPayload()); + assertNull(this.barChannel.receive(0)); + + this.routerMethodInput.send(barMessage); + assertNull(this.fooChannel.receive(0)); + Message result2b = this.barChannel.receive(2000); + assertNotNull(result2b); + assertEquals("bar", result2b.getPayload()); + + try { + this.routerMethodInput.send(badMessage); + fail("MessageDeliveryException expected."); + } + catch (MessageDeliveryException e) { + assertThat(e.getMessage(), + containsString("No channel resolved by router")); + } + + } + + @Test + public void testMethodInvokingRouter2() { + Message fooMessage = MessageBuilder.withPayload("foo").setHeader("targetChannel", "foo").build(); + Message barMessage = MessageBuilder.withPayload("bar").setHeader("targetChannel", "bar").build(); + Message badMessage = MessageBuilder.withPayload("bad").setHeader("targetChannel", "bad").build(); + + this.routerMethod2Input.send(fooMessage); + + Message result1a = this.fooChannel.receive(2000); + assertNotNull(result1a); + assertEquals("foo", result1a.getPayload()); + assertNull(this.barChannel.receive(0)); + + this.routerMethod2Input.send(barMessage); + assertNull(this.fooChannel.receive(0)); + Message result2b = this.barChannel.receive(2000); + assertNotNull(result2b); + assertEquals("bar", result2b.getPayload()); + + try { + this.routerMethod2Input.send(badMessage); + fail("DestinationResolutionException expected."); + } + catch (MessagingException e) { + assertThat(e.getCause(), instanceOf(DestinationResolutionException.class)); + assertThat(e.getCause().getMessage(), + containsString("failed to look up MessageChannel with name 'bad-channel'")); + } + + } + + @Test + public void testMethodInvokingRouter3() { + Message fooMessage = new GenericMessage<>("foo"); + Message barMessage = new GenericMessage<>("bar"); + Message badMessage = new GenericMessage<>("bad"); + + this.routerMethod3Input.send(fooMessage); + + Message result1a = this.fooChannel.receive(2000); + assertNotNull(result1a); + assertEquals("foo", result1a.getPayload()); + assertNull(this.barChannel.receive(0)); + + this.routerMethod3Input.send(barMessage); + assertNull(this.fooChannel.receive(0)); + Message result2b = this.barChannel.receive(2000); + assertNotNull(result2b); + assertEquals("bar", result2b.getPayload()); + + try { + this.routerMethod3Input.send(badMessage); + fail("DestinationResolutionException expected."); + } + catch (MessagingException e) { + assertThat(e.getCause(), instanceOf(DestinationResolutionException.class)); + assertThat(e.getCause().getMessage(), + containsString("failed to look up MessageChannel with name 'bad-channel'")); + } + } + + @Test + public void testMultiRouter() { + + Message fooMessage = new GenericMessage<>("foo"); + Message barMessage = new GenericMessage<>("bar"); + Message badMessage = new GenericMessage<>("bad"); + + this.routerMultiInput.send(fooMessage); + Message result1a = this.fooChannel.receive(2000); + assertNotNull(result1a); + assertEquals("foo", result1a.getPayload()); + Message result1b = this.barChannel.receive(2000); + assertNotNull(result1b); + assertEquals("foo", result1b.getPayload()); + + this.routerMultiInput.send(barMessage); + Message result2a = this.fooChannel.receive(2000); + assertNotNull(result2a); + assertEquals("bar", result2a.getPayload()); + Message result2b = this.barChannel.receive(2000); + assertNotNull(result2b); + assertEquals("bar", result2b.getPayload()); + + try { + this.routerMultiInput.send(badMessage); + fail("MessageDeliveryException expected."); + } + catch (MessageDeliveryException e) { + assertThat(e.getMessage(), + containsString("No channel resolved by router")); + } + } + + @Autowired + @Qualifier("payloadTypeRouteFlow.input") + private MessageChannel payloadTypeRouteFlowInput; + + @Autowired + @Qualifier("stringsChannel") + private PollableChannel stringsChannel; + + @Autowired + @Qualifier("integersChannel") + private PollableChannel integersChannel; + + @Test + public void testPayloadTypeRouteFlow() { + this.payloadTypeRouteFlowInput.send(new GenericMessage<>("foo")); + this.payloadTypeRouteFlowInput.send(new GenericMessage<>(22)); + this.payloadTypeRouteFlowInput.send(new GenericMessage<>(33)); + this.payloadTypeRouteFlowInput.send(new GenericMessage<>("BAR")); + + Message receive = this.stringsChannel.receive(10000); + assertNotNull(receive); + assertEquals("foo", receive.getPayload()); + + receive = this.stringsChannel.receive(10000); + assertNotNull(receive); + assertEquals("BAR", receive.getPayload()); + + assertNull(this.stringsChannel.receive(10)); + + receive = this.integersChannel.receive(10000); + assertNotNull(receive); + assertEquals(22, receive.getPayload()); + + receive = this.integersChannel.receive(10000); + assertNotNull(receive); + assertEquals(33, receive.getPayload()); + + assertNull(this.integersChannel.receive(10)); + } + + @Autowired + @Qualifier("recipientListOrderFlow.input") + private MessageChannel recipientListOrderFlowInput; + + @Autowired + @Qualifier("recipientListOrderResult") + private PollableChannel recipientListOrderResult; + + @Test + @SuppressWarnings("unchecked") + public void testRecipientListRouterOrder() { + this.recipientListOrderFlowInput.send(new GenericMessage<>(new AtomicReference<>(""))); + Message receive = this.recipientListOrderResult.receive(10000); + assertNotNull(receive); + + AtomicReference result = (AtomicReference) receive.getPayload(); + assertEquals("Hello World", result.get()); + + receive = this.recipientListOrderResult.receive(10000); + assertNotNull(receive); + result = (AtomicReference) receive.getPayload(); + assertEquals("Hello World", result.get()); + } + + @Autowired + @Qualifier("routerAsNonLastFlow.input") + private MessageChannel routerAsNonLastFlowChannel; + + @Autowired + @Qualifier("routerAsNonLastDefaultOutputChannel") + private PollableChannel routerAsNonLastDefaultOutputChannel; + + @Test + public void testRouterAsNonLastComponent() { + this.routerAsNonLastFlowChannel.send(new GenericMessage<>("Hello World")); + Message receive = this.routerAsNonLastDefaultOutputChannel.receive(1000); + assertNotNull(receive); + assertEquals("Hello World", receive.getPayload()); + } + + @Autowired + @Qualifier("scatterGatherFlow.input") + private MessageChannel scatterGatherFlowInput; + + @Test + public void testScatterGather() { + QueueChannel replyChannel = new QueueChannel(); + Message request = MessageBuilder.withPayload("foo") + .setReplyChannel(replyChannel) + .build(); + this.scatterGatherFlowInput.send(request); + Message bestQuoteMessage = replyChannel.receive(10000); + assertNotNull(bestQuoteMessage); + Object payload = bestQuoteMessage.getPayload(); + assertThat(payload, instanceOf(List.class)); + assertThat(((List) payload).size(), greaterThanOrEqualTo(1)); + } + + @Configuration + @EnableIntegration + @EnableMessageHistory({ "recipientListOrder*", "recipient1*", "recipient2*" }) + public static class ContextConfiguration { + + @Bean + public QueueChannel evenChannel() { + return new QueueChannel(); + } + + @Bean + public IntegrationFlow routeFlow() { + return IntegrationFlows.from("routerInput") + .route(p -> p % 2 == 0, + m -> m.channelMapping(true, "evenChannel") + .subFlowMapping(false, f -> + f.handle((p, h) -> p * 3)) + .defaultOutputToParentFlow()) + .channel(MessageChannels.queue("oddChannel")) + .get(); + } + + @Bean + public IntegrationFlow routeSubflowToReplyChannelFlow() { + return f -> f + .route("true", m -> m + .subFlowMapping(true, sf -> sf + .handle((p, h) -> p.toUpperCase()) + ) + ); + } + + @Bean + public IntegrationFlow routeSubflowWithoutReplyToMainFlow() { + return f -> f + .route("BOO"::equals, m -> m + .resolutionRequired(false) + .subFlowMapping(true, sf -> sf + .transform(String.class, String::toLowerCase) + .channel(MessageChannels.queue("routerSubflowResult"))) + .defaultSubFlowMapping(sf -> sf.channel("defaultOutputChannel"))); + } + + @Bean + public IntegrationFlow routerTwoSubFlows() { + return f -> f + .split() + .route(p -> p % 2 == 0, m -> m + .subFlowMapping(true, sf -> sf.handle((p, h) -> p * 2)) + .subFlowMapping(false, sf -> sf.handle((p, h) -> p * 3))) + .aggregate() + .channel(MessageChannels.queue("routerTwoSubFlowsOutput")); + } + + @Bean(name = "foo-channel") + public MessageChannel fooChannel() { + return new QueueChannel(); + } + + @Bean(name = "bar-channel") + public MessageChannel barChannel() { + return new QueueChannel(); + } + + @Bean + public MessageChannel defaultOutputChannel() { + return new QueueChannel(); + } + + @Bean + public IntegrationFlow recipientListFlow() { + return IntegrationFlows.from("recipientListInput") + .transform(p -> p.replaceFirst("Payload", "")) + .routeToRecipients(r -> r + .recipient("foo-channel", "'foo' == payload") + .recipientMessageSelector("bar-channel", m -> + m.getHeaders().containsKey("recipient") + && (boolean) m.getHeaders().get("recipient")) + .recipientFlow("'foo' == payload or 'bar' == payload or 'baz' == payload", + f -> f.transform(String::toUpperCase) + .channel(MessageChannels.queue("recipientListSubFlow1Result"))) + .recipientFlow((String p) -> p.startsWith("baz"), + f -> f.transform("Hello "::concat) + .channel(MessageChannels.queue("recipientListSubFlow2Result"))) + .recipientFlow(new FunctionExpression>(m -> "bax".equals(m.getPayload())), + f -> f.channel(MessageChannels.queue("recipientListSubFlow3Result"))) + .defaultOutputToParentFlow()) + .channel("defaultOutputChannel") + .get(); + } + + + @Bean + public RoutingTestBean routingTestBean() { + return new RoutingTestBean(); + } + + @Bean + public IntegrationFlow routeMethodInvocationFlow() { + return IntegrationFlows.from("routerMethodInput") + .route("routingTestBean", "routeMessage") + .get(); + } + + @Bean + public IntegrationFlow routeMethodInvocationFlow2() { + return IntegrationFlows.from("routerMethod2Input") + .route(new RoutingTestBean()) + .get(); + } + + @Bean + public IntegrationFlow routeMethodInvocationFlow3() { + return f -> f.route((String p) -> routingTestBean().routePayload(p)); + } + + @Bean + public IntegrationFlow routeMultiMethodInvocationFlow() { + return IntegrationFlows.from("routerMultiInput") + .route(String.class, p -> p.equals("foo") || p.equals("bar") ? new String[] {"foo", "bar"} : null, + s -> s.suffix("-channel")) + .get(); + } + + @Bean + public PollableChannel stringsChannel() { + return new QueueChannel(); + } + + @Bean + public PollableChannel integersChannel() { + return new QueueChannel(); + } + + @Bean + public IntegrationFlow payloadTypeRouteFlow() { + return f -> f + .>route(Object::getClass, m -> m + .channelMapping(String.class, "stringsChannel") + .channelMapping(Integer.class, "integersChannel")); + } + + @Bean + public IntegrationFlow routerAsNonLastFlow() { + return f -> f + .route(p -> p, r -> + r.resolutionRequired(false) + .defaultOutputToParentFlow()) + .channel(MessageChannels.queue("routerAsNonLastDefaultOutputChannel")); + } + + @Bean + public IntegrationFlow recipientListOrderFlow() { + return f -> f + .routeToRecipients(r -> r + .recipient("recipient2.input") + .recipient("recipient1.input")); + } + + @Bean + public IntegrationFlow recipient1() { + return f -> f + .>handle((p, h) -> { + p.set(p.get() + "World"); + return p; + }) + .channel("recipientListOrderResult"); + } + + @Bean + public IntegrationFlow recipient2() { + return f -> f + .>handle((p, h) -> { + p.set(p.get() + "Hello "); + return p; + }) + .channel("recipientListOrderResult"); + } + + @Bean + public PollableChannel recipientListOrderResult() { + return new QueueChannel(); + } + + @Bean + public IntegrationFlow scatterGatherFlow() { + return f -> f + .scatterGather(scatterer -> scatterer + .applySequence(true) + .recipientFlow(m -> true, sf -> sf.handle((p, h) -> Math.random() * 10)) + .recipientFlow(m -> true, sf -> sf.handle((p, h) -> Math.random() * 10)) + .recipientFlow(m -> true, sf -> sf.handle((p, h) -> Math.random() * 10)), + gatherer -> gatherer + .releaseStrategy(group -> + group.size() == 3 || + group.getMessages() + .stream() + .anyMatch(m -> (Double) m.getPayload() > 5)), + scatterGather -> scatterGather + .gatherTimeout(10_000)); + } + + } + + private static class RoutingTestBean { + + RoutingTestBean() { + super(); + } + + public String routePayload(String name) { + return name + "-channel"; + } + + @Router + public String routeByHeader(@Header("targetChannel") String name) { + return name + "-channel"; + } + + @SuppressWarnings("unused") + public String routeMessage(Message message) { + if (message.getPayload().equals("foo")) { + return "foo-channel"; + } + else if (message.getPayload().equals("bar")) { + return "bar-channel"; + } + return null; + } + + } + + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transaction/TransactionInterceptorBuilderTests.java b/spring-integration-core/src/test/java/org/springframework/integration/transaction/TransactionInterceptorBuilderTests.java new file mode 100644 index 0000000000..e5d31527e7 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/transaction/TransactionInterceptorBuilderTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.transaction; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +/** + * @author Gary Russell + * @author Artem Bilan + * + * @since 5.0 + */ +@RunWith(SpringRunner.class) +public class TransactionInterceptorBuilderTests { + + @Autowired + private PlatformTransactionManager txm; + + @Autowired + private TransactionInterceptor interceptor1; + + @Autowired + private TransactionInterceptor interceptor2; + + @Test + public void test() throws Throwable { + verify(this.interceptor1, this.txm); + verify(this.interceptor2, null); + } + + private void verify(TransactionInterceptor interceptor, PlatformTransactionManager txm) { + assertSame(txm, interceptor.getTransactionManager()); + TransactionAttribute atts = interceptor.getTransactionAttributeSource() + .getTransactionAttribute(null, null); + Assert.assertThat(atts.getPropagationBehavior(), equalTo(Propagation.REQUIRES_NEW.value())); + Assert.assertThat(atts.getIsolationLevel(), equalTo(Isolation.SERIALIZABLE.value())); + Assert.assertThat(atts.getTimeout(), equalTo(42)); + assertTrue(atts.isReadOnly()); + } + + + @Configuration + public static class Config { + + @Bean + public PseudoTransactionManager transactionManager() { + return new PseudoTransactionManager(); + } + + @Bean + public TransactionInterceptor interceptor1(PlatformTransactionManager transactionManager) { + return new TransactionInterceptorBuilder() + .propagation(Propagation.REQUIRES_NEW) + .isolation(Isolation.SERIALIZABLE) + .timeout(42) + .readOnly(true) + .transactionManager(transactionManager) + .build(); + } + + @Bean + public TransactionInterceptor interceptor2() { + return new TransactionInterceptorBuilder() + .propagation(Propagation.REQUIRES_NEW) + .isolation(Isolation.SERIALIZABLE) + .timeout(42) + .readOnly(true) + .build(); + } + + } + +} diff --git a/spring-integration-event/src/test/java/org/springframework/integration/event/dsl/IntegrationFlowEventsTests.java b/spring-integration-event/src/test/java/org/springframework/integration/event/dsl/IntegrationFlowEventsTests.java new file mode 100644 index 0000000000..c70a681953 --- /dev/null +++ b/spring-integration-event/src/test/java/org/springframework/integration/event/dsl/IntegrationFlowEventsTests.java @@ -0,0 +1,213 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.event.dsl; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.integration.event.core.MessagingEvent; +import org.springframework.integration.event.inbound.ApplicationEventListeningMessageProducer; +import org.springframework.integration.event.outbound.ApplicationEventPublishingMessageHandler; +import org.springframework.integration.handler.GenericHandler; +import org.springframework.integration.store.MessageGroupStore; +import org.springframework.integration.store.SimpleMessageStore; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Artem Bilan + * + * @since 5.0 + */ +@RunWith(SpringRunner.class) +@DirtiesContext +public class IntegrationFlowEventsTests { + + private static MessageGroupStore messageGroupStore = new SimpleMessageStore(); + + private static String GROUP_ID = "testGroup"; + + @BeforeClass + public static void setup() { + messageGroupStore.addMessageToGroup(GROUP_ID, new GenericMessage<>("foo")); + } + + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private PollableChannel resultsChannel; + + @Autowired + private PollableChannel delayedResults; + + @Autowired + @Qualifier("flow3Input") + private MessageChannel flow3Input; + + @Autowired + private AtomicReference eventHolder; + + @Test + public void testEventsFlow() { + assertNull(this.eventHolder.get()); + this.flow3Input.send(new GenericMessage<>("2")); + assertNotNull(this.eventHolder.get()); + assertEquals(4, this.eventHolder.get()); + } + + @Test + public void testRawApplicationEventListeningMessageProducer() { + this.applicationContext.publishEvent(new TestApplicationEvent1()); + Message receive = this.resultsChannel.receive(10000); + assertNotNull(receive); + assertThat(receive.getPayload(), instanceOf(TestApplicationEvent1.class)); + + this.applicationContext.publishEvent(new TestApplicationEvent2()); + receive = this.resultsChannel.receive(10000); + assertNotNull(receive); + assertThat(receive.getPayload(), instanceOf(TestApplicationEvent2.class)); + } + + @Test + public void testDelayRescheduling() { + Message receive = this.delayedResults.receive(10000); + assertNotNull(receive); + assertEquals("foo", receive.getPayload()); + assertEquals(1, messageGroupStore.getMessageGroupCount()); + assertEquals(0, messageGroupStore.getMessageCountForAllMessageGroups()); + } + + + @Configuration + @EnableIntegration + public static class ContextConfiguration { + + @Bean + public AtomicReference eventHolder() { + return new AtomicReference<>(); + } + + @Bean + public ApplicationListener eventListener() { + return event -> eventHolder().set(event.getMessage().getPayload()); + } + + @Bean + public IntegrationFlow flow3() { + return IntegrationFlows.from("flow3Input") + .handle(Integer.class, new GenericHandler() { + + @SuppressWarnings("unused") + public void setFoo(String foo) { + } + + @SuppressWarnings("unused") + public void setFoo(Integer foo) { + } + + @Override + public Object handle(Integer p, Map h) { + return p * 2; + } + + }) + .handle(new ApplicationEventPublishingMessageHandler()) + .get(); + } + + @Bean + public ApplicationListener applicationListener() { + ApplicationEventListeningMessageProducer producer = new ApplicationEventListeningMessageProducer(); + producer.setEventTypes(TestApplicationEvent1.class); + producer.setOutputChannel(resultsChannel()); + return producer; + } + + + @Bean + public PollableChannel resultsChannel() { + return new QueueChannel(); + } + + @Bean + public IntegrationFlow eventProducerFlow() { + ApplicationEventListeningMessageProducer producer = new ApplicationEventListeningMessageProducer(); + producer.setEventTypes(TestApplicationEvent2.class); + + return IntegrationFlows.from(producer) + .channel(resultsChannel()) + .get(); + } + + @Bean + public IntegrationFlow delayFlow() { + return flow -> flow + .delay(GROUP_ID, e -> e + .messageStore(messageGroupStore) + .id("delayer")) + .channel(MessageChannels.queue("delayedResults")); + } + + } + + @SuppressWarnings("serial") + private static final class TestApplicationEvent1 extends ApplicationEvent { + + TestApplicationEvent1() { + super("TestApplicationEvent1"); + } + + } + + @SuppressWarnings("serial") + private static final class TestApplicationEvent2 extends ApplicationEvent { + + TestApplicationEvent2() { + super("TestApplicationEvent2"); + } + + } + +}