提交 7893fa31 编写于 作者: J Joram Barrez

Merge branch 'master' of github.com:Activiti/Activiti

......@@ -38,6 +38,7 @@ public interface BpmnXMLConstants {
public static final String ATTRIBUTE_ID = "id";
public static final String ATTRIBUTE_NAME = "name";
public static final String ATTRIBUTE_TYPE = "type";
public static final String ATTRIBUTE_DEFAULT = "default";
public static final String ATTRIBUTE_ITEM_REF = "itemRef";
public static final String ELEMENT_DEFINITIONS = "definitions";
public static final String ELEMENT_DOCUMENTATION = "documentation";
......@@ -75,7 +76,6 @@ public interface BpmnXMLConstants {
public static final String ATTRIBUTE_ACTIVITY_ASYNCHRONOUS = "async";
public static final String ATTRIBUTE_ACTIVITY_EXCLUSIVE = "exclusive";
public static final String ATTRIBUTE_ACTIVITY_DEFAULT = "default";
public static final String ATTRIBUTE_ACTIVITY_ISFORCOMPENSATION = "isForCompensation";
public static final String ELEMENT_IMPORT = "import";
......@@ -238,6 +238,7 @@ public interface BpmnXMLConstants {
public static final String ELEMENT_DI_BOUNDS = "Bounds";
public static final String ELEMENT_DI_WAYPOINT = "waypoint";
public static final String ATTRIBUTE_DI_BPMNELEMENT = "bpmnElement";
public static final String ATTRIBUTE_DI_IS_EXPANDED = "isExpanded";
public static final String ATTRIBUTE_DI_WIDTH = "width";
public static final String ATTRIBUTE_DI_HEIGHT = "height";
public static final String ATTRIBUTE_DI_X = "x";
......
......@@ -111,7 +111,7 @@ public abstract class BaseBpmnXMLConverter implements BpmnXMLConstants {
String elementName = xtr.getAttributeValue(null, ATTRIBUTE_NAME);
boolean async = parseAsync(xtr);
boolean notExclusive = parseNotExclusive(xtr);
String defaultFlow = xtr.getAttributeValue(null, "default");
String defaultFlow = xtr.getAttributeValue(null, ATTRIBUTE_DEFAULT);
boolean isForCompensation = parseForCompensation(xtr);
BaseElement parsedElement = convertXMLToElement(xtr);
......@@ -172,20 +172,25 @@ public abstract class BaseBpmnXMLConverter implements BpmnXMLConstants {
}
if (baseElement instanceof Activity) {
Activity activity = (Activity) baseElement;
final Activity activity = (Activity) baseElement;
if (activity.isAsynchronous()) {
writeQualifiedAttribute(ATTRIBUTE_ACTIVITY_ASYNCHRONOUS, ATTRIBUTE_VALUE_TRUE, xtw);
}
if (activity.isNotExclusive()) {
writeQualifiedAttribute(ATTRIBUTE_ACTIVITY_EXCLUSIVE, ATTRIBUTE_VALUE_FALSE, xtw);
}
writeDefaultAttribute(ATTRIBUTE_ACTIVITY_DEFAULT, activity.getDefaultFlow(), xtw);
writeDefaultAttribute(ATTRIBUTE_DEFAULT, activity.getDefaultFlow(), xtw);
}
if (baseElement instanceof Gateway) {
final Gateway gateway = (Gateway) baseElement;
writeDefaultAttribute(ATTRIBUTE_DEFAULT, gateway.getDefaultFlow(), xtw);
}
writeAdditionalAttributes(baseElement, xtw);
if (baseElement instanceof FlowElement) {
FlowElement flowElement = (FlowElement) baseElement;
final FlowElement flowElement = (FlowElement) baseElement;
if (StringUtils.isNotEmpty(flowElement.getDocumentation())) {
xtw.writeStartElement(ELEMENT_DOCUMENTATION);
......@@ -202,7 +207,7 @@ public abstract class BaseBpmnXMLConverter implements BpmnXMLConstants {
writeAdditionalChildElements(baseElement, xtw);
if (baseElement instanceof Activity) {
Activity activity = (Activity) baseElement;
final Activity activity = (Activity) baseElement;
MultiInstanceExport.writeMultiInstance(activity, xtw);
}
......
......@@ -16,8 +16,7 @@ import javax.xml.stream.XMLStreamReader;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.HasExecutionListeners;
/**
* @author Tijs Rademakers
......@@ -32,10 +31,8 @@ public class ExecutionListenerParser extends ActivitiListenerParser {
super.parseChildElement(xtr, parentElement, model);
if (parentElement instanceof FlowElement) {
((FlowElement) parentElement).getExecutionListeners().add(listener);
} else if (parentElement instanceof Process){
((Process) parentElement).getExecutionListeners().add(listener);
if (parentElement instanceof HasExecutionListeners) {
((HasExecutionListeners) parentElement).getExecutionListeners().add(listener);
}
}
}
/* 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.activiti.bpmn.converter.export;
import java.util.List;
......@@ -8,33 +20,32 @@ import org.activiti.bpmn.constants.BpmnXMLConstants;
import org.activiti.bpmn.converter.util.BpmnXMLUtil;
import org.activiti.bpmn.model.ActivitiListener;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.HasExecutionListeners;
import org.activiti.bpmn.model.ImplementationType;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.UserTask;
import org.apache.commons.lang.StringUtils;
public class ActivitiListenerExport implements BpmnXMLConstants {
public static boolean writeListeners(BaseElement element, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception {
List<ActivitiListener> listenerList = null;
String xmlElementName = ELEMENT_EXECUTION_LISTENER;
if (element instanceof UserTask) {
listenerList = ((UserTask) element).getTaskListeners();
xmlElementName = ELEMENT_TASK_LISTENER;
} else if (element instanceof FlowElement) {
listenerList = ((FlowElement) element).getExecutionListeners();
} else if (element instanceof Process) {
listenerList = ((Process) element).getExecutionListeners();
if(element instanceof HasExecutionListeners) {
didWriteExtensionStartElement = writeListeners(ELEMENT_EXECUTION_LISTENER, ((HasExecutionListeners) element).getExecutionListeners(), didWriteExtensionStartElement, xtw);
}
// In case of a usertaks, also add task-listeners
if(element instanceof UserTask) {
didWriteExtensionStartElement = writeListeners(ELEMENT_TASK_LISTENER, ((UserTask) element).getTaskListeners(), didWriteExtensionStartElement, xtw);
}
return didWriteExtensionStartElement;
}
private static boolean writeListeners(String xmlElementName, List<ActivitiListener> listenerList, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception {
if (listenerList != null) {
for (ActivitiListener listener : listenerList) {
if (StringUtils.isNotEmpty(listener.getEvent())) {
if (didWriteExtensionStartElement == false) {
if (!didWriteExtensionStartElement) {
xtw.writeStartElement(ELEMENT_EXTENSIONS);
didWriteExtensionStartElement = true;
}
......
/* 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.activiti.bpmn.converter.export;
import java.util.List;
......@@ -8,6 +20,7 @@ import org.activiti.bpmn.constants.BpmnXMLConstants;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.bpmn.model.SubProcess;
import org.apache.commons.lang.StringUtils;
public class BPMNDIExport implements BpmnXMLConstants {
......@@ -39,6 +52,11 @@ public class BPMNDIExport implements BpmnXMLConstants {
xtw.writeAttribute(ATTRIBUTE_ID, "BPMNShape_" + elementId);
GraphicInfo graphicInfo = model.getGraphicInfo(elementId);
FlowElement flowElement = model.getFlowElement(elementId);
if (flowElement != null && flowElement instanceof SubProcess) {
xtw.writeAttribute(ATTRIBUTE_DI_IS_EXPANDED, String.valueOf(graphicInfo.isExpanded()));
}
xtw.writeStartElement(OMGDC_PREFIX, ELEMENT_DI_BOUNDS, OMGDC_NAMESPACE);
xtw.writeAttribute(ATTRIBUTE_DI_HEIGHT, "" + graphicInfo.getHeight());
xtw.writeAttribute(ATTRIBUTE_DI_WIDTH, "" + graphicInfo.getWidth());
......
/* 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.activiti.bpmn.converter.export;
import javax.xml.stream.XMLStreamWriter;
......
/* 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.activiti.bpmn.converter.export;
import java.util.List;
......
/* 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.activiti.bpmn.converter.export;
import javax.xml.stream.XMLStreamWriter;
......
/* 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.activiti.bpmn.converter.export;
import javax.xml.stream.XMLStreamWriter;
......
/* 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.activiti.bpmn.converter.export;
import javax.xml.stream.XMLStreamWriter;
......
/* 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.activiti.bpmn.converter.export;
import javax.xml.stream.XMLStreamWriter;
......
......@@ -35,14 +35,13 @@ public class BpmnShapeParser implements BpmnXMLConstants {
if (xtr.isStartElement() && ELEMENT_DI_BOUNDS.equalsIgnoreCase(xtr.getLocalName())) {
graphicInfo.setX(Double.valueOf(xtr.getAttributeValue(null, ATTRIBUTE_DI_X)).intValue());
graphicInfo.setY(Double.valueOf(xtr.getAttributeValue(null, ATTRIBUTE_DI_Y)).intValue());
/*FlowElement flowElement = model.getFlowElement(id);
if (flowElement instanceof Event) {
graphicInfo.width = 30;
graphicInfo.height = 30;
} else {*/
graphicInfo.setWidth(Double.valueOf(xtr.getAttributeValue(null, ATTRIBUTE_DI_WIDTH)).intValue());
graphicInfo.setHeight(Double.valueOf(xtr.getAttributeValue(null, ATTRIBUTE_DI_HEIGHT)).intValue());
//}
graphicInfo.setWidth(Double.valueOf(xtr.getAttributeValue(null, ATTRIBUTE_DI_WIDTH)).intValue());
graphicInfo.setHeight(Double.valueOf(xtr.getAttributeValue(null, ATTRIBUTE_DI_HEIGHT)).intValue());
String strIsExpanded = xtr.getAttributeValue(null, ATTRIBUTE_DI_IS_EXPANDED);
if ("true".equalsIgnoreCase(strIsExpanded)) {
graphicInfo.setExpanded(true);
}
model.addGraphicInfo(id, graphicInfo);
break;
......
......@@ -58,8 +58,8 @@ public class SubProcessParser implements BpmnXMLConstants {
subProcess.setAsynchronous(async);
subProcess.setNotExclusive(notExclusive);
if(StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_ACTIVITY_DEFAULT))) {
subProcess.setDefaultFlow(xtr.getAttributeValue(null, ATTRIBUTE_ACTIVITY_DEFAULT));
if(StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_DEFAULT))) {
subProcess.setDefaultFlow(xtr.getAttributeValue(null, ATTRIBUTE_DEFAULT));
}
if(activeSubProcessList.size() > 1) {
......
......@@ -84,5 +84,11 @@ public class UserTaskConverterTest extends AbstractConverterTest {
assertTrue(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equals(listener.getImplementationType()));
assertEquals("${someDelegateExpression}", listener.getImplementation());
assertEquals("complete", listener.getEvent());
List<ActivitiListener> executionListeners = userTask.getExecutionListeners();
assertEquals(1, executionListeners.size());
ActivitiListener executionListener = executionListeners.get(0);
assertEquals("end", executionListener.getEvent());
}
}
......@@ -12,6 +12,8 @@
<activiti:taskListener event="create" class="org.test.TestClass"></activiti:taskListener>
<activiti:taskListener event="assignment" expression="${someExpression}"></activiti:taskListener>
<activiti:taskListener event="complete" delegateExpression="${someDelegateExpression}"></activiti:taskListener>
<activiti:executionListener expression="${someExpression}" event="end" />
</extensionElements>
</userTask>
</process>
......
......@@ -19,7 +19,7 @@ import java.util.List;
/**
* @author Tijs Rademakers
*/
public class FlowElement extends BaseElement {
public class FlowElement extends BaseElement implements HasExecutionListeners {
protected String name;
protected String documentation;
......
......@@ -23,6 +23,7 @@ public class GraphicInfo {
protected double height;
protected double width;
protected FlowElement element;
protected boolean expanded;
protected int xmlRowNumber;
protected int xmlColumnNumber;
public double getX() {
......@@ -49,6 +50,12 @@ public class GraphicInfo {
public void setWidth(double width) {
this.width = width;
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
public FlowElement getElement() {
return element;
}
......
/* 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.activiti.bpmn.model;
import java.util.List;
/**
* Interface indicating an element has execution-listeners
*
* @author Frederik Heremans
*/
public interface HasExecutionListeners {
List<ActivitiListener> getExecutionListeners();
void setExecutionListeners(List<ActivitiListener> executionListeners);
}
......@@ -19,7 +19,7 @@ import java.util.List;
/**
* @author Tijs Rademakers
*/
public class Process extends FlowElementsContainer {
public class Process extends FlowElementsContainer implements HasExecutionListeners {
protected String name;
protected boolean executable = true;
......
......@@ -12,21 +12,10 @@
*/
package org.activiti.bpmn.model;
import java.util.ArrayList;
import java.util.List;
/**
* @author Tijs Rademakers
*/
public class ThrowEvent extends Event {
protected List<ActivitiListener> executionListeners = new ArrayList<ActivitiListener>();
public List<ActivitiListener> getExecutionListeners() {
return executionListeners;
}
public void setExecutionListeners(List<ActivitiListener> executionListeners) {
this.executionListeners = executionListeners;
}
}
......@@ -4,7 +4,6 @@
<modelVersion>4.0.0</modelVersion>
<name>Activiti - Camel</name>
<groupId>org.activiti</groupId>
<artifactId>activiti-camel</artifactId>
<packaging>bundle</packaging>
......@@ -20,7 +19,6 @@
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
......@@ -30,7 +28,20 @@
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>provided</scope>
</dependency>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
......
/* 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.activiti.camel;
import java.util.HashMap;
import java.util.Map;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.Expression;
import org.activiti.engine.impl.bpmn.behavior.BpmnActivityBehavior;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.pvm.delegate.ActivityBehavior;
import org.activiti.engine.impl.pvm.delegate.ActivityExecution;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.impl.DefaultExchange;
import org.apache.commons.lang.StringUtils;
public class CamelBehavior extends BpmnActivityBehavior implements ActivityBehavior {
private static final long serialVersionUID = 1L;
protected Expression camelContext;
public void execute(ActivityExecution execution) throws Exception {
ProcessEngineConfiguration engineConfiguration = Context.getProcessEngineConfiguration();
if (engineConfiguration instanceof SpringProcessEngineConfiguration == false) {
throw new ActivitiException("Expecting a Spring process engine configuration for the Activiti Camel module");
}
SpringProcessEngineConfiguration springConfiguration = (SpringProcessEngineConfiguration) engineConfiguration;
String camelContextValue = getStringFromField(camelContext, execution);
if (StringUtils.isEmpty(camelContextValue)) {
camelContextValue = springConfiguration.getDefaultCamelContext();
}
ActivitiEndpoint endpoint = createEndpoint(execution, springConfiguration, camelContextValue);
Exchange exchange = createExchange(execution, endpoint, springConfiguration, camelContextValue);
endpoint.process(exchange);
execution.setVariables(ExchangeUtils.prepareVariables(exchange, endpoint));
performDefaultOutgoingBehavior(execution);
}
private ActivitiEndpoint createEndpoint(ActivityExecution execution, SpringProcessEngineConfiguration springConfiguration, String camelContext) {
String uri = "activiti://" + getProcessDefinitionKey(execution) + ":" + execution.getActivity().getId();
return getEndpoint(getContext(springConfiguration, camelContext), uri);
}
private ActivitiEndpoint getEndpoint(CamelContext ctx, String key) {
for (Endpoint e : ctx.getEndpoints()) {
if (e.getEndpointKey().equals(key) && (e instanceof ActivitiEndpoint)) {
return (ActivitiEndpoint) e;
}
}
throw new RuntimeException("Activiti endpoint not defined for " + key);
}
private CamelContext getContext(SpringProcessEngineConfiguration springConfiguration, String camelContext) {
Object ctx = springConfiguration.getApplicationContext().getBean(camelContext);
if (ctx == null || ctx instanceof CamelContext == false) {
throw new RuntimeException("Could not find camel context " + camelContext);
}
return (CamelContext) ctx;
}
private Exchange createExchange(ActivityExecution activityExecution, ActivitiEndpoint endpoint,
SpringProcessEngineConfiguration springConfiguration, String camelContext) {
Exchange ex = new DefaultExchange(getContext(springConfiguration, camelContext));
Map<String, Object> variables = activityExecution.getVariables();
if (endpoint.isCopyVariablesToProperties()) {
for (Map.Entry<String, Object> var : variables.entrySet()) {
ex.setProperty(var.getKey(), var.getValue());
}
}
if (endpoint.isCopyVariablesToBody()) {
ex.getIn().setBody(new HashMap<String,Object>(variables));
}
return ex;
}
private String getProcessDefinitionKey(ActivityExecution execution) {
String id = execution.getActivity().getProcessDefinition().getId();
return id.substring(0, id.indexOf(":"));
}
protected String getStringFromField(Expression expression, DelegateExecution execution) {
if (expression != null) {
Object value = expression.getValue(execution);
if (value != null) {
return value.toString();
}
}
return null;
}
public void setCamelContext(Expression camelContext) {
this.camelContext = camelContext;
}
}
/* 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.activiti.camel;
import org.activiti.engine.test.Deployment;
import org.activiti.spring.impl.test.SpringActivitiTestCase;
import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.component.mock.MockEndpoint;
import org.springframework.test.context.ContextConfiguration;
import java.util.Collections;
import java.util.Map;
@ContextConfiguration("classpath:custom-camel-activiti-context.xml")
public class CustomContextTest extends SpringActivitiTestCase {
MockEndpoint service1;
MockEndpoint service2;
public void setUp() {
CamelContext ctx = applicationContext.getBean(CamelContext.class);
service1 = (MockEndpoint) ctx.getEndpoint("mock:service1");
service1.reset();
service2 = (MockEndpoint) ctx.getEndpoint("mock:service2");
service2.reset();
}
@Deployment(resources = {"process/custom.bpmn20.xml"})
public void testRunProcess() throws Exception {
CamelContext ctx = applicationContext.getBean(CamelContext.class);
ProducerTemplate tpl = ctx.createProducerTemplate();
service1.expectedBodiesReceived("ala");
String instanceId = (String) tpl.requestBody("direct:start", Collections.singletonMap("var1", "ala"));
tpl.sendBodyAndProperty("direct:receive", null, ActivitiProducer.PROCESS_ID_PROPERTY, instanceId);
assertProcessEnded(instanceId);
service1.assertIsSatisfied();
Map m = service2.getExchanges().get(0).getIn().getBody(Map.class);
assertEquals("ala", m.get("var1"));
assertEquals("var2", m.get("var2"));
}
}
......@@ -36,28 +36,12 @@
<bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService"/>
<camelContext id="camelProcess" xmlns="http://camel.apache.org/schema/spring">
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
<packageScan>
<package>org.activiti.camel.route</package>
</packageScan>
</camelContext>
<bean id="camel" class="org.activiti.camel.CamelBehaviour">
<constructor-arg index="0">
<list>
<bean class="org.activiti.camel.SimpleContextProvider">
<constructor-arg index="0" value="camelProcess"/>
<constructor-arg index="1" ref="camelProcess"/>
</bean>
<bean class="org.activiti.camel.SimpleContextProvider">
<constructor-arg index="0" value="asyncCamelProcess"/>
<constructor-arg index="1" ref="camelProcess"/>
</bean>
</list>
</constructor-arg>
</bean>
<bean id="sleepBean" class="org.activiti.camel.SleepBean"/>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xmlns:camel="http://camel.apache.org/schema/spring"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<property name="targetDataSource">
<bean class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driverClass" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="databaseSchemaUpdate" value="true"/>
<property name="jobExecutorActivate" value="false"/>
</bean>
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration"/>
</bean>
<bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService"/>
<camelContext id="customCamelContext" xmlns="http://camel.apache.org/schema/spring">
<packageScan>
<package>org.activiti.camel.route</package>
</packageScan>
</camelContext>
<bean id="sleepBean" class="org.activiti.camel.SleepBean"/>
</beans>
log4j.rootLogger=DEBUG, CA
log4j.rootLogger=INFO, CA
# ConsoleAppender
log4j.appender.CA=org.apache.log4j.ConsoleAppender
......
......@@ -17,11 +17,11 @@
<parallelGateway id="parallelSplit"/>
<sequenceFlow sourceRef="parallelSplit" targetRef="serviceTaskAsync1"/>
<sequenceFlow sourceRef="parallelSplit" targetRef="serviceTaskAsync2"/>
<serviceTask id="serviceTaskAsync1" activiti:delegateExpression="${camel}"/>
<serviceTask id="serviceTaskAsync1" activiti:type="camel"/>
<sequenceFlow sourceRef="serviceTaskAsync1" targetRef="receive1"/>
<receiveTask id="receive1" name="Wait State" />
<sequenceFlow sourceRef="receive1" targetRef="parallelJoin"/>
<serviceTask id="serviceTaskAsync2" activiti:delegateExpression="${camel}"/>
<serviceTask id="serviceTaskAsync2" activiti:type="camel"/>
<sequenceFlow sourceRef="serviceTaskAsync2" targetRef="receive2"/>
<receiveTask id="receive2" name="Wait State" />
<sequenceFlow sourceRef="receive2" targetRef="parallelJoin"/>
......
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd">
<process id="camelProcess">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="serviceTask1"/>
<serviceTask id="serviceTask1" activiti:type="camel">
<extensionElements>
<activiti:field name="camelContext" stringValue="customCamelContext" />
</extensionElements>
</serviceTask>
<sequenceFlow id="flow2" sourceRef="serviceTask1" targetRef="receive"/>
<receiveTask id="receive" name="Wait State" />
<sequenceFlow id="flow3" sourceRef="receive" targetRef="serviceTask2"/>
<serviceTask id="serviceTask2" activiti:type="camel">
<extensionElements>
<activiti:field name="camelContext" stringValue="customCamelContext" />
</extensionElements>
</serviceTask>
<sequenceFlow id="flow4" sourceRef="serviceTask2" targetRef="end"/>
<endEvent id="end"/>
</process>
</definitions>
......@@ -13,15 +13,14 @@
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="serviceTask1"/>
<serviceTask id="serviceTask1" activiti:delegateExpression="${camel}"/>
<serviceTask id="serviceTask1" activiti:type="camel"/>
<sequenceFlow id="flow2" sourceRef="serviceTask1" targetRef="receive"/>
<receiveTask id="receive" name="Wait State" />
<sequenceFlow id="flow3" sourceRef="receive" targetRef="serviceTask2"/>
<serviceTask id="serviceTask2" activiti:delegateExpression="${camel}"/>
<serviceTask id="serviceTask2" activiti:type="camel"/>
<sequenceFlow id="flow4" sourceRef="serviceTask2" targetRef="end"/>
<endEvent id="end"/>
......
......@@ -121,8 +121,8 @@
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
......
package org.activiti.engine.impl.db;
package liquibase.ext;
import liquibase.database.structure.type.DataType;
import liquibase.database.structure.type.IntType;
......
package org.activiti.engine.impl.db;
package liquibase.ext;
import liquibase.database.structure.type.BooleanType;
import liquibase.database.structure.type.DataType;
......
package org.activiti.engine.impl.db;
package liquibase.ext;
import liquibase.database.structure.type.BigIntType;
import liquibase.database.structure.type.BlobType;
......
package org.activiti.engine.impl.db;
package liquibase.ext;
import liquibase.database.structure.type.BlobType;
import liquibase.database.structure.type.BooleanType;
......
package org.activiti.engine.impl.db;
package liquibase.ext;
import liquibase.database.structure.type.BigIntType;
import liquibase.database.structure.type.BooleanType;
......
package org.activiti.engine.impl.db;
package liquibase.ext;
import liquibase.database.structure.type.DataType;
import liquibase.database.structure.type.DateTimeType;
......
package org.activiti.engine.impl.db;
package liquibase.ext;
import liquibase.database.structure.type.DataType;
import liquibase.database.structure.type.DateTimeType;
......
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 liquibase.ext;
import liquibase.logging.LogLevel;
import liquibase.logging.core.AbstractLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LiquibaseSlf4jLogger extends AbstractLogger {
Logger logger = LoggerFactory.getLogger("liquibase");
public int getPriority() {
return 5;
}
public void setName(String name) {
logger = LoggerFactory.getLogger(name);
}
public void setLogLevel(String logLevel, String logFile) {
// ignore
}
public void severe(String message) {
logger.error(message);
}
public void severe(String message, Throwable e) {
logger.error(message, e);
}
public void warning(String message) {
logger.warn(message);
}
public void warning(String message, Throwable e) {
logger.warn(message, e);
}
public void info(String message) {
logger.info(message);
}
public void info(String message, Throwable e) {
logger.info(message, e);
}
public void debug(String message) {
logger.debug(message);
}
public void debug(String message, Throwable e) {
logger.debug(message, e);
}
public void setLogLevel(String level) {
}
public void setLogLevel(LogLevel level) {
}
public LogLevel getLogLevel() {
return null;
}
}
......@@ -125,6 +125,8 @@ public abstract class ProcessEngineConfiguration implements EngineServices {
protected boolean jpaHandleTransaction;
protected boolean jpaCloseEntityManager;
protected String defaultCamelContext = "camelContext";
protected String activityFontName = "Arial";
protected ClassLoader classLoader;
......@@ -504,6 +506,15 @@ public abstract class ProcessEngineConfiguration implements EngineServices {
this.dataSourceJndiName = dataSourceJndiName;
}
public String getDefaultCamelContext() {
return defaultCamelContext;
}
public ProcessEngineConfiguration setDefaultCamelContext(String defaultCamelContext) {
this.defaultCamelContext = defaultCamelContext;
return this;
}
public String getActivityFontName() {
return activityFontName;
}
......
......@@ -101,7 +101,7 @@ public class BpmnParse implements BpmnXMLConstants {
protected BpmnModel bpmnModel;
protected String targetNamespace;
/** The deployment to which the parsed process definitions will be added. */
protected DeploymentEntity deployment;
......
......@@ -119,5 +119,4 @@ public class BpmnParser extends Parser {
public void setBpmnParserHandlers(BpmnParseHandlers bpmnParserHandlers) {
this.bpmnParserHandlers = bpmnParserHandlers;
}
}
......@@ -136,6 +136,10 @@ public interface ActivityBehaviorFactory {
public abstract ActivityBehavior createMuleActivityBehavior(ServiceTask serviceTask, BpmnModel bpmnModel);
public abstract ActivityBehavior createMuleActivityBehavior(SendTask sendTask, BpmnModel bpmnModel);
public abstract ActivityBehavior createCamelActivityBehavior(ServiceTask serviceTask, BpmnModel bpmnModel);
public abstract ActivityBehavior createCamelActivityBehavior(SendTask sendTask, BpmnModel bpmnModel);
public abstract ShellActivityBehavior createShellActivityBehavior(ServiceTask serviceTask);
......
......@@ -185,6 +185,30 @@ public class DefaultActivityBehaviorFactory extends AbstractBehaviorFactory impl
}
}
// We do not want a hard dependency on Camel, hence we return ActivityBehavior and instantiate
// the delegate instance using a string instead of the Class itself.
public ActivityBehavior createCamelActivityBehavior(ServiceTask serviceTask, BpmnModel bpmnModel) {
return createCamelActivityBehavior(serviceTask, serviceTask.getFieldExtensions(), bpmnModel);
}
public ActivityBehavior createCamelActivityBehavior(SendTask sendTask, BpmnModel bpmnModel) {
return createCamelActivityBehavior(sendTask, sendTask.getFieldExtensions(), bpmnModel);
}
protected ActivityBehavior createCamelActivityBehavior(Task task, List<FieldExtension> fieldExtensions, BpmnModel bpmnModel) {
try {
Class< ? > theClass = Class.forName("org.activiti.camel.CamelBehavior");
List<FieldDeclaration> fieldDeclarations = createFieldDeclarations(fieldExtensions);
return (ActivityBehavior) ClassDelegate.instantiateDelegate(theClass, fieldDeclarations);
} catch (ClassNotFoundException e) {
bpmnModel.addProblem("Could not find org.activiti.camel.CamelBehavior: " + e.getMessage(), task);
return null;
}
}
public ShellActivityBehavior createShellActivityBehavior(ServiceTask serviceTask) {
List<FieldDeclaration> fieldDeclarations = createFieldDeclarations(serviceTask.getFieldExtensions());
return (ShellActivityBehavior) ClassDelegate.instantiateDelegate(ShellActivityBehavior.class, fieldDeclarations);
......
......@@ -49,6 +49,8 @@ public class SendTaskParseHandler extends AbstractExternalInvocationBpmnParseHan
activity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createMailActivityBehavior(sendTask));
} else if (sendTask.getType().equalsIgnoreCase("mule")) {
activity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createMuleActivityBehavior(sendTask, bpmnParse.getBpmnModel()));
} else if (sendTask.getType().equalsIgnoreCase("camel")) {
activity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createCamelActivityBehavior(sendTask, bpmnParse.getBpmnModel()));
} else {
bpmnParse.getBpmnModel().addProblem("Invalid usage of type attribute: '" + sendTask.getType() + "'.", sendTask);
}
......
......@@ -50,6 +50,9 @@ public class ServiceTaskParseHandler extends AbstractExternalInvocationBpmnParse
} else if (serviceTask.getType().equalsIgnoreCase("mule")) {
activity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createMuleActivityBehavior(serviceTask, bpmnParse.getBpmnModel()));
} else if (serviceTask.getType().equalsIgnoreCase("camel")) {
activity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createCamelActivityBehavior(serviceTask, bpmnParse.getBpmnModel()));
} else if (serviceTask.getType().equalsIgnoreCase("shell")) {
validateFieldDeclarationsForShell(bpmnParse, serviceTask, serviceTask.getFieldExtensions());
activity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createShellActivityBehavior(serviceTask));
......
......@@ -1594,6 +1594,8 @@ public abstract class ProcessEngineConfigurationImpl extends ProcessEngineConfig
super.setHistory(history);
return this;
}
@Override
public ProcessEngineConfigurationImpl setIdBlockSize(int idBlockSize) {
......@@ -1737,8 +1739,14 @@ public abstract class ProcessEngineConfigurationImpl extends ProcessEngineConfig
public ProcessEngineConfigurationImpl setJdbcPingConnectionNotUsedFor(int jdbcPingNotUsedFor) {
this.jdbcPingConnectionNotUsedFor = jdbcPingNotUsedFor;
return this;
}
}
@Override
public ProcessEngineConfigurationImpl setDefaultCamelContext(String defaultCamelContext) {
super.defaultCamelContext = defaultCamelContext;
return this;
}
public boolean isDbIdentityUsed() {
return isDbIdentityUsed;
}
......
......@@ -762,6 +762,7 @@ public class DbSqlSession implements Session {
JdbcConnection connection = new JdbcConnection(sqlSession.getConnection());
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection);
database.setDefaultSchemaName(this.connectionMetadataDefaultSchema);
database.setAutoCommit(false);
Liquibase liquibase = new Liquibase("org/activiti/db/liquibase/activiti-master.xml", new ClassLoaderResourceAccessor(), database);
liquibase.getDatabase().setDatabaseChangeLogLockTableName("ACT_" + liquibase.getDatabase().getDatabaseChangeLogLockTableName());
liquibase.getDatabase().setDatabaseChangeLogTableName("ACT_" + liquibase.getDatabase().getDatabaseChangeLogTableName());
......@@ -776,6 +777,7 @@ public class DbSqlSession implements Session {
JdbcConnection connection = new JdbcConnection(sqlSession.getConnection());
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection);
database.setDefaultSchemaName(this.connectionMetadataDefaultSchema);
database.setAutoCommit(false);
Liquibase liquibase = new Liquibase("org/activiti/db/liquibase/activiti-master.xml", new ClassLoaderResourceAccessor(), database);
liquibase.getDatabase().setDatabaseChangeLogLockTableName("ACT_" + liquibase.getDatabase().getDatabaseChangeLogLockTableName());
liquibase.getDatabase().setDatabaseChangeLogTableName("ACT_" + liquibase.getDatabase().getDatabaseChangeLogTableName());
......@@ -809,6 +811,7 @@ public class DbSqlSession implements Session {
JdbcConnection connection = new JdbcConnection(sqlSession.getConnection());
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection);
database.setDefaultSchemaName(this.connectionMetadataDefaultSchema);
database.setAutoCommit(false);
Liquibase liquibase = new Liquibase("org/activiti/db/liquibase/activiti-upgrade-" + dbVersion + ".xml", new ClassLoaderResourceAccessor(), database);
liquibase.getDatabase().setDatabaseChangeLogLockTableName("ACT_" + liquibase.getDatabase().getDatabaseChangeLogLockTableName());
liquibase.getDatabase().setDatabaseChangeLogTableName("ACT_" + liquibase.getDatabase().getDatabaseChangeLogTableName());
......
......@@ -17,8 +17,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import liquibase.database.typeconversion.TypeConverterFactory;
import org.activiti.engine.impl.cfg.IdGenerator;
import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.interceptor.SessionFactory;
......@@ -101,14 +99,6 @@ public class DbSqlSessionFactory implements SessionFactory {
databaseSpecificLimitBetweenStatements.put("mssql", ", row_number() over (ORDER BY ${orderBy}) rnk FROM ( select distinct RES.* ");
databaseSpecificOrderByStatements.put("mssql", "");
addDatabaseSpecificStatement("mssql", "selectExclusiveJobsToExecute", "selectExclusiveJobsToExecute_integerBoolean");
TypeConverterFactory.getInstance().register(new ActivitiDb2TypeConverter());
TypeConverterFactory.getInstance().register(new ActivitiH2TypeConverter());
TypeConverterFactory.getInstance().register(new ActivitiMySQLTypeConverter());
TypeConverterFactory.getInstance().register(new ActivitiMSSQLTypeConverter());
TypeConverterFactory.getInstance().register(new ActivitiOracleTypeConverter());
TypeConverterFactory.getInstance().register(new ActivitiPostgresTypeConverter());
TypeConverterFactory.getInstance().register(new ActivitiPostgres83TypeConverter());
}
protected String databaseType;
......
......@@ -31,9 +31,9 @@
<modifyDataType tableName="ACT_RU_TASK" columnName="OWNER_" newDataType="NVARCHAR(255)"/>
</changeSet>
<changeSet id="4 Activiti 5.12 Update ACT_HI_ACTINST remove owner column DB2-onmy" author="fheremans">
<changeSet id="4 Activiti 5.12 Update ACT_HI_ACTINST remove owner column DB2-onmy" dbms="db2" author="fheremans">
<preConditions onFail="CONTINUE">
<dbms type="db2" />
<columnExists tableName="ACT_HI_ACTINST" columnName="OWNER_" />
</preConditions>
<dropColumn tableName="ACT_HI_ACTINST" columnName="OWNER_"/>
</changeSet>
......
......@@ -31,6 +31,8 @@ import org.mule.api.client.LocalMuleClient;
*/
public class MuleSendActivitiBehavior extends AbstractBpmnActivityBehavior {
private static final long serialVersionUID = 1L;
private MuleContext muleContext;
private MessageExchangePattern mep = MessageExchangePattern.REQUEST_RESPONSE;
......
......@@ -31,6 +31,9 @@ import org.activiti.engine.impl.interceptor.CommandInterceptor;
import org.activiti.engine.impl.interceptor.LogInterceptor;
import org.activiti.engine.impl.variable.EntityManagerSession;
import org.activiti.engine.repository.DeploymentBuilder;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ContextResource;
import org.springframework.core.io.Resource;
......@@ -44,12 +47,12 @@ import org.springframework.transaction.support.TransactionTemplate;
* @author David Syer
* @author Joram Barrez
*/
public class SpringProcessEngineConfiguration extends ProcessEngineConfigurationImpl {
public class SpringProcessEngineConfiguration extends ProcessEngineConfigurationImpl implements ApplicationContextAware {
protected PlatformTransactionManager transactionManager;
protected String deploymentName = "SpringAutoDeployment";
protected Resource[] deploymentResources = new Resource[0];
protected ApplicationContext applicationContext;
public SpringProcessEngineConfiguration() {
transactionsExternallyManaged = true;
......@@ -179,4 +182,13 @@ public class SpringProcessEngineConfiguration extends ProcessEngineConfiguration
public void setDeploymentResources(Resource[] deploymentResources) {
this.deploymentResources = deploymentResources;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
......@@ -957,7 +957,18 @@
<para>
<emphasis role="bold">Request:</emphasis>
<literal>PUT /task/{taskId}/[claim|unclaim|complete|assign]</literal>
<programlisting>{}</programlisting>
Claim and unclaim don't need a JSON body, but for complete you can provide a number of variables and for assign a userId field is required.
An example body for the complete operation:
<programlisting>
{
"variableName1": "variableValue1",
"variableName2": "variableValue2"
}</programlisting>
An example body for the assign operation:
<programlisting>
{
"userId": "newAssignee"
}</programlisting>
</para>
</listitem>
<listitem>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册