From f9e061a6c7c06de54a3ea841d707adccd36b8c56 Mon Sep 17 00:00:00 2001 From: Josh Long Date: Fri, 3 Sep 2010 21:56:13 -0700 Subject: [PATCH 1/5] adding the sandbox project for spring-integration-activiti --- pom.xml | 1 + spring-integration-activiti/pom.xml | 102 +++++++ .../activiti/ActivitiConstants.java | 35 +++ .../integration/activiti/ProcessSupport.java | 55 ++++ .../ActivitiOutboundChannelAdapter.java | 86 ++++++ .../config/ActivitiNamespaceHandler.java | 74 +++++ ...tyBehaviorMessagingGatewayFactoryBean.java | 109 +++++++ ...AsyncActivityBehaviorMessagingGateway.java | 263 +++++++++++++++++ .../SyncActivityBehaviorMessagingGateway.java | 272 ++++++++++++++++++ .../src/main/resources/META-INF/MANIFEST.MF | 0 .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 2 + .../spring-integration-activiti-2.0.xsd | 119 ++++++++ .../integration/activiti/GatewayTest.java | 48 ++++ .../activiti/OutboundAdapterTest.java | 44 +++ .../impls/AnnouncingServiceActivator.java | 24 ++ .../impls/DelayedchoServiceActivator.java | 43 +++ .../impls/SimpleCustomActivityBehavior.java | 49 ++++ .../SimpleProcessTriggeringMessageSource.java | 45 +++ .../activiti/GatewayTest-context.xml | 87 ++++++ .../activiti/OutboundAdapterTest-context.xml | 80 ++++++ .../processes/hello-world.bpmn20.xml | 24 ++ .../processes/si_gateway_example.bpmn20.xml | 24 ++ spring-integration-activiti/template.mf | 15 + src/assembly/distribution.xml | 1 + 25 files changed, 1603 insertions(+) create mode 100644 spring-integration-activiti/pom.xml create mode 100644 spring-integration-activiti/src/main/java/org/springframework/integration/activiti/ActivitiConstants.java create mode 100644 spring-integration-activiti/src/main/java/org/springframework/integration/activiti/ProcessSupport.java create mode 100644 spring-integration-activiti/src/main/java/org/springframework/integration/activiti/adapter/ActivitiOutboundChannelAdapter.java create mode 100644 spring-integration-activiti/src/main/java/org/springframework/integration/activiti/config/ActivitiNamespaceHandler.java create mode 100644 spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/ActivityBehaviorMessagingGatewayFactoryBean.java create mode 100644 spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/AsyncActivityBehaviorMessagingGateway.java create mode 100644 spring-integration-activiti/src/main/java/org/springframework/integration/activiti/gateway/SyncActivityBehaviorMessagingGateway.java create mode 100644 spring-integration-activiti/src/main/resources/META-INF/MANIFEST.MF create mode 100644 spring-integration-activiti/src/main/resources/META-INF/spring.handlers create mode 100644 spring-integration-activiti/src/main/resources/META-INF/spring.schemas create mode 100644 spring-integration-activiti/src/main/resources/org/springframework/integration/activiti/config/spring-integration-activiti-2.0.xsd create mode 100644 spring-integration-activiti/src/test/java/org/springframework/integration/activiti/GatewayTest.java create mode 100644 spring-integration-activiti/src/test/java/org/springframework/integration/activiti/OutboundAdapterTest.java create mode 100644 spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/AnnouncingServiceActivator.java create mode 100644 spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/DelayedchoServiceActivator.java create mode 100644 spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/SimpleCustomActivityBehavior.java create mode 100644 spring-integration-activiti/src/test/java/org/springframework/integration/activiti/impls/SimpleProcessTriggeringMessageSource.java create mode 100644 spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/GatewayTest-context.xml create mode 100644 spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/OutboundAdapterTest-context.xml create mode 100644 spring-integration-activiti/src/test/resources/processes/hello-world.bpmn20.xml create mode 100644 spring-integration-activiti/src/test/resources/processes/si_gateway_example.bpmn20.xml create mode 100644 spring-integration-activiti/template.mf 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 From 95ea2d54eeae9343ea33777b9d54a9ebb24d0537 Mon Sep 17 00:00:00 2001 From: Josh Long Date: Sat, 4 Sep 2010 18:53:18 -0700 Subject: [PATCH 2/5] adding a lot of simple examples of the activiti gateway support. --- .../integration/activiti/CustomerService.java | 37 +++++++++ .../activiti/signup/CheckForm.java | 25 ++++++ .../activiti/signup/SendEmail.java | 17 ++++ .../activiti/signup/SignupClient.java | 81 +++++++++++++++++++ .../activiti/SignupTest-context.xml | 50 ++++++++++++ .../integration/activiti/SimpleContext.xml | 28 +++++++ .../resources/processes/signup.bpmn20.xml | 74 +++++++++++++++++ 7 files changed, 312 insertions(+) create mode 100644 spring-integration-activiti/src/test/java/org/springframework/integration/activiti/CustomerService.java create mode 100644 spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/CheckForm.java create mode 100644 spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/SendEmail.java create mode 100644 spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/SignupClient.java create mode 100644 spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/SignupTest-context.xml create mode 100644 spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/SimpleContext.xml create mode 100644 spring-integration-activiti/src/test/resources/processes/signup.bpmn20.xml diff --git a/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/CustomerService.java b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/CustomerService.java new file mode 100644 index 0000000000..f698ddd6e5 --- /dev/null +++ b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/CustomerService.java @@ -0,0 +1,37 @@ +package org.springframework.integration.activiti; + +import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + + +/** + * Simple bean that you want to use from Activiti. + * You've configured it all from Spring and don't want to + * have to rencode all the configuration inside of Activiti + */ +public class CustomerService { + + private DataSource dataSource ; + private SimpleJdbcTemplate jdbcTemplate; + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + this.jdbcTemplate = new SimpleJdbcTemplate ( dataSource); + } + + public void createCustomer( String firstName, String lastName , String email ){ + Map parms = new HashMap() ; + parms.put( "fn", firstName ) ; + parms.put( "ln", lastName); + parms.put( "email", email ); + + this.jdbcTemplate.update( + "INSERT INTO CUSTOMER( FIRST_NAME, LAST_NAME, EMAIL) VALUES(:fn,:ln,:email)" , + parms ); + } +} + + diff --git a/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/CheckForm.java b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/CheckForm.java new file mode 100644 index 0000000000..55c0482a75 --- /dev/null +++ b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/CheckForm.java @@ -0,0 +1,25 @@ + package org.springframework.integration.activiti.signup; + + import org.activiti.engine.impl.bpmn.BpmnActivityBehavior; + import org.activiti.pvm.activity.ActivityBehavior; + import org.activiti.pvm.activity.ActivityExecution; + import org.springframework.stereotype.Component; + + @Component + public class CheckForm extends BpmnActivityBehavior implements ActivityBehavior { + + public void execute(ActivityExecution activityExecution) throws Exception { + System.out.println( getClass()+" : checking form: "); + + boolean formOK = Math.random() > .7 ; + + activityExecution.setVariable( "formOK", formOK); + System.out.println( getClass()+" : form OK? " + formOK); + + + performDefaultOutgoingBehavior( activityExecution ); + + } + } + + diff --git a/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/SendEmail.java b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/SendEmail.java new file mode 100644 index 0000000000..30ecc648cd --- /dev/null +++ b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/SendEmail.java @@ -0,0 +1,17 @@ +package org.springframework.integration.activiti.signup; + +import org.activiti.engine.impl.bpmn.BpmnActivityBehavior; +import org.activiti.pvm.activity.ActivityBehavior; +import org.activiti.pvm.activity.ActivityExecution; +import org.springframework.stereotype.Component; + + +@Component +public class SendEmail extends BpmnActivityBehavior implements ActivityBehavior { + + public void execute(ActivityExecution activityExecution) throws Exception { + System.out.println( getClass() + ": sending email ") ; + + this.performDefaultOutgoingBehavior( activityExecution); + } +} diff --git a/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/SignupClient.java b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/SignupClient.java new file mode 100644 index 0000000000..34371826a6 --- /dev/null +++ b/spring-integration-activiti/src/test/java/org/springframework/integration/activiti/signup/SignupClient.java @@ -0,0 +1,81 @@ +package org.springframework.integration.activiti.signup; + +import org.activiti.engine.ProcessEngine; +import org.activiti.engine.RepositoryService; +import org.activiti.engine.RuntimeService; +import org.activiti.engine.TaskService; +import org.activiti.engine.task.Task; + +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; + + +@Component +public class SignupClient { + private TaskService taskService; + private RuntimeService runtimeService; + private RepositoryService repositoryService; + + @Resource + private ProcessEngine processEngine; + + @PostConstruct + public void start() throws Exception { + this.taskService = this.processEngine.getTaskService(); + this.repositoryService = this.processEngine.getRepositoryService(); + this.runtimeService = this.processEngine.getRuntimeService(); + + repositoryService.createDeployment().addClasspathResource("processes/signup.bpmn20.xml").deploy(); + } + + private List tasksForUser(String user, String name) + throws Exception { + return taskService.createTaskQuery().name(name).assignee(user).list(); + } + + private boolean handleWorkForUser(String user, String taskName) + throws Exception { + + boolean workToBeDone = false; + for (Task t : tasksForUser(user, taskName)) { + workToBeDone = true ; + System.out.println("starting " + t.getId() + " : " + t.getName()); + taskService.complete(t.getId()); + System.out.println("completed " + t.getId() + " : " + t.getName()); + } + return workToBeDone ; + } + + public void startSignupProcess(long customerId) throws Exception { + Map parms = new HashMap(); + parms.put("customerId", customerId); + + runtimeService.startProcessInstanceByKey("signup", parms); + } + + public static void main(String[] args) throws Throwable { + ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("/org/springframework/integration/activiti/SignupTest-context.xml"); + classPathXmlApplicationContext.start(); + + SignupClient signupClient = classPathXmlApplicationContext.getBean(SignupClient.class); + signupClient.startSignupProcess(System.currentTimeMillis()); + signupClient.handleWorkForUser("customer", "sign-up"); + + while(signupClient.handleWorkForUser( "customer", "fix-errors")) + ; + + signupClient.handleWorkForUser("customer", "confirm-email"); + + + + + } +} diff --git a/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/SignupTest-context.xml b/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/SignupTest-context.xml new file mode 100644 index 0000000000..fc93be5a26 --- /dev/null +++ b/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/SignupTest-context.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/SimpleContext.xml b/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/SimpleContext.xml new file mode 100644 index 0000000000..fe817bb77e --- /dev/null +++ b/spring-integration-activiti/src/test/resources/org/springframework/integration/activiti/SimpleContext.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/spring-integration-activiti/src/test/resources/processes/signup.bpmn20.xml b/spring-integration-activiti/src/test/resources/processes/signup.bpmn20.xml new file mode 100644 index 0000000000..ed059e5078 --- /dev/null +++ b/spring-integration-activiti/src/test/resources/processes/signup.bpmn20.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + the potential customer must fill out the form online first + + + + customer + + + + + + + + + + + + + + ${formOK == true} + + + + ${formOK == false} + + + + + the potential customer must fill out the form online first + + + + customer + + + + + + + + + + + + + confirm the user's email + + + + customer + + + + + + + + + From a30bd927e855f92519532d50b67b5dd45fe22c1e Mon Sep 17 00:00:00 2001 From: Josh Long Date: Sat, 4 Sep 2010 22:23:48 -0700 Subject: [PATCH 3/5] updated code to support building against activiti repositories. --- pom.xml | 5 +++++ spring-integration-activiti/pom.xml | 12 ++++++++++-- spring-integration-activiti/template.mf | 5 ++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index e8531daea7..8842b4868f 100644 --- a/pom.xml +++ b/pom.xml @@ -410,6 +410,11 @@ + + activiti + Activiti.org site + http://maven.alfresco.com/nexus/content/repositories/activiti/ + repository.springframework.maven.release Spring Framework Maven Release Repository diff --git a/spring-integration-activiti/pom.xml b/spring-integration-activiti/pom.xml index 61b22c10a5..a056a4bddd 100644 --- a/spring-integration-activiti/pom.xml +++ b/spring-integration-activiti/pom.xml @@ -91,12 +91,20 @@ 5.0.beta1 - + + - + + + + + activiti + Activiti.org site + http://maven.alfresco.com/nexus/content/repositories/activiti/ + diff --git a/spring-integration-activiti/template.mf b/spring-integration-activiti/template.mf index d92598ec33..c13d3670f4 100644 --- a/spring-integration-activiti/template.mf +++ b/spring-integration-activiti/template.mf @@ -9,7 +9,10 @@ Import-Template: 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.scheduling.*;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)", + org.springframework.transaction.*;version="[3.0.0, 4.0.0)", + org.activiti.engine.*;version="[5.0.0, 6.0.0)", + org.activiti.pvm.*;version="[5.0.0, 6.0.0)", javax.*;version="0", org.w3c.dom.*;version="0" From 0bfa8c81bc8f8b81c525d9d7e79b947e9c5c1ab1 Mon Sep 17 00:00:00 2001 From: Josh Long Date: Thu, 9 Sep 2010 03:13:34 -0700 Subject: [PATCH 4/5] ading files --- ...tyBehaviorMessagingGatewayFactoryBean.java | 66 ++++++++++--------- .../activiti/GatewayTest-context.xml | 3 +- .../activiti/OutboundAdapterTest-context.xml | 8 ++- .../main/webapp/WEB-INF/servlet-config.xml | 11 +++- 4 files changed, 52 insertions(+), 36 deletions(-) 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 index 1a136374ba..e0df2081e7 100644 --- 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 @@ -8,12 +8,15 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.AbstractFactoryBean; import org.springframework.integration.MessageChannel; import org.springframework.transaction.PlatformTransactionManager; -public class ActivityBehaviorMessagingGatewayFactoryBean implements FactoryBean, BeanFactoryAware, BeanNameAware { +public class ActivityBehaviorMessagingGatewayFactoryBean + extends AbstractFactoryBean implements + BeanFactoryAware, BeanNameAware { private volatile boolean updateProcessVariablesFromReplyMessageHeaders = false; private volatile boolean forwardProcessVariablesAsMessageHeaders = false; private volatile PlatformTransactionManager platformTransactionManager; @@ -29,6 +32,38 @@ public class ActivityBehaviorMessagingGatewayFactoryBean implements FactoryBean, this.async = async; } + @Override + protected Object createInstance() 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; + } + } + @SuppressWarnings("unused") public void setProcessEngine(ProcessEngine processEngine) { this.processEngine = processEngine; @@ -65,35 +100,6 @@ public class ActivityBehaviorMessagingGatewayFactoryBean implements FactoryBean, 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; 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 index bc0bef42c4..6294ee9f3c 100644 --- 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 @@ -8,9 +8,10 @@ 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/activiti http://www.springframework.org/schema/integration/activiti/spring-integration-activiti-2.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 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 index e802cd7c19..2788318328 100644 --- 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 @@ -71,10 +71,14 @@ - + - + diff --git a/spring-integration-samples/multipart-http/src/main/webapp/WEB-INF/servlet-config.xml b/spring-integration-samples/multipart-http/src/main/webapp/WEB-INF/servlet-config.xml index 3fe44b98d6..e4ee368bc1 100644 --- a/spring-integration-samples/multipart-http/src/main/webapp/WEB-INF/servlet-config.xml +++ b/spring-integration-samples/multipart-http/src/main/webapp/WEB-INF/servlet-config.xml @@ -10,9 +10,14 @@ xmlns:int-http="http://www.springframework.org/schema/integration/http"> - - - + + + + + From 5683b6b607315eaf740198aa53c7e3bd2f2ee1df Mon Sep 17 00:00:00 2001 From: Josh Long Date: Thu, 14 Oct 2010 11:23:45 -0700 Subject: [PATCH 5/5] adding better citation --- .../file/entries/AcceptAllEntryListFilter.java | 1 + .../file/entries/CompositeEntryListFilter.java | 10 +++++++++- .../file/entries/PatternMatchingEntryListFilter.java | 7 ++++--- .../integration/ftp/AbstractFtpClientFactory.java | 10 +++++++++- .../integration/ftp/DefaultFtpsClientFactory.java | 5 +++-- .../ftp/FtpSendingMessageHandlerFactoryBean.java | 1 + .../ftp/FtpsSendingMessageHandlerFactoryBean.java | 6 ++++++ .../impl/FtpInboundRemoteFileSystemSynchronizer.java | 1 + ...oundRemoteFileSystemSynchronizingMessageSource.java | 1 + ...ileSystemSynchronizingMessageSourceFactoryBean.java | 1 + ...ileSystemSynchronizingMessageSourceFactoryBean.java | 1 + .../integration/sftp/config/SftpNamespaceHandler.java | 3 ++- 12 files changed, 39 insertions(+), 8 deletions(-) diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptAllEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptAllEntryListFilter.java index 735caee080..deb9c1abf1 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptAllEntryListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptAllEntryListFilter.java @@ -21,6 +21,7 @@ package org.springframework.integration.file.entries; * Simple NOOP implementation for {@link org.springframework.integration.file.entries.EntryListFilter} implementation. * Suitable as a default in implementations. * + * @author Iwein Fuld * @author Josh Long * @param */ diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java index 4321610e80..0ee88f6033 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java @@ -20,7 +20,15 @@ import org.springframework.util.Assert; import java.util.*; - +/** + * Simple {@link org.springframework.integration.file.entries.EntryListFilter} that predicates its matches + * against any of many configured {@link org.springframework.integration.file.entries.EntryListFilter} + * + * @author Iwein Fuld + * @author Josh Long + * + * @param + */ public class CompositeEntryListFilter implements EntryListFilter { private final Set> fileFilters; diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java index 590748618a..fe9778f77b 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java @@ -23,11 +23,12 @@ import java.util.regex.Pattern; /** - * experimental - *

