diff --git a/pom.xml b/pom.xml
index a98557da76..1538fd1c3d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,6 +29,7 @@
spring-integration-ftp
spring-integration-sftp
spring-integration-twitter
+ spring-integration-activiti
scm:svn:https://src.springframework.org/svn/spring-integration/trunk
diff --git a/spring-integration-activiti/pom.xml b/spring-integration-activiti/pom.xml
new file mode 100644
index 0000000000..61b22c10a5
--- /dev/null
+++ b/spring-integration-activiti/pom.xml
@@ -0,0 +1,102 @@
+
+
+ 4.0.0
+
+ org.springframework.integration
+ spring-integration-parent
+ 2.0.0.BUILD-SNAPSHOT
+
+ org.springframework.integration
+ spring-integration-activiti
+ jar
+ Spring Integration Activiti Support
+
+
+
+ commons-dbcp
+ commons-dbcp
+ 1.2.2
+
+
+ cglib
+ cglib-nodep
+ 2.2
+
+
+ commons-lang
+ commons-lang
+ 2.5
+
+
+ org.springframework
+ spring-core
+ 3.0.3.RELEASE
+
+
+ org.springframework
+ spring-orm
+ 3.0.3.RELEASE
+
+
+ org.springframework
+ spring-beans
+ 3.0.3.RELEASE
+
+
+ org.springframework
+ spring-test
+ 3.0.3.RELEASE
+
+
+ org.springframework.integration
+ spring-integration-event
+ ${project.version}
+
+
+ org.springframework.integration
+ spring-integration-jms
+ ${project.version}
+
+
+
+ org.springframework.integration
+ spring-integration-core
+ ${project.version}
+
+
+ org.springframework
+ spring-context
+ 3.0.3.RELEASE
+
+
+ org.springframework
+ spring-context-support
+ 3.0.3.RELEASE
+
+
+ org.springframework
+ spring-aop
+ 3.0.3.RELEASE
+
+
+ com.h2database
+ h2
+ 1.2.132
+
+
+ org.activiti
+ activiti-engine
+ 5.0.beta1
+
+
+
+
+
+
diff --git a/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/ActivitiConstants.java b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/ActivitiConstants.java
new file mode 100644
index 0000000000..fca24687b4
--- /dev/null
+++ b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/ActivitiConstants.java
@@ -0,0 +1,35 @@
+/* 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.activiti;
+
+/**
+ * @author Josh Long
+ */
+public class ActivitiConstants {
+
+ /**
+ * In order for the gateway to correctly signal execution to Activiti, it needs to the executionId so that it can look up the {@link org.activiti.engine.runtime.Execution} instance.
+ * the executionId is expected to be under this header.
+ */
+ public static final String WELL_KNOWN_EXECUTION_ID_HEADER_KEY = "activiti_spring_integration_executionId";
+
+ /**
+ * Assuming #updateProcessVariablesFromReplyMessageHeaders is true, then any {@link org.springframework.integration.MessageHeaders} header key that starts with String will be propagated as an Activiti process variable.
+ */
+ public static final String WELL_KNOWN_SPRING_INTEGRATION_HEADER_PREFIX = "activiti_spring_integration_";
+
+ /**
+ * This is the key under which we will look up the custom processDefinitionName up. This value will be used to spawn
+ */
+ public static final String WELL_KNOWN_PROCESS_DEFINITION_NAME_HEADER_KEY = "activiti_spring_integration_processDefinitionName";
+}
diff --git a/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/ProcessSupport.java b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/ProcessSupport.java
new file mode 100644
index 0000000000..d0ab59de42
--- /dev/null
+++ b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/ProcessSupport.java
@@ -0,0 +1,55 @@
+/* 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.activiti;
+
+import org.springframework.integration.MessageHeaders;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentSkipListSet;
+
+
+/**
+ * Provides utility logic to take headers from a {@link org.springframework.integration.MessageHeaders} instance and propagate them as
+ * values that can be used as process variables for a {@link org.activiti.engine.runtime.ProcessInstance}.
+ *
+ * @author Josh Long
+ */
+public class ProcessSupport {
+ private final int headerPrefixLength = ActivitiConstants.WELL_KNOWN_SPRING_INTEGRATION_HEADER_PREFIX.length();
+ private Collection blackList;
+
+ public ProcessSupport() {
+ this.blackList = new ConcurrentSkipListSet(Arrays.asList(ActivitiConstants.WELL_KNOWN_EXECUTION_ID_HEADER_KEY, ActivitiConstants.WELL_KNOWN_PROCESS_DEFINITION_NAME_HEADER_KEY));
+ }
+
+ public Map processVariablesFromMessageHeaders(MessageHeaders msg) {
+ return this.processVariablesFromMessageHeaders(new HashSet(), msg);
+ }
+
+ public Map processVariablesFromMessageHeaders(Set whiteListOfMustCopyMessageHeaderKeyNames, MessageHeaders msgHeaders) {
+ Map procVars = new HashMap();
+
+ Set headers = msgHeaders.keySet();
+ Set wl = (whiteListOfMustCopyMessageHeaderKeyNames == null) ? new HashSet() : whiteListOfMustCopyMessageHeaderKeyNames;
+
+ for (String messageHeaderKey : headers) {
+ if ((!blackList.contains(messageHeaderKey) && messageHeaderKey.startsWith(ActivitiConstants.WELL_KNOWN_SPRING_INTEGRATION_HEADER_PREFIX)) || wl.contains(messageHeaderKey)) {
+ String pvName = messageHeaderKey.startsWith(ActivitiConstants.WELL_KNOWN_SPRING_INTEGRATION_HEADER_PREFIX) ? messageHeaderKey.substring(headerPrefixLength) : messageHeaderKey;
+ procVars.put(pvName, msgHeaders.get(messageHeaderKey));
+ }
+ }
+
+ return procVars;
+ }
+}
diff --git a/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/adapter/ActivitiOutboundChannelAdapter.java b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/adapter/ActivitiOutboundChannelAdapter.java
new file mode 100644
index 0000000000..4c50100bc3
--- /dev/null
+++ b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/adapter/ActivitiOutboundChannelAdapter.java
@@ -0,0 +1,86 @@
+/* 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.activiti.adapter;
+
+import org.activiti.engine.ProcessEngine;
+import org.springframework.integration.Message;
+import org.springframework.integration.activiti.ActivitiConstants;
+import org.springframework.integration.activiti.ProcessSupport;
+import org.springframework.integration.context.IntegrationObjectSupport;
+import org.springframework.integration.core.MessageHandler;
+import org.springframework.util.Assert;
+
+import java.util.Map;
+
+
+/**
+ * Supports spawning a {@link org.activiti.engine.runtime.ProcessInstance} as a result of a trigger {@link org.springframework.integration.Message}.
+ *
+ * The component also supports propagating headers as processVariables.
+ *
+ * This support is simialar to the classic EIP book's "Process Manager" pattern.
+ *
+ * Thanks to Mark Fisherfor the idea.
+ *
+ * @author Josh Long
+ */
+public class ActivitiOutboundChannelAdapter extends IntegrationObjectSupport implements MessageHandler {
+ /**
+ * A reference to the {@link ProcessEngine} (see {@link org.activiti.engine.impl.cfg.spring.ProcessEngineFactoryBean}
+ */
+ private ProcessEngine processEngine;
+
+ /**
+ * Do you want all flows that come into this component to launch the same business process? Hard code the process name here.
+ * If this is null, the component will expect a well known header value and use that to spawn the process definition name.
+ */
+ private String processDefinitionName;
+
+ /**
+ * Provides convenience methods
+ */
+ private ProcessSupport processSupport = new ProcessSupport();
+
+ @Override
+ protected void onInit() throws Exception {
+ Assert.notNull(this.processEngine, "'processEngine' is required!");
+ }
+
+ @SuppressWarnings("unused")
+ public void setProcessEngine(ProcessEngine processEngine) {
+ this.processEngine = processEngine;
+ }
+
+ @SuppressWarnings("unused")
+ public void setProcessDefinitionName(String processDefinitionName) {
+ this.processDefinitionName = processDefinitionName;
+ }
+
+ public void handleMessage(Message> message) {
+ Map procVars = processSupport.processVariablesFromMessageHeaders(message.getHeaders());
+
+ String procName = (String) message.getHeaders().get(ActivitiConstants.WELL_KNOWN_PROCESS_DEFINITION_NAME_HEADER_KEY);
+
+ if ((procName == null) || procName.trim().equals("")) {
+ procName = this.processDefinitionName;
+ }
+
+ Assert.isTrue(procName != null,
+ "you must specify a processDefinitionName, either through " +
+ "an inbound header mapped to the key " + ActivitiConstants.WELL_KNOWN_PROCESS_DEFINITION_NAME_HEADER_KEY +
+ ", or on the 'process-definition-name' property of this adapter"
+ );
+
+ processEngine.getRuntimeService().startProcessInstanceByKey(this.processDefinitionName, procVars);
+ }
+}
diff --git a/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/config/ActivitiNamespaceHandler.java b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/config/ActivitiNamespaceHandler.java
new file mode 100644
index 0000000000..4dd987e658
--- /dev/null
+++ b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/config/ActivitiNamespaceHandler.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 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.activiti.config;
+
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
+import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.integration.activiti.adapter.ActivitiOutboundChannelAdapter;
+import org.springframework.integration.activiti.gateway.ActivityBehaviorMessagingGatewayFactoryBean;
+import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
+import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.w3c.dom.Element;
+
+
+/**
+ * Provides namespace support for defining a Gateway that communicates from Acitviti to Spring Integration.
+ *
+ * @author Josh Long
+ */
+@SuppressWarnings("unused")
+public class ActivitiNamespaceHandler extends NamespaceHandlerSupport {
+
+ public void init() {
+ this.registerBeanDefinitionParser("inbound-gateway", new ActivitiInboundGatewayParser());
+ this.registerBeanDefinitionParser("outbound-channel-adapter", new ActivitiOutboundChannelAdapterParser());
+ }
+
+ private static class ActivitiOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
+ @Override
+ protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ActivitiOutboundChannelAdapter.class.getName());
+ IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "process-engine");
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "process-definition-name");
+ return builder.getBeanDefinition();
+ }
+ }
+
+ private static class ActivitiInboundGatewayParser extends AbstractSingleBeanDefinitionParser {
+ @Override
+ protected String getBeanClassName(Element element) {
+ return ActivityBehaviorMessagingGatewayFactoryBean.class.getName();
+ }
+
+ @Override
+ protected boolean shouldGenerateIdAsFallback() {
+ return true;
+ }
+
+ @Override
+ protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
+ IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "request-channel");
+ IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel");
+ IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "process-engine");
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "update-process-variables-from-reply-message-headers");
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "async");
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "forward-process-variables-as-message-headers");
+ }
+ }
+}
diff --git a/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/ActivityBehaviorMessagingGatewayFactoryBean.java b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/ActivityBehaviorMessagingGatewayFactoryBean.java
new file mode 100644
index 0000000000..1a136374ba
--- /dev/null
+++ b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/ActivityBehaviorMessagingGatewayFactoryBean.java
@@ -0,0 +1,109 @@
+package org.springframework.integration.activiti.gateway;
+
+import org.activiti.engine.ProcessEngine;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.FactoryBean;
+
+import org.springframework.integration.MessageChannel;
+
+import org.springframework.transaction.PlatformTransactionManager;
+
+
+public class ActivityBehaviorMessagingGatewayFactoryBean implements FactoryBean, BeanFactoryAware, BeanNameAware {
+ private volatile boolean updateProcessVariablesFromReplyMessageHeaders = false;
+ private volatile boolean forwardProcessVariablesAsMessageHeaders = false;
+ private volatile PlatformTransactionManager platformTransactionManager;
+ private volatile MessageChannel requestChannel;
+ private volatile MessageChannel replyChannel;
+ private volatile ProcessEngine processEngine;
+ private volatile boolean async;
+ private BeanFactory beanFactory;
+ private String beanName;
+
+ @SuppressWarnings("unused")
+ public void setAsync(boolean async) {
+ this.async = async;
+ }
+
+ @SuppressWarnings("unused")
+ public void setProcessEngine(ProcessEngine processEngine) {
+ this.processEngine = processEngine;
+ }
+
+ @SuppressWarnings("unused")
+ public void setUpdateProcessVariablesFromReplyMessageHeaders(boolean updateProcessVariablesFromReplyMessageHeaders) {
+ this.updateProcessVariablesFromReplyMessageHeaders = updateProcessVariablesFromReplyMessageHeaders;
+ }
+
+ @SuppressWarnings("unused")
+ public void setForwardProcessVariablesAsMessageHeaders(boolean forwardProcessVariablesAsMessageHeaders) {
+ this.forwardProcessVariablesAsMessageHeaders = forwardProcessVariablesAsMessageHeaders;
+ }
+
+ @SuppressWarnings("unused")
+ public void setPlatformTransactionManager(PlatformTransactionManager platformTransactionManager) {
+ this.platformTransactionManager = platformTransactionManager;
+ }
+
+ @SuppressWarnings("unused")
+ public void setRequestChannel(MessageChannel requestChannel) {
+ this.requestChannel = requestChannel;
+ }
+
+ @SuppressWarnings("unused")
+ public void setBeanFactory(BeanFactory beanFactory)
+ throws BeansException {
+ this.beanFactory = beanFactory;
+ }
+
+ @SuppressWarnings("unused")
+ public void setReplyChannel(MessageChannel replyChannel) {
+ this.replyChannel = replyChannel;
+ }
+
+ public Object getObject() throws Exception {
+ if (this.async) {
+ AsyncActivityBehaviorMessagingGateway asyncActivityBehaviorMessagingGateway = new AsyncActivityBehaviorMessagingGateway();
+ asyncActivityBehaviorMessagingGateway.setForwardProcessVariablesAsMessageHeaders(this.forwardProcessVariablesAsMessageHeaders);
+ asyncActivityBehaviorMessagingGateway.setUpdateProcessVariablesFromReplyMessageHeaders(this.updateProcessVariablesFromReplyMessageHeaders);
+ asyncActivityBehaviorMessagingGateway.setPlatformTransactionManager(this.platformTransactionManager);
+ asyncActivityBehaviorMessagingGateway.setReplyChannel(this.replyChannel);
+ asyncActivityBehaviorMessagingGateway.setProcessEngine(this.processEngine);
+ asyncActivityBehaviorMessagingGateway.setRequestChannel(this.requestChannel);
+ asyncActivityBehaviorMessagingGateway.setBeanFactory(this.beanFactory);
+ asyncActivityBehaviorMessagingGateway.setBeanName(this.beanName);
+ asyncActivityBehaviorMessagingGateway.afterPropertiesSet();
+
+ return asyncActivityBehaviorMessagingGateway;
+ } else {
+ SyncActivityBehaviorMessagingGateway syncActivityBehaviorMessagingGateway = new SyncActivityBehaviorMessagingGateway();
+ syncActivityBehaviorMessagingGateway.setForwardProcessVariablesAsMessageHeaders(this.forwardProcessVariablesAsMessageHeaders);
+ syncActivityBehaviorMessagingGateway.setUpdateProcessVariablesFromReplyMessageHeaders(this.updateProcessVariablesFromReplyMessageHeaders);
+ syncActivityBehaviorMessagingGateway.setPlatformTransactionManager(this.platformTransactionManager);
+ syncActivityBehaviorMessagingGateway.setReplyChannel(this.replyChannel);
+ syncActivityBehaviorMessagingGateway.setProcessEngine(this.processEngine);
+ syncActivityBehaviorMessagingGateway.setRequestChannel(this.requestChannel);
+ syncActivityBehaviorMessagingGateway.setBeanFactory(this.beanFactory);
+ syncActivityBehaviorMessagingGateway.setBeanName(this.beanName);
+ syncActivityBehaviorMessagingGateway.afterPropertiesSet();
+
+ return syncActivityBehaviorMessagingGateway;
+ }
+ }
+
+ public Class> getObjectType() {
+ return this.async ? AsyncActivityBehaviorMessagingGateway.class : SyncActivityBehaviorMessagingGateway.class;
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+ public void setBeanName(String name) {
+ this.beanName = name;
+ }
+}
diff --git a/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/AsyncActivityBehaviorMessagingGateway.java b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/AsyncActivityBehaviorMessagingGateway.java
new file mode 100644
index 0000000000..c4677d0f17
--- /dev/null
+++ b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/AsyncActivityBehaviorMessagingGateway.java
@@ -0,0 +1,263 @@
+/* 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.activiti.gateway;
+
+
+import org.activiti.engine.ProcessEngine;
+import org.activiti.engine.RuntimeService;
+import org.activiti.engine.impl.bpmn.ReceiveTaskActivity;
+import org.activiti.engine.impl.cfg.spring.ProcessEngineFactoryBean;
+import org.activiti.pvm.activity.ActivityBehavior;
+import org.activiti.pvm.activity.ActivityExecution;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.integration.*;
+import org.springframework.integration.activiti.ActivitiConstants;
+import org.springframework.integration.activiti.ProcessSupport;
+import org.springframework.integration.config.ConsumerEndpointFactoryBean;
+import org.springframework.integration.core.*;
+import org.springframework.integration.endpoint.AbstractEndpoint;
+import org.springframework.integration.scheduling.PollerMetadata;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.scheduling.support.PeriodicTrigger;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.util.Assert;
+
+import org.activiti.engine.runtime.Execution ;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * This class is plugged into an Activiti workflow. let's us plugin a custom {@link ActivityBehavior}.
+ * We need to build an {@link ActivityBehavior} that can send and receive the message,
+ * propagating the {@code executionId} and potentially process variables/ header variables.
+ *
+ * Simply svn co http://svn.codehaus.org/activiti/activiti/branches/alpha4-spring-integration-adapter that repository and then mvn clean install it.
+ *
+ * The class forwards ("pushes") control from a BPM process (in-thread) to a Spring Integration channel where, of course, Spring Integration acts as a client and
+ * can push the execution forward anyway it wants to.
+ *
+ * Possible use cases include forwarding the job through an outbound JMS adapter or gateway, forwarding the job through an XMPP adapter, or forwarding the job through
+ * to an outbound email adapter.
+ *
+ *
+ * The only requirement for the reply is that the {@link org.springframework.integration.Message} arrive on the #replyChannel and that it contain a header of
+ * {@link org.springframework.integration.activiti.ActivitiConstants#WELL_KNOWN_EXECUTION_ID_HEADER_KEY} (which the outbound {@link org.springframework.integration.Message} will have)
+ * so that the Activiti runtime can signal that execution has completed successfully.
+ *
+ * Thanks to Dave Syer and Tom Baeyens for the help brainstorming.
+ *
+ * @author Josh Long
+ * @see ReceiveTaskActivity the {@link ActivityBehavior} impl that ships w/ Activiti that has the machinery to wake up when signaled
+ * @see ProcessEngine the process engine instance is required to be able to use this namespace
+ * @see ProcessEngineFactoryBean - use this class to create the aforementioned ProcessEngine instance!
+ */
+public class AsyncActivityBehaviorMessagingGateway extends ReceiveTaskActivity implements BeanFactoryAware, BeanNameAware, ActivityBehavior, InitializingBean {
+ /**
+ * Used to handle sending in a standard way
+ */
+ private MessagingTemplate messagingTemplate = new MessagingTemplate();
+
+ /**
+ * This is the channel on which we expect requests - {@link Execution}s from Activiti - to arrive
+ */
+ private volatile MessageChannel requestChannel;
+
+ /**
+ * This is the channel on which we expect to send replies - ie, the result of our work in
+ * Spring Integration - back to Activiti, which should be waiting for the results
+ */
+ private volatile MessageChannel replyChannel;
+
+ /**
+ * Injected from Spring or some other mechanism. Recommended approach is through a {@link ProcessEngineFactoryBean}
+ */
+ private volatile ProcessEngine processEngine;
+
+ /**
+ * Should we update the process variables based on the reply {@link org.springframework.integration.Message}'s {@link org.springframework.integration.MessageHeaders}?
+ */
+ private volatile boolean updateProcessVariablesFromReplyMessageHeaders = false;
+
+ /**
+ * Should we pass the workflow process variables as message headers when we send a message into the Spring Integration framework?
+ */
+ private volatile boolean forwardProcessVariablesAsMessageHeaders = false;
+
+ /**
+ * Forwarded to the {@link org.springframework.integration.core.MessagingTemplate} instance.
+ */
+ private volatile PlatformTransactionManager platformTransactionManager;
+
+ /**
+ * A reference to the {@link org.springframework.beans.factory.BeanFactory} that's hosting this component. Spring will inject this reference automatically assuming
+ * this object is hosted in a Spring context.
+ */
+ private volatile BeanFactory beanFactory;
+
+ /**
+ * The process engine instance that controls the Activiti PVM. Recommended creation is through {@link ProcessEngineFactoryBean}
+ */
+ private RuntimeService processService;
+
+ /**
+ * Provides common logic for things like sifting through inbound message headers and arriving at process variable candidates
+ */
+ private ProcessSupport processSupport = new ProcessSupport();
+ private String beanName;
+
+ @SuppressWarnings("unused")
+ public void setPlatformTransactionManager(PlatformTransactionManager platformTransactionManager) {
+ this.platformTransactionManager = platformTransactionManager;
+ }
+
+ @SuppressWarnings("unused")
+ public void setRequestChannel(MessageChannel requestChannel) {
+ this.requestChannel = requestChannel;
+ }
+
+ @SuppressWarnings("unused")
+ public void setReplyChannel(MessageChannel replyChannel) {
+ this.replyChannel = replyChannel;
+ }
+
+ @SuppressWarnings("unused")
+ public void setProcessEngine(ProcessEngine processEngine) {
+ this.processEngine = processEngine;
+ }
+
+ @SuppressWarnings("unused")
+ public void setForwardProcessVariablesAsMessageHeaders(boolean forwardProcessVariablesAsMessageHeaders) {
+ this.forwardProcessVariablesAsMessageHeaders = forwardProcessVariablesAsMessageHeaders;
+ }
+
+ @SuppressWarnings("unused")
+ public void setUpdateProcessVariablesFromReplyMessageHeaders(boolean updateProcessVariablesFromReplyMessageHeaders) {
+ this.updateProcessVariablesFromReplyMessageHeaders = updateProcessVariablesFromReplyMessageHeaders;
+ }
+
+ public void setBeanFactory(BeanFactory beanFactory)
+ throws BeansException {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public void signal(ActivityExecution execution, String signalName, Object data) throws Exception {
+
+ if (data instanceof MessageHeaders) {
+ MessageHeaders messageHeaders = (MessageHeaders) data;
+
+ if (this.updateProcessVariablesFromReplyMessageHeaders) {
+ Map vars = execution .getVariables();
+ Set existingVars = vars.keySet();
+
+ Map procVars = this.processSupport.processVariablesFromMessageHeaders(existingVars, messageHeaders);
+
+ for (String key : procVars.keySet())
+ execution.setVariable(key, procVars.get(key));
+ }
+ }
+
+ super.signal(execution, signalName, data);
+ }
+
+ /**
+ * This is the main interface method from {@link ActivityBehavior}. It will be called when the BPMN process executes the node referencing this logic.
+ *
+ * @param execution the {@link ActivityExecution} as given to use by the engine
+ * @throws Exception
+ */
+ public void execute(ActivityExecution execution) throws Exception {
+ String executionId = execution.getId();
+
+ MessageBuilder> messageBuilder = MessageBuilder.withPayload(execution).setHeader(ActivitiConstants.WELL_KNOWN_EXECUTION_ID_HEADER_KEY, executionId).setCorrelationId(executionId);
+
+ if (this.forwardProcessVariablesAsMessageHeaders) {
+ Map variables = execution.getVariables();
+
+ if ((variables != null) && (variables.size() > 0)) {
+ messageBuilder = messageBuilder.copyHeadersIfAbsent(variables);
+ }
+ }
+
+ Message> msg = messageBuilder.setReplyChannel(replyChannel).build();
+
+ this.messagingTemplate.send(this.requestChannel, msg);
+ }
+
+ /**
+ * Verify the presence of references to a request and reply {@link org.springframework.integration.MessageChannel},
+ * the {@link ProcessEngine}, and setup the {@link org.springframework.integration.core.MessageHandler} that handles the replies
+ *
+ * @throws Exception
+ */
+ public void afterPropertiesSet() throws Exception {
+ Assert.state(this.replyChannel != null, "'replyChannel' can't be null!");
+ Assert.state(this.requestChannel != null, "'requestChannel' can't be null!");
+ Assert.state(this.processEngine != null, "'processEngine' can't be null!");
+
+ processService = this.processEngine.getRuntimeService();
+
+ if (this.platformTransactionManager != null) {
+ this.messagingTemplate.setTransactionManager(this.platformTransactionManager);
+ }
+
+ MessageHandler handler = new ReplyMessageHandler();
+
+ PollerMetadata pollerMetadata = new PollerMetadata();
+ pollerMetadata.setReceiveTimeout(-1);
+ pollerMetadata.setTransactionManager(this.platformTransactionManager);
+ pollerMetadata.setTrigger(new PeriodicTrigger(10));
+
+ ConsumerEndpointFactoryBean consumerEndpointFactoryBean = new ConsumerEndpointFactoryBean();
+ consumerEndpointFactoryBean.setAutoStartup(false);
+
+ if (this.replyChannel instanceof PollableChannel)
+ consumerEndpointFactoryBean.setPollerMetadata(pollerMetadata);
+
+ consumerEndpointFactoryBean.setBeanFactory(this.beanFactory);
+ consumerEndpointFactoryBean.setHandler(handler);
+ consumerEndpointFactoryBean.setInputChannel(this.replyChannel);
+ consumerEndpointFactoryBean.setBeanName(this.beanName);
+
+ AbstractEndpoint correlator = consumerEndpointFactoryBean.getObject();
+
+ if (correlator != null) {
+ correlator.start();
+ }
+ }
+
+ public void setBeanName(String s) {
+ this.beanName = s;
+ }
+
+ /**
+ * This class listens for results on the reply channel and causes the flow of execution to proceed inside the business process
+ */
+ class ReplyMessageHandler implements MessageHandler {
+ public void handleMessage(Message> message) throws MessageHandlingException, MessageDeliveryException {
+ try {
+ MessageHeaders messageHeaders = message.getHeaders();
+ String executionId = (String) message.getHeaders().get(ActivitiConstants.WELL_KNOWN_EXECUTION_ID_HEADER_KEY);
+ Execution execution = processService.findExecutionById(executionId);
+ processEngine.getRuntimeService().signal(execution.getId(), AsyncActivityBehaviorMessagingGateway.class.getName() , messageHeaders);
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }
+ }
+}
diff --git a/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/SyncActivityBehaviorMessagingGateway.java b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/SyncActivityBehaviorMessagingGateway.java
new file mode 100644
index 0000000000..558f9e594f
--- /dev/null
+++ b/spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/SyncActivityBehaviorMessagingGateway.java
@@ -0,0 +1,272 @@
+/* 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.activiti.gateway;
+
+
+import org.activiti.engine.ProcessEngine;
+import org.activiti.engine.RuntimeService;
+import org.activiti.engine.impl.runtime.ExecutionEntity;
+import org.activiti.engine.runtime.Execution;
+import org.activiti.pvm.activity.ActivityBehavior;
+import org.activiti.pvm.activity.ActivityExecution;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.integration.*;
+import org.springframework.integration.activiti.ActivitiConstants;
+import org.springframework.integration.activiti.ProcessSupport;
+import org.springframework.integration.config.ConsumerEndpointFactoryBean;
+import org.springframework.integration.core.*;
+import org.springframework.integration.endpoint.AbstractEndpoint;
+import org.springframework.integration.scheduling.PollerMetadata;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.scheduling.support.PeriodicTrigger;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.util.Assert;
+
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * This class is plugged into an Activiti workflow. let's us plugin a custom {@link ActivityBehavior}.
+ * We need to build an {@link ActivityBehavior} that can send and receive the message,
+ * propagating the {@code executionId} and potentially process variables/ header variables.
+ *
+ * Simply svn co http://svn.codehaus.org/activiti/activiti/branches/alpha4-spring-integration-adapter that repository and then mvn clean install it.
+ *
+ * The class forwards ("pushes") control from a BPM process (in-thread) to a Spring Integration channel where, of course, Spring Integration acts as a client and
+ * can push the execution forward anyway it wants to.
+ *
+ * Possible use cases include forwarding the job through an outbound JMS adapter or gateway, forwarding the job through an XMPP adapter, or forwarding the job through
+ * to an outbound email adapter.
+ *
+ *
+ * The only requirement for the reply is that the {@link org.springframework.integration.Message} arrive on the #replyChannel and that it contain a header of
+ * {@link org.springframework.integration.activiti.ActivitiConstants#WELL_KNOWN_EXECUTION_ID_HEADER_KEY} (which the outbound {@link org.springframework.integration.Message} will have)
+ * so that the Activiti runtime can signal that execution has completed successfully.
+ *
+ * Thanks to Dave Syer and Tom Baeyens for the help brainstorming.
+ *
+ * This is very much like {@link org.springframework.integration.activiti.gateway.AsyncActivityBehaviorMessagingGateway} except that it assumes that
+ * the request/reply sequence will happen in the same Activiti transaction, so don't keep it waiting! Use the {@link org.springframework.integration.activiti.gateway.AsyncActivityBehaviorMessagingGateway}
+ * which is modeled as a wait-state and deals perfectly with asynchronous continuations.
+ *
+ * @author Josh Long
+ * @see org.activiti.engine.impl.bpmn.ReceiveTaskActivity the {@link org.activiti.pvm.activity.ActivityBehavior} impl that ships w/ Activiti that has the machinery to wake up when signaled
+ * @see org.activiti.engine.ProcessEngine the process engine instance is required to be able to use this namespace
+ * @see org.activiti.engine.impl.cfg.spring.ProcessEngineFactoryBean - use this class to create the aforementioned ProcessEngine instance!
+ */
+public class SyncActivityBehaviorMessagingGateway implements BeanFactoryAware, BeanNameAware, ActivityBehavior, InitializingBean {
+ /**
+ * Used to handle sending in a standard way
+ */
+ private MessagingTemplate messagingTemplate = new MessagingTemplate();
+
+ /**
+ * This is the channel on which we expect requests - {@link org.activiti.engine.runtime.Execution}s from Activiti - to arrive
+ */
+ private volatile MessageChannel requestChannel;
+
+ /**
+ * This is the channel on which we expect to send replies - ie, the result of our work in
+ * Spring Integration - back to Activiti, which should be waiting for the results
+ */
+ private volatile MessageChannel replyChannel;
+
+ /**
+ * Injected from Spring or some other mechanism. Recommended approach is through a {@link org.activiti.engine.impl.cfg.spring.ProcessEngineFactoryBean}
+ */
+ private volatile ProcessEngine processEngine;
+
+ /**
+ * Should we update the process variables based on the reply {@link org.springframework.integration.Message}'s {@link org.springframework.integration.MessageHeaders}?
+ */
+ private volatile boolean updateProcessVariablesFromReplyMessageHeaders = false;
+
+ /**
+ * Should we pass the workflow process variables as message headers when we send a message into the Spring Integration framework?
+ */
+ private volatile boolean forwardProcessVariablesAsMessageHeaders = false;
+
+ /**
+ * Forwarded to the {@link org.springframework.integration.core.MessagingTemplate} instance.
+ */
+ private volatile PlatformTransactionManager platformTransactionManager;
+
+ /**
+ * A reference to the {@link org.springframework.beans.factory.BeanFactory} that's hosting this component. Spring will inject this reference automatically assuming
+ * this object is hosted in a Spring context.
+ */
+ private volatile BeanFactory beanFactory;
+
+ /**
+ * The process engine instance that controls the Activiti PVM.
+ */
+ private RuntimeService runtimeService;
+
+ /**
+ * Provides common logic for things like sifting through inbound message headers and arriving at process variable candidates
+ */
+ private ProcessSupport processSupport = new ProcessSupport();
+ private String beanName;
+
+ @SuppressWarnings("unused")
+ public void setPlatformTransactionManager(PlatformTransactionManager platformTransactionManager) {
+ this.platformTransactionManager = platformTransactionManager;
+ }
+
+ @SuppressWarnings("unused")
+ public void setRequestChannel(MessageChannel requestChannel) {
+ this.requestChannel = requestChannel;
+ }
+
+ @SuppressWarnings("unused")
+ public void setReplyChannel(MessageChannel replyChannel) {
+ this.replyChannel = replyChannel;
+ }
+
+ @SuppressWarnings("unused")
+ public void setProcessEngine(ProcessEngine processEngine) {
+ this.processEngine = processEngine;
+ }
+
+ @SuppressWarnings("unused")
+ public void setForwardProcessVariablesAsMessageHeaders(boolean forwardProcessVariablesAsMessageHeaders) {
+ this.forwardProcessVariablesAsMessageHeaders = forwardProcessVariablesAsMessageHeaders;
+ }
+
+ @SuppressWarnings("unused")
+ public void setUpdateProcessVariablesFromReplyMessageHeaders(boolean updateProcessVariablesFromReplyMessageHeaders) {
+ this.updateProcessVariablesFromReplyMessageHeaders = updateProcessVariablesFromReplyMessageHeaders;
+ }
+
+ public void setBeanFactory(BeanFactory beanFactory)
+ throws BeansException {
+ this.beanFactory = beanFactory;
+ }
+
+ /**
+ * This is the main interface method from {@link ActivityBehavior}. It will be called when the BPMN process executes the node referencing this logic.
+ *
+ * @param execution the {@link ActivityExecution} as given to use by the engine
+ * @throws Exception
+ */
+ public void execute(ActivityExecution execution) throws Exception {
+ ExecutionEntity dbExecution = ((ExecutionEntity) execution);
+ String executionId = dbExecution.getId();
+ MessageBuilder> messageBuilder = MessageBuilder.withPayload(execution)
+ .setHeader(ActivitiConstants.WELL_KNOWN_EXECUTION_ID_HEADER_KEY, executionId)
+ .setCorrelationId(executionId);
+
+ if (this.forwardProcessVariablesAsMessageHeaders) {
+ Map variables = dbExecution.getVariables();
+
+ if ((variables != null) && (variables.size() > 0)) {
+ messageBuilder = messageBuilder.copyHeadersIfAbsent(variables);
+ }
+ }
+
+ Message> msg = messageBuilder.setReplyChannel(replyChannel).build();
+ Message> response = this.messagingTemplate.sendAndReceive(requestChannel, msg);
+
+ handleReply(dbExecution, response);
+
+ }
+
+ /**
+ * The transaction won't have committed, so there's simply no need to
+ * @param execution the execution
+ * @param msg the inbound message
+ * @throws Exception escape hatch exception
+ */
+ protected void handleReply(ExecutionEntity execution, Message> msg)
+ throws Exception {
+ MessageHeaders messageHeaders = msg.getHeaders();
+
+ if (this.updateProcessVariablesFromReplyMessageHeaders) {
+ Map vars = execution.getVariables();
+ Set existingVars = vars.keySet();
+ Map procVars = this.processSupport.processVariablesFromMessageHeaders(existingVars, messageHeaders);
+
+ for (String varName : procVars.keySet())
+ execution.getProcessInstance().setVariable(varName, procVars.get(varName));
+ }
+ }
+
+ /**
+ * Verify the presence of references to a request and reply {@link MessageChannel},
+ * the {@link ProcessEngine}, and setup the {@link org.springframework.integration.core.MessageHandler} that handles the replies
+ *
+ * @throws Exception
+ */
+ public void afterPropertiesSet() throws Exception {
+ Assert.state(this.replyChannel != null, "'replyChannel' can't be null!");
+ Assert.state(this.requestChannel != null, "'requestChannel' can't be null!");
+ Assert.state(this.processEngine != null, "'processEngine' can't be null!");
+
+ runtimeService = this.processEngine.getRuntimeService();
+
+ if (this.platformTransactionManager != null) {
+ this.messagingTemplate.setTransactionManager(this.platformTransactionManager);
+ }
+
+ MessageHandler handler = new ReplyMessageHandler();
+
+ PollerMetadata pollerMetadata = new PollerMetadata();
+ pollerMetadata.setReceiveTimeout(-1);
+ pollerMetadata.setTransactionManager(this.platformTransactionManager);
+ pollerMetadata.setTrigger(new PeriodicTrigger(10));
+
+ ConsumerEndpointFactoryBean consumerEndpointFactoryBean = new ConsumerEndpointFactoryBean();
+ consumerEndpointFactoryBean.setAutoStartup(false);
+
+ if (this.replyChannel instanceof PollableChannel) {
+ consumerEndpointFactoryBean.setPollerMetadata(pollerMetadata);
+ }
+
+ consumerEndpointFactoryBean.setBeanFactory(this.beanFactory);
+ consumerEndpointFactoryBean.setHandler(handler);
+ consumerEndpointFactoryBean.setInputChannel(this.replyChannel);
+ consumerEndpointFactoryBean.setBeanName(this.beanName);
+
+ AbstractEndpoint correlator = consumerEndpointFactoryBean.getObject();
+
+ if (correlator != null) {
+ correlator.start();
+ }
+ }
+
+ public void setBeanName(String s) {
+ this.beanName = s;
+ }
+
+ /**
+ * This class listens for results on the reply channel and causes the flow of execution to proceed inside the business process
+ */
+ class ReplyMessageHandler implements MessageHandler {
+ public void handleMessage(Message> message) throws MessageHandlingException, MessageDeliveryException {
+ try {
+ MessageHeaders messageHeaders = message.getHeaders();
+ String executionId = (String) message.getHeaders().get(ActivitiConstants.WELL_KNOWN_EXECUTION_ID_HEADER_KEY);
+ Execution execution = runtimeService.findExecutionById(executionId);
+ processEngine.getRuntimeService().signal(execution.getId(), StringUtils.EMPTY, messageHeaders);
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }
+ }
+}
diff --git a/spring-integration-activiti/src/main/resources/META-INF/MANIFEST.MF b/spring-integration-activiti/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/spring-integration-activiti/src/main/resources/META-INF/spring.handlers b/spring-integration-activiti/src/main/resources/META-INF/spring.handlers
new file mode 100644
index 0000000000..57bcf69e07
--- /dev/null
+++ b/spring-integration-activiti/src/main/resources/META-INF/spring.handlers
@@ -0,0 +1 @@
+http\://www.springframework.org/schema/integration/activiti=org.springframework.integration.activiti.config.ActivitiNamespaceHandler
\ No newline at end of file
diff --git a/spring-integration-activiti/src/main/resources/META-INF/spring.schemas b/spring-integration-activiti/src/main/resources/META-INF/spring.schemas
new file mode 100644
index 0000000000..31d7c9a8a5
--- /dev/null
+++ b/spring-integration-activiti/src/main/resources/META-INF/spring.schemas
@@ -0,0 +1,2 @@
+http\://www.springframework.org/schema/integration/activiti/spring-integration-activiti-2.0.xsd=org/springframework/integration/activiti/config/spring-integration-activiti-2.0.xsd
+http\://www.springframework.org/schema/integration/activiti/spring-integration-activiti.xsd=org/springframework/integration/activiti/config/spring-integration-activiti-2.0.xsd
diff --git a/spring-integration-activiti/src/main/resources/org/springframework/integration/activiti/config/spring-integration-activiti-2.0.xsd b/spring-integration-activiti/src/main/resources/org/springframework/integration/activiti/config/spring-integration-activiti-2.0.xsd
new file mode 100644
index 0000000000..01b76f170d
--- /dev/null
+++ b/spring-integration-activiti/src/main/resources/org/springframework/integration/activiti/config/spring-integration-activiti-2.0.xsd
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines an inbound Activiti Messaging Gateway.
+
+ This bean should be referenced from a BPMN workflow artifact. Once entered, execution of the task will be dispatched to Spring Integration
+ where Spring Integration can then do whatever it likes so long as execution ultimately travels through the 'reply-channel'.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/GatewayTest.java b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/GatewayTest.java
new file mode 100644
index 0000000000..c062de97bf
--- /dev/null
+++ b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/GatewayTest.java
@@ -0,0 +1,48 @@
+/* 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.activiti;
+
+import org.activiti.engine.ProcessEngine;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * This component demonstrates creating a {@link org.springframework.integration.activiti.gateway.AsyncActivityBehaviorMessagingGateway} (factoried from Spring)
+ * and exposed for use in a BPMN 2 process.
+ *
+ * @author Josh Long
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class GatewayTest extends AbstractJUnit4SpringContextTests {
+
+ @Autowired private ProcessEngine processEngine ;
+
+ @Test
+ public void testGateway() throws Throwable {
+ processEngine.getRepositoryService().createDeployment().addClasspathResource("processes/si_gateway_example.bpmn20.xml").deploy();
+ Map vars = new HashMap();
+ vars.put("customerId", 232);
+ processEngine.getRuntimeService().startProcessInstanceByKey("sigatewayProcess", vars);
+ Thread.sleep(10000);
+
+ }
+}
diff --git a/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/OutboundAdapterTest.java b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/OutboundAdapterTest.java
new file mode 100644
index 0000000000..e3f3c0ca44
--- /dev/null
+++ b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/OutboundAdapterTest.java
@@ -0,0 +1,44 @@
+/* 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.activiti;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.integration.MessageChannel;
+import org.springframework.integration.core.MessagingTemplate;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+
+public class OutboundAdapterTest {
+
+
+ @Value( "#{tc}")
+ private MessageChannel messageChannel ;
+
+ private MessagingTemplate messagingTemplate = new MessagingTemplate();
+
+ @Test
+ public void testOutboundAdapter() throws Throwable {
+ this.messagingTemplate.send( this.messageChannel , MessageBuilder.withPayload( "hello, from "+ System.currentTimeMillis())
+ .setHeader( ActivitiConstants.WELL_KNOWN_PROCESS_DEFINITION_NAME_HEADER_KEY+"customerId",2324).build());
+ }
+
+}
diff --git a/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/AnnouncingServiceActivator.java b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/AnnouncingServiceActivator.java
new file mode 100644
index 0000000000..71ac909fbd
--- /dev/null
+++ b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/AnnouncingServiceActivator.java
@@ -0,0 +1,24 @@
+/* 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.activiti.impls;
+
+import org.springframework.integration.Message;
+import org.springframework.integration.annotation.ServiceActivator;
+
+
+public class AnnouncingServiceActivator {
+ @ServiceActivator
+ public void hello(Message> msg) throws Throwable {
+ System.out.println("got it ");
+ }
+}
diff --git a/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/DelayedchoServiceActivator.java b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/DelayedchoServiceActivator.java
new file mode 100644
index 0000000000..636b7bd34d
--- /dev/null
+++ b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/DelayedchoServiceActivator.java
@@ -0,0 +1,43 @@
+/* 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.activiti.impls;
+
+import org.springframework.integration.Message;
+import org.springframework.integration.activiti.ActivitiConstants;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.support.MessageBuilder;
+
+
+/**
+ * This is a trivial component that demonstrates that the flow of control lives outside of the BPMN process (ie, that we have truly implemented a wait-state)
+ * and that gateway does the right thing if you send back message headers whose key corresponds with process variables, or new message headers that you want as process variables.
+ *
+ * The only requirement for any component wishing to reply to an Activiti business process is that there be a header named using the static variable
+ * {@link org.springframework.integration.activiti.ActivitiConstants#WELL_KNOWN_EXECUTION_ID_HEADER_KEY}.
+ *
+ * @author Josh Long
+ */
+public class DelayedchoServiceActivator {
+
+ @ServiceActivator
+ public Message> sayHello(Message> requestComingFromActiviti)
+ throws Throwable {
+ System.out.println("entering ServiceActivator:sayHello");
+ Thread.sleep(5 * 1000);
+ System.out.println("exiting ServiceActivator:sayHello");
+ return MessageBuilder.withPayload(requestComingFromActiviti.getPayload()).
+ copyHeadersIfAbsent(requestComingFromActiviti.getHeaders()).setHeader(
+ ActivitiConstants.WELL_KNOWN_SPRING_INTEGRATION_HEADER_PREFIX + "test", "1 + 1").
+ build();
+ }
+}
diff --git a/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/SimpleCustomActivityBehavior.java b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/SimpleCustomActivityBehavior.java
new file mode 100644
index 0000000000..e7bc049383
--- /dev/null
+++ b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/SimpleCustomActivityBehavior.java
@@ -0,0 +1,49 @@
+/* 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.activiti.impls;
+
+import org.activiti.engine.impl.runtime.ExecutionEntity;
+
+import org.activiti.pvm.activity.ActivityBehavior;
+import org.activiti.pvm.activity.ActivityExecution;
+import org.activiti.pvm.process.PvmTransition;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.List;
+
+
+/**
+ * A simple component that implements {@link ActivityBehavior}
+ *
+ * @author Josh Long
+ */
+public class SimpleCustomActivityBehavior implements ActivityBehavior, InitializingBean {
+ public void execute(ActivityExecution dbExecution)
+ throws Exception {
+ System.out.println("Hello from a custom ActivityBehavior hosted in the Spring context. " + this);
+
+
+ for (String varName : dbExecution.getVariables().keySet())
+ System.out.println(varName + "=" + dbExecution.getVariable(varName));
+
+
+ List transitions = dbExecution.getActivity().getOutgoingTransitions();
+
+ dbExecution.take(((null == transitions) || (transitions.size() == 0)) ? null : transitions.get(0));
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ System.out.println("Starting " + SimpleCustomActivityBehavior.class.getName());
+ }
+}
diff --git a/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/SimpleProcessTriggeringMessageSource.java b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/SimpleProcessTriggeringMessageSource.java
new file mode 100644
index 0000000000..0332686c67
--- /dev/null
+++ b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/SimpleProcessTriggeringMessageSource.java
@@ -0,0 +1,45 @@
+/* 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.activiti.impls;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.integration.Message;
+import org.springframework.integration.activiti.ActivitiConstants;
+import org.springframework.integration.core.MessageSource;
+import org.springframework.integration.support.MessageBuilder;
+
+
+/**
+ * Triggers a BPMN process that has process variables.
+ *
+ * @author Josh Long
+ */
+public class SimpleProcessTriggeringMessageSource implements MessageSource {
+
+ private final Log logger = LogFactory.getLog(SimpleProcessTriggeringMessageSource.class.getName());
+
+ public Message receive() {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ logger.debug("couldn't sleep 1000 ms", e);
+ }
+ return MessageBuilder.withPayload(
+ "hello from " + System.currentTimeMillis()
+ ).
+ setHeader(ActivitiConstants.WELL_KNOWN_SPRING_INTEGRATION_HEADER_PREFIX + "customerId", 232).
+ setHeader( ActivitiConstants.WELL_KNOWN_PROCESS_DEFINITION_NAME_HEADER_KEY, "helloWorldProcess" ).build();
+
+ }
+}
diff --git a/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/GatewayTest-context.xml b/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/GatewayTest-context.xml
new file mode 100644
index 0000000000..bc0bef42c4
--- /dev/null
+++ b/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/GatewayTest-context.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/OutboundAdapterTest-context.xml b/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/OutboundAdapterTest-context.xml
new file mode 100644
index 0000000000..e802cd7c19
--- /dev/null
+++ b/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/OutboundAdapterTest-context.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ classpath:/processes/hello-world.bpmn20.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-activiti/src/test/resources/processes/hello-world.bpmn20.xml b/spring-integration-activiti/src/test/resources/processes/hello-world.bpmn20.xml
new file mode 100644
index 0000000000..4fa42827e3
--- /dev/null
+++ b/spring-integration-activiti/src/test/resources/processes/hello-world.bpmn20.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-activiti/src/test/resources/processes/si_gateway_example.bpmn20.xml b/spring-integration-activiti/src/test/resources/processes/si_gateway_example.bpmn20.xml
new file mode 100644
index 0000000000..475d1ff93d
--- /dev/null
+++ b/spring-integration-activiti/src/test/resources/processes/si_gateway_example.bpmn20.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-activiti/template.mf b/spring-integration-activiti/template.mf
new file mode 100644
index 0000000000..d92598ec33
--- /dev/null
+++ b/spring-integration-activiti/template.mf
@@ -0,0 +1,15 @@
+Bundle-SymbolicName: org.springframework.integration.activiti
+Bundle-Name: Spring Integration Activiti Support
+Bundle-Vendor: SpringSource
+Bundle-ManifestVersion: 2
+Import-Template:
+ org.apache.commons.logging;version="[1.1.1, 2.0.0)",
+ org.apache.commons.lang.*;version="[2.5.0, 3.0.0)",
+ org.springframework.integration.*;version="[2.0.0, 2.0.1)",
+ org.springframework.beans.*;version="[3.0.0, 4.0.0)",
+ org.springframework.context;version="[3.0.0, 4.0.0)",
+ org.springframework.core.*;version="[3.0.0, 4.0.0)",
+ org.springframework.util;version="[3.0.0, 4.0.0)",
+ org.jivesoftware.*;version="[3.1.0, 4.0.0)",
+ javax.*;version="0",
+ org.w3c.dom.*;version="0"
diff --git a/src/assembly/distribution.xml b/src/assembly/distribution.xml
index 6b3326c9a5..5463bf3959 100644
--- a/src/assembly/distribution.xml
+++ b/src/assembly/distribution.xml
@@ -82,6 +82,7 @@
org.springframework.integration:spring-integration-sftp
org.springframework.integration:spring-integration-ftp
org.springframework.integration:spring-integration-twitter
+ org.springframework.integration:spring-integration-activiti
dist