INT-4133: Merge Java DSL Core functionality

JIRA: https://jira.spring.io/browse/INT-4133

Mostly copy/paste and changes according Java 8 and Reactor 3.0 foundations

Fix `IntegrationFlow` JavaDocs

Increase timeout in the `AbstractCorrelatingMessageHandlerTests`

Polishing - reduce timeout after first expire attempt
This commit is contained in:
Artem Bilan
2016-11-02 12:45:27 -04:00
committed by Gary Russell
parent 2f0b377cfb
commit bde1efa9ee
80 changed files with 13171 additions and 2 deletions

View File

@@ -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));
}
}
}

View File

@@ -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<ApplicationListener<?>> applicationListeners = new HashSet<ApplicationListener<?>>();
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<Object> 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<Object> 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<Object> 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();
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides config classes of the Spring Integration Java DSL.
*/
package org.springframework.integration.config.dsl;

View File

@@ -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 <S> the target {@link AbstractRouterSpec} implementation type.
* @param <R> the {@link AbstractMessageRouter} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public class AbstractRouterSpec<S extends AbstractRouterSpec<S, R>, R extends AbstractMessageRouter>
extends MessageHandlerSpec<S, R> implements ComponentsRegistration {
protected final List<Object> subFlows = new ArrayList<Object>();
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<Object> getComponentsToRegister() {
return this.subFlows;
}
}

View File

@@ -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, AggregatingMessageHandler> {
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);
}
}
}
}

View File

@@ -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<BarrierSpec, BarrierMessageHandler> {
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<ConsumerEndpointFactoryBean, BarrierMessageHandler> 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);
}
}

View File

@@ -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}.
* <p>
* For internal use only.
*
* @author Artem Bilan
*
* @since 5.0
*/
@FunctionalInterface
public interface ComponentsRegistration {
Collection<Object> getComponentsToRegister();
}

View File

@@ -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 <S> the target {@link ConsumerEndpointSpec} implementation type.
* @param <H> the target {@link MessageHandler} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class ConsumerEndpointSpec<S extends ConsumerEndpointSpec<S, H>, H extends MessageHandler>
extends EndpointSpec<S, ConsumerEndpointFactoryBean, H> {
protected final List<Advice> 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();
}
}

View File

@@ -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 <S> the target {@link CorrelationHandlerSpec} implementation type.
* @param <H> the {@link AbstractCorrelatingMessageHandler} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class
CorrelationHandlerSpec<S extends CorrelationHandlerSpec<S, H>, H extends AbstractCorrelatingMessageHandler>
extends ConsumerEndpointSpec<S, H> {
private final List<Advice> forceReleaseAdviceChain = new LinkedList<Advice>();
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:
* <p>{@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<MessageGroup, Long> 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();
}
}

View File

@@ -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<DelayerEndpointSpec, DelayHandler> {
private final List<Advice> delayedAdvice = new LinkedList<Advice>();
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:
* <pre class="code">
* {@code
* .<Foo>delay("delayer", m -> m.getPayload().getDate(),
* c -> c.advice(this.delayedAdvice).messageStore(this.messageStore()))
* }
* </pre>
* @param delayFunction the {@link Function} to determine delay.
* @param <P> the payload type.
* @return the endpoint spec.
*/
public <P> DelayerEndpointSpec delayFunction(Function<Message<P>, Object> delayFunction) {
this.handler.setDelayExpression(new FunctionExpression<>(delayFunction));
return this;
}
}

View File

@@ -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<Tuple2<?, ?>> 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<Recipient> recipients = new ArrayList<Recipient>(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.");
}
}
}
}

View File

@@ -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 <S> the target {@link ConsumerEndpointSpec} implementation type.
* @param <F> the target {@link BeanNameAware} implementation type.
* @param <H> the target {@link MessageHandler} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class EndpointSpec<S extends EndpointSpec<S, F, H>, F extends BeanNameAware, H>
extends IntegrationComponentSpec<S, Tuple2<F, H>>
implements ComponentsRegistration {
protected final Collection<Object> componentToRegister = new ArrayList<Object>();
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<PollerFactory, PollerSpec> 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<Object> 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<Object> getComponentsToRegister() {
return this.componentToRegister.isEmpty()
? null
: this.componentToRegister;
}
@Override
protected Tuple2<F, H> doGet() {
return Tuples.of(this.endpointFactoryBean, this.handler);
}
protected void assertHandler() {
Assert.state(this.handler != null, "'this.handler' must not be null.");
}
}

View File

