提交 61cb8ab4 编写于 作者: F fheremans

Adding final ActivitiEvent implementation + tests + docs

上级 cf1905a1
...@@ -69,9 +69,19 @@ public interface BpmnXMLConstants { ...@@ -69,9 +69,19 @@ public interface BpmnXMLConstants {
public static final String ELEMENT_TASK_LISTENER = "taskListener"; public static final String ELEMENT_TASK_LISTENER = "taskListener";
public static final String ATTRIBUTE_LISTENER_EVENT = "event"; public static final String ATTRIBUTE_LISTENER_EVENT = "event";
public static final String ATTRIBUTE_LISTENER_EVENTS = "events"; public static final String ATTRIBUTE_LISTENER_EVENTS = "events";
public static final String ATTRIBUTE_LISTENER_ENTITY_TYPE = "entityType";
public static final String ATTRIBUTE_LISTENER_CLASS = "class"; public static final String ATTRIBUTE_LISTENER_CLASS = "class";
public static final String ATTRIBUTE_LISTENER_EXPRESSION = "expression"; public static final String ATTRIBUTE_LISTENER_EXPRESSION = "expression";
public static final String ATTRIBUTE_LISTENER_DELEGATEEXPRESSION = "delegateExpression"; public static final String ATTRIBUTE_LISTENER_DELEGATEEXPRESSION = "delegateExpression";
public static final String ATTRIBUTE_LISTENER_THROW_EVENT_TYPE = "throwEvent";
public static final String ATTRIBUTE_LISTENER_THROW_SIGNAL_EVENT_NAME = "signalName";
public static final String ATTRIBUTE_LISTENER_THROW_MESSAGE_EVENT_NAME = "messageName";
public static final String ATTRIBUTE_LISTENER_THROW_ERROR_EVENT_CODE = "errorCode";
public static final String ATTRIBUTE_LISTENER_THROW_EVENT_TYPE_SIGNAL = "signal";
public static final String ATTRIBUTE_LISTENER_THROW_EVENT_TYPE_GLOBAL_SIGNAL = "globalSignal";
public static final String ATTRIBUTE_LISTENER_THROW_EVENT_TYPE_MESSAGE = "message";
public static final String ATTRIBUTE_LISTENER_THROW_EVENT_TYPE_ERROR = "error";
public static final String ATTRIBUTE_VALUE_TRUE = "true"; public static final String ATTRIBUTE_VALUE_TRUE = "true";
public static final String ATTRIBUTE_VALUE_FALSE = "false"; public static final String ATTRIBUTE_VALUE_FALSE = "false";
......
...@@ -28,7 +28,6 @@ import org.apache.commons.lang3.StringUtils; ...@@ -28,7 +28,6 @@ import org.apache.commons.lang3.StringUtils;
public class ActivitiEventListenerParser extends BaseChildElementParser { public class ActivitiEventListenerParser extends BaseChildElementParser {
public void parseChildElement(XMLStreamReader xtr, BaseElement parentElement, BpmnModel model) throws Exception { public void parseChildElement(XMLStreamReader xtr, BaseElement parentElement, BpmnModel model) throws Exception {
EventListener listener = new EventListener(); EventListener listener = new EventListener();
BpmnXMLUtil.addXMLLocation(listener, xtr); BpmnXMLUtil.addXMLLocation(listener, xtr);
if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_CLASS))) { if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_CLASS))) {
...@@ -37,10 +36,28 @@ public class ActivitiEventListenerParser extends BaseChildElementParser { ...@@ -37,10 +36,28 @@ public class ActivitiEventListenerParser extends BaseChildElementParser {
} else if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_DELEGATEEXPRESSION))) { } else if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_DELEGATEEXPRESSION))) {
listener.setImplementation(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_DELEGATEEXPRESSION)); listener.setImplementation(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_DELEGATEEXPRESSION));
listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
} else if(StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_THROW_EVENT_TYPE))) {
String eventType = xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_THROW_EVENT_TYPE);
if(ATTRIBUTE_LISTENER_THROW_EVENT_TYPE_SIGNAL.equals(eventType)) {
listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_THROW_SIGNAL_EVENT);
listener.setImplementation(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_THROW_SIGNAL_EVENT_NAME));
} else if(ATTRIBUTE_LISTENER_THROW_EVENT_TYPE_GLOBAL_SIGNAL.equals(eventType)) {
listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_THROW_GLOBAL_SIGNAL_EVENT);
listener.setImplementation(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_THROW_SIGNAL_EVENT_NAME));
} else if(ATTRIBUTE_LISTENER_THROW_EVENT_TYPE_MESSAGE.equals(eventType)) {
listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_THROW_MESSAGE_EVENT);
listener.setImplementation(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_THROW_MESSAGE_EVENT_NAME));
} else if(ATTRIBUTE_LISTENER_THROW_EVENT_TYPE_ERROR.equals(eventType)) {
listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_THROW_ERROR_EVENT);
listener.setImplementation(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_THROW_ERROR_EVENT_CODE));
} else {
model.addProblem("Unsupported value of 'throwEvent' attribute: " + eventType, xtr);
}
} else { } else {
model.addProblem("Element 'class' or 'delegateExpression' is mandatory on eventListener", xtr); model.addProblem("Element 'class', 'delegateExpression' or 'throwEvent' is mandatory on eventListener", xtr);
} }
listener.setEvents(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_EVENTS)); listener.setEvents(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_EVENTS));
listener.setEntityType(xtr.getAttributeValue(null, ATTRIBUTE_LISTENER_ENTITY_TYPE));
Process parentProcess = (Process) parentElement; Process parentProcess = (Process) parentElement;
parentProcess.getEventListeners().add(listener); parentProcess.getEventListeners().add(listener);
......
...@@ -58,11 +58,19 @@ public class ActivitiListenerExport implements BpmnXMLConstants { ...@@ -58,11 +58,19 @@ public class ActivitiListenerExport implements BpmnXMLConstants {
xtw.writeStartElement(ACTIVITI_EXTENSIONS_PREFIX, ELEMENT_EVENT_LISTENER, ACTIVITI_EXTENSIONS_NAMESPACE); xtw.writeStartElement(ACTIVITI_EXTENSIONS_PREFIX, ELEMENT_EVENT_LISTENER, ACTIVITI_EXTENSIONS_NAMESPACE);
BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_LISTENER_EVENTS, eventListener.getEvents(), xtw); BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_LISTENER_EVENTS, eventListener.getEvents(), xtw);
BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_LISTENER_ENTITY_TYPE, eventListener.getEntityType(), xtw);
if (ImplementationType.IMPLEMENTATION_TYPE_CLASS.equals(eventListener.getImplementationType())) { if (ImplementationType.IMPLEMENTATION_TYPE_CLASS.equals(eventListener.getImplementationType())) {
BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_LISTENER_CLASS, eventListener.getImplementation(), xtw); BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_LISTENER_CLASS, eventListener.getImplementation(), xtw);
} else if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equals(eventListener.getImplementationType())) { } else if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equals(eventListener.getImplementationType())) {
BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_LISTENER_DELEGATEEXPRESSION, eventListener.getImplementation(), xtw); BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_LISTENER_DELEGATEEXPRESSION, eventListener.getImplementation(), xtw);
} else if(ImplementationType.IMPLEMENTATION_TYPE_THROW_SIGNAL_EVENT.equals(eventListener.getImplementationType())
|| ImplementationType.IMPLEMENTATION_TYPE_THROW_SIGNAL_EVENT.equals(eventListener.getImplementationType())) {
BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_LISTENER_THROW_SIGNAL_EVENT_NAME, eventListener.getImplementation(), xtw);
} else if(ImplementationType.IMPLEMENTATION_TYPE_THROW_MESSAGE_EVENT.equals(eventListener.getImplementationType())) {
BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_LISTENER_THROW_MESSAGE_EVENT_NAME, eventListener.getImplementation(), xtw);
} else if(ImplementationType.IMPLEMENTATION_TYPE_THROW_ERROR_EVENT.equals(eventListener.getImplementationType())) {
BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_LISTENER_THROW_ERROR_EVENT_CODE, eventListener.getImplementation(), xtw);
} }
xtw.writeEndElement(); xtw.writeEndElement();
......
...@@ -477,6 +477,78 @@ ...@@ -477,6 +477,78 @@
</complexType> </complexType>
</element> </element>
<element name="eventListener">
<annotation>
<documentation>
Extension element for defining event-listeners on a process-definition.
</documentation>
</annotation>
<complexType>
<attribute name="events">
<annotation>
<documentation>
Comma-separated list of event-types an event-listener is configured to be notified of. Can also be a single event-type.
</documentation>
</annotation>
</attribute>
<attribute name="entityType">
<annotation>
<documentation>
Type of entity that should be targeted by events for which the event-listener should be notified.
</documentation>
</annotation>
<simpleType>
<restriction base="string">
<enumeration value="attachment" />
<enumeration value="comment" />
<enumeration value="execution" />
<enumeration value="identity-link" />
<enumeration value="job" />
<enumeration value="process-definition" />
<enumeration value="process-instance" />
<enumeration value="task" />
</restriction>
</simpleType>
</attribute>
<attribute name="throwEvent">
<annotation>
<documentation>
Type of event to be throw as a response to a matching activiti-event being dispatched.
</documentation>
</annotation>
<simpleType>
<restriction base="string">
<enumeration value="signal" />
<enumeration value="globalSignal" />
<enumeration value="message" />
<enumeration value="error" />
</restriction>
</simpleType>
</attribute>
<attribute name="signalName">
<annotation>
<documentation>
Name of a signal.
</documentation>
</annotation>
</attribute>
<attribute name="messageName">
<annotation>
<documentation>
Name of a message.
</documentation>
</annotation>
</attribute>
<attribute name="errorCode">
<annotation>
<documentation>
Error-code of an error event.
</documentation>
</annotation>
</attribute>
</complexType>
</element>
<element name="in"> <element name="in">
<annotation> <annotation>
<documentation> <documentation>
......
...@@ -4,9 +4,9 @@ import static org.junit.Assert.assertEquals; ...@@ -4,9 +4,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import org.activiti.bpmn.constants.BpmnXMLConstants;
import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.EventListener; import org.activiti.bpmn.model.EventListener;
import org.activiti.bpmn.model.ImplementationType;
import org.activiti.bpmn.model.Process; import org.activiti.bpmn.model.Process;
import org.junit.Test; import org.junit.Test;
...@@ -30,24 +30,55 @@ public class EventListenerConverterTest extends AbstractConverterTest { ...@@ -30,24 +30,55 @@ public class EventListenerConverterTest extends AbstractConverterTest {
Process process = model.getMainProcess(); Process process = model.getMainProcess();
assertNotNull(process); assertNotNull(process);
assertNotNull(process.getEventListeners()); assertNotNull(process.getEventListeners());
assertEquals(3, process.getEventListeners().size()); assertEquals(8, process.getEventListeners().size());
// Listener with class // Listener with class
EventListener listener = process.getEventListeners().get(0); EventListener listener = process.getEventListeners().get(0);
assertEquals("ENTITY_CREATE", listener.getEvents()); assertEquals("ENTITY_CREATE", listener.getEvents());
assertEquals("org.activiti.test.MyListener", listener.getImplementation()); assertEquals("org.activiti.test.MyListener", listener.getImplementation());
assertEquals(BpmnXMLConstants.ATTRIBUTE_LISTENER_CLASS, listener.getImplementationType()); assertEquals(ImplementationType.IMPLEMENTATION_TYPE_CLASS, listener.getImplementationType());
// Listener with class, but no specific event (== all events) // Listener with class, but no specific event (== all events)
listener = process.getEventListeners().get(1); listener = process.getEventListeners().get(1);
assertNull(listener.getEvents()); assertNull(listener.getEvents());
assertEquals("org.activiti.test.AllEventTypesListener", listener.getImplementation()); assertEquals("org.activiti.test.AllEventTypesListener", listener.getImplementation());
assertEquals(BpmnXMLConstants.ATTRIBUTE_LISTENER_CLASS, listener.getImplementationType()); assertEquals(ImplementationType.IMPLEMENTATION_TYPE_CLASS, listener.getImplementationType());
// Listener with delegate expression // Listener with delegate expression
listener = process.getEventListeners().get(2); listener = process.getEventListeners().get(2);
assertEquals("ENTITY_DELETE", listener.getEvents()); assertEquals("ENTITY_DELETE", listener.getEvents());
assertEquals("${myListener}", listener.getImplementation()); assertEquals("${myListener}", listener.getImplementation());
assertEquals(BpmnXMLConstants.ATTRIBUTE_LISTENER_DELEGATEEXPRESSION, listener.getImplementationType()); assertEquals(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION, listener.getImplementationType());
// Listener that throws a signal-event
listener = process.getEventListeners().get(3);
assertEquals("ENTITY_DELETE", listener.getEvents());
assertEquals("theSignal", listener.getImplementation());
assertEquals(ImplementationType.IMPLEMENTATION_TYPE_THROW_SIGNAL_EVENT, listener.getImplementationType());
// Listener that throws a global signal-event
listener = process.getEventListeners().get(4);
assertEquals("ENTITY_DELETE", listener.getEvents());
assertEquals("theSignal", listener.getImplementation());
assertEquals(ImplementationType.IMPLEMENTATION_TYPE_THROW_GLOBAL_SIGNAL_EVENT, listener.getImplementationType());
// Listener that throws a message-event
listener = process.getEventListeners().get(5);
assertEquals("ENTITY_DELETE", listener.getEvents());
assertEquals("theMessage", listener.getImplementation());
assertEquals(ImplementationType.IMPLEMENTATION_TYPE_THROW_MESSAGE_EVENT, listener.getImplementationType());
// Listener that throws an error-event
listener = process.getEventListeners().get(6);
assertEquals("ENTITY_DELETE", listener.getEvents());
assertEquals("123", listener.getImplementation());
assertEquals(ImplementationType.IMPLEMENTATION_TYPE_THROW_ERROR_EVENT, listener.getImplementationType());
// Listener restricted to a specific entity
listener = process.getEventListeners().get(7);
assertEquals("ENTITY_DELETE", listener.getEvents());
assertEquals("123", listener.getImplementation());
assertEquals(ImplementationType.IMPLEMENTATION_TYPE_THROW_ERROR_EVENT, listener.getImplementationType());
assertEquals("job", listener.getEntityType());
} }
} }
...@@ -5,6 +5,11 @@ ...@@ -5,6 +5,11 @@
<activiti:eventListener events="ENTITY_CREATE" class="org.activiti.test.MyListener" /> <activiti:eventListener events="ENTITY_CREATE" class="org.activiti.test.MyListener" />
<activiti:eventListener class="org.activiti.test.AllEventTypesListener" /> <activiti:eventListener class="org.activiti.test.AllEventTypesListener" />
<activiti:eventListener events="ENTITY_DELETE" delegateExpression="${myListener}" /> <activiti:eventListener events="ENTITY_DELETE" delegateExpression="${myListener}" />
<activiti:eventListener events="ENTITY_DELETE" throwEvent="signal" signalName="theSignal" />
<activiti:eventListener events="ENTITY_DELETE" throwEvent="globalSignal" signalName="theSignal" />
<activiti:eventListener events="ENTITY_DELETE" throwEvent="message" messageName="theMessage" />
<activiti:eventListener events="ENTITY_DELETE" throwEvent="error" errorCode="123" />
<activiti:eventListener events="ENTITY_DELETE" throwEvent="error" errorCode="123" entityType="job" />
</extensionElements> </extensionElements>
<endEvent id="sid-F7795B38-72CD-41A4-9549-6CFB7D3E5FB5"></endEvent> <endEvent id="sid-F7795B38-72CD-41A4-9549-6CFB7D3E5FB5"></endEvent>
<sequenceFlow id="sid-C6C56AC3-9561-49E0-A58A-624F6CB8BB82" sourceRef="sid-F62B554B-FF4F-475E-94FF-A3F44EDA6A6A" targetRef="servicetask"></sequenceFlow> <sequenceFlow id="sid-C6C56AC3-9561-49E0-A58A-624F6CB8BB82" sourceRef="sid-F62B554B-FF4F-475E-94FF-A3F44EDA6A6A" targetRef="servicetask"></sequenceFlow>
......
...@@ -23,6 +23,7 @@ public class EventListener extends BaseElement { ...@@ -23,6 +23,7 @@ public class EventListener extends BaseElement {
protected String events; protected String events;
protected String implementationType; protected String implementationType;
protected String implementation; protected String implementation;
protected String entityType;
public String getEvents() { public String getEvents() {
return events; return events;
...@@ -42,6 +43,12 @@ public class EventListener extends BaseElement { ...@@ -42,6 +43,12 @@ public class EventListener extends BaseElement {
public void setImplementation(String implementation) { public void setImplementation(String implementation) {
this.implementation = implementation; this.implementation = implementation;
} }
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public String getEntityType() {
return entityType;
}
public EventListener clone() { public EventListener clone() {
EventListener clone = new EventListener(); EventListener clone = new EventListener();
...@@ -53,5 +60,6 @@ public class EventListener extends BaseElement { ...@@ -53,5 +60,6 @@ public class EventListener extends BaseElement {
setEvents(otherListener.getEvents()); setEvents(otherListener.getEvents());
setImplementation(otherListener.getImplementation()); setImplementation(otherListener.getImplementation());
setImplementationType(otherListener.getImplementationType()); setImplementationType(otherListener.getImplementationType());
setEntityType(otherListener.getEntityType());
} }
} }
...@@ -17,6 +17,10 @@ public class ImplementationType { ...@@ -17,6 +17,10 @@ public class ImplementationType {
public static String IMPLEMENTATION_TYPE_CLASS = "class"; public static String IMPLEMENTATION_TYPE_CLASS = "class";
public static String IMPLEMENTATION_TYPE_EXPRESSION = "expression"; public static String IMPLEMENTATION_TYPE_EXPRESSION = "expression";
public static String IMPLEMENTATION_TYPE_DELEGATEEXPRESSION = "delegateExpression"; public static String IMPLEMENTATION_TYPE_DELEGATEEXPRESSION = "delegateExpression";
public static String IMPLEMENTATION_TYPE_THROW_SIGNAL_EVENT = "throwSignalEvent";
public static String IMPLEMENTATION_TYPE_THROW_GLOBAL_SIGNAL_EVENT = "throwGlobalSignalEvent";
public static String IMPLEMENTATION_TYPE_THROW_MESSAGE_EVENT = "throwMessageEvent";
public static String IMPLEMENTATION_TYPE_THROW_ERROR_EVENT = "throwErrorEvent";
public static String IMPLEMENTATION_TYPE_WEBSERVICE = "##WebService"; public static String IMPLEMENTATION_TYPE_WEBSERVICE = "##WebService";
} }
/* 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.engine.delegate.event;
/**
* An {@link ActivitiEvent} related to an error being sent to an activity.
*
* @author Frederik Heremans
*/
public interface ActivitiErrorEvent extends ActivitiActivityEvent {
/**
* @return the error-code of the error. Returns null, if no specific error-code has been specified
* when the error was thrown.
*/
public String getErrorCode();
}
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
package org.activiti.engine.delegate.event; package org.activiti.engine.delegate.event;
/** /**
* Describes a class that listens for {@link ActivitiEvent} dispatched by the engine. * Describes a class that listens for {@link ActivitiEvent}s dispatched by the engine.
* *
* @author Frederik Heremans * @author Frederik Heremans
*/ */
......
...@@ -88,7 +88,7 @@ public enum ActivitiEventType { ...@@ -88,7 +88,7 @@ public enum ActivitiEventType {
ENGINE_CLOSED, ENGINE_CLOSED,
/** /**
* An activiyt is starting to execute. This event is dispatch right before an activity is executed. * An activity is starting to execute. This event is dispatch right before an activity is executed.
*/ */
ACTIVITY_STARTED, ACTIVITY_STARTED,
...@@ -115,6 +115,18 @@ public enum ActivitiEventType { ...@@ -115,6 +115,18 @@ public enum ActivitiEventType {
*/ */
ACTIVITY_MESSAGE_RECEIVED, ACTIVITY_MESSAGE_RECEIVED,
/**
* An activity has received an error event. Dispatched before the actual error has been received by
* the activity. This event will be either followed by a {@link #ACTIVITY_SIGNALLED} event or {@link #ACTIVITY_COMPLETE}
* for the involved activity, if the error was delivered successfully.
*/
ACTIVITY_ERROR_RECEIVED,
/**
* When a BPMN Error was thrown, but was not caught within in the process.
*/
UNCAUGHT_BPMN_ERROR,
/** /**
* A new variable has been created. * A new variable has been created.
*/ */
......
/* 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.engine.delegate.event.impl;
import org.activiti.engine.delegate.event.ActivitiErrorEvent;
import org.activiti.engine.delegate.event.ActivitiEventType;
/**
* Implementation of an {@link ActivitiErrorEvent}.
* @author Frederik Heremans
*/
public class ActivitiErrorEventImpl extends ActivitiActivityEventImpl implements ActivitiErrorEvent {
protected String errorCode;
public ActivitiErrorEventImpl(ActivitiEventType type) {
super(type);
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
@Override
public String getErrorCode() {
return errorCode;
}
}
...@@ -15,6 +15,7 @@ package org.activiti.engine.delegate.event.impl; ...@@ -15,6 +15,7 @@ package org.activiti.engine.delegate.event.impl;
import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.event.ActivitiActivityEvent; import org.activiti.engine.delegate.event.ActivitiActivityEvent;
import org.activiti.engine.delegate.event.ActivitiEntityEvent; import org.activiti.engine.delegate.event.ActivitiEntityEvent;
import org.activiti.engine.delegate.event.ActivitiErrorEvent;
import org.activiti.engine.delegate.event.ActivitiEvent; import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventType; import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.delegate.event.ActivitiExceptionEvent; import org.activiti.engine.delegate.event.ActivitiExceptionEvent;
...@@ -25,8 +26,8 @@ import org.activiti.engine.delegate.event.ActivitiVariableEvent; ...@@ -25,8 +26,8 @@ import org.activiti.engine.delegate.event.ActivitiVariableEvent;
import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.context.ExecutionContext; import org.activiti.engine.impl.context.ExecutionContext;
import org.activiti.engine.impl.persistence.entity.IdentityLinkEntity; import org.activiti.engine.impl.persistence.entity.IdentityLinkEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Job; import org.activiti.engine.runtime.Job;
import org.activiti.engine.task.Attachment;
import org.activiti.engine.task.Task; import org.activiti.engine.task.Task;
/** /**
...@@ -46,6 +47,14 @@ public class ActivitiEventBuilder { ...@@ -46,6 +47,14 @@ public class ActivitiEventBuilder {
return newEvent; return newEvent;
} }
public static ActivitiEvent createEvent(ActivitiEventType type, String executionId, String processInstanceId, String processDefinitionId) {
ActivitiEventImpl newEvent = new ActivitiEventImpl(type);
newEvent.setExecutionId(executionId);
newEvent.setProcessDefinitionId(processDefinitionId);
newEvent.setProcessInstanceId(processInstanceId);
return newEvent;
}
/** /**
* @param type type of event * @param type type of event
* @param entity the entity this event targets * @param entity the entity this event targets
...@@ -139,6 +148,16 @@ public class ActivitiEventBuilder { ...@@ -139,6 +148,16 @@ public class ActivitiEventBuilder {
return newEvent; return newEvent;
} }
public static ActivitiErrorEvent createErrorEvent(ActivitiEventType type, String activityId, String errorCode, String executionId, String processInstanceId, String processDefinitionId) {
ActivitiErrorEventImpl newEvent = new ActivitiErrorEventImpl(type);
newEvent.setActivityId(activityId);
newEvent.setExecutionId(executionId);
newEvent.setProcessDefinitionId(processDefinitionId);
newEvent.setProcessInstanceId(processInstanceId);
newEvent.setErrorCode(errorCode);
return newEvent;
}
public static ActivitiVariableEvent createVariableEvent(ActivitiEventType type, String variableName, Object variableValue, String taskId, public static ActivitiVariableEvent createVariableEvent(ActivitiEventType type, String variableName, Object variableValue, String taskId,
String executionId, String processInstanceId, String processDefinitionId) { String executionId, String processInstanceId, String processDefinitionId) {
ActivitiVariableEventImpl newEvent = new ActivitiVariableEventImpl(type); ActivitiVariableEventImpl newEvent = new ActivitiVariableEventImpl(type);
...@@ -199,6 +218,8 @@ public class ActivitiEventBuilder { ...@@ -199,6 +218,8 @@ public class ActivitiEventBuilder {
event.setProcessInstanceId(((Task)persistendObject).getProcessInstanceId()); event.setProcessInstanceId(((Task)persistendObject).getProcessInstanceId());
event.setExecutionId(((Task)persistendObject).getExecutionId()); event.setExecutionId(((Task)persistendObject).getExecutionId());
event.setProcessDefinitionId(((Task)persistendObject).getProcessDefinitionId()); event.setProcessDefinitionId(((Task)persistendObject).getProcessDefinitionId());
} else if(persistendObject instanceof ProcessDefinition) {
event.setProcessDefinitionId(((ProcessDefinition) persistendObject).getId());
} }
} }
} }
......
...@@ -12,12 +12,15 @@ ...@@ -12,12 +12,15 @@
*/ */
package org.activiti.engine.delegate.event.impl; package org.activiti.engine.delegate.event.impl;
import org.activiti.engine.delegate.event.ActivitiEntityEvent;
import org.activiti.engine.delegate.event.ActivitiEvent; import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventDispatcher; import org.activiti.engine.delegate.event.ActivitiEventDispatcher;
import org.activiti.engine.delegate.event.ActivitiEventListener; import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.delegate.event.ActivitiEventType; import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.ProcessDefinition;
/** /**
* Class capable of dispatching events. * Class capable of dispatching events.
...@@ -28,55 +31,95 @@ public class ActivitiEventDispatcherImpl implements ActivitiEventDispatcher { ...@@ -28,55 +31,95 @@ public class ActivitiEventDispatcherImpl implements ActivitiEventDispatcher {
protected ActivitiEventSupport eventSupport; protected ActivitiEventSupport eventSupport;
protected boolean enabled = true; protected boolean enabled = true;
public ActivitiEventDispatcherImpl() { public ActivitiEventDispatcherImpl() {
eventSupport = new ActivitiEventSupport(); eventSupport = new ActivitiEventSupport();
} }
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
this.enabled = enabled; this.enabled = enabled;
} }
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return enabled;
} }
@Override @Override
public void addEventListener(ActivitiEventListener listenerToAdd) { public void addEventListener(ActivitiEventListener listenerToAdd) {
eventSupport.addEventListener(listenerToAdd); eventSupport.addEventListener(listenerToAdd);
} }
@Override @Override
public void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType... types) { public void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType... types) {
eventSupport.addEventListener(listenerToAdd, types); eventSupport.addEventListener(listenerToAdd, types);
} }
@Override @Override
public void removeEventListener(ActivitiEventListener listenerToRemove) { public void removeEventListener(ActivitiEventListener listenerToRemove) {
eventSupport.removeEventListener(listenerToRemove); eventSupport.removeEventListener(listenerToRemove);
} }
@Override @Override
public void dispatchEvent(ActivitiEvent event) { public void dispatchEvent(ActivitiEvent event) {
if(enabled) { if (enabled) {
eventSupport.dispatchEvent(event); eventSupport.dispatchEvent(event);
} }
// Check if a process context is active. If so, we also call the process-definition // Check if a process context is active. If so, we also call the
// process-definition
// specific listeners (if any). // specific listeners (if any).
if(Context.isExecutionContextActive()) { if (Context.isExecutionContextActive()) {
ProcessDefinitionEntity definition = Context.getExecutionContext().getProcessDefinition(); ProcessDefinitionEntity definition = Context.getExecutionContext().getProcessDefinition();
if(definition != null) { if (definition != null) {
definition.getEventSupport().dispatchEvent(event); definition.getEventSupport().dispatchEvent(event);
} }
} else { } else {
// // Try getting hold of the Process definition, based on the process definition-key, if a // Try getting hold of the Process definition, based on the process
// // context is active // definition-key, if a
// CommandContext commandContext = Context.getCommandContext(); // context is active
// if(commandContext != null) { CommandContext commandContext = Context.getCommandContext();
// commandContext.getProcessEngineConfiguration().getDeploymentManager() if (commandContext != null) {
// .resolveProcessDefinition(processDefinition) ProcessDefinitionEntity processDefinition = extractProcessDefinitionEntityFromEvent(event);
// } if (processDefinition != null) {
processDefinition.getEventSupport().dispatchEvent(event);
}
}
} }
} }
/**
* In case no process-context is active, this method attempts to extract a
* process-definition based on the event. In case it's an event related to an
* entity, this can be deducted by inspecting the entity, without additional
* queries to the database.
*
* If not an entity-related event, the process-definition will be retrieved
* based on the processDefinitionId (if filled in). This requires an
* additional query to the database in case not already cached. However,
* queries will only occur when the definition is not yet in the cache, which
* is very unlikely to happen, unless evicted.
*
* @param event
* @return
*/
protected ProcessDefinitionEntity extractProcessDefinitionEntityFromEvent(ActivitiEvent event) {
ProcessDefinitionEntity result = null;
if (event.getProcessDefinitionId() != null) {
result = Context.getProcessEngineConfiguration().getDeploymentManager().getProcessDefinitionCache()
.get(event.getProcessDefinitionId());
if (result != null) {
result = Context.getProcessEngineConfiguration().getDeploymentManager().resolveProcessDefinition(result);
}
}
if(result == null && event instanceof ActivitiEntityEvent) {
Object entity = ((ActivitiEntityEvent) event).getEntity();
if(entity instanceof ProcessDefinition) {
result = (ProcessDefinitionEntity) entity;
}
}
return result;
}
} }
/* 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.engine.impl.bpmn.helper;
import org.activiti.engine.delegate.event.ActivitiEntityEvent;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
/**
* Base implementation of a {@link ActivitiEventListener}, used when creating event-listeners
* that are part of a BPMN definition.
*
* @author Frederik Heremans
*/
public abstract class BaseDelegateEventListener implements ActivitiEventListener {
protected Class<?> entityClass;
public void setEntityClass(Class<?> entityClass) {
this.entityClass = entityClass;
}
protected boolean isValidEvent(ActivitiEvent event) {
boolean valid = false;
if(entityClass != null) {
if(event instanceof ActivitiEntityEvent) {
Object entity = ((ActivitiEntityEvent) event).getEntity();
if(entity != null) {
valid = entityClass.isAssignableFrom(entity.getClass());
}
}
} else {
// If no class is specified, all events are valid
valid = true;
}
return valid;
}
}
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
package org.activiti.engine.impl.bpmn.helper; package org.activiti.engine.impl.bpmn.helper;
import org.activiti.engine.ActivitiIllegalArgumentException; import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.delegate.event.ActivitiEntityEvent;
import org.activiti.engine.delegate.event.ActivitiEvent; import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener; import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.impl.util.ReflectUtil; import org.activiti.engine.impl.util.ReflectUtil;
...@@ -20,22 +21,29 @@ import org.activiti.engine.impl.util.ReflectUtil; ...@@ -20,22 +21,29 @@ import org.activiti.engine.impl.util.ReflectUtil;
/** /**
* An {@link ActivitiEventListener} implementation which uses a classname to * An {@link ActivitiEventListener} implementation which uses a classname to
* create a delegate {@link ActivitiEventListener} instance to use for event notification. * create a delegate {@link ActivitiEventListener} instance to use for event notification.
* <br><br>
*
* In case an entityClass was passed in the constructor, only events that are {@link ActivitiEntityEvent}'s
* that target an entity of the given type, are dispatched to the delegate.
* *
* @author Frederik Heremans * @author Frederik Heremans
*/ */
public class DelegateActivitiEventListener implements ActivitiEventListener { public class DelegateActivitiEventListener extends BaseDelegateEventListener {
protected String className; protected String className;
protected ActivitiEventListener delegateInstance; protected ActivitiEventListener delegateInstance;
protected boolean failOnException = true; protected boolean failOnException = true;
public DelegateActivitiEventListener(String className) { public DelegateActivitiEventListener(String className, Class<?> entityClass) {
this.className = className; this.className = className;
setEntityClass(entityClass);
} }
@Override @Override
public void onEvent(ActivitiEvent event) { public void onEvent(ActivitiEvent event) {
getDelegateInstance().onEvent(event); if(isValidEvent(event)) {
getDelegateInstance().onEvent(event);
}
} }
@Override @Override
......
...@@ -14,6 +14,7 @@ package org.activiti.engine.impl.bpmn.helper; ...@@ -14,6 +14,7 @@ package org.activiti.engine.impl.bpmn.helper;
import org.activiti.engine.ActivitiIllegalArgumentException; import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.delegate.Expression; import org.activiti.engine.delegate.Expression;
import org.activiti.engine.delegate.event.ActivitiEntityEvent;
import org.activiti.engine.delegate.event.ActivitiEvent; import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener; import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.impl.el.NoExecutionVariableScope; import org.activiti.engine.impl.el.NoExecutionVariableScope;
...@@ -21,39 +22,44 @@ import org.activiti.engine.impl.el.NoExecutionVariableScope; ...@@ -21,39 +22,44 @@ import org.activiti.engine.impl.el.NoExecutionVariableScope;
/** /**
* An {@link ActivitiEventListener} implementation which resolves an expression * An {@link ActivitiEventListener} implementation which resolves an expression
* to a delegate {@link ActivitiEventListener} instance and uses this for event notification. * to a delegate {@link ActivitiEventListener} instance and uses this for event notification.
* * <br><br>
* In case an entityClass was passed in the constructor, only events that are {@link ActivitiEntityEvent}'s
* that target an entity of the given type, are dispatched to the delegate.
*
* @author Frederik Heremans * @author Frederik Heremans
*/ */
public class DelegateExpressionActivitiEventListener implements ActivitiEventListener { public class DelegateExpressionActivitiEventListener extends BaseDelegateEventListener {
protected Expression expression; protected Expression expression;
protected boolean failOnException = true; protected boolean failOnException = true;
public DelegateExpressionActivitiEventListener(Expression expression) { public DelegateExpressionActivitiEventListener(Expression expression, Class<?> entityClass) {
this.expression = expression; this.expression = expression;
setEntityClass(entityClass);
} }
@Override @Override
public void onEvent(ActivitiEvent event) { public void onEvent(ActivitiEvent event) {
NoExecutionVariableScope scope = new NoExecutionVariableScope(); if(isValidEvent(event)) {
NoExecutionVariableScope scope = new NoExecutionVariableScope();
Object delegate = expression.getValue(scope);
if (delegate instanceof ActivitiEventListener) {
// Cache result of isFailOnException() from delegate-instance untill next
// event is received. This prevents us from having to resolve the expression twice when
// an error occurs.
failOnException = ((ActivitiEventListener) delegate).isFailOnException();
// Call the delegate
((ActivitiEventListener) delegate).onEvent(event);
} else {
// Force failing, since the exception we're about to throw cannot be ignored, because it Object delegate = expression.getValue(scope);
// did not originate from the listener itself if (delegate instanceof ActivitiEventListener) {
failOnException = true; // Cache result of isFailOnException() from delegate-instance until next
throw new ActivitiIllegalArgumentException("Delegate expression " + expression // event is received. This prevents us from having to resolve the expression twice when
+ " did not resolve to an implementation of " + ActivitiEventListener.class.getName()); // an error occurs.
failOnException = ((ActivitiEventListener) delegate).isFailOnException();
// Call the delegate
((ActivitiEventListener) delegate).onEvent(event);
} else {
// Force failing, since the exception we're about to throw cannot be ignored, because it
// did not originate from the listener itself
failOnException = true;
throw new ActivitiIllegalArgumentException("Delegate expression " + expression
+ " did not resolve to an implementation of " + ActivitiEventListener.class.getName());
}
} }
} }
......
...@@ -17,9 +17,12 @@ import java.util.List; ...@@ -17,9 +17,12 @@ import java.util.List;
import org.activiti.engine.ActivitiException; import org.activiti.engine.ActivitiException;
import org.activiti.engine.delegate.BpmnError; import org.activiti.engine.delegate.BpmnError;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.delegate.event.impl.ActivitiEventBuilder;
import org.activiti.engine.impl.bpmn.behavior.EventSubProcessStartEventActivityBehavior; import org.activiti.engine.impl.bpmn.behavior.EventSubProcessStartEventActivityBehavior;
import org.activiti.engine.impl.bpmn.parser.BpmnParse; import org.activiti.engine.impl.bpmn.parser.BpmnParse;
import org.activiti.engine.impl.bpmn.parser.ErrorEventDefinition; import org.activiti.engine.impl.bpmn.parser.ErrorEventDefinition;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.pvm.PvmActivity; import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmProcessDefinition; import org.activiti.engine.impl.pvm.PvmProcessDefinition;
...@@ -58,7 +61,7 @@ public class ErrorPropagation { ...@@ -58,7 +61,7 @@ public class ErrorPropagation {
// TODO: merge two approaches (super process / regular process approach) // TODO: merge two approaches (super process / regular process approach)
if(eventHandlerId != null) { if(eventHandlerId != null) {
executeCatch(eventHandlerId, execution); executeCatch(eventHandlerId, execution, errorCode);
}else { }else {
ActivityExecution superExecution = getSuperExecution(execution); ActivityExecution superExecution = getSuperExecution(execution);
if (superExecution != null) { if (superExecution != null) {
...@@ -66,6 +69,11 @@ public class ErrorPropagation { ...@@ -66,6 +69,11 @@ public class ErrorPropagation {
} else { } else {
LOG.info("{} throws error event with errorCode '{}', but no catching boundary event was defined. Execution will simply be ended (none end event semantics).", LOG.info("{} throws error event with errorCode '{}', but no catching boundary event was defined. Execution will simply be ended (none end event semantics).",
execution.getActivity().getId(), errorCode); execution.getActivity().getId(), errorCode);
if(Context.getProcessEngineConfiguration() != null && Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
ActivitiEventBuilder.createErrorEvent(ActivitiEventType.UNCAUGHT_BPMN_ERROR, null, errorCode, execution.getId(), execution.getProcessInstanceId(), execution.getProcessDefinitionId()));
}
execution.end(); execution.end();
} }
} }
...@@ -99,7 +107,7 @@ public class ErrorPropagation { ...@@ -99,7 +107,7 @@ public class ErrorPropagation {
private static void executeCatchInSuperProcess(String errorCode, ActivityExecution superExecution) { private static void executeCatchInSuperProcess(String errorCode, ActivityExecution superExecution) {
String errorHandlerId = findLocalErrorEventHandler(superExecution, errorCode); String errorHandlerId = findLocalErrorEventHandler(superExecution, errorCode);
if (errorHandlerId != null) { if (errorHandlerId != null) {
executeCatch(errorHandlerId, superExecution); executeCatch(errorHandlerId, superExecution, errorCode);
} else { // no matching catch found, going one level up in process hierarchy } else { // no matching catch found, going one level up in process hierarchy
ActivityExecution superSuperExecution = getSuperExecution(superExecution); ActivityExecution superSuperExecution = getSuperExecution(superExecution);
if (superSuperExecution != null) { if (superSuperExecution != null) {
...@@ -120,7 +128,7 @@ public class ErrorPropagation { ...@@ -120,7 +128,7 @@ public class ErrorPropagation {
return superExecution; return superExecution;
} }
private static void executeCatch(String errorHandlerId, ActivityExecution execution) { private static void executeCatch(String errorHandlerId, ActivityExecution execution, String errorCode) {
ProcessDefinitionImpl processDefinition = ((ExecutionEntity) execution).getProcessDefinition(); ProcessDefinitionImpl processDefinition = ((ExecutionEntity) execution).getProcessDefinition();
ActivityImpl errorHandler = processDefinition.findActivity(errorHandlerId); ActivityImpl errorHandler = processDefinition.findActivity(errorHandlerId);
if (errorHandler == null) { if (errorHandler == null) {
...@@ -140,7 +148,7 @@ public class ErrorPropagation { ...@@ -140,7 +148,7 @@ public class ErrorPropagation {
} }
if(catchingScope instanceof PvmProcessDefinition) { if(catchingScope instanceof PvmProcessDefinition) {
executeEventHandler(errorHandler, ((ExecutionEntity)execution).getProcessInstance()); executeEventHandler(errorHandler, ((ExecutionEntity)execution).getProcessInstance(), errorCode);
} else { } else {
if (currentActivity.getId().equals(catchingScope.getId())) { if (currentActivity.getId().equals(catchingScope.getId())) {
...@@ -171,7 +179,7 @@ public class ErrorPropagation { ...@@ -171,7 +179,7 @@ public class ErrorPropagation {
} }
if (matchingParentFound && leavingExecution != null) { if (matchingParentFound && leavingExecution != null) {
executeEventHandler(errorHandler, leavingExecution); executeEventHandler(errorHandler, leavingExecution, errorCode);
} else { } else {
throw new ActivitiException("No matching parent execution for activity " + errorHandlerId + " found"); throw new ActivitiException("No matching parent execution for activity " + errorHandlerId + " found");
} }
...@@ -179,7 +187,12 @@ public class ErrorPropagation { ...@@ -179,7 +187,12 @@ public class ErrorPropagation {
} }
private static void executeEventHandler(ActivityImpl borderEventActivity, ActivityExecution leavingExecution) { private static void executeEventHandler(ActivityImpl borderEventActivity, ActivityExecution leavingExecution, String errorCode) {
if(Context.getProcessEngineConfiguration() != null && Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
ActivitiEventBuilder.createErrorEvent(ActivitiEventType.ACTIVITY_ERROR_RECEIVED, borderEventActivity.getId(), errorCode, leavingExecution.getId(), leavingExecution.getProcessInstanceId(), leavingExecution.getProcessDefinitionId()));
}
if(borderEventActivity.getActivityBehavior() instanceof EventSubProcessStartEventActivityBehavior) { if(borderEventActivity.getActivityBehavior() instanceof EventSubProcessStartEventActivityBehavior) {
InterpretableExecution execution = (InterpretableExecution) leavingExecution; InterpretableExecution execution = (InterpretableExecution) leavingExecution;
execution.setActivity(borderEventActivity.getParentActivity()); execution.setActivity(borderEventActivity.getParentActivity());
......
/* 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.engine.impl.bpmn.helper;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
/**
* An {@link ActivitiEventListener} that throws a error event when an event is
* dispatched to it.
*
* @author Frederik Heremans
*
*/
public class ErrorThrowingEventListener extends BaseDelegateEventListener {
protected String errorCode;
@Override
public void onEvent(ActivitiEvent event) {
if(isValidEvent(event)) {
ExecutionEntity execution = null;
if (Context.isExecutionContextActive()) {
execution = Context.getExecutionContext().getExecution();
} else if(event.getExecutionId() != null){
// Get the execution based on the event's execution ID instead
execution = Context.getCommandContext().getExecutionEntityManager()
.findExecutionById(event.getExecutionId());
}
if(execution == null) {
throw new ActivitiException("No execution context active and event is not related to an execution. No compensation event can be thrown.");
}
try {
ErrorPropagation.propagateError(errorCode, execution);
} catch (Exception e) {
throw new ActivitiException("Error while propagating error-event", e);
}
}
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
@Override
public boolean isFailOnException() {
return 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.engine.impl.bpmn.helper;
import java.util.List;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.event.MessageEventHandler;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.EventSubscriptionEntity;
/**
* An {@link ActivitiEventListener} that throws a message event when an event is
* dispatched to it. Sends the message to the execution the event was fired from. If the execution
* is not subscribed to a message, the process-instance is checked.
*
* @author Frederik Heremans
*
*/
public class MessageThrowingEventListener extends BaseDelegateEventListener {
protected String messageName;
protected Class<?> entityClass;
@Override
public void onEvent(ActivitiEvent event) {
if(isValidEvent(event)) {
if (event.getProcessInstanceId() == null) {
throw new ActivitiIllegalArgumentException(
"Cannot throw process-instance scoped message, since the dispatched event is not part of an ongoing process instance");
}
CommandContext commandContext = Context.getCommandContext();
List<EventSubscriptionEntity> subscriptionEntities = commandContext.getEventSubscriptionEntityManager()
.findEventSubscriptionsByNameAndExecution(MessageEventHandler.EVENT_HANDLER_TYPE, messageName, event.getExecutionId());
// Revert to messaging the process instance
if(subscriptionEntities.isEmpty() && event.getProcessInstanceId() != null && !event.getExecutionId().equals(event.getProcessInstanceId())) {
subscriptionEntities = commandContext.getEventSubscriptionEntityManager()
.findEventSubscriptionsByNameAndExecution(MessageEventHandler.EVENT_HANDLER_TYPE, messageName, event.getProcessInstanceId());
}
for (EventSubscriptionEntity signalEventSubscriptionEntity : subscriptionEntities) {
signalEventSubscriptionEntity.eventReceived(null, false);
}
}
}
public void setMessageName(String messageName) {
this.messageName = messageName;
}
@Override
public boolean isFailOnException() {
return 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.engine.impl.bpmn.helper;
import java.util.List;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.SignalEventSubscriptionEntity;
/**
* An {@link ActivitiEventListener} that throws a signal event when an event is
* dispatched to it.
*
* @author Frederik Heremans
*
*/
public class SignalThrowingEventListener extends BaseDelegateEventListener {
protected String signalName;
protected boolean processInstanceScope = true;
@Override
public void onEvent(ActivitiEvent event) {
if (isValidEvent(event)) {
if (event.getProcessInstanceId() == null && processInstanceScope) {
throw new ActivitiIllegalArgumentException(
"Cannot throw process-instance scoped signal, since the dispatched event is not part of an ongoing process instance");
}
CommandContext commandContext = Context.getCommandContext();
List<SignalEventSubscriptionEntity> subscriptionEntities = null;
if (processInstanceScope) {
subscriptionEntities = commandContext.getEventSubscriptionEntityManager()
.findSignalEventSubscriptionsByProcessInstanceAndEventName(event.getProcessInstanceId(), signalName);
} else {
subscriptionEntities = commandContext.getEventSubscriptionEntityManager()
.findSignalEventSubscriptionsByEventName(signalName);
}
for (SignalEventSubscriptionEntity signalEventSubscriptionEntity : subscriptionEntities) {
signalEventSubscriptionEntity.eventReceived(null, false);
}
}
}
public void setSignalName(String signalName) {
this.signalName = signalName;
}
public void setProcessInstanceScope(boolean processInstanceScope) {
this.processInstanceScope = processInstanceScope;
}
@Override
public boolean isFailOnException() {
return true;
}
}
...@@ -12,19 +12,36 @@ ...@@ -12,19 +12,36 @@
*/ */
package org.activiti.engine.impl.bpmn.parser.factory; package org.activiti.engine.impl.bpmn.parser.factory;
import java.util.HashMap;
import java.util.Map;
import org.activiti.bpmn.model.ActivitiListener; import org.activiti.bpmn.model.ActivitiListener;
import org.activiti.bpmn.model.EventListener; import org.activiti.bpmn.model.EventListener;
import org.activiti.bpmn.model.ImplementationType;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.delegate.ExecutionListener; import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.delegate.TaskListener; import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.delegate.event.ActivitiEventListener; import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.impl.bpmn.helper.BaseDelegateEventListener;
import org.activiti.engine.impl.bpmn.helper.ClassDelegate; import org.activiti.engine.impl.bpmn.helper.ClassDelegate;
import org.activiti.engine.impl.bpmn.helper.DelegateActivitiEventListener; import org.activiti.engine.impl.bpmn.helper.DelegateActivitiEventListener;
import org.activiti.engine.impl.bpmn.helper.DelegateExpressionActivitiEventListener; import org.activiti.engine.impl.bpmn.helper.DelegateExpressionActivitiEventListener;
import org.activiti.engine.impl.bpmn.helper.ErrorThrowingEventListener;
import org.activiti.engine.impl.bpmn.helper.MessageThrowingEventListener;
import org.activiti.engine.impl.bpmn.helper.SignalThrowingEventListener;
import org.activiti.engine.impl.bpmn.listener.DelegateExpressionExecutionListener; import org.activiti.engine.impl.bpmn.listener.DelegateExpressionExecutionListener;
import org.activiti.engine.impl.bpmn.listener.DelegateExpressionTaskListener; import org.activiti.engine.impl.bpmn.listener.DelegateExpressionTaskListener;
import org.activiti.engine.impl.bpmn.listener.ExpressionExecutionListener; import org.activiti.engine.impl.bpmn.listener.ExpressionExecutionListener;
import org.activiti.engine.impl.bpmn.listener.ExpressionTaskListener; import org.activiti.engine.impl.bpmn.listener.ExpressionTaskListener;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.Job;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Attachment;
import org.activiti.engine.task.Comment;
import org.activiti.engine.task.IdentityLink;
import org.activiti.engine.task.Task;
/** /**
* Default implementation of the {@link ListenerFactory}. * Default implementation of the {@link ListenerFactory}.
...@@ -34,7 +51,19 @@ import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl; ...@@ -34,7 +51,19 @@ import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
* @author Joram Barrez * @author Joram Barrez
*/ */
public class DefaultListenerFactory extends AbstractBehaviorFactory implements ListenerFactory { public class DefaultListenerFactory extends AbstractBehaviorFactory implements ListenerFactory {
public static final Map<String, Class<?>> ENTITY_MAPPING = new HashMap<String, Class<?>>();
static {
ENTITY_MAPPING.put("attachment", Attachment.class);
ENTITY_MAPPING.put("comment", Comment.class);
ENTITY_MAPPING.put("execution", Execution.class);
ENTITY_MAPPING.put("identity-link", IdentityLink.class);
ENTITY_MAPPING.put("job", Job.class);
ENTITY_MAPPING.put("process-definition", ProcessDefinition.class);
ENTITY_MAPPING.put("process-instance", ProcessInstance.class);
ENTITY_MAPPING.put("task", Task.class);
}
public TaskListener createClassDelegateTaskListener(ActivitiListener activitiListener) { public TaskListener createClassDelegateTaskListener(ActivitiListener activitiListener) {
return new ClassDelegate(activitiListener.getImplementation(), createFieldDeclarations(activitiListener.getFieldExtensions())); return new ClassDelegate(activitiListener.getImplementation(), createFieldDeclarations(activitiListener.getFieldExtensions()));
} }
...@@ -63,12 +92,56 @@ public class DefaultListenerFactory extends AbstractBehaviorFactory implements L ...@@ -63,12 +92,56 @@ public class DefaultListenerFactory extends AbstractBehaviorFactory implements L
@Override @Override
public ActivitiEventListener createClassDelegateEventListener(EventListener eventListener) { public ActivitiEventListener createClassDelegateEventListener(EventListener eventListener) {
return new DelegateActivitiEventListener(eventListener.getImplementation()); return new DelegateActivitiEventListener(eventListener.getImplementation(), getEntityType(eventListener.getEntityType()));
} }
@Override @Override
public ActivitiEventListener createDelegateExpressionEventListener(EventListener eventListener) { public ActivitiEventListener createDelegateExpressionEventListener(EventListener eventListener) {
return new DelegateExpressionActivitiEventListener(expressionManager.createExpression( return new DelegateExpressionActivitiEventListener(expressionManager.createExpression(
eventListener.getImplementation())); eventListener.getImplementation()), getEntityType(eventListener.getEntityType()));
}
@Override
public ActivitiEventListener createEventThrowingEventListener(EventListener eventListener) {
BaseDelegateEventListener result = null;
if (ImplementationType.IMPLEMENTATION_TYPE_THROW_SIGNAL_EVENT.equals(eventListener.getImplementationType())) {
result = new SignalThrowingEventListener();
((SignalThrowingEventListener) result).setSignalName(eventListener.getImplementation());
((SignalThrowingEventListener) result).setProcessInstanceScope(true);
} else if (ImplementationType.IMPLEMENTATION_TYPE_THROW_GLOBAL_SIGNAL_EVENT.equals(eventListener.getImplementationType())) {
result = new SignalThrowingEventListener();
((SignalThrowingEventListener) result).setSignalName(eventListener.getImplementation());
((SignalThrowingEventListener) result).setProcessInstanceScope(false);
} else if (ImplementationType.IMPLEMENTATION_TYPE_THROW_MESSAGE_EVENT.equals(eventListener.getImplementationType())) {
result = new MessageThrowingEventListener();
((MessageThrowingEventListener) result).setMessageName(eventListener.getImplementation());
} else if (ImplementationType.IMPLEMENTATION_TYPE_THROW_ERROR_EVENT.equals(eventListener.getImplementationType())) {
result = new ErrorThrowingEventListener();
((ErrorThrowingEventListener) result).setErrorCode(eventListener.getImplementation());
}
if (result == null) {
throw new ActivitiIllegalArgumentException("Cannot create an event-throwing event-listener, unknown implementation type: "
+ eventListener.getImplementationType());
}
result.setEntityClass(getEntityType(eventListener.getEntityType()));
return result;
}
/**
* @param entityType the name of the entity
* @return
* @throws ActivitiIllegalArgumentException when the given entity name
*/
protected Class<?> getEntityType(String entityType) {
if(entityType != null) {
Class<?> entityClass = ENTITY_MAPPING.get(entityType.trim());
if(entityClass == null) {
throw new ActivitiIllegalArgumentException("Unsupported entity-type for an ActivitiEventListener: " + entityType);
}
return entityClass;
}
return null;
} }
} }
...@@ -53,5 +53,7 @@ public interface ListenerFactory { ...@@ -53,5 +53,7 @@ public interface ListenerFactory {
public abstract ActivitiEventListener createClassDelegateEventListener(EventListener eventListener); public abstract ActivitiEventListener createClassDelegateEventListener(EventListener eventListener);
public abstract ActivitiEventListener createDelegateExpressionEventListener(EventListener eventListener); public abstract ActivitiEventListener createDelegateExpressionEventListener(EventListener eventListener);
public abstract ActivitiEventListener createEventThrowingEventListener(EventListener eventListener);
} }
\ No newline at end of file
...@@ -110,6 +110,12 @@ public class ProcessParseHandler extends AbstractBpmnParseHandler<Process> { ...@@ -110,6 +110,12 @@ public class ProcessParseHandler extends AbstractBpmnParseHandler<Process> {
} else if(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equals(eventListener.getImplementationType())) { } else if(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equals(eventListener.getImplementationType())) {
currentProcessDefinition.getEventSupport().addEventListener( currentProcessDefinition.getEventSupport().addEventListener(
bpmnParse.getListenerFactory().createDelegateExpressionEventListener(eventListener), types); bpmnParse.getListenerFactory().createDelegateExpressionEventListener(eventListener), types);
} else if(ImplementationType.IMPLEMENTATION_TYPE_THROW_SIGNAL_EVENT.equals(eventListener.getImplementationType())
|| ImplementationType.IMPLEMENTATION_TYPE_THROW_GLOBAL_SIGNAL_EVENT.equals(eventListener.getImplementationType())
|| ImplementationType.IMPLEMENTATION_TYPE_THROW_MESSAGE_EVENT.equals(eventListener.getImplementationType())
|| ImplementationType.IMPLEMENTATION_TYPE_THROW_ERROR_EVENT.equals(eventListener.getImplementationType())){
currentProcessDefinition.getEventSupport().addEventListener(
bpmnParse.getListenerFactory().createEventThrowingEventListener(eventListener), types);
} else { } else {
bpmnParse.getBpmnModel().addProblem( bpmnParse.getBpmnModel().addProblem(
"Unsupported implementation type for EventLIstener: " + eventListener.getImplementationType(), "Unsupported implementation type for EventLIstener: " + eventListener.getImplementationType(),
......
...@@ -15,6 +15,7 @@ package org.activiti.engine.test.api.event; ...@@ -15,6 +15,7 @@ package org.activiti.engine.test.api.event;
import java.util.Collections; import java.util.Collections;
import org.activiti.engine.delegate.event.ActivitiActivityEvent; import org.activiti.engine.delegate.event.ActivitiActivityEvent;
import org.activiti.engine.delegate.event.ActivitiErrorEvent;
import org.activiti.engine.delegate.event.ActivitiEvent; import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventType; import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.delegate.event.ActivitiMessageEvent; import org.activiti.engine.delegate.event.ActivitiMessageEvent;
...@@ -345,8 +346,85 @@ public class ActivityEventsTest extends PluggableActivitiTestCase { ...@@ -345,8 +346,85 @@ public class ActivityEventsTest extends PluggableActivitiTestCase {
assertEquals(processInstance.getProcessDefinitionId(), signalEvent.getProcessDefinitionId()); assertEquals(processInstance.getProcessDefinitionId(), signalEvent.getProcessDefinitionId());
assertEquals("compensationDone", signalEvent.getSignalName()); assertEquals("compensationDone", signalEvent.getSignalName());
assertNull(signalEvent.getSignalData()); assertNull(signalEvent.getSignalData());
// Check if the process is still alive
processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(processInstance);
}
/**
* Test events related to error-events
*/
@Deployment
public void testActivityErrorEvents() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("errorProcess");
assertNotNull(processInstance);
// Error-handling should have ended the process
ProcessInstance afterErrorInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNull(afterErrorInstance);
ActivitiErrorEvent errorEvent = null;
for(ActivitiEvent event : listener.getEventsReceived()) {
if(event instanceof ActivitiErrorEvent) {
if(errorEvent == null) {
errorEvent = (ActivitiErrorEvent) event;
} else {
fail("Only one ActivityErrorEvent expected");
}
}
}
assertNotNull(errorEvent);
assertEquals(ActivitiEventType.ACTIVITY_ERROR_RECEIVED, errorEvent.getType());
assertEquals("catchError", errorEvent.getActivityId());
assertEquals("123", errorEvent.getErrorCode());
assertEquals(processInstance.getId(), errorEvent.getProcessInstanceId());
assertEquals(processInstance.getProcessDefinitionId(), errorEvent.getProcessDefinitionId());
assertFalse(processInstance.getId().equals(errorEvent.getExecutionId()));
} }
/**
* Test events related to error-events, thrown from within process-execution (eg. service-task).
*/
@Deployment
public void testActivityErrorEventsFromBPMNError() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("errorProcess");
assertNotNull(processInstance);
// Error-handling should have ended the process
ProcessInstance afterErrorInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNull(afterErrorInstance);
ActivitiErrorEvent errorEvent = null;
for(ActivitiEvent event : listener.getEventsReceived()) {
if(event instanceof ActivitiErrorEvent) {
if(errorEvent == null) {
errorEvent = (ActivitiErrorEvent) event;
} else {
fail("Only one ActivityErrorEvent expected");
}
}
}
assertNotNull(errorEvent);
assertEquals(ActivitiEventType.ACTIVITY_ERROR_RECEIVED, errorEvent.getType());
assertEquals("catchError", errorEvent.getActivityId());
assertEquals("23", errorEvent.getErrorCode());
assertEquals(processInstance.getId(), errorEvent.getProcessInstanceId());
assertEquals(processInstance.getProcessDefinitionId(), errorEvent.getProcessDefinitionId());
assertFalse(processInstance.getId().equals(errorEvent.getExecutionId()));
}
@Override @Override
protected void initializeServices() { protected void initializeServices() {
......
/* 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.engine.test.api.event;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.impl.bpmn.helper.ErrorThrowingEventListener;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.Deployment;
/**
* Test case for all {@link ActivitiEventListener}s that throws an error BPMN event when an {@link ActivitiEvent}
* has been dispatched.
*
* @author Frederik Heremans
*/
public class ErrorThrowingEventListenerTest extends PluggableActivitiTestCase {
@Deployment
public void testThrowError() throws Exception {
ErrorThrowingEventListener listener = null;
try {
listener = new ErrorThrowingEventListener();
processEngineConfiguration.getEventDispatcher().addEventListener(listener, ActivitiEventType.TASK_ASSIGNED);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testError");
assertNotNull(processInstance);
// Fetch the task and assign it. Should cause error-event to be dispatched
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("userTask")
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
// Error-handling should have been called, and "escalate" task should be available instead of original one
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("escalatedTask")
.singleResult();
assertNotNull(task);
} finally {
processEngineConfiguration.getEventDispatcher().removeEventListener(listener);
}
}
@Deployment
public void testThrowErrorDefinedInProcessDefinition() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testError");
assertNotNull(processInstance);
// Fetch the task and assign it. Should cause error-event to be dispatched
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("userTask")
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
// Error-handling should have been called, and "escalate" task should be available instead of original one
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("escalatedTask")
.singleResult();
assertNotNull(task);
}
@Deployment
public void testThrowErrorWithErrorcode() throws Exception {
ErrorThrowingEventListener listener = null;
try {
listener = new ErrorThrowingEventListener();
listener.setErrorCode("123");
processEngineConfiguration.getEventDispatcher().addEventListener(listener, ActivitiEventType.TASK_ASSIGNED);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testError");
assertNotNull(processInstance);
// Fetch the task and assign it. Should cause error-event to be dispatched
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("userTask")
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
// Error-handling should have been called, and "escalate" task should be available instead of original one
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("escalatedTask")
.singleResult();
assertNotNull(task);
// Try with a different error-code, resulting in a different task being created
listener.setErrorCode("456");
processInstance = runtimeService.startProcessInstanceByKey("testError");
assertNotNull(processInstance);
// Fetch the task and assign it. Should cause error-event to be dispatched
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("userTask")
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("escalatedTask2")
.singleResult();
assertNotNull(task);
} finally {
processEngineConfiguration.getEventDispatcher().removeEventListener(listener);
}
}
@Deployment
public void testThrowErrorWithErrorcodeDefinedInProcessDefinition() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testError");
assertNotNull(processInstance);
// Fetch the task and assign it. Should cause error-event to be dispatched
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("userTask")
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
// Error-handling should have been called, and "escalate" task should be available instead of original one
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("escalatedTask")
.singleResult();
assertNotNull(task);
}
}
/* 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.engine.test.api.event;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.impl.bpmn.helper.MessageThrowingEventListener;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.Deployment;
/**
* Test case for all {@link ActivitiEventListener}s that throw a message BPMN event when an {@link ActivitiEvent}
* has been dispatched.
*
* @author Frederik Heremans
*/
public class MessageThrowingEventListenerTest extends PluggableActivitiTestCase {
@Deployment
public void testThrowMessage() throws Exception {
MessageThrowingEventListener listener = null;
try {
listener = new MessageThrowingEventListener();
listener.setMessageName("Message");
processEngineConfiguration.getEventDispatcher().addEventListener(listener, ActivitiEventType.TASK_ASSIGNED);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testMessage");
assertNotNull(processInstance);
// Fetch the task and re-assig it to trigger the event-listener
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
// Boundary-event should have been messaged and a new task should be available, on top of the already
// existing one, since the cancelActivity='false'
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("subTask")
.singleResult();
assertNotNull(task);
assertEquals("kermit", task.getAssignee());
Task boundaryTask = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("boundaryTask")
.singleResult();
assertNotNull(boundaryTask);
} finally {
processEngineConfiguration.getEventDispatcher().removeEventListener(listener);
}
}
@Deployment
public void testThrowMessageDefinedInProcessDefinition() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testMessage");
assertNotNull(processInstance);
// Fetch the task and re-assig it to trigger the event-listener
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
// Boundary-event should have been messaged and a new task should be available, on top of the already
// existing one, since the cancelActivity='false'
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("subTask")
.singleResult();
assertNotNull(task);
assertEquals("kermit", task.getAssignee());
Task boundaryTask = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("boundaryTask")
.singleResult();
assertNotNull(boundaryTask);
}
@Deployment
public void testThrowMessageInterrupting() throws Exception {
MessageThrowingEventListener listener = null;
try {
listener = new MessageThrowingEventListener();
listener.setMessageName("Message");
processEngineConfiguration.getEventDispatcher().addEventListener(listener, ActivitiEventType.TASK_ASSIGNED);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testMessage");
assertNotNull(processInstance);
// Fetch the task and re-assig it to trigger the event-listener
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
// Boundary-event should have been messaged and a new task should be available, the already
// existing one should be removed, since the cancelActivity='true'
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("subTask")
.singleResult();
assertNull(task);
Task boundaryTask = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("boundaryTask")
.singleResult();
assertNotNull(boundaryTask);
} finally {
processEngineConfiguration.getEventDispatcher().removeEventListener(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.engine.test.api.event;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.impl.bpmn.helper.SignalThrowingEventListener;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.runtime.Job;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.Deployment;
/**
* Test case for all {@link ActivitiEventListener}s that throws a signal BPMN event when an {@link ActivitiEvent}
* has been dispatched.
*
* @author Frederik Heremans
*/
public class SignalThrowingEventListenerTest extends PluggableActivitiTestCase {
@Deployment
public void testThrowSignal() throws Exception {
SignalThrowingEventListener listener = null;
try {
listener = new SignalThrowingEventListener();
listener.setSignalName("Signal");
listener.setProcessInstanceScope(true);
processEngineConfiguration.getEventDispatcher().addEventListener(listener, ActivitiEventType.TASK_ASSIGNED);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testSignal");
assertNotNull(processInstance);
// Fetch the task and re-assign it to trigger the event-listener
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
// Boundary-event should have been signaled and a new task should be available, on top of the already
// existing one, since the cancelActivity='false'
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("subTask")
.singleResult();
assertNotNull(task);
assertEquals("kermit", task.getAssignee());
Task boundaryTask = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("boundaryTask")
.singleResult();
assertNotNull(boundaryTask);
} finally {
processEngineConfiguration.getEventDispatcher().removeEventListener(listener);
}
}
@Deployment
public void testThrowSignalDefinedInProcessDefinition() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testSignal");
assertNotNull(processInstance);
// Fetch the task and re-assign it to trigger the event-listener
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
// Boundary-event should have been signaled and a new task should be available, on top of the already
// existing one, since the cancelActivity='false'
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("subTask")
.singleResult();
assertNotNull(task);
assertEquals("kermit", task.getAssignee());
Task boundaryTask = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("boundaryTask")
.singleResult();
assertNotNull(boundaryTask);
}
@Deployment
public void testThrowSignalInterrupting() throws Exception {
SignalThrowingEventListener listener = null;
try {
listener = new SignalThrowingEventListener();
listener.setSignalName("Signal");
listener.setProcessInstanceScope(true);
processEngineConfiguration.getEventDispatcher().addEventListener(listener, ActivitiEventType.TASK_ASSIGNED);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testSignal");
assertNotNull(processInstance);
// Fetch the task and re-assig it to trigger the event-listener
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(task);
taskService.setAssignee(task.getId(), "kermit");
// Boundary-event should have been signalled and a new task should be available, the already
// existing one is gone, since the cancelActivity='true'
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("subTask")
.singleResult();
assertNull(task);
Task boundaryTask = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.taskDefinitionKey("boundaryTask")
.singleResult();
assertNotNull(boundaryTask);
} finally {
processEngineConfiguration.getEventDispatcher().removeEventListener(listener);
}
}
/**
* Test signal throwing when a job failed and the retries are decremented, affectively
* starting a new transaction.
*/
@Deployment
public void testThrowSignalInNewTransaction() throws Exception {
SignalThrowingEventListener listener = null;
try {
listener = new SignalThrowingEventListener();
listener.setSignalName("Signal");
listener.setProcessInstanceScope(true);
processEngineConfiguration.getEventDispatcher().addEventListener(listener, ActivitiEventType.JOB_RETRIES_DECREMENTED);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testSignal");
assertNotNull(processInstance);
waitForJobExecutorToProcessAllJobs(2000, 100);
Job failedJob = managementService.createJobQuery()
.withException()
.processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(failedJob);
assertEquals(0, failedJob.getRetries());
// Three retries should each have triggered dispatching of a retry-decrement event
assertEquals(3, taskService.createTaskQuery().processInstanceId(processInstance.getId()).count());
try {
managementService.executeJob(failedJob.getId());
fail("Exception expected");
} catch(ActivitiException ae) {
// Ignore, expected exception
assertEquals(4, taskService.createTaskQuery().processInstanceId(processInstance.getId()).count());
}
} finally {
processEngineConfiguration.getEventDispatcher().removeEventListener(listener);
}
}
/**
* Test signal throwing when a job failed, signaling will happen in the rolled back transaction,
* not doing anything in the end...
*/
@Deployment(resources = {"org/activiti/engine/test/api/event/SignalThrowingEventListenerTest.testThrowSignalInNewTransaction.bpmn20.xml"})
public void testThrowSignalInRolledbackTransaction() throws Exception {
SignalThrowingEventListener listener = null;
try {
listener = new SignalThrowingEventListener();
listener.setSignalName("Signal");
listener.setProcessInstanceScope(true);
processEngineConfiguration.getEventDispatcher().addEventListener(listener, ActivitiEventType.JOB_EXECUTION_FAILURE);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testSignal");
assertNotNull(processInstance);
waitForJobExecutorToProcessAllJobs(2000, 100);
Job failedJob = managementService.createJobQuery()
.withException()
.processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(failedJob);
assertEquals(0, failedJob.getRetries());
// Three retries should each have triggered dispatching of a retry-decrement event
assertEquals(0, taskService.createTaskQuery().processInstanceId(processInstance.getId()).count());
try {
managementService.executeJob(failedJob.getId());
fail("Exception expected");
} catch(ActivitiException ae) {
// Ignore, expected exception
assertEquals(0, taskService.createTaskQuery().processInstanceId(processInstance.getId()).count());
}
} finally {
processEngineConfiguration.getEventDispatcher().removeEventListener(listener);
}
}
/**
* Test if an engine-wide signal is thrown as response to a dispatched event.
*/
@Deployment(resources = {
"org/activiti/engine/test/api/event/SignalThrowingEventListenerTest.globalSignal.bpmn20.xml",
"org/activiti/engine/test/api/event/SignalThrowingEventListenerTest.globalSignalExternalProcess.bpmn20.xml"
})
public void testGlobalSignal() throws Exception {
SignalThrowingEventListener listener = null;
try {
listener = new SignalThrowingEventListener();
listener.setSignalName("Signal");
listener.setProcessInstanceScope(false);
processEngineConfiguration.getEventDispatcher().addEventListener(listener, ActivitiEventType.TASK_ASSIGNED);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("globalSignalProcess");
assertNotNull(processInstance);
ProcessInstance externalProcess = runtimeService.startProcessInstanceByKey("globalSignalProcessExternal");
assertNotNull(processInstance);
// Make sure process is not ended yet by querying it again
externalProcess = runtimeService.createProcessInstanceQuery().processInstanceId(externalProcess.getId())
.singleResult();
assertNotNull(externalProcess);
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(task);
// Assign task to trigger signal
taskService.setAssignee(task.getId(), "kermit");
// Second process should have been signaled
externalProcess = runtimeService.createProcessInstanceQuery().processInstanceId(externalProcess.getId())
.singleResult();
assertNull(externalProcess);
// Task assignee should still be set
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(task);
assertEquals("kermit", task.getAssignee());
} finally {
processEngineConfiguration.getEventDispatcher().removeEventListener(listener);
}
}
/**
* Test if an engine-wide signal is thrown as response to a dispatched event.
*/
@Deployment(resources = {
"org/activiti/engine/test/api/event/SignalThrowingEventListenerTest.globalSignalDefinedInProcessDefinition.bpmn20.xml",
"org/activiti/engine/test/api/event/SignalThrowingEventListenerTest.globalSignalExternalProcess.bpmn20.xml"
})
public void testGlobalSignalDefinedInProcessDefinition() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("globalSignalProcess");
assertNotNull(processInstance);
ProcessInstance externalProcess = runtimeService.startProcessInstanceByKey("globalSignalProcessExternal");
assertNotNull(processInstance);
// Make sure process is not ended yet by querying it again
externalProcess = runtimeService.createProcessInstanceQuery().processInstanceId(externalProcess.getId())
.singleResult();
assertNotNull(externalProcess);
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(task);
// Assign task to trigger signal
taskService.setAssignee(task.getId(), "kermit");
// Second process should have been signaled
externalProcess = runtimeService.createProcessInstanceQuery().processInstanceId(externalProcess.getId())
.singleResult();
assertNull(externalProcess);
// Task assignee should still be set
task = taskService.createTaskQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNotNull(task);
assertEquals("kermit", task.getAssignee());
}
}
/* 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.engine.test.api.event;
import org.activiti.engine.delegate.event.ActivitiErrorEvent;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.test.Deployment;
/**
* Test case for {@link ActivitiEvent} thrown when a BPMNError is not caught
* in the process.
*
* @author Frederik Heremans
*/
public class UncaughtErrorEventTest extends PluggableActivitiTestCase {
private TestActivitiEventListener listener;
/**
* Test events related to error-events, thrown from within process-execution (eg. service-task).
*/
@Deployment
public void testUncaughtError() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("errorProcess");
assertNotNull(processInstance);
// Error-handling should have ended the process
ProcessInstance afterErrorInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNull(afterErrorInstance);
ActivitiEvent errorEvent = null;
for(ActivitiEvent event : listener.getEventsReceived()) {
if(ActivitiEventType.UNCAUGHT_BPMN_ERROR.equals(event.getType())) {
if(errorEvent == null) {
errorEvent = event;
} else {
fail("Only one ActivityErrorEvent expected");
}
}
}
assertNotNull(errorEvent);
assertEquals(ActivitiEventType.UNCAUGHT_BPMN_ERROR, errorEvent.getType());
assertTrue(errorEvent instanceof ActivitiErrorEvent);
assertEquals("123", ((ActivitiErrorEvent) errorEvent).getErrorCode());
assertNull(((ActivitiErrorEvent) errorEvent).getActivityId());
assertEquals(processInstance.getId(), errorEvent.getProcessInstanceId());
assertEquals(processInstance.getProcessDefinitionId(), errorEvent.getProcessDefinitionId());
assertFalse(processInstance.getId().equals(errorEvent.getExecutionId()));
}
/**
* Test events related to error-events, thrown from within process-execution (eg. service-task).
*/
@Deployment
public void testUncaughtErrorFromBPMNError() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("errorProcess");
assertNotNull(processInstance);
// Error-handling should have ended the process
ProcessInstance afterErrorInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getId())
.singleResult();
assertNull(afterErrorInstance);
ActivitiEvent errorEvent = null;
for(ActivitiEvent event : listener.getEventsReceived()) {
if(ActivitiEventType.UNCAUGHT_BPMN_ERROR.equals(event.getType())) {
if(errorEvent == null) {
errorEvent = event;
} else {
fail("Only one ActivityErrorEvent expected");
}
}
}
assertNotNull(errorEvent);
assertEquals(ActivitiEventType.UNCAUGHT_BPMN_ERROR, errorEvent.getType());
assertTrue(errorEvent instanceof ActivitiErrorEvent);
assertEquals("23", ((ActivitiErrorEvent) errorEvent).getErrorCode());
assertNull(((ActivitiErrorEvent) errorEvent).getActivityId());
assertEquals(processInstance.getId(), errorEvent.getProcessInstanceId());
assertEquals(processInstance.getProcessDefinitionId(), errorEvent.getProcessDefinitionId());
assertFalse(processInstance.getId().equals(errorEvent.getExecutionId()));
}
@Override
protected void initializeServices() {
super.initializeServices();
listener = new TestActivitiEventListener();
processEngineConfiguration.getEventDispatcher().addEventListener(listener);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
if (listener != null) {
listener.clearEventsReceived();
processEngineConfiguration.getEventDispatcher().removeEventListener(listener);
}
}
}
...@@ -17,10 +17,11 @@ import java.util.List; ...@@ -17,10 +17,11 @@ import java.util.List;
import org.activiti.engine.ActivitiClassLoadingException; import org.activiti.engine.ActivitiClassLoadingException;
import org.activiti.engine.ActivitiException; import org.activiti.engine.ActivitiException;
import org.activiti.engine.ActivitiIllegalArgumentException; import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.delegate.event.ActivitiEntityEvent;
import org.activiti.engine.delegate.event.ActivitiEvent; import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventType; import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.delegate.event.ActivitiEntityEvent;
import org.activiti.engine.impl.test.ResourceActivitiTestCase; import org.activiti.engine.impl.test.ResourceActivitiTestCase;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task; import org.activiti.engine.task.Task;
import org.activiti.engine.test.Deployment; import org.activiti.engine.test.Deployment;
...@@ -60,9 +61,15 @@ public class ProcessDefinitionScopedEventListenerDefinitionTest extends Resource ...@@ -60,9 +61,15 @@ public class ProcessDefinitionScopedEventListenerDefinitionTest extends Resource
assertEquals(ActivitiEventType.ENTITY_CREATED, event.getType()); assertEquals(ActivitiEventType.ENTITY_CREATED, event.getType());
} }
// First event received should be creation of Process-instance // First event received should be creation of Process-definition
assertTrue(testListenerBean.getEventsReceived().get(0) instanceof ActivitiEntityEvent); assertTrue(testListenerBean.getEventsReceived().get(0) instanceof ActivitiEntityEvent);
ActivitiEntityEvent event = (ActivitiEntityEvent) testListenerBean.getEventsReceived().get(0); ActivitiEntityEvent event = (ActivitiEntityEvent) testListenerBean.getEventsReceived().get(0);
assertTrue(event.getEntity() instanceof ProcessDefinition);
assertEquals(processInstance.getProcessDefinitionId(), ((ProcessDefinition) event.getEntity()).getId());
// First event received should be creation of Process-instance
assertTrue(testListenerBean.getEventsReceived().get(0) instanceof ActivitiEntityEvent);
event = (ActivitiEntityEvent) testListenerBean.getEventsReceived().get(1);
assertTrue(event.getEntity() instanceof ProcessInstance); assertTrue(event.getEntity() instanceof ProcessInstance);
assertEquals(processInstance.getId(), ((ProcessInstance) event.getEntity()).getId()); assertEquals(processInstance.getId(), ((ProcessInstance) event.getEntity()).getId());
...@@ -88,14 +95,12 @@ public class ProcessDefinitionScopedEventListenerDefinitionTest extends Resource ...@@ -88,14 +95,12 @@ public class ProcessDefinitionScopedEventListenerDefinitionTest extends Resource
* Test to verify listeners defined in the BPMN xml with invalid class/delegateExpression * Test to verify listeners defined in the BPMN xml with invalid class/delegateExpression
* values cause an exception when process is started. * values cause an exception when process is started.
*/ */
@Deployment(resources = {
"org/activiti/standalone/event/invalidEventListenerClass.bpmn20.xml",
"org/activiti/standalone/event/invalidEventListenerExpression.bpmn20.xml"})
public void testProcessDefinitionListenerDefinitionError() throws Exception { public void testProcessDefinitionListenerDefinitionError() throws Exception {
// Start process with expression which references an unexisting bean // Deploy process with expression which references an unexisting bean
try { try {
runtimeService.startProcessInstanceByKey("testInvalidEventExpression"); repositoryService.createDeployment().addClasspathResource("org/activiti/standalone/event/invalidEventListenerExpression.bpmn20.xml")
.deploy();
fail("Exception expected"); fail("Exception expected");
} catch(ActivitiException ae) { } catch(ActivitiException ae) {
assertEquals("Exception while executing event-listener", ae.getMessage()); assertEquals("Exception while executing event-listener", ae.getMessage());
...@@ -103,9 +108,10 @@ public class ProcessDefinitionScopedEventListenerDefinitionTest extends Resource ...@@ -103,9 +108,10 @@ public class ProcessDefinitionScopedEventListenerDefinitionTest extends Resource
assertEquals("Unknown property used in expression: ${unexistingBean}", ae.getCause().getMessage()); assertEquals("Unknown property used in expression: ${unexistingBean}", ae.getCause().getMessage());
} }
// Start process with listener which references an unexisting class // Deploy process with listener which references an unexisting class
try { try {
runtimeService.startProcessInstanceByKey("testInvalidEventClass"); repositoryService.createDeployment().addClasspathResource("org/activiti/standalone/event/invalidEventListenerClass.bpmn20.xml")
.deploy();
fail("Exception expected"); fail("Exception expected");
} catch(ActivitiException ae) { } catch(ActivitiException ae) {
assertEquals("Exception while executing event-listener", ae.getMessage()); assertEquals("Exception while executing event-listener", ae.getMessage());
...@@ -142,6 +148,27 @@ public class ProcessDefinitionScopedEventListenerDefinitionTest extends Resource ...@@ -142,6 +148,27 @@ public class ProcessDefinitionScopedEventListenerDefinitionTest extends Resource
} }
} }
/**
* Test to verify listeners defined in the BPMN xml are added to the process
* definition and are active, for all entity types
*/
@Deployment
public void testProcessDefinitionListenerDefinitionEntities() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testEventListeners");
assertNotNull(processInstance);
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
assertNotNull(task);
// Attachment entity
TestActivitiEventListener theListener = (TestActivitiEventListener) processEngineConfiguration.getBeans().get("testAttachmentEventListener");
assertNotNull(theListener);
assertEquals(0, theListener.getEventsReceived().size());
taskService.createAttachment("test", task.getId(), processInstance.getId(), "test", "test", "url");
assertEquals(1, theListener.getEventsReceived().size());
}
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp(); super.setUp();
......
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<error id="myError" errorCode="123" />
<process id="errorProcess">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="subProcess" />
<subProcess id="subProcess">
<startEvent id="subStart"></startEvent>
<sequenceFlow sourceRef="subStart" targetRef="subEnd" />
<endEvent id="subEnd">
<errorEventDefinition errorRef="myError" />
</endEvent>
</subProcess>
<boundaryEvent id="catchError" attachedToRef="subProcess">
<errorEventDefinition errorRef="myError"/>
</boundaryEvent>
<sequenceFlow sourceRef="catchError" targetRef="theEnd" />
<endEvent id="errorEnd" />
<sequenceFlow sourceRef="subProcess" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<error id="myError" errorCode="23" />
<process id="errorProcess">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="subProcess" />
<subProcess id="subProcess">
<startEvent id="subStart"></startEvent>
<sequenceFlow sourceRef="subStart" targetRef="serviceFail" />
<serviceTask id="serviceFail" activiti:class="org.activiti.engine.test.bpmn.event.error.ThrowBpmnErrorDelegate" />
<sequenceFlow sourceRef="serviceFail" targetRef="subEnd" />
<endEvent id="subEnd" />
</subProcess>
<boundaryEvent id="catchError" attachedToRef="subProcess">
<errorEventDefinition errorRef="myError"/>
</boundaryEvent>
<sequenceFlow sourceRef="catchError" targetRef="theEnd" />
<endEvent id="errorEnd" />
<sequenceFlow sourceRef="subProcess" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<error id="error" errorCode="123" />
<process id="testError">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="userTask" />
<userTask id="userTask" />
<boundaryEvent id="catchError" attachedToRef="userTask">
<errorEventDefinition />
</boundaryEvent>
<sequenceFlow id="flow3" sourceRef="catchError" targetRef="escalatedTask" />
<userTask id="escalatedTask" name="Escalated Task" />
<sequenceFlow id="flow4" sourceRef="userTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<error id="error" errorCode="123" />
<process id="testError">
<extensionElements>
<activiti:eventListener events="TASK_ASSIGNED" throwEvent="error" />
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="userTask" />
<userTask id="userTask" />
<boundaryEvent id="catchError" attachedToRef="userTask">
<errorEventDefinition />
</boundaryEvent>
<sequenceFlow id="flow3" sourceRef="catchError" targetRef="escalatedTask" />
<userTask id="escalatedTask" name="Escalated Task" />
<sequenceFlow id="flow4" sourceRef="userTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<error id="error" errorCode="123" />
<error id="error2" errorCode="456" />
<process id="testError">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="userTask" />
<userTask id="userTask" />
<boundaryEvent id="catchError" attachedToRef="userTask">
<errorEventDefinition errorRef="error" />
</boundaryEvent>
<boundaryEvent id="catchError2" attachedToRef="userTask">
<errorEventDefinition errorRef="error2" />
</boundaryEvent>
<sequenceFlow id="flow3" sourceRef="catchError" targetRef="escalatedTask" />
<userTask id="escalatedTask" name="Escalated Task" />
<sequenceFlow sourceRef="catchError2" targetRef="escalatedTask2" />
<userTask id="escalatedTask2" name="Escalated Task" />
<sequenceFlow id="flow4" sourceRef="userTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<error id="error" errorCode="123" />
<error id="error2" errorCode="456" />
<process id="testError">
<extensionElements>
<activiti:eventListener events="TASK_ASSIGNED" throwEvent="error" errorCode="123" />
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="userTask" />
<userTask id="userTask" />
<boundaryEvent id="catchError" attachedToRef="userTask">
<errorEventDefinition errorRef="error" />
</boundaryEvent>
<boundaryEvent id="catchError2" attachedToRef="userTask">
<errorEventDefinition errorRef="error2" />
</boundaryEvent>
<sequenceFlow id="flow3" sourceRef="catchError" targetRef="escalatedTask" />
<userTask id="escalatedTask" name="Escalated Task" />
<sequenceFlow sourceRef="catchError2" targetRef="escalatedTask2" />
<userTask id="escalatedTask2" name="Escalated Task" />
<sequenceFlow id="flow4" sourceRef="userTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<message id="myMessage" name="Message" />
<process id="testMessage">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="subProcess" />
<subProcess id="subProcess">
<startEvent id="subStart" />
<sequenceFlow sourceRef="subStart" targetRef="subTask" />
<userTask id="subTask" />
<sequenceFlow sourceRef="subTask" targetRef="subEnd" />
<endEvent id="subEnd" />
</subProcess>
<boundaryEvent id="boundary" attachedToRef="subProcess"
cancelActivity="false">
<messageEventDefinition messageRef="myMessage" />
</boundaryEvent>
<sequenceFlow sourceRef="boundary" targetRef="boundaryTask" />
<userTask id="boundaryTask" />
<sequenceFlow sourceRef="subProcess" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<message id="myMessage" name="Message" />
<process id="testMessage">
<extensionElements>
<activiti:eventListener events="TASK_ASSIGNED" throwEvent="message" messageName="Message" />
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="subProcess" />
<subProcess id="subProcess">
<startEvent id="subStart" />
<sequenceFlow sourceRef="subStart" targetRef="subTask" />
<userTask id="subTask" />
<sequenceFlow sourceRef="subTask" targetRef="subEnd" />
<endEvent id="subEnd" />
</subProcess>
<boundaryEvent id="boundary" attachedToRef="subProcess"
cancelActivity="false">
<messageEventDefinition messageRef="myMessage" />
</boundaryEvent>
<sequenceFlow sourceRef="boundary" targetRef="boundaryTask" />
<userTask id="boundaryTask" />
<sequenceFlow sourceRef="subProcess" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<message id="myMessage" name="Message" />
<process id="testMessage">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="subProcess" />
<subProcess id="subProcess">
<startEvent id="subStart" />
<sequenceFlow sourceRef="subStart" targetRef="subTask" />
<userTask id="subTask" />
<sequenceFlow sourceRef="subTask" targetRef="subEnd" />
<endEvent id="subEnd" />
</subProcess>
<boundaryEvent id="boundary" attachedToRef="subProcess"
cancelActivity="true">
<messageEventDefinition messageRef="myMessage" />
</boundaryEvent>
<sequenceFlow sourceRef="boundary" targetRef="boundaryTask" />
<userTask id="boundaryTask" />
<sequenceFlow sourceRef="subProcess" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<process id="globalSignalProcess">
<startEvent id="Start" />
<sequenceFlow sourceRef="Start" targetRef="Task" />
<userTask id="Task" />
<sequenceFlow sourceRef="Task" targetRef="End" />
<endEvent id="End" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<process id="globalSignalProcess">
<extensionElements>
<activiti:eventListener events="TASK_ASSIGNED" throwEvent="globalSignal" signalName="Signal" />
</extensionElements>
<startEvent id="Start" />
<sequenceFlow sourceRef="Start" targetRef="Task" />
<userTask id="Task" />
<sequenceFlow sourceRef="Task" targetRef="End" />
<endEvent id="End" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<signal id="mySignal" name="Signal" />
<process id="globalSignalProcessExternal">
<startEvent id="Start" />
<sequenceFlow sourceRef="Start" targetRef="signal" />
<intermediateCatchEvent id="signal">
<signalEventDefinition signalRef="mySignal" />
</intermediateCatchEvent>
<sequenceFlow sourceRef="signal" targetRef="End" />
<endEvent id="End" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<signal id="mySignal" name="Signal" />
<process id="testSignal">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="eventSubProcess" />
<subProcess id="eventSubProcess">
<startEvent id="subStart" />
<sequenceFlow sourceRef="subStart" targetRef="subTask" />
<userTask id="subTask" />
<sequenceFlow sourceRef="subTask" targetRef="subEnd" />
<endEvent id="subEnd" />
</subProcess>
<boundaryEvent id="boundary" attachedToRef="eventSubProcess"
cancelActivity="false">
<signalEventDefinition signalRef="mySignal" />
</boundaryEvent>
<sequenceFlow sourceRef="boundary" targetRef="boundaryTask" />
<userTask id="boundaryTask" />
<sequenceFlow sourceRef="eventSubProcess" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<signal id="mySignal" name="Signal" />
<process id="testSignal">
<extensionElements>
<activiti:eventListener events="TASK_ASSIGNED" throwEvent="signal" signalName="Signal" />
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="eventSubProcess" />
<subProcess id="eventSubProcess">
<startEvent id="subStart" />
<sequenceFlow sourceRef="subStart" targetRef="subTask" />
<userTask id="subTask" />
<sequenceFlow sourceRef="subTask" targetRef="subEnd" />
<endEvent id="subEnd" />
</subProcess>
<boundaryEvent id="boundary" attachedToRef="eventSubProcess"
cancelActivity="false">
<signalEventDefinition signalRef="mySignal" />
</boundaryEvent>
<sequenceFlow sourceRef="boundary" targetRef="boundaryTask" />
<userTask id="boundaryTask" />
<sequenceFlow sourceRef="eventSubProcess" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<signal id="mySignal" name="Signal" />
<process id="testSignal">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="eventSubProcess" />
<subProcess id="eventSubProcess">
<startEvent id="subStart" />
<sequenceFlow sourceRef="subStart" targetRef="service" />
<serviceTask id="service" activiti:class="org.activiti.engine.test.bpmn.async.FailingAsyncService" activiti:async="true" />
<sequenceFlow sourceRef="service" targetRef="subEnd" />
<endEvent id="subEnd" />
</subProcess>
<boundaryEvent id="boundary" attachedToRef="eventSubProcess"
cancelActivity="false">
<signalEventDefinition signalRef="mySignal" />
</boundaryEvent>
<sequenceFlow sourceRef="boundary" targetRef="boundaryTask" />
<userTask id="boundaryTask" />
<sequenceFlow sourceRef="eventSubProcess" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<signal id="mySignal" name="Signal" />
<process id="testSignal">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="eventSubProcess" />
<subProcess id="eventSubProcess">
<startEvent id="subStart" />
<sequenceFlow sourceRef="subStart" targetRef="subTask" />
<userTask id="subTask" />
<sequenceFlow sourceRef="subTask" targetRef="subEnd" />
<endEvent id="subEnd" />
</subProcess>
<boundaryEvent id="boundary" attachedToRef="eventSubProcess"
cancelActivity="true">
<signalEventDefinition signalRef="mySignal" />
</boundaryEvent>
<sequenceFlow sourceRef="boundary" targetRef="boundaryTask" />
<userTask id="boundaryTask" />
<sequenceFlow sourceRef="eventSubProcess" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<error id="myError" errorCode="123" />
<process id="errorProcess">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="subProcess" />
<subProcess id="subProcess">
<startEvent id="subStart"></startEvent>
<sequenceFlow sourceRef="subStart" targetRef="subEnd" />
<endEvent id="subEnd">
<errorEventDefinition errorRef="myError" />
</endEvent>
</subProcess>
<sequenceFlow sourceRef="subProcess" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<error id="myError" errorCode="23" />
<process id="errorProcess">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="subProcess" />
<subProcess id="subProcess">
<startEvent id="subStart"></startEvent>
<sequenceFlow sourceRef="subStart" targetRef="serviceFail" />
<serviceTask id="serviceFail" activiti:class="org.activiti.engine.test.bpmn.event.error.ThrowBpmnErrorDelegate" />
<sequenceFlow sourceRef="serviceFail" targetRef="subEnd" />
<endEvent id="subEnd" />
</subProcess>
<sequenceFlow sourceRef="subProcess" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples">
<process id="testEventListeners">
<extensionElements>
<activiti:eventListener delegateExpression="${testAttachmentEventListener}" entityType="attachment" />
<activiti:eventListener delegateExpression="${testCommentEventListener}" entityType="comment" />
<activiti:eventListener delegateExpression="${testExecutionEventListener}" entityType="execution" />
<activiti:eventListener delegateExpression="${testIdentityLinkEventListener}" entityType="identity-link" />
<activiti:eventListener delegateExpression="${testJobEventListener}" entityType="job" />
<activiti:eventListener delegateExpression="${testProcessDefinitionEventListener}" entityType="process-definition" />
<activiti:eventListener delegateExpression="${testProcessInstanceEventListener}" entityType="process-instance" />
<activiti:eventListener delegateExpression="${testTaskEventListener}" entityType="task" />
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="task" />
<userTask id="task" />
<sequenceFlow sourceRef="task" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
...@@ -29,6 +29,14 @@ ...@@ -29,6 +29,14 @@
<map> <map>
<entry key="eventListener" value-ref="eventListener" /> <entry key="eventListener" value-ref="eventListener" />
<entry key="testEventListener" value-ref="testEventListener" /> <entry key="testEventListener" value-ref="testEventListener" />
<entry key="testAttachmentEventListener" value-ref="testAttachmentEventListener" />
<entry key="testCommentEventListener" value-ref="testCommentEventListener" />
<entry key="testExecutionEventListener" value-ref="testExecutionEventListener" />
<entry key="testIdentityLinkEventListener" value-ref="testIdentityLinkEventListener" />
<entry key="testJobEventListener" value-ref="testJobEventListener" />
<entry key="testProcessDefinitionEventListener" value-ref="testProcessDefinitionEventListener" />
<entry key="testProcessInstanceEventListener" value-ref="testProcessInstanceEventListener" />
<entry key="testTaskEventListener" value-ref="testTaskEventListener" />
</map> </map>
</property> </property>
</bean> </bean>
...@@ -39,4 +47,12 @@ ...@@ -39,4 +47,12 @@
<!-- An event-listener instance used from within an expression in BPMN XML--> <!-- An event-listener instance used from within an expression in BPMN XML-->
<bean id="testEventListener" class="org.activiti.engine.test.api.event.TestActivitiEventListener" /> <bean id="testEventListener" class="org.activiti.engine.test.api.event.TestActivitiEventListener" />
<bean id="testAttachmentEventListener" class="org.activiti.engine.test.api.event.TestActivitiEventListener" />
<bean id="testCommentEventListener" class="org.activiti.engine.test.api.event.TestActivitiEventListener" />
<bean id="testExecutionEventListener" class="org.activiti.engine.test.api.event.TestActivitiEventListener" />
<bean id="testIdentityLinkEventListener" class="org.activiti.engine.test.api.event.TestActivitiEventListener" />
<bean id="testJobEventListener" class="org.activiti.engine.test.api.event.TestActivitiEventListener" />
<bean id="testProcessDefinitionEventListener" class="org.activiti.engine.test.api.event.TestActivitiEventListener" />
<bean id="testProcessInstanceEventListener" class="org.activiti.engine.test.api.event.TestActivitiEventListener" />
<bean id="testTaskEventListener" class="org.activiti.engine.test.api.event.TestActivitiEventListener" />
</beans> </beans>
...@@ -418,7 +418,7 @@ public class RestResponseFactory { ...@@ -418,7 +418,7 @@ public class RestResponseFactory {
result.setTaskUrl(securedResource.createFullResourceUrl(RestUrls.URL_TASK, attachment.getTaskId())); result.setTaskUrl(securedResource.createFullResourceUrl(RestUrls.URL_TASK, attachment.getTaskId()));
} }
if(attachment.getProcessInstanceId() != null) { if(attachment.getProcessInstanceId() != null) {
result.setTaskUrl(securedResource.createFullResourceUrl(RestUrls.URL_PROCESS_INSTANCE, attachment.getProcessInstanceId())); result.setProcessInstanceUrl(securedResource.createFullResourceUrl(RestUrls.URL_PROCESS_INSTANCE, attachment.getProcessInstanceId()));
} }
return result ; return result ;
} }
......
...@@ -584,7 +584,7 @@ executionId=%X{mdcExecutionId} mdcProcessInstanceID=%X{mdcProcessInstanceID} mdc ...@@ -584,7 +584,7 @@ executionId=%X{mdcExecutionId} mdcProcessInstanceID=%X{mdcProcessInstanceID} mdc
</section> </section>
<section id="eventDispatcher"> <section id="eventDispatcher">
<title>[API AND NAMING NOT FINAL YET] Event handlers</title> <title>Event handlers</title>
<para> <para>
An event mechanism has been introduced in Activiti 5.15. It allows you to get notified when various events occur within the engine. An event mechanism has been introduced in Activiti 5.15. It allows you to get notified when various events occur within the engine.
Take a look at <link linkend="eventDispatcherEventTypes">all supported event types</link> for an overview of the events available. Take a look at <link linkend="eventDispatcherEventTypes">all supported event types</link> for an overview of the events available.
...@@ -728,9 +728,11 @@ void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType... ...@@ -728,9 +728,11 @@ void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType...
<title>Adding listeners to process definitions</title> <title>Adding listeners to process definitions</title>
<para>It's possible to add listeners to a specific process-definition. The listeners will only be called for events <para>It's possible to add listeners to a specific process-definition. The listeners will only be called for events
related to the process definition and to all events related to process instances that are started with that specific process related to the process definition and to all events related to process instances that are started with that specific process
definition. The listener implementations can be defined using a fully qualified classname or an expression that resolves to a bean that implements definition. The listener implementations can be defined using a fully qualified classname, an expression that resolves to a bean that implements
the listener interface. the listener interface or can be configured to throw a message/signal/error BPMN event.
</para> </para>
<section>
<title>Listeners executing user-defined logic</title>
<para>The snippet below adds 2 listeners to a process-definition. The first listener will receive events of any type, with a listener implementation based on a fully-qualified class name. <para>The snippet below adds 2 listeners to a process-definition. The first listener will receive events of any type, with a listener implementation based on a fully-qualified class name.
The second listener is only notified when a job is successfully executed or when it failed, using a listener that has been defined in the <literal>beans</literal> property of the process engine configuration.</para> The second listener is only notified when a job is successfully executed or when it failed, using a listener that has been defined in the <literal>beans</literal> property of the process engine configuration.</para>
<programlisting> <programlisting>
...@@ -742,9 +744,78 @@ void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType... ...@@ -742,9 +744,78 @@ void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType...
... ...
&lt;/process&gt;</programlisting> &lt;/process&gt;</programlisting>
<para>For events related to entities, it's also possible to add listeners to a process-definition that get only notified when enitity-events occur for a certain
entity type. The snippet below shows how this can be achieved. It can be used along for ALL entity-events (first example) or for specific event types only (second example).</para>
<programlisting>
&lt;process id="testEventListeners"&gt;
&lt;extensionElements&gt;
&lt;activiti:eventListener class=&quot;org.activiti.engine.test.MyEventListener&quot; entityType=&quot;task&quot; /&gt;
&lt;activiti:eventListener delegateExpression=&quot;${testEventListener}&quot; events=&quot;ENTITY_CREATED&quot; entityType=&quot;task&quot; /&gt;
&lt;/extensionElements&gt;
<para>Please note that: ...
&lt;/process&gt;</programlisting>
<para>For events related to entities, it's also possible to add listeners to a process-definition that get notified only enitity-events occur for a certain
entity type. The snippet below shows how this can be done. It can be used along for ALL entity-events (first example) or for specific event types only (second example).</para>
<programlisting>
&lt;process id="testEventListeners"&gt;
&lt;extensionElements&gt;
&lt;activiti:eventListener class=&quot;org.activiti.engine.test.MyEventListener&quot; entityType=&quot;task&quot; /&gt;
&lt;activiti:eventListener delegateExpression=&quot;${testEventListener}&quot; events=&quot;ENTITY_CREATED&quot; entityType=&quot;task&quot; /&gt;
&lt;/extensionElements&gt;
...
&lt;/process&gt;</programlisting>
<para>Supported values for the <literal>entityType</literal> are: <literal>attachment</literal>, <literal>comment</literal>, <literal>execution</literal>,<literal>identity-link</literal>, <literal>job</literal>, <literal>process-instance</literal>,
<literal>process-definition</literal>, <literal>task</literal>.</para>
</section>
<section>
<title>Listeners throwing BPMN events</title>
<para>
<link linkend="experimental">
<emphasis role="bold">[EXPERIMENTAL]</emphasis>
</link>
</para>
<para>Another way of handling events being dispatched is to throw a BPMN event. Please bare in mind that it only makes sense to throw BPMN-events with certain kinds of
activiti event types. For example, throwing a BPMN event when the process-instance is deleted will result in an error. The snippet below shows how to throw a signal inside process-instance, throw a signal to an external
process (global), throw a message-event inside the process-instance and throw an error-event inside the process-instance. Instead of using the <literal>class</literal> or <literal>delegateExpression</literal>,
the attribute <literal>throwEvent</literal> is used, along with an additional attribute, specific to the type of event being thrown.</para>
<programlisting>
&lt;process id="testEventListeners"&gt;
&lt;extensionElements&gt;
&lt;activiti:eventListener throwEvent=&quot;signal&quot; signalName=&quot;My signal&quot; events=&quot;TASK_ASSIGNED&quot; /&gt;
&lt;/extensionElements&gt;
&lt;/process&gt;</programlisting>
<programlisting>
&lt;process id="testEventListeners"&gt;
&lt;extensionElements&gt;
&lt;activiti:eventListener throwEvent=&quot;globalSignal&quot; signalName=&quot;My signal&quot; events=&quot;TASK_ASSIGNED&quot; /&gt;
&lt;/extensionElements&gt;
&lt;/process&gt;</programlisting>
<programlisting>
&lt;process id="testEventListeners"&gt;
&lt;extensionElements&gt;
&lt;activiti:eventListener throwEvent=&quot;message&quot; messageName=&quot;My message&quot; events=&quot;TASK_ASSIGNED&quot; /&gt;
&lt;/extensionElements&gt;
&lt;/process&gt;</programlisting>
<programlisting>
&lt;process id="testEventListeners"&gt;
&lt;extensionElements&gt;
&lt;activiti:eventListener throwEvent=&quot;error&quot; errorCode=&quot;123&quot; events=&quot;TASK_ASSIGNED&quot; /&gt;
&lt;/extensionElements&gt;
&lt;/process&gt;</programlisting>
<para>If additional logic is needed to decide wether or not to throw the BPMN-event, it's possible to extend the listener-classes provided by Activiti. By overriding the <literal>isValidEvent(ActivitiEvent event)</literal>
in your subclass, BPMN-event throwing can be prevented. The classes involved are <literal>org.activiti.engine.test.api.event.SignalThrowingEventListenerTest</literal>, <literal>org.activiti.engine.impl.bpmn.helper.MessageThrowingEventListener</literal> and <literal>org.activiti.engine.impl.bpmn.helper.ErrorThrowingEventListener</literal>.</para>
</section>
<section>
<title>Notes on listeners on a process-definition</title>
<para>
<itemizedlist> <itemizedlist>
<listitem> <listitem>
<para> <para>
...@@ -764,12 +835,13 @@ void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType... ...@@ -764,12 +835,13 @@ void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType...
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
When an illegal event-type is used in the <literal>events</literal> attribute, an exception will be thrown when the process-definition is deployed (effectievly failing the deployment). When an illegal value for <literal>class</literal> or <literal>delegateExecution</literal> is supplied (either unexisting class, unexisting bean referenced or delegate not implementing listener interface), an exception will When an illegal event-type is used in the <literal>events</literal> attribute or illegal <literal>throwEvent</literal> value is used, an exception will be thrown when the process-definition is deployed (effectievly failing the deployment). When an illegal value for <literal>class</literal> or <literal>delegateExecution</literal> is supplied (either unexisting class, unexisting bean referenced or delegate not implementing listener interface), an exception will
be thrown when the process is started (or when the first valid event for that process-definition is dispatched to the listener). Make sure the referenced classes are on the classpath and that the expressions resolve to a valid instance. be thrown when the process is started (or when the first valid event for that process-definition is dispatched to the listener). Make sure the referenced classes are on the classpath and that the expressions resolve to a valid instance.
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
</para> </para>
</section>
</section> </section>
<section id="eventDispatcherCustomEvents"> <section id="eventDispatcherCustomEvents">
<title>Dispatching events through API</title> <title>Dispatching events through API</title>
...@@ -879,6 +951,19 @@ void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType... ...@@ -879,6 +951,19 @@ void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType...
be dispatched for this activity, depending on the type (boudnary-event or event-subprocess start-event)</entry> be dispatched for this activity, depending on the type (boudnary-event or event-subprocess start-event)</entry>
<entry><literal>org.activiti...ActivitiMessageEvent</literal></entry> <entry><literal>org.activiti...ActivitiMessageEvent</literal></entry>
</row> </row>
<row>
<entry>ACTIVITY_ERROR_RECEIVED</entry>
<entry>An activity has received an error event. Dispatched before the actual error has been handled by
the activity. The event's <literal>activityId</literal> contains a reference to the error-handling activity.
This event will be either followed by a <literal>ACTIVITY_SIGNALLED</literal> event or <literal>ACTIVITY_COMPLETE</literal>
for the involved activity, if the error was delivered successfully.</entry>
<entry><literal>org.activiti...ActivitiErrorEvent</literal></entry>
</row>
<row>
<entry>UNCAUGHT_BPMN_ERROR</entry>
<entry>An uncaught BPMN error has been thrown. The process did not have any handlers for that specific error. The event's <literal>activityId</literal> will be empty.</entry>
<entry><literal>org.activiti...ActivitiErrorEvent</literal></entry>
</row>
<row> <row>
<entry>ACTIVITY_COMPENSATE</entry> <entry>ACTIVITY_COMPENSATE</entry>
<entry>An activity is about to be compensated. The event contains the id of the activity that is will be executed for compensation.</entry> <entry>An activity is about to be compensated. The event contains the id of the activity that is will be executed for compensation.</entry>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册