/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jbpm.graph.def;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jbpm.JbpmContext;
import org.jbpm.JbpmException;
import org.jbpm.graph.action.Script;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.graph.exe.RuntimeAction;
import org.jbpm.graph.exe.Token;
import org.jbpm.graph.log.ActionLog;
import org.jbpm.instantiation.UserCodeInterceptorConfig;
import org.jbpm.job.ExecuteActionJob;
import org.jbpm.msg.MessageService;
import org.jbpm.persistence.db.DbPersistenceService;
import org.jbpm.scheduler.def.CancelTimerAction;
import org.jbpm.scheduler.def.CreateTimerAction;
import org.jbpm.signal.EventService;
import org.jbpm.svc.Service;
import org.jbpm.svc.Services;
import org.jbpm.util.EqualsUtil;
import net.conselldemallorca.helium.jbpm3.integracio.Jbpm3HeliumBridge;
import net.conselldemallorca.helium.v3.core.api.dto.ExpedientDto;
@SuppressWarnings({"unchecked", "rawtypes"})
public abstract class GraphElement implements Identifiable, Serializable {
private static final long serialVersionUID = 1L;
long id = 0;
protected String name = null;
protected String description = null;
protected ProcessDefinition processDefinition = null;
protected Map events = null;
protected List exceptionHandlers = null;
public GraphElement() {
}
public GraphElement(String name) {
setName(name);
}
// events ///////////////////////////////////////////////////////////////////
/**
* indicative set of event types supported by this graph element. this is currently only used by the process designer
* to know which event types to show on a given graph element. in process definitions and at runtime, there are no
* contstraints on the event-types.
*/
public abstract String[] getSupportedEventTypes();
/**
* gets the events, keyd by eventType (java.lang.String).
*/
public Map getEvents() {
return events;
}
public boolean hasEvents() {
return ((events != null) && (events.size() > 0));
}
public Event getEvent(String eventType) {
Event event = null;
if (events != null) {
event = (Event) events.get(eventType);
}
return event;
}
public boolean hasEvent(String eventType) {
boolean hasEvent = false;
if (events != null) {
hasEvent = events.containsKey(eventType);
}
return hasEvent;
}
public Event addEvent(Event event) {
if (event == null) {
throw new IllegalArgumentException("can't add null event to graph element");
}
if (event.getEventType() == null) {
throw new IllegalArgumentException("can't add an event without type to graph element");
}
if (events == null) {
events = new HashMap();
}
events.put(event.getEventType(), event);
event.graphElement = this;
return event;
}
public Event removeEvent(Event event) {
Event removedEvent = null;
if (event == null) {
throw new IllegalArgumentException("can't remove null event from graph element");
}
if (event.getEventType() == null) {
throw new IllegalArgumentException("can't remove an event without type from graph element");
}
if (events != null) {
removedEvent = (Event) events.remove(event.getEventType());
if (removedEvent != null) {
event.graphElement = null;
}
}
return removedEvent;
}
// exception handlers ///////////////////////////////////////////////////////
/**
* is the list of exception handlers associated to this graph element.
*/
public List getExceptionHandlers() {
return exceptionHandlers;
}
public ExceptionHandler addExceptionHandler(ExceptionHandler exceptionHandler) {
if (exceptionHandler == null) {
throw new IllegalArgumentException("can't add null exceptionHandler to graph element");
}
if (exceptionHandlers == null) {
exceptionHandlers = new ArrayList();
}
exceptionHandlers.add(exceptionHandler);
exceptionHandler.graphElement = this;
return exceptionHandler;
}
public void removeExceptionHandler(ExceptionHandler exceptionHandler) {
if (exceptionHandler == null) {
throw new IllegalArgumentException("can't remove null exceptionHandler from graph element");
}
if (exceptionHandlers != null && exceptionHandlers.remove(exceptionHandler)) {
exceptionHandler.graphElement = null;
}
}
public void reorderExceptionHandler(int oldIndex, int newIndex) {
if ((exceptionHandlers != null)
&& (Math.min(oldIndex, newIndex) >= 0)
&& (Math.max(oldIndex, newIndex) < exceptionHandlers.size())) {
Object o = exceptionHandlers.remove(oldIndex);
exceptionHandlers.add(newIndex, o);
} else {
throw new IndexOutOfBoundsException("couldn't reorder element from index '" + oldIndex
+ "' to index '" + newIndex + "' in " + exceptionHandlers);
}
}
// event handling ///////////////////////////////////////////////////////////
public void fireEvent(String eventType, ExecutionContext executionContext) {
log.debug("event '" + eventType + "' on '" + this + "' for '" + executionContext.getToken() + "'");
try {
executionContext.setEventSource(this);
// TODO: Is it a valid condition that the jbpmContext is not there?
JbpmContext jbpmContext = executionContext.getJbpmContext();
if (jbpmContext != null) {
Services services = jbpmContext.getServices();
if (services != null) {
EventService evService = (EventService) services.getService(EventService.SERVICE_NAME);
if (evService != null) {
evService.fireEvent(eventType, this, executionContext);
}
}
}
fireAndPropagateEvent(eventType, executionContext);
} finally {
executionContext.setEventSource(null);
}
}
public void fireAndPropagateEvent(String eventType, ExecutionContext executionContext) {
// calculate if the event was fired on this element or if it was a
// propagated event
boolean isPropagated = !(this.equals(executionContext.getEventSource()));
// execute static actions
Event event = getEvent(eventType);
if (event != null) {
// update the context
executionContext.setEvent(event);
// execute the static actions specified in the process definition
executeActions(event.getActions(), executionContext, isPropagated);
}
// execute the runtime actions
List runtimeActions = getRuntimeActionsForEvent(executionContext, eventType);
executeActions(runtimeActions, executionContext, isPropagated);
// remove the event from the context
executionContext.setEvent(null);
// propagate the event to the parent element
GraphElement parent = getParent();
if (parent != null) {
parent.fireAndPropagateEvent(eventType, executionContext);
}
}
void executeActions(List actions, ExecutionContext executionContext, boolean isPropagated) {
if (actions != null) {
Iterator iter = actions.iterator();
while (iter.hasNext()) {
Action action = (Action) iter.next();
if (action.acceptsPropagatedEvents() || (!isPropagated)) {
if (action.isAsync()) {
ExecuteActionJob job = createAsyncActionExecutionJob(executionContext.getToken(), action);
MessageService messageService = (MessageService) Services.getCurrentService(Services.SERVICENAME_MESSAGE);
messageService.send(job);
} else {
executeAction(action, executionContext);
}
}
}
}
}
protected ExecuteActionJob createAsyncActionExecutionJob(Token token, Action action) {
ExecuteActionJob job = new ExecuteActionJob(token);
job.setAction(action);
job.setDueDate(new Date());
job.setExclusive(action.isAsyncExclusive());
return job;
}
public void executeAction(Action action, ExecutionContext executionContext) {
Token token = executionContext.getToken();
ExpedientDto exp = null;
if ( Jbpm3HeliumBridge.getInstanceService().mesuraIsActiu()) {
exp = Jbpm3HeliumBridge.getInstanceService().getExpedientArrelAmbProcessInstanceId(String.valueOf(executionContext.getProcessInstance().getId()));
if (exp == null) {
exp = Jbpm3HeliumBridge.getInstanceService().getExpedientIniciant();
}
Jbpm3HeliumBridge.getInstanceService().mesuraIniciar("ACCIO: " + (action != null ? action.getName() : "null"), "tasques", (exp == null) ? "NULL" : exp.getTipus().getNom(), null, null);
}
// create action log
ActionLog actionLog = new ActionLog(action);
token.startCompositeLog(actionLog);
// if this is an action being executed in an event,
// the token needs to be locked. if this is an action
// being executed as the node behaviour or if the token
// is already locked, the token doesn't need to be locked.
boolean actionMustBeLocked = (executionContext.getEvent() != null) && (!token.isLocked());
try {
// update the execution context
executionContext.setAction(action);
// execute the action
log.debug("executing action '" + action + "'");
String lockOwnerId = "token[" + token.getId() + "]";
try {
if (actionMustBeLocked) {
token.lock(lockOwnerId);
}
// // Si el handler implementa HeliumActionhandler passarem l'HeliumContext que restringeix l'accés a
// if (action != null && action.getActionDelegation() != null) {
// Object actionHandler = action.getActionDelegation().getInstance();
// if (actionHandler instanceof HeliumActionHandler) {
// HeliumApi heliumApi = new HeliumApiImpl(executionContext);
// }
// }
if (UserCodeInterceptorConfig.userCodeInterceptor != null) {
UserCodeInterceptorConfig.userCodeInterceptor.executeAction(action, executionContext);
} else {
action.execute(executionContext);
}
} finally {
if (actionMustBeLocked) {
token.unlock(lockOwnerId);
if ( Jbpm3HeliumBridge.getInstanceService().mesuraIsActiu()) {
Jbpm3HeliumBridge.getInstanceService().mesuraCalcular("ACCIO: " + (action != null ? action.getName() : "null"), "tasques", exp.getTipus().getNom(), null, null);
}
}
}
} catch (Exception exception) {
// NOTE that Errors are not caught because that might halt the JVM and mask the original Error
if (action != null) {
String actionName = (action.getName() != null ? action.getName() :
action instanceof CreateTimerAction ? ((CreateTimerAction)action).getTimerName() :
action instanceof CancelTimerAction ? ((CancelTimerAction)action).getTimerName() :
action instanceof Script ? ((Script)action).getExpression() :
"-SENSE NOM-");
log.error(">>> ERROR EXECUTANT LA ACCIO (" + actionName + ") AMB ID (" + action.getId() + "): " + action.toString() + " <<< " + exception.getMessage(), exception);
} else {
log.error(">>> ERROR EXECUTANT L'ACCIO. NO S'HA TROBAT ACTION. <<<" + exception.getMessage(), exception);
}
// log the action exception
actionLog.setException(exception);
// if an exception handler is available
raiseException(exception, executionContext);
} finally {
executionContext.setAction(null);
token.endCompositeLog();
}
}
List getRuntimeActionsForEvent(ExecutionContext executionContext, String eventType) {
List runtimeActionsForEvent = null;
List runtimeActions = executionContext.getProcessInstance().getRuntimeActions();
if (runtimeActions != null) {
Iterator iter = runtimeActions.iterator();
while (iter.hasNext()) {
RuntimeAction runtimeAction = (RuntimeAction) iter.next();
// if the runtime-action action is registered on this element and this eventType
if ((this.equals(runtimeAction.getGraphElement()))
&& (eventType.equals(runtimeAction.getEventType()))) {
// ... add its action to the list of runtime actions
if (runtimeActionsForEvent == null)
runtimeActionsForEvent = new ArrayList();
runtimeActionsForEvent.add(runtimeAction.getAction());
}
}
}
return runtimeActionsForEvent;
}
/*
* // the next instruction merges the actions specified in the process definition with the runtime actions List
* actions = event.collectActions(executionContext);
*
* // loop over all actions of this event Iterator iter = actions.iterator(); while (iter.hasNext()) { Action action =
* (Action) iter.next(); executionContext.setAction(action);
*
* if ( (!isPropagated) || (action.acceptsPropagatedEvents() ) ) {
*
* // create action log ActionLog actionLog = new ActionLog(action);
* executionContext.getToken().startCompositeLog(actionLog);
*
* try { // execute the action action.execute(executionContext);
*
* } catch (Exception exception) { // NOTE that Error's are not caught because that might halt the JVM and mask the
* original Error. Event.log.error("action threw exception: "+exception.getMessage(), exception);
*
* // log the action exception actionLog.setException(exception);
*
* // if an exception handler is available event.graphElement.raiseException(exception, executionContext); } finally {
* executionContext.getToken().endCompositeLog(); } } } }
*/
/**
* throws an ActionException if no applicable exception handler is found. An ExceptionHandler is searched for in this
* graph element and then recursively up the parent hierarchy. If an exception handler is found, it is applied. If the
* exception handler does not throw an exception, the exception is considered handled. Otherwise the search for an
* applicable exception handler continues where it left of with the newly thrown exception.
*/
public void raiseException(Throwable exception, ExecutionContext executionContext)
throws DelegationException {
if (isAbleToHandleExceptions(executionContext)) {
if (exceptionHandlers != null) {
try {
ExceptionHandler exceptionHandler = findExceptionHandler(exception);
if (exceptionHandler != null) {
executionContext.setException(exception);
exceptionHandler.handleException(this, executionContext);
return;
}
} catch (Exception e) {
// NOTE that Error's are not caught because that might halt the JVM
// and mask the original Error.
exception = e;
}
}
GraphElement parent = getParent();
// if this graph element has a parent
if ((parent != null) && (!equals(parent))) {
// raise to the parent
parent.raiseException(exception, executionContext);
return;
}
}
// rollback the actions
// rollbackActions(executionContext);
// if there is no parent we need to throw a delegation exception to the client
throw exception instanceof JbpmException ? (JbpmException) exception : new DelegationException(
exception, executionContext);
}
/**
* Tells whether the given context is valid for exception handling by checking for:
* <ul>
* <li>the absence of a previous exception</li>
* <li>an active transaction, or no transaction at all</li>
* </ul>
*/
private static boolean isAbleToHandleExceptions(ExecutionContext executionContext) {
/* if an exception is already set, we are already handling an exception;
* in this case don't give the exception to the handlers but throw it to the client
* see https://jira.jboss.org/jira/browse/JBPM-1887 */
if (executionContext.getException() != null)
return false;
/* check whether the transaction is still active before scanning the exception handlers.
* that way we can load the exception handlers lazily
* see https://jira.jboss.org/jira/browse/JBPM-1775 */
JbpmContext jbpmContext = executionContext.getJbpmContext();
if (jbpmContext != null) {
Services services = jbpmContext.getServices();
if (services != null) {
Service service = services.getPersistenceService();
if (service instanceof DbPersistenceService) {
DbPersistenceService persistenceService = (DbPersistenceService) service;
return persistenceService.isTransactionActive() || persistenceService.getTransaction() == null;
}
}
}
// no transaction detected, probably running in memory only
return true;
}
protected ExceptionHandler findExceptionHandler(Throwable exception) {
ExceptionHandler exceptionHandler = null;
if (exceptionHandlers != null) {
Iterator iter = exceptionHandlers.iterator();
while (iter.hasNext() && (exceptionHandler == null)) {
ExceptionHandler candidate = (ExceptionHandler) iter.next();
if (candidate.matches(exception)) {
exceptionHandler = candidate;
}
}
}
return exceptionHandler;
}
public GraphElement getParent() {
return processDefinition;
}
/**
* @return all the parents of this graph element ordered by age.
*/
public List getParents() {
List parents = new ArrayList();
GraphElement parent = getParent();
if (parent != null) {
parent.addParentChain(parents);
}
return parents;
}
/**
* @return this graph element plus all the parents ordered by age.
*/
public List getParentChain() {
List parents = new ArrayList();
this.addParentChain(parents);
return parents;
}
void addParentChain(List parentChain) {
parentChain.add(this);
GraphElement parent = getParent();
if (parent != null) {
parent.addParentChain(parentChain);
}
}
public String toString() {
String className = getClass().getName();
className = className.substring(className.lastIndexOf('.') + 1);
if (name != null) {
className = className + "(" + name + ")";
} else {
className = className + "(" + Integer.toHexString(System.identityHashCode(this)) + ")";
}
return className;
}
// equals ///////////////////////////////////////////////////////////////////
// hack to support comparing hibernate proxies against the real objects
// since this always falls back to ==, we don't need to overwrite the hashcode
public boolean equals(Object o) {
return EqualsUtil.equals(this, o);
}
// getters and setters //////////////////////////////////////////////////////
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public ProcessDefinition getProcessDefinition() {
return processDefinition;
}
public void setProcessDefinition(ProcessDefinition processDefinition) {
this.processDefinition = processDefinition;
}
// logger ///////////////////////////////////////////////////////////////////
private static final Log log = LogFactory.getLog(GraphElement.class);
}