@@ -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<EnricherSpec, ContentEnricher> {
private final ContentEnricher enricher = new ContentEnricher();
private final Map<String, Expression> propertyExpressions = new HashMap<String, Expression>();
private final Map<String, HeaderValueMessageProcessor<?>> headerExpressions =
new HashMap<String, HeaderValueMessageProcessor<?>>();
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 <P> the payload type.
* @return the enricher spec.
* @see ContentEnricher#setRequestPayloadExpression(Expression)
* @see FunctionExpression
*/
public <P> EnricherSpec requestPayload(Function<Message<P>, ?> 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 <V> the value type.
* @return the enricher spec.
* @see ContentEnricher#setPropertyExpressions(Map)
*/
public <V> EnricherSpec property(String key, V value) {
this.propertyExpressions.put(key, new ValueExpression<V>(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 <P> the payload type.
* @return the enricher spec.
* @see ContentEnricher#setPropertyExpressions(Map)
* @see FunctionExpression
*/
public <P> EnricherSpec propertyFunction(String key, Function<Message<P>, 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 <V> the value type.
* @return the enricher spec.
* @see ContentEnricher#setHeaderExpressions(Map)
*/
public <V> 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 <V> the value type.
* @return the enricher spec.
* @see ContentEnricher#setHeaderExpressions(Map)
*/
public <V> EnricherSpec header(String name, V value, Boolean overwrite) {
AbstractHeaderValueMessageProcessor<V> headerValueMessageProcessor =
new StaticHeaderValueMessageProcessor<V>(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 <P> the payload type.
* @return the enricher spec.
* @see ContentEnricher#setHeaderExpressions(Map)
* @see FunctionExpression
*/
public <P> EnricherSpec headerFunction(String name, Function<Message<P>, 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 <P> the payload type.
* @return the enricher spec.
* @see ContentEnricher#setHeaderExpressions(Map)
* @see FunctionExpression
*/
public <P> EnricherSpec headerFunction(String name, Function<Message<P>, 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 <V> the value type.
* @return the enricher spec.
* @see ContentEnricher#setHeaderExpressions(Map)
*/
public <V> EnricherSpec header(String name, HeaderValueMessageProcessor<V> 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;
}
}

View File

@@ -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> {
FilterEndpointSpec(MessageFilter messageFilter) {
super(messageFilter);
}
/**
* The default value is <code>false</code> meaning that rejected
* Messages will be quietly dropped or sent to the discard channel if
* available. Typically this value would not be <code>true</code> 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 <em>then</em> 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();
}
}

View File

@@ -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, GatewayMessageHandler> {
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;
}
}

View File

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

View File

@@ -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 <H> the {@link MessageHandler} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public final class GenericEndpointSpec<H extends MessageHandler>
extends ConsumerEndpointSpec<GenericEndpointSpec<H>, H> {
GenericEndpointSpec(H messageHandler) {
super(messageHandler);
}
}

View File

@@ -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<HeaderEnricherSpec, HeaderEnricher> {
private final Map<String, HeaderValueMessageProcessor<?>> 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<Object>(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<Object>(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 <b>not</b> overwrite existing headers, unless
* {@link #defaultOverwrite(boolean)} is true.
* @param headers the header map builder.
* @return the header enricher spec.
*/
public HeaderEnricherSpec headers(MapBuilder<?, String, Object> 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<?, String, Object> 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 <em>not</em> overwrite existing headers, unless
* {@link #defaultOverwrite(boolean)} is true.
* @param headers The header builder.
* @return the header enricher spec.
*/
public HeaderEnricherSpec headers(Map<String, Object> 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<String, Object> headers, Boolean overwrite) {
Assert.notNull(headers);
for (Entry<String, Object> entry : headers.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
if (value instanceof Expression) {
AbstractHeaderValueMessageProcessor<Object> processor =
new ExpressionEvaluatingHeaderValueMessageProcessor<Object>((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 <b>not</b>
* overwrite existing headers, unless {@link #defaultOverwrite(boolean)} is true.
* @param headers the header map builder.
* @return the header enricher spec.
*/
public HeaderEnricherSpec headerExpressions(MapBuilder<?, String, String> 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<?, String, String> 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 <b>not</b>
* overwrite existing headers, unless {@link #defaultOverwrite(boolean)} is true.
* Usually used with a JDK8 lambda:
* <pre class="code">
* {@code
* .enrichHeaders(s -> s.headerExpressions(c -> c
* .put(MailHeaders.SUBJECT, "payload.subject")
* .put(MailHeaders.FROM, "payload.from[0].toString()")))
* }
* </pre>
* @param configurer the configurer.
* @return the header enricher spec.
*/
public HeaderEnricherSpec headerExpressions(Consumer<StringStringMapBuilder> 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:
* <pre class="code">
* {@code
* .enrichHeaders(s -> s.headerExpressions(c -> c
* .put(MailHeaders.SUBJECT, "payload.subject")
* .put(MailHeaders.FROM, "payload.from[0].toString()"), true))
* }
* </pre>
* @param configurer the configurer.
* @param overwrite true to overwrite existing headers.
* @return the header enricher spec.
*/
public HeaderEnricherSpec headerExpressions(Consumer<StringStringMapBuilder> 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 <b>not</b> overwrite existing headers,
* unless {@link #defaultOverwrite(boolean)} is true.
* @param headers the headers.
* @return the header enricher spec.
*/
public HeaderEnricherSpec headerExpressions(Map<String, String> 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<String, String> headers, Boolean overwrite) {
Assert.notNull(headers);
for (Entry<String, String> entry : headers.entrySet()) {
AbstractHeaderValueMessageProcessor<Object> processor =
new ExpressionEvaluatingHeaderValueMessageProcessor<Object>(entry.getValue(), null);
processor.setOverwrite(overwrite);
header(entry.getKey(), processor);
}
return this;
}
/**
* Add a single header specification. If the header exists, it will <b>not</b> be
* overwritten unless {@link #defaultOverwrite(boolean)} is true.
* @param name the header name.
* @param value the header value (not an {@link Expression}).
* @param <V> the value type.
* @return the header enricher spec.
*/
public <V> 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 <V> the value type.
* @return the header enricher spec.
*/
public <V> HeaderEnricherSpec header(String name, V value, Boolean overwrite) {
AbstractHeaderValueMessageProcessor<V> headerValueMessageProcessor =
new StaticHeaderValueMessageProcessor<V>(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 <b>not</b> 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 <b>not</b> be overwritten
* unless {@link #defaultOverwrite(boolean)} is true.
* @param name the header name.
* @param function the function.
* @param <P> the payload type.
* @return the header enricher spec.
* @see FunctionExpression
*/
public <P> HeaderEnricherSpec headerFunction(String name, Function<Message<P>, 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 <P> the payload type.
* @return the header enricher spec.
* @see FunctionExpression
*/
public <P> HeaderEnricherSpec headerFunction(String name, Function<Message<P>, Object> function,
Boolean overwrite) {
return headerExpression(name, new FunctionExpression<>(function), overwrite);
}
private HeaderEnricherSpec headerExpression(String name, Expression expression, Boolean overwrite) {
AbstractHeaderValueMessageProcessor<?> headerValueMessageProcessor =
new ExpressionEvaluatingHeaderValueMessageProcessor<Object>(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 <V> the value type.
* @return the header enricher spec.
*/
public <V> HeaderEnricherSpec header(String name, HeaderValueMessageProcessor<V> 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;
}
}

View File

@@ -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 <S> the target {@link IntegrationComponentSpec} implementation type.
* @param <T> the target type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class IntegrationComponentSpec<S extends IntegrationComponentSpec<S, T>, T>
implements FactoryBean<T> {
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();
}
}

View File

@@ -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.
* <p>
* 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 &#64;Bean} definition:
* <pre class="code">
* &#64;Bean
* public IntegrationFlow fileReadingFlow() {
* return IntegrationFlows
* .from(Files.inboundAdapter(tmpDir.getRoot()), e -&gt; e.poller(Pollers.fixedDelay(100)))
* .transform(Files.fileToString())
* .channel(MessageChannels.queue("fileReadingResultChannel"))
* .get();
* }
* </pre>
* <p>
* Can be used as a Lambda for top level definition as well as sub-flow definitions:
* <pre class="code">
* &#64;Bean
* public IntegrationFlow routerTwoSubFlows() {
* return f -&gt; f
* .split()
* .&lt;Integer, Boolean&gt;route(p -&gt; p % 2 == 0, m -&gt; m
* .subFlowMapping(true, sf -&gt; sf.&lt;Integer&gt;handle((p, h) -&gt; p * 2))
* .subFlowMapping(false, sf -&gt; sf.&lt;Integer&gt;handle((p, h) -&gt; p * 3)))
* .aggregate()
* .channel(MessageChannels..queue("routerTwoSubFlowsOutput"));
* }
*
* </pre>
* <p>
* Also this interface can be implemented directly to encapsulate the integration logic
* in the target service:
* <pre class="code">
* &#64;Component
* public class MyFlow implements IntegrationFlow {
*
* &#64;Override
* public void configure(IntegrationFlowDefinition&lt;?&gt; f) {
* f.&lt;String, String&gt;transform(String::toUpperCase);
* }
*
* }
* </pre>
*
* @author Artem Bilan
*
* @since 5.0
*
* @see IntegrationFlowBuilder
* @see StandardIntegrationFlow
* @see IntegrationFlowAdapter
*/
@FunctionalInterface
public interface IntegrationFlow {
void configure(IntegrationFlowDefinition<?> flow);
}

View File

@@ -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.
* <p>
* Typically is used for target service implementation:
* <pre class="code">
* &#64;Component
* public class MyFlowAdapter extends IntegrationFlowAdapter {
*
* &#64;Autowired
* private ConnectionFactory rabbitConnectionFactory;
*
* &#64;Override
* protected IntegrationFlowDefinition&lt;?&gt; buildFlow() {
* return from(Amqp.inboundAdapter(this.rabbitConnectionFactory, "myQueue"))
* .&lt;String, String&gt;transform(String::toLowerCase)
* .channel(c -&gt; c.queue("myFlowAdapterOutput"));
* }
*
* }
* </pre>
*
* @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<?, ? extends MessageSource<?>> messageSourceSpec,
Consumer<SourcePollingChannelAdapterSpec> endpointConfigurer) {
return IntegrationFlows.from(messageSourceSpec, endpointConfigurer);
}
protected IntegrationFlowDefinition<?> from(MessageSource<?> messageSource,
Consumer<SourcePollingChannelAdapterSpec> 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<?, ? extends MessageSource<?>> 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<SourcePollingChannelAdapterSpec> endpointConfigurer) {
return IntegrationFlows.from(service, methodName, endpointConfigurer);
}
protected abstract IntegrationFlowDefinition<?> buildFlow();
}

View File

@@ -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> {
IntegrationFlowBuilder() {
super();
}
@Override
public StandardIntegrationFlow get() {
return super.get();
}
}

View File

@@ -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<?, ? extends MessageSource<?>> messageSourceSpec) {
return from(messageSourceSpec, (Consumer<SourcePollingChannelAdapterSpec>) 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<?, ? extends MessageSource<?>> messageSourceSpec,
Consumer<SourcePollingChannelAdapterSpec> 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<SourcePollingChannelAdapterSpec> 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<SourcePollingChannelAdapterSpec>) 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<SourcePollingChannelAdapterSpec> endpointConfigurer) {
return from(messageSource, endpointConfigurer, null);
}
private static IntegrationFlowBuilder from(MessageSource<?> messageSource,
Consumer<SourcePollingChannelAdapterSpec> 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() {
}
}

View File

@@ -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<Object>, 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<Method> 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);
}
}
}

View File

@@ -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 <S> the target {@link ConsumerEndpointSpec} implementation type.
* @param <H> the target {@link MessageHandler} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class MessageHandlerSpec<S extends MessageHandlerSpec<S, H>, H extends MessageHandler>
extends IntegrationComponentSpec<S, H> {
}

View File

@@ -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 <S> the target {@link MessageProcessorSpec} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class MessageProcessorSpec<S extends MessageProcessorSpec<S>>
extends IntegrationComponentSpec<S, MessageProcessor<?>> {
}

View File

@@ -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 <S> the target {@link MessageProducerSpec} implementation type.
* @param <P> the target {@link MessageProducerSupport} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class MessageProducerSpec<S extends MessageProducerSpec<S, P>, P extends MessageProducerSupport>
extends IntegrationComponentSpec<S, P> {
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();
}
}

View File

@@ -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 <S> the target {@link MessageSourceSpec} implementation type.
* @param <H> the target {@link MessageSource} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class MessageSourceSpec<S extends MessageSourceSpec<S, H>, H extends MessageSource<?>>
extends IntegrationComponentSpec<S, H> {
}

View File

@@ -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 <S> the target {@link MessagingGatewaySpec} implementation type.
* @param <G> the target {@link MessagingGatewaySupport} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class MessagingGatewaySpec<S extends MessagingGatewaySpec<S, G>, G extends MessagingGatewaySupport>
extends IntegrationComponentSpec<S, G> {
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();
}
}

View File

@@ -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:
* <pre class="code">
* {@code
* c -> c.poller(p -> p.fixedRate(100))
* }
* </pre>
*
* @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();
}
}

View File

@@ -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<PollerSpec, PollerMetadata>
implements ComponentsRegistration {
private final List<Advice> adviceChain = new LinkedList<>();
private final Collection<Object> 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<Object> getComponentsToRegister() {
return this.componentsToRegister;
}
}

View File

@@ -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() {
}
}

View File

@@ -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<PublishSubscribeSpec> {
private final List<Object> 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<Object> getComponentsToRegister() {
List<Object> objects = new ArrayList<Object>();
objects.addAll(super.getComponentsToRegister());
objects.addAll(this.subscriberFlows);
return objects;
}
}

View File

@@ -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 <T> the message payload type.
*
* @author Artem Bilan
*
* @since 5.0
*/
class PublisherIntegrationFlow<T> extends StandardIntegrationFlow implements Publisher<Message<T>> {
private static final Subscription NO_OP_SUBSCRIPTION = new Subscription() {
@Override
public void request(long n) {
}
@Override
public void cancel() {
}
};
private final Queue<Subscriber<? super Message<T>>> subscribers = new LinkedBlockingQueue<>();
private final MessageChannel messageChannel;
private final Executor executor;
PublisherIntegrationFlow(Set<Object> integrationComponents, MessageChannel messageChannel, Executor executor) {
super(integrationComponents);
this.messageChannel = messageChannel;
this.executor = executor;
start();
}
@Override
@SuppressWarnings("unchecked")
public void subscribe(Subscriber<? super Message<T>> 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<Message<?>>) subscriber));
}
else if (this.messageChannel instanceof PollableChannel) {
subscriber.onSubscribe(new PollableSubscription((Subscriber<Message<?>>) 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<? super Message<T>> subscriber;
while ((subscriber = this.subscribers.poll()) != null) {
subscriber.onComplete();
}
}
private abstract class SubscriberSubscription implements Subscription {
final Subscriber<Message<?>> subscriber;
volatile boolean terminated;
SubscriberSubscription(Subscriber<Message<?>> 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<Long> pendingRequests = new LinkedBlockingQueue<>();
private final AtomicReference<Long> currentRequest = new AtomicReference<>();
private final AtomicLong count = new AtomicLong();
private volatile boolean unbounded;
MessageHandlerSubscription(Subscriber<Message<?>> 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<Message<?>> 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++;
}
}
}
});
}
}
}

View File

@@ -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, RecipientListRouter> {
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<Message<?>>) 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 <P> the selector source type.
* @return the router spec.
*/
public <P> RecipientListRouterSpec recipient(String channelName, GenericSelector<P> 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<Message<?>>) 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 <P> the selector source type.
* @return the router spec.
*/
public <P> RecipientListRouterSpec recipient(MessageChannel channel, GenericSelector<P> 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<Message<?>>) 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 <P> the selector source type.
* @return the router spec.
*/
public <P> RecipientListRouterSpec recipientFlow(GenericSelector<P> 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;
}
}

View File

@@ -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, ResequencingMessageHandler> {
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();
}
}

View File

@@ -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 <K> the key type.
* @param <R> the {@link AbstractMappingMessageRouter} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public final class RouterSpec<K, R extends AbstractMappingMessageRouter>
extends AbstractRouterSpec<RouterSpec<K, R>, 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<K, R> 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<K, R> 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<K, R> 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<K, R> 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<K, R> 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<Object> 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<Object, NamedComponent> mapping = new HashMap<Object, NamedComponent>();
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<Object, NamedComponent> 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());
}
}
}
}

View File

@@ -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> {
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;
}
}

View File

@@ -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, SourcePollingChannelAdapterFactoryBean, MessageSource<?>> {
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();
}
}

View File

@@ -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 <S> the target {@link SplitterEndpointSpec} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public final class SplitterEndpointSpec<S extends AbstractMessageSplitter>
extends ConsumerEndpointSpec<SplitterEndpointSpec<S>, 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<S> applySequence(boolean applySequence) {
this.handler.setApplySequence(applySequence);
return _this();
}
/**
* Set delimiters to tokenize String values. The default is
* <code>null</code> 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<S> 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;
}
}

View File

@@ -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<Object> integrationComponents;
private final List<SmartLifecycle> lifecycles = new LinkedList<SmartLifecycle>();
private final boolean registerComponents = true;
private boolean running;
StandardIntegrationFlow(Set<Object> integrationComponents) {
this.integrationComponents = new LinkedList<Object>(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<Object> integrationComponents) {
this.integrationComponents.clear();
this.integrationComponents.addAll(integrationComponents);
}
public List<Object> getIntegrationComponents() {
return this.integrationComponents;
}
@Override
public void configure(IntegrationFlowDefinition<?> flow) {
throw new UnsupportedOperationException();
}
@Override
public void start() {
if (!this.running) {
ListIterator<Object> 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<SmartLifecycle> 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<SmartLifecycle> 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();
}
}
}
}

View File

@@ -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<Object> 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<Object> deserializer) {
PayloadDeserializingTransformer transformer = new PayloadDeserializingTransformer();
if (deserializer != null) {
transformer.setDeserializer(deserializer);
}
return transformer;
}
public static <T, U> PayloadTypeConvertingTransformer<T, U> converter(Converter<T, U> converter) {
Assert.notNull(converter, "The Converter<?, ?> is required for the PayloadTypeConvertingTransformer");
PayloadTypeConvertingTransformer<T, U> 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 <T> the {@code payload} type.
* @return the {@link EncodingPayloadTransformer} instance.
*/
public static <T> EncodingPayloadTransformer<T> 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 <T> the target type.
* @return the {@link DecodingTransformer} instance.
*/
public static <T> DecodingTransformer<T> decoding(Codec codec, Class<T> 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 <T> the target type.
* @return the {@link DecodingTransformer} instance.
*/
public static <T> DecodingTransformer<T> 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 <T> the target type.
* @return the {@link DecodingTransformer} instance.
*/
public static <T> DecodingTransformer<T> decoding(Codec codec, Function<Message<?>, Class<T>> 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 <T> the target type.
* @return the {@link DecodingTransformer} instance.
*/
public static <T> DecodingTransformer<T> decoding(Codec codec, Expression typeExpression) {
return new DecodingTransformer<>(codec, typeExpression);
}
}

View File

@@ -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<DirectChannelSpec, DirectChannel> {
@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();
}
}

View File

@@ -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<ExecutorChannelSpec, ExecutorChannel> {
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();
}
}

