adding the sandbox project for spring-integration-activiti

This commit is contained in:
Josh Long
2010-09-03 21:56:13 -07:00
parent 38cc72ac6b
commit f9e061a6c7
25 changed files with 1603 additions and 0 deletions

View File

@@ -29,6 +29,7 @@
<module>spring-integration-ftp</module>
<module>spring-integration-sftp</module>
<module>spring-integration-twitter</module>
<module>spring-integration-activiti</module>
</modules>
<scm>
<connection>scm:svn:https://src.springframework.org/svn/spring-integration/trunk</connection>

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-parent</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-activiti</artifactId>
<packaging>jar</packaging>
<name>Spring Integration Activiti Support</name>
<dependencies>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-event</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jms</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>3.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.2.132</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>5.0.beta1</version>
</dependency>
</dependencies>
<build>
<!-- <plugins>
<plugin>
<groupId>com.springsource.bundlor</groupId>
<artifactId>com.springsource.bundlor.maven</artifactId>
</plugin>
</plugins> -->
</build>
</project>

View File

@@ -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 <code>executionId</code> 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 <code>processDefinitionName</code> up. This value will be used to spawn
*/
public static final String WELL_KNOWN_PROCESS_DEFINITION_NAME_HEADER_KEY = "activiti_spring_integration_processDefinitionName";
}

View File

@@ -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<String> blackList;
public ProcessSupport() {
this.blackList = new ConcurrentSkipListSet<String>(Arrays.asList(ActivitiConstants.WELL_KNOWN_EXECUTION_ID_HEADER_KEY, ActivitiConstants.WELL_KNOWN_PROCESS_DEFINITION_NAME_HEADER_KEY));
}
public Map<String, Object> processVariablesFromMessageHeaders(MessageHeaders msg) {
return this.processVariablesFromMessageHeaders(new HashSet<String>(), msg);
}
public Map<String, Object> processVariablesFromMessageHeaders(Set<String> whiteListOfMustCopyMessageHeaderKeyNames, MessageHeaders msgHeaders) {
Map<String, Object> procVars = new HashMap<String, Object>();
Set<String> headers = msgHeaders.keySet();
Set<String> wl = (whiteListOfMustCopyMessageHeaderKeyNames == null) ? new HashSet<String>() : 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;
}
}

View File

@@ -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}.
* <p/>
* The component also supports propagating headers as processVariables.
* <p/>
* This support is simialar to the classic EIP book's "Process Manager" pattern.
* <p/>
* 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<String, Object> 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);
}
}

View File

@@ -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 <em>from</em> Acitviti <em>to</em> 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");
}
}
}

View File

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

View File

@@ -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. <serviceTask /> 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.
* <p/>
* Simply <code>svn co http://svn.codehaus.org/activiti/activiti/branches/alpha4-spring-integration-adapter</code> that repository and then <code>mvn clean install </code> it.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* <p/>
* 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.
* <p/>
* 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<String, Object> vars = execution .getVariables();
Set<String> existingVars = vars.keySet();
Map<String, Object> 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<String, Object> 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);
}
}
}
}

View File

@@ -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. <serviceTask /> 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.
* <p/>
* Simply <code>svn co http://svn.codehaus.org/activiti/activiti/branches/alpha4-spring-integration-adapter</code> that repository and then <code>mvn clean install </code> it.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* <p/>
* 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.
* <p/>
* Thanks to Dave Syer and Tom Baeyens for the help brainstorming.
* <p/>
* 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<String, Object> 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<String, Object> vars = execution.getVariables();
Set<String> existingVars = vars.keySet();
Map<String, Object> 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);
}
}
}
}

View File

@@ -0,0 +1 @@
http\://www.springframework.org/schema/integration/activiti=org.springframework.integration.activiti.config.ActivitiNamespaceHandler

View File

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