+ * + * * Filters a listing of entries (T) by qualifying their 'name' (as determined by {@link org.springframework.integration.file.entries.EntryNamer}) * against a regular expression (an instance of {@link java.util.regex.Pattern}) * + * @author Iwein Fuld * @author Josh Long * @param the type of entry */ @@ -59,7 +60,7 @@ public class PatternMatchingEntryListFilter extends AbstractEntryListFilter + */ abstract public class AbstractFtpClientFactory implements FtpClientFactory { private static final Log logger = LogFactory.getLog(FtpClientFactory.class); private static final String DEFAULT_REMOTE_WORKING_DIRECTORY = "/"; diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/DefaultFtpsClientFactory.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/DefaultFtpsClientFactory.java index 9a7d539ce0..1cf3ec4ed1 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/DefaultFtpsClientFactory.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/DefaultFtpsClientFactory.java @@ -26,9 +26,10 @@ import javax.net.ssl.TrustManager; /** - * provides a working FTPS implementation - * + * provides a working FTPS implementation. Based heavily on {@link org.springframework.integration.ftp.DefaultFtpClientFactory} + * * @author Josh Long + * @author Iwein Fuld */ public class DefaultFtpsClientFactory extends AbstractFtpClientFactory { private Boolean useClientMode; diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java index 26782253f5..b5c0d0dd77 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java @@ -13,6 +13,7 @@ import org.springframework.core.io.ResourceLoader; /** * A factory bean implementation that handles constructing an outbound FTP adapter. * + * @author Iwein Fuld * @author Josh Long */ public class FtpSendingMessageHandlerFactoryBean extends AbstractFactoryBean implements ResourceLoaderAware, ApplicationContextAware { diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpsSendingMessageHandlerFactoryBean.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpsSendingMessageHandlerFactoryBean.java index d74ccfb216..3c3fe92583 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpsSendingMessageHandlerFactoryBean.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpsSendingMessageHandlerFactoryBean.java @@ -3,6 +3,12 @@ package org.springframework.integration.ftp; import javax.net.ssl.KeyManager; import javax.net.ssl.TrustManager; +/** + * Sends files to a remote FTPS file system. Based heavily on {@link org.springframework.integration.ftp.FtpSendingMessageHandler} + * + * @author Josh Long + * @author Iwein Fuld + */ public class FtpsSendingMessageHandlerFactoryBean extends FtpSendingMessageHandlerFactoryBean { /** * Sets whether the connection is implicit. Local testing reveals this to be a good choice. diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizer.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizer.java index 2b9cfc1c19..1e589af800 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizer.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizer.java @@ -21,6 +21,7 @@ import java.util.Collection; /** * An FTP-adapter implementation of {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer} * + * @author Iwein Fuld * @author Josh Long */ public class FtpInboundRemoteFileSystemSynchronizer extends AbstractInboundRemoteFileSystemSychronizer { diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizingMessageSource.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizingMessageSource.java index c868fd0c8c..75d78a3845 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizingMessageSource.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizingMessageSource.java @@ -8,6 +8,7 @@ import org.springframework.integration.ftp.FtpClientPool; /** * a {@link org.springframework.integration.core.MessageSource} implementation for FTP * + * @author Iwein Fuld * @author Josh Long */ public class FtpInboundRemoteFileSystemSynchronizingMessageSource extends AbstractInboundRemoteFileSystemSynchronizingMessageSource { diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java index 2713aee47b..d656f01c1f 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java @@ -21,6 +21,7 @@ import java.io.File; /** * Factory to make building the namespace easier * + * @author Iwein Fuld * @author Josh Long */ public class FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends AbstractFactoryBean implements ResourceLoaderAware { diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean.java index 76dfa16115..ce47821db8 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean.java @@ -30,6 +30,7 @@ import javax.net.ssl.TrustManager; /** * Factory to make building the namespace easier * + * @author Iwein Fuld * @author Josh Long */ public class FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean extends FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean { diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java index 4e2cbe8960..41ca6416c9 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java @@ -28,7 +28,8 @@ import org.w3c.dom.Element; /** - * Provides namespace support for using SFTP + * Provides namespace support for using SFTP. + * This is very largely based on the FTP support by Iwein Fuld. * * @author Josh Long */