View File

@@ -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 <S> the target {@link LoadBalancingChannelSpec} implementation type.
* @param <C> the target {@link AbstractMessageChannel} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class LoadBalancingChannelSpec<S extends MessageChannelSpec<S, C>, C extends AbstractMessageChannel>
extends MessageChannelSpec<S, C> {
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();
}
}

View File

@@ -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 <S> the target {@link MessageChannelSpec} implementation type.
* @param <C> the target {@link AbstractMessageChannel} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class MessageChannelSpec<S extends MessageChannelSpec<S, C>, C extends AbstractMessageChannel>
extends IntegrationComponentSpec<S, C>
implements ComponentsRegistration {
private final List<Object> componentsToRegister = new ArrayList<>();
private final List<Class<?>> datatypes = new ArrayList<>();
private final List<ChannelInterceptor> 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<Object> 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;
}
}

View File

@@ -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<Message<?>> queue) {
return new QueueChannelSpec(queue);
}
public static QueueChannelSpec queue(String id, Queue<Message<?>> 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 <S extends PublishSubscribeChannelSpec<S>> PublishSubscribeChannelSpec<S> publishSubscribe() {
return new PublishSubscribeChannelSpec<S>();
}
public static <S extends PublishSubscribeChannelSpec<S>> PublishSubscribeChannelSpec<S> publishSubscribe(
String id) {
return MessageChannels.<S>publishSubscribe().id(id);
}
public static <S extends PublishSubscribeChannelSpec<S>> PublishSubscribeChannelSpec<S> publishSubscribe(
Executor executor) {
return new PublishSubscribeChannelSpec<S>(executor);
}
public static <S extends PublishSubscribeChannelSpec<S>> PublishSubscribeChannelSpec<S> publishSubscribe(String id,
Executor executor) {
return MessageChannels.<S>publishSubscribe(executor).id(id);
}
private MessageChannels() {
}
}

View File

@@ -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<PriorityChannelSpec, PriorityChannel> {
private int capacity;
private Comparator<Message<?>> comparator;
public PriorityChannelSpec setCapacity(int capacity) {
this.capacity = capacity;
return this;
}
public PriorityChannelSpec setComparator(Comparator<Message<?>> comparator) {
this.comparator = comparator;
return this;
}
@Override
protected PriorityChannel doGet() {
this.channel = new PriorityChannel(this.capacity, this.comparator);
return super.doGet();
}
PriorityChannelSpec() {
super();
}
}

View File

@@ -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 <S> the target {@link PublishSubscribeChannelSpec} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public class PublishSubscribeChannelSpec<S extends PublishSubscribeChannelSpec<S>>
extends MessageChannelSpec<S, PublishSubscribeChannel> {
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();
}
}

View File

@@ -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<QueueChannelSpec, QueueChannel> {
protected Queue<Message<?>> queue;
protected Integer capacity;
QueueChannelSpec() {
super();
}
QueueChannelSpec(Queue<Message<?>> 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();
}
}
}

View File

@@ -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, RendezvousChannel> {
RendezvousChannelSpec() {
this.channel = new RendezvousChannel();
}
}

View File

@@ -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<WireTapSpec, WireTap> 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<Object> getComponentsToRegister() {
if (this.selector != null) {
return Arrays.asList(this.selector, this.target);
}
else {
return Collections.singletonList(this.target);
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* Contains MessageChannel Builders DSL.
*/
package org.springframework.integration.dsl.channel;

View File

@@ -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.
* <p>
* 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.
* <p>
* The typical use-case, and, therefore algorithm, is:
* <ul>
* <li> create {@link IntegrationFlow} depending of the business logic
* <li> register that {@link IntegrationFlow} in this {@link IntegrationFlowContext},
* with optional {@code id} and {@code autoStartup} flag
* <li> 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}
* <li> remove the {@link IntegrationFlow} by its {@code id} from this {@link IntegrationFlowContext}
* </ul>
* <p>
* 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<String, IntegrationFlowRegistration> 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}.
* <p> Any {@link IntegrationFlow} bean (not only manually registered) can be used for this method.
* <p> 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<Object, String> additionalBeans = new HashMap<Object, String>();
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;
}
}
}

View File

@@ -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}.
* <p> Any {@link IntegrationFlow} bean (not only manually registered) can be used for this method.
* <p> 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> T receiveAndConvert(Class<T> 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);
}
}

View File

@@ -0,0 +1,4 @@
/**
* The context support classes for Spring Integration Java DSL.
*/
package org.springframework.integration.dsl.context;

View File

@@ -0,0 +1,4 @@
/**
* Root package of the Spring Integration Java DSL.
*/
package org.springframework.integration.dsl;

View File

@@ -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 + '\'' +
'}';
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides various support classes used across Spring Integration Java DSL Components.
*/
package org.springframework.integration.dsl.support;

View File

@@ -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<Object> {
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);
}
}

View File

@@ -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 <T> the expected {@link #processMessage} result type.
*
* @author Artem Bilan
* @since 5.0
*/
public class BeanNameMessageProcessor<T> implements MessageProcessor<T>, BeanFactoryAware {
private final String beanName;
private final String methodName;
private MessageProcessor<T> 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);
}
}

View File

@@ -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:
* <pre class="code">
* {@code
* .<Integer>handle((p, h) -> p / 2)
* }
* </pre>
*
* @param <P> the expected {@code payload} type.
*
* @author Artem Bilan
*
* @since 5.0
*/
@FunctionalInterface
public interface GenericHandler<P> {
Object handle(P payload, Map<String, Object> headers);
}