View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<xsd:schema xmlns="http://www.springframework.org/schema/integration/activiti"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:integration="http://www.springframework.org/schema/integration"
targetNamespace="http://www.springframework.org/schema/integration/activiti"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
<xsd:import namespace="http://www.springframework.org/schema/integration" schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.0.xsd"/>
<!--
outbound adapter
-->
<xsd:element name="outbound-channel-adapter">
<xsd:annotation>
<xsd:documentation><![CDATA[
Builds an outbound-channel-adapter that writes files to a remote FTP endpoint.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="channel" use="required" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.core.MessageChannel"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="process-engine" use="required" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.activiti.ProcessEngine"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="process-definition-name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="inbound-gateway">
<xsd:annotation>
<xsd:documentation>
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'.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:ID"/>
<xsd:attribute name="update-process-variables-from-reply-message-headers" type="xsd:boolean"/>
<xsd:attribute name="async" type="xsd:boolean"/>
<xsd:attribute name="forward-process-variables-as-message-headers" type="xsd:boolean"/>
<xsd:attribute name="request-channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.core.MessageChannel"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reply-channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.core.MessageChannel"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="process-engine" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.activiti.ProcessEngine"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -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<String, Object> vars = new HashMap<String, Object>();
vars.put("customerId", 232);
processEngine.getRuntimeService().startProcessInstanceByKey("sigatewayProcess", vars);
Thread.sleep(10000);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String> {
private final Log logger = LogFactory.getLog(SimpleProcessTriggeringMessageSource.class.getName());
public Message<String> 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();
}
}

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:activiti="http://www.springframework.org/schema/integration/activiti"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/integration/activiti http://www.springframework.org/schema/integration/activiti/spring-integration-activiti-2.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
<!--
==========================================================
Context Globals
==========================================================
-->
<context:annotation-config/>
<int:poller default="true">
<int:interval-trigger interval="1000"/>
</int:poller>
<bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<property name="targetDataSource">
<bean class="org.apache.commons.dbcp.BasicDataSource"
p:url="jdbc:h2:tcp://localhost/~/activiti_example"
p:driverClassName="org.h2.Driver"
p:username="sa"
p:password=""
/>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<bean id="processEngine" class="org.activiti.engine.impl.cfg.spring.ProcessEngineFactoryBean"
p:dataSource-ref="dataSource"
p:transactionManager-ref="transactionManager"
p:dbSchemaStrategy="DROP_CREATE"
/>
<!--
==========================================================
Spring Integration Flow
==========================================================
-->
<!-- Activiti sends a message 'to' (inbound) Spring Integration 'from' Activiti -->
<activiti:inbound-gateway
process-engine="processEngine"
request-channel="request"
async="true"
reply-channel="response"
forward-process-variables-as-message-headers="true"
update-process-variables-from-reply-message-headers="true"
id="gateway"
/>
<!-- the message comes 'into' Spring Integration from 'request' channel -->
<int:channel id="request">
<int:queue capacity="10"/>
</int:channel>
<!-- where the service activator eventually reads the request channel and does some processing
(though, of course, this may as well have been a JMS adapter or an email adapter). Finally the response is created and sent on..
-->
<bean id="echoServiceActivator" class="org.springframework.integration.activiti.impls.DelayedchoServiceActivator"/>
<int:service-activator input-channel="request" ref="echoServiceActivator" output-channel="response"/>
<!--the response channel -->
<int:channel id="response"/>
</beans>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:activiti="http://www.springframework.org/schema/integration/activiti"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/integration/activiti http://www.springframework.org/schema/integration/activiti/spring-integration-activiti-2.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
<!--
==========================================================
Context Globals
==========================================================
-->
<context:annotation-config/>
<int:poller default="true">
<int:interval-trigger interval="1000"/>
</int:poller>
<bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<property name="targetDataSource">
<bean class="org.apache.commons.dbcp.BasicDataSource"
p:url="jdbc:h2:tcp://localhost/~/activiti_example"
p:driverClassName="org.h2.Driver"
p:username="sa"
p:password=""
/>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<bean id="processEngine" class="org.activiti.engine.impl.cfg.spring.ProcessEngineFactoryBean"
p:dataSource-ref="dataSource"
p:transactionManager-ref="transactionManager"
p:dbSchemaStrategy="DROP_CREATE"
>
<property name="deploymentResources">
<array>
<value>classpath:/processes/hello-world.bpmn20.xml</value>
</array>
</property>
</bean>
<bean lazy-init="true" class="org.springframework.integration.activiti.impls.SimpleCustomActivityBehavior" id="activityBehavior"/>
<!--
==========================================================
Spring Integration Flow
==========================================================
-->
<!-- Activiti sends a message 'to' (inbound) Spring Integration 'from' Activiti -->
<bean class="org.springframework.integration.activiti.impls.AnnouncingServiceActivator" id="activator"/>
<int:channel id="tc">
<int:queue capacity="100"/>
</int:channel>
<activiti:outbound-channel-adapter channel="tc" process-engine="processEngine" process-definition-name="helloWorldProcess"/>
</beans>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:activiti="http://activiti.org/bpmn-extensions"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.activiti.org/bpmn2.0">
<process id="helloWorldProcess">
<startEvent id="theStart"/>
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="ab"/>
<serviceTask id="ab" name="Simple POJO delegation" activiti:class="#{activityBehavior}"/>
<sequenceFlow id="flow2" sourceRef="ab" targetRef="theEnd"/>
<endEvent id="theEnd"/>
</process>
</definitions>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:activiti="http://activiti.org/bpmn-extensions"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.activiti.org/bpmn2.0">
<process id="sigatewayProcess">
<startEvent id="theStart"/>
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="sigw"/>
<serviceTask id="sigw" name="Spring Integration Gateway" activiti:class="#{gateway}"/>
<sequenceFlow id="flow2" sourceRef="sigw" targetRef="theEnd"/>
<endEvent id="theEnd"/>
</process>
</definitions>

View File

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

View File

@@ -82,6 +82,7 @@
<include>org.springframework.integration:spring-integration-sftp</include>
<include>org.springframework.integration:spring-integration-ftp</include>
<include>org.springframework.integration:spring-integration-twitter</include>
<include>org.springframework.integration:spring-integration-activiti</include>
</includes>
<binaries>
<outputDirectory>dist</outputDirectory>