/*
* 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.pvm.impl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.jbpm.PvmException;
import org.jbpm.client.Command;
import org.jbpm.client.CommandService;
import org.jbpm.env.Environment;
import org.jbpm.env.EnvironmentFactory;
import org.jbpm.env.session.DbSession;
import org.jbpm.pvm.Activity;
import org.jbpm.pvm.ExceptionHandler;
import org.jbpm.pvm.ProcessDefinition;
import org.jbpm.pvm.impl.ExecutionImpl.UserCodeType;
import org.jbpm.tx.Transaction;
import org.jbpm.util.Listener;
import org.jbpm.wire.Descriptor;
/**
* @author Tom Baeyens
*/
public class ExceptionHandlerImpl implements Serializable, ExceptionHandler {
private static final long serialVersionUID = 1L;
private static final Logger log = Logger.getLogger(ExceptionHandlerImpl.class.getName());
protected long dbid;
protected int dbversion;
protected String exceptionClassName;
protected boolean isTransactional;
protected boolean isRethrowMasked;
protected List<ObjectReference<Activity>> activityReferences;
protected String transitionName; // mutually exclusive with nodeName
protected String nodeName; // mutually exclusive with transitionName
// construction methods /////////////////////////////////////////////////////
public ObjectReference<Activity> createActivityReference(Activity activity) {
ObjectReference<Activity> actionReference = createActionReference();
actionReference.set(activity);
return actionReference;
}
public ObjectReference<Activity> createActivityReference(Descriptor descriptor) {
ObjectReference<Activity> actionReference = createActionReference();
actionReference.setDescriptor(descriptor);
return actionReference;
}
public ObjectReference<Activity> createActivityReference(String expression) {
ObjectReference<Activity> actionReference = createActionReference();
actionReference.setExpression(expression);
return actionReference;
}
public ObjectReference<Activity> createActionReference() {
if (activityReferences==null) {
activityReferences = new ArrayList<ObjectReference<Activity>>();
}
ObjectReference<Activity> actionObjectReference = new ObjectReference<Activity>();
activityReferences.add(actionObjectReference);
return actionObjectReference;
}
public List<Activity> getActivities() {
if (activityReferences==null) {
return null;
}
List<Activity> activities = new ArrayList<Activity>(activityReferences.size());
for (ObjectReference<Activity> activityReference : activityReferences) {
Activity activity = activityReference.get();
activities.add(activity);
}
return activities;
}
// runtime behaviour methods ////////////////////////////////////////////////
public boolean matches(Exception exception) {
return matches(exception.getClass());
}
public boolean matches(Class<?> exceptionClass) {
if (exceptionClass==null) {
return false;
}
if ( (exceptionClassName==null)
|| (exceptionClass.getName().equals(exceptionClassName))
) {
return true;
}
Class<?> superClass = exceptionClass.getSuperclass();
if (superClass!=null) {
return matches(superClass);
}
return false;
}
public void handle(ExecutionImpl execution, Exception exception) {
if (isTransactional) {
Environment environment = Environment.getCurrent();
Transaction transaction = (environment!=null ? environment.getTransaction() : null);
if (transaction!=null) {
log.finest("registering exception handler to "+transaction);
AfterTxCompletionListener afterTxCompletionListener = new AfterTxCompletionListener(
execution,
exception,
environment.getEnvironmentFactory()
);
transaction.addListener(afterTxCompletionListener, Transaction.EVENT_AFTERCOMPLETION);
log.finest("registering exception handler to "+transaction);
throw new PvmException("transaction exception handler registered handler after transaction completed. make sure this transaction is rolled back", exception);
} else {
throw new PvmException("no transaction present in the environment for transactional exception handler", exception);
}
} else {
executeHandler(execution, exception);
}
}
private void executeHandler(ExecutionImpl execution, Exception exception) {
if (activityReferences!=null) {
for (ObjectReference<Activity> actionReference: activityReferences) {
Activity activity = actionReference.get();
log.finest("executing "+activity+" for "+this);
try {
execution.userCodeType = UserCodeType.EXCEPTION_HANDLER;
activity.execute(execution);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new PvmException("couldn't execute "+activity, e);
} finally {
execution.userCodeType = null;
}
}
}
if (transitionName!=null) {
NodeImpl node = execution.getNode();
if (node==null) {
// If the execution is not positioned in a node, it must be
// positioned in a transition. In that case we check if the
// transition is present on the enclosing node.
// The weird way of getting checking and fetching the parent node
// is because hibernate doesn't support instanceof. The transition-->parent
// relation is mapped as a ProcessElementImpl. So we can't cast it to
// a NodeImpl. Ouch.
// The workaround is to check if the parent is equal to the
// process definition. If that is not the case, we can be sure
// that the parent is a node. In that case we look up the node
// from the process definition by name. OuchOuchSquare :-)
TransitionImpl transition = execution.getTransition();
log.finest("no current node. searching for transition from parent of "+transition);
if (transition!=null) {
ProcessDefinition processDefinition = transition.getProcessDefinition();
ObservableElementImpl transitionParent = transition.getParent();
if ( (transitionParent!=null)
&& (! transitionParent.equals(processDefinition))
) {
node = (NodeImpl) processDefinition.findNode(transitionParent.getName());
}
}
}
if (node!=null) {
TransitionImpl transition = node.findOutgoingTransition(transitionName);
if (transition!=null) {
log.finest(toString()+" takes transition "+transitionName);
execution.setTransition(transition);
execution.performAtomicOperationSync(ExecutionImpl.TAKE_TRANSITION);
} else {
log.warning(toString()+" couldn't find transition "+transitionName+" on "+node);
}
} else {
log.warning(toString()+" couldn't find current node to take transition "+transitionName);
}
} else if (nodeName!=null) {
// execute child node
NodeImpl node = execution.getNode();
NodeImpl childNode = ( node!=null ? node.getNode(nodeName) : null );
if (childNode!=null) {
log.finest(toString()+" takes transition "+transitionName);
execution.performAtomicOperationSync(new MoveToChildNodeOp(childNode));
} else {
log.warning(toString()+" couldn't find child node "+nodeName);
}
}
}
private class AfterTxCompletionListener implements Listener, Command {
private static final long serialVersionUID = 1L;
protected ExecutionImpl execution;
protected Exception exception;
protected EnvironmentFactory environmentFactory;
public AfterTxCompletionListener(ExecutionImpl execution, Exception exception, EnvironmentFactory environmentFactory) {
this.execution = execution;
this.exception = exception;
this.environmentFactory = environmentFactory;
}
public void event(Object source, String eventName, Object info) {
Transaction transaction = (Transaction) source;
if (! transaction.isRolledBack()) {
log.warning("no rollback after transactional exception handler. did you forget to rollback the transaction ?");
}
CommandService commandService = environmentFactory.get(CommandService.class);
if (commandService==null) {
throw new PvmException("environment factory doesn't have a command service");
}
commandService.execute(this);
}
public Object execute(Environment environment) {
// reload the execution
DbSession dbSession = environment.get(DbSession.class);
if (dbSession==null) {
throw new PvmException("no "+DbSession.class.getName()+" available in the environment for reloading the execution");
}
execution = dbSession.get(ExecutionImpl.class, execution.getDbid());
executeHandler(execution, exception);
return null;
}
}
public static void rethrow(Exception exception, String prefixMessage) {
log.finest("rethrowing "+exception);
if (exception instanceof RuntimeException) {
throw (RuntimeException) exception;
} else {
throw new PvmException(prefixMessage+": "+exception.getMessage(), exception);
}
}
public String toString() {
return (exceptionClassName!=null ? "exception-handler("+exceptionClassName+")" : "exception-handler");
}
// getters and setters //////////////////////////////////////////////////////
public long getDbid() {
return dbid;
}
public String getExceptionClassName() {
return exceptionClassName;
}
public void setExceptionClassName(String exceptionClassName) {
this.exceptionClassName = exceptionClassName;
}
public boolean isTransactional() {
return isTransactional;
}
public void setTransactional(boolean isTransactional) {
this.isTransactional = isTransactional;
}
public String getTransitionName() {
return transitionName;
}
public void setTransitionName(String transitionName) {
this.transitionName = transitionName;
}
public String getNodeName() {
return nodeName;
}
public void setNodeName(String nodeName) {
this.nodeName = nodeName;
}
public boolean isRethrowMasked() {
return isRethrowMasked;
}
public void setRethrowMasked(boolean isRethrowMasked) {
this.isRethrowMasked = isRethrowMasked;
}
public List<ObjectReference<Activity>> getActivityReferences() {
return activityReferences;
}
public void setActivityReferences(List<ObjectReference<Activity>> activityReferences) {
this.activityReferences = activityReferences;
}
}