View File

@@ -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 <B> The type of target {@link MapBuilder} implementation.
* @param <K> The Map key type.
* @param <V> The Map value type.
*
* @author Artem Bilan
* @since 5.0
*/
public class MapBuilder<B extends MapBuilder<B, K, V>, K, V> {
protected final static SpelExpressionParser PARSER = new SpelExpressionParser();
private final Map<K, V> map = new HashMap<K, V>();
public B put(K key, V value) {
this.map.put(key, value);
return _this();
}
public Map<K, V> get() {
return this.map;
}
@SuppressWarnings("unchecked")
protected final B _this() {
return (B) this;
}
}

View File

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

View File

@@ -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<StringStringMapBuilder, String, String> {
}

View File

@@ -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.
* <p>
* 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.
* <p>
* 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);
}
}

View File

@@ -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.
* <p>
* 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;
}
}

View File

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

View File

@@ -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) {

View File

@@ -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"));
}
}

View File

@@ -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<Character> 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<Object> result = (List<Object>) 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<String> first = new ArrayList<>();
first.add("1,2,3");
first.add("4,5,6");
List<String> 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()))
.<String, Integer>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<String> first;
final List<String> second;
TestSplitterPojo(List<String> first, List<String> second) {
this.first = first;
this.second = second;
}
@SuppressWarnings("unused")
public List<String> getFirst() {
return first;
}
@SuppressWarnings("unused")
public List<String> getSecond() {
return second;
}
@SuppressWarnings("unused")
public List<List<String>> buildList() {
return Arrays.asList(this.first, this.second);
}
}
}

View File

@@ -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<String> 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<Object>(1000));
Message<?> discarded = this.discardChannel.receive(5000);
assertNotNull(discarded);
assertEquals("Discarded: 1000", discarded.getPayload());
}
@Test
public void testBridge() {
GenericMessage<String> 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<String> 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<String> 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()
.<String, Integer>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
.<Integer>handle((p, h) -> p / 2)
.channel(MessageChannels.queue("subscriber1Results")))
.subscribe(f -> f
.<Integer>handle((p, h) -> p * 2)
.channel(MessageChannels.queue("subscriber2Results"))))
.<Integer>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
.<String, String>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))
.<String, String>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();
}
}
}

View File

@@ -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")
.<String, String>transform(String::toUpperCase)
.channel("replyChannel")
.get();
}
}
@Component
public static class MyFlow implements IntegrationFlow {
@Override
public void configure(IntegrationFlowDefinition<?> f) {
f.<String, String>transform(String::toUpperCase);
}
}
@Component
public static class MyFlowAdapter extends IntegrationFlowAdapter {
private final AtomicReference<Date> 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<Message<?>> messages) {
return messages.size() == 3;
}
@Aggregator
public String aggregate(List<String> payloads) {
return payloads.stream().collect(Collectors.joining());
}
@Filter
public boolean filter(@Header Optional<String> foo) {
return foo.isPresent();
}
@ServiceActivator
public String handle(String payload, @Header String foo) {
return payload + ":" + foo;
}
}
}

View File

@@ -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
.<String, String>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<Date> 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()
}
}
}

View File

@@ -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<Message<String>> publisher;
@Autowired
@Qualifier("pollableReactiveFlow")
private Publisher<Message<Integer>> pollablePublisher;
@Autowired
@Qualifier("reactiveSteamsMessageSource")
private Lifecycle messageSource;
@Autowired
@Qualifier("inputChannel")
private MessageChannel inputChannel;
@Test
public void testReactiveFlow() throws InterruptedException {
List<String> 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<List<Integer>> future =
Executors.newSingleThreadExecutor().submit(() ->
Flux.fromArray(new String[] { "11,12,13" })
.map(v -> v.split(","))
.map(Arrays::asList)
.flatMapIterable(data -> data)
.map(Integer::parseInt)
.<Message<Integer>>map(GenericMessage<Integer>::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<Integer> 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<Message<String>> 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<Message<Integer>> pollableReactiveFlow() {
return IntegrationFlows
.from("inputChannel")
.split(s -> s.delimiters(","))
.<String, Integer>transform(Integer::parseInt)
.channel(MessageChannels.queue())
.toReactivePublisher();
}
}
}

View File

@@ -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<Object>(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<Integer> results = (List<Integer>) 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<String> fooMessage = MessageBuilder.withPayload("fooPayload").setHeader("recipient", true).build();
Message<String> barMessage = MessageBuilder.withPayload("barPayload").setHeader("recipient", true).build();
Message<String> bazMessage = new GenericMessage<>("baz");
Message<String> 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<String> fooMessage = new GenericMessage<>("foo");
Message<String> barMessage = new GenericMessage<>("bar");
Message<String> 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<String> fooMessage = MessageBuilder.withPayload("foo").setHeader("targetChannel", "foo").build();
Message<String> barMessage = MessageBuilder.withPayload("bar").setHeader("targetChannel", "bar").build();
Message<String> 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<String> fooMessage = new GenericMessage<>("foo");
Message<String> barMessage = new GenericMessage<>("bar");
Message<String> 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<String> fooMessage = new GenericMessage<>("foo");
Message<String> barMessage = new GenericMessage<>("bar");
Message<String> 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<String> result = (AtomicReference<String>) receive.getPayload();
assertEquals("Hello World", result.get());
receive = this.recipientListOrderResult.receive(10000);
assertNotNull(receive);
result = (AtomicReference<String>) 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<String> 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")
.<Integer, Boolean>route(p -> p % 2 == 0,
m -> m.channelMapping(true, "evenChannel")
.subFlowMapping(false, f ->
f.<Integer>handle((p, h) -> p * 3))
.defaultOutputToParentFlow())
.channel(MessageChannels.queue("oddChannel"))
.get();
}
@Bean
public IntegrationFlow routeSubflowToReplyChannelFlow() {
return f -> f
.<Boolean>route("true", m -> m
.subFlowMapping(true, sf -> sf
.<String>handle((p, h) -> p.toUpperCase())
)
);
}
@Bean
public IntegrationFlow routeSubflowWithoutReplyToMainFlow() {
return f -> f
.<String, Boolean>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()
.<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"));
}
@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")
.<String, String>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.<String, String>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<Message<?>>(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
.<Object, Class<?>>route(Object::getClass, m -> m
.channelMapping(String.class, "stringsChannel")
.channelMapping(Integer.class, "integersChannel"));
}
@Bean
public IntegrationFlow routerAsNonLastFlow() {
return f -> f
.<String, String>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
.<AtomicReference<String>>handle((p, h) -> {
p.set(p.get() + "World");
return p;
})
.channel("recipientListOrderResult");
}
@Bean
public IntegrationFlow recipient2() {
return f -> f
.<AtomicReference<String>>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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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<Object> 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<Object> eventHolder() {
return new AtomicReference<>();
}
@Bean
public ApplicationListener<MessagingEvent> eventListener() {
return event -> eventHolder().set(event.getMessage().getPayload());
}
@Bean
public IntegrationFlow flow3() {
return IntegrationFlows.from("flow3Input")
.handle(Integer.class, new GenericHandler<Integer>() {
@SuppressWarnings("unused")
public void setFoo(String foo) {
}
@SuppressWarnings("unused")
public void setFoo(Integer foo) {
}
@Override
public Object handle(Integer p, Map<String, Object> 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");
}
}
}