/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: WorkflowEngineEJB.java 3205 2009-09-27 17:13:47Z mlipp $
*
* $Log$
* Revision 1.29 2007/06/10 05:55:30 drmlipp
* Using local interface for optimization.
*
* Revision 1.28 2007/05/03 21:58:25 mlipp
* Internal refactoring for making better use of local EJBs.
*
*/
package de.danet.an.workflow.ejbs.admin;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.lang.reflect.InvocationTargetException;
import java.rmi.NoSuchObjectException;
import java.rmi.RemoteException;
import java.security.Principal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.FinderException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.TransactionRolledbackLocalException;
import javax.jms.JMSException;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnectionFactory;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import de.danet.an.util.EJBUtil;
import de.danet.an.util.JDBCUtil;
import de.danet.an.util.ResourceNotAvailableException;
import de.danet.an.util.UniversalPrepStmt;
import de.danet.an.util.JDBCUtil.DBProperties;
import de.danet.an.util.logging.RequestLog;
import de.danet.an.util.logging.RequestScope;
import de.danet.an.util.persistentmaps.JDBCPersistentMap;
import de.danet.an.util.persistentmaps.PersistentMapSQLException;
import de.danet.an.workflow.localapi.ProcessLocal;
import de.danet.an.workflow.omgcore.CannotCompleteException;
import de.danet.an.workflow.omgcore.CannotStopException;
import de.danet.an.workflow.omgcore.HistoryNotAvailableException;
import de.danet.an.workflow.omgcore.InvalidDataException;
import de.danet.an.workflow.omgcore.InvalidStateException;
import de.danet.an.workflow.omgcore.NotRunningException;
import de.danet.an.workflow.omgcore.ProcessData;
import de.danet.an.workflow.omgcore.TransitionNotAllowedException;
import de.danet.an.workflow.omgcore.WfActivity;
import de.danet.an.workflow.omgcore.WfAssignment;
import de.danet.an.workflow.omgcore.WfAssignmentAuditEvent;
import de.danet.an.workflow.omgcore.WfAuditEvent;
import de.danet.an.workflow.omgcore.WfCreateProcessAuditEvent;
import de.danet.an.workflow.omgcore.WfDataAuditEvent;
import de.danet.an.workflow.omgcore.WfExecutionObject;
import de.danet.an.workflow.omgcore.WfProcess;
import de.danet.an.workflow.omgcore.WfResource;
import de.danet.an.workflow.omgcore.WfStateAuditEvent;
import de.danet.an.workflow.omgcore.WfExecutionObject.State;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.ActivityUniqueKey;
import de.danet.an.workflow.api.Batch;
import de.danet.an.workflow.api.Configuration;
import de.danet.an.workflow.api.DefaultProcessData;
import de.danet.an.workflow.api.InvalidKeyException;
import de.danet.an.workflow.api.NoSuchResourceException;
import de.danet.an.workflow.api.ProcessDefinition;
import de.danet.an.workflow.api.ProcessDirectory;
import de.danet.an.workflow.domain.AbstractActivity;
import de.danet.an.workflow.domain.AbstractProcess;
import de.danet.an.workflow.domain.DefaultAssignmentAuditEvent;
import de.danet.an.workflow.domain.DefaultAuditEvent;
import de.danet.an.workflow.domain.DefaultCreateProcessAuditEvent;
import de.danet.an.workflow.domain.DefaultDataAuditEvent;
import de.danet.an.workflow.domain.DefaultStateAuditEvent;
import de.danet.an.workflow.domain.ImplCompleteAuditEvent;
import de.danet.an.workflow.domain.ToolInvocationFailedAuditEvent;
import de.danet.an.workflow.apix.ExtActivity;
import de.danet.an.workflow.ejbs.core.ActivityProxy;
import de.danet.an.workflow.ejbs.core.WfProcessEJB;
import de.danet.an.workflow.ejbs.util.QueuerUtils;
import de.danet.an.workflow.ejbs.util.RedeliveryRequiredException;
import de.danet.an.workflow.internalapi.ExtActivityLocal;
import de.danet.an.workflow.internalapi.ExtApplication;
import de.danet.an.workflow.internalapi.ExtProcessDirectoryLocal;
import de.danet.an.workflow.internalapi.ExtProcessLocal;
import de.danet.an.workflow.internalapi.ExtExecutionObjectLocal.RunningState;
import de.danet.an.workflow.spis.aii.ApplicationNotStoppedException;
import de.danet.an.workflow.spis.aii.ResultProvider.ExceptionResult;
import de.danet.an.workflow.spis.ras.ResourceAssignmentService;
import de.danet.an.workflow.spis.ras.ResourceAssignmentServiceFactory;
/**
* The session bean <code>WorkflowEngineEJB</code> gives access to the
* workflow engine. The remote and home interfaces of this EJB are
* declared in package <code>de.danet.an.workflow.ejbs.util</code> as
* {@link de.danet.an.workflow.ejbs.WorkflowEngine
* <code>WorkflowEngine</code>} and {@link
* de.danet.an.workflow.ejbs.WorkflowEngineHome
* <code>WorkflowEngineHome</code>} because the interfaces are needed
* in all sub-packages and putting the interfaces in a subpackage will
* result in cyclic dependencies. Implementation is in {@link
* de.danet.an.workflow.ejbs.admin this package}, however, because it
* relies on other EJBs from this package. <P>
*
* Setting log level to debug for this class results in progress
* messages during event handling, adds stack traces to warning
* messages and logs the event queue data delivered to clients.
*
* @ejbHome <{de.danet.an.workflow.api.ejbhomes.WorkflowEngineHome}>
* @ejbRemote <{de.danet.an.workflow.ejbs.admin.WorkflowEngine}>
*
* @ejb.bean name="WorkflowEngine" display-name="WorkflowEngine"
* jndi-name="ejb/@@@_JNDI_Name_Prefix_@@@WorkflowEngine"
* type="Stateless" transaction-type="Container" view-type="both"
* @jonas.bean ejb-name="WorkflowEngine"
* jndi-name="ejb/@@@_JNDI_Name_Prefix_@@@WorkflowEngine"
* @ejb.home
* remote-class="de.danet.an.workflow.ejbs.WorkflowEngineHome"
* local-class="de.danet.an.workflow.ejbs.admin.WorkflowEngineLocalHome"
* @ejb.interface remote-class="de.danet.an.workflow.ejbs.WorkflowEngine"
* local-class="de.danet.an.workflow.ejbs.admin.WorkflowEngineLocal"
* extends="javax.ejb.EJBObject, de.danet.an.workflow.spis.rms.ResourceAssignmentContext"
* @ejb.transaction type="Required"
* @ejb.permission role-name="WfMOpenAdmin"
* @ejb.ejb-ref ejb-name="ConfigurationBean" view-type="remote"
* ref-name="ejb/Configuration"
* @ejb.ejb-ref ejb-name="ProcessDefinitionDirectory" view-type="remote"
* @ejb.ejb-ref ejb-name="ProcessDirectory" view-type="remote"
* @ejb.ejb-ref ejb-name="ProcessDirectory" view-type="local"
* @ejb.ejb-ref ejb-name="WorkflowEngine" view-type="remote"
* @ejb.ejb-ref ejb-name="SimpleApplicationDirectory" view-type="local"
* @ejb.ejb-ref ejb-name="SimpleApplicationDirectory" view-type="remote"
* @ejb.ejb-ref ejb-name="TimerHandler" view-type="local"
* ref-name="ejb/TimerHandlerLocal"
* @ejb.ejb-external-ref ref-name="ejb/JdbcKeyGenLocal" link="KeyGen"
* type="Session" view-type="local" home="de.danet.an.util.KeyGenLocalHome"
* business="de.danet.an.util.KeyGenLocal"
* @ejb.resource-ref res-ref-name="jdbc/WfEngine"
* res-type="javax.sql.DataSource" res-auth="Container"
* @jonas.resource res-ref-name="jdbc/WfEngine" jndi-name="jdbc_1"
* @weblogic.enable-call-by-reference True
* @weblogic.resource-description res-ref-name="jdbc/WfEngine"
* jndi-name="DefaultDS"
* @ejb.resource-ref res-ref-name="jms/TCFRemote"
* res-type="javax.jms.TopicConnectionFactory" res-auth="Application"
* @jboss.resource-ref res-ref-name="jms/TCFRemote"
* jndi-name="ConnectionFactory"
* @jonas.resource res-ref-name="jms/TCFRemote" jndi-name="JCF"
* @weblogic.resource-description res-ref-name="jms/TCFRemote"
* jndi-name="weblogic.jms.ConnectionFactory"
* @ejb.resource-ref res-ref-name="jms/TCF"
* res-type="javax.jms.TopicConnectionFactory" res-auth="Container"
* jndi-name="TCF"
* @jboss.resource-ref res-ref-name="jms/TCF" jndi-name="java:/JmsXA"
* @jonas.resource res-ref-name="jms/TCF" jndi-name="TCF"
* @weblogic.resource-description res-ref-name="jms/TCF"
* jndi-name="weblogic.jms.XAConnectionFactory"
* @ejb.resource-ref res-ref-name="jms/QCF"
* res-type="javax.jms.QueueConnectionFactory" res-auth="Container"
* jndi-name="QCF"
* @jboss.resource-ref res-ref-name="jms/QCF" jndi-name="java:/JmsXA"
* @jonas.resource res-ref-name="jms/QCF" jndi-name="QCF"
* @weblogic.resource-description res-ref-name="jms/QCF"
* jndi-name="weblogic.jms.XAConnectionFactory"
* @ejb.resource-ref res-ref-name="jms/InternalEventQueue"
* res-type="javax.jms.Queue" res-auth="Container"
* @jboss.resource-ref res-ref-name="jms/InternalEventQueue"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@InternalEventQueue"
* @jonas.resource res-ref-name="jms/InternalEventQueue"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@InternalEventQueue"
* @weblogic.resource-description res-ref-name="jms/InternalEventQueue"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@InternalEventQueue"
* @ejb.resource-ref res-ref-name="jms/EventService"
* res-type="javax.jms.Topic" res-auth="Container"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@EventService"
* @jboss.resource-ref res-ref-name="jms/EventService"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@EventService"
* @jonas.resource res-ref-name="jms/EventService"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@EventService"
* @weblogic.resource-description res-ref-name="jms/EventService"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@EventService"
* @ejb.resource-ref res-ref-name="jms/ChannelIn"
* res-type="javax.jms.Queue" res-auth="Container"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ChannelInMessages"
* @jboss.resource-ref res-ref-name="jms/ChannelIn"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ChannelInMessages"
* @jonas.resource res-ref-name="jms/ChannelIn"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ChannelInMessages"
* @weblogic.resource-description res-ref-name="jms/ChannelIn"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ChannelInMessages"
* @ejb.resource-ref res-ref-name="jms/ChannelOut"
* res-type="javax.jms.Topic" res-auth="Container"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@ChannelOutMessages"
* @jboss.resource-ref res-ref-name="jms/ChannelOut"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@ChannelOutMessages"
* @jonas.resource res-ref-name="jms/ChannelOut"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@ChannelOutMessages"
* @weblogic.resource-description res-ref-name="jms/ChannelOut"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@ChannelOutMessages"
* @ejb.env-entry type="java.lang.String"
* name="GlobalConnectionFactoryName" value="null"
* @ejb.env-entry type="java.lang.String"
* name="GlobalQueueConnectionFactoryName" value="null"
* @ejb.env-entry type="java.lang.String"
* name="GlobalTopicConnectionFactoryName" value="null"
* @ejb.env-entry type="java.lang.String" name="GlobalEventTopicName"
* value="topic/@@@_JNDI_Name_Prefix_@@@EventService"
* @ejb.env-entry type="java.lang.String" name="GlobalChannelOutTopicName"
* value="topic/@@@_JNDI_Name_Prefix_@@@ChannelOutMessages"
* @ejb.env-entry type="java.lang.String"
* name="de.danet.an.workflow.assignment.assignmentService"
* value="ejb/@@@_JNDI_Name_Prefix_@@@AssignmentService"
* @weblogic.transaction-isolation TRANSACTION_READ_COMMITTED
*/
public class WorkflowEngineEJB implements SessionBean {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog
(WorkflowEngineEJB.class);
/** Database name */
private static final String DB_NAME = "java:comp/env/jdbc/WfEngine";
/** Hibernate session factory savepoint. */
private static final String HSF_NAME
= "java:comp/env/hibernate/SessionFactory";
/** The SessionContext interface of the instance. */
private SessionContext ctx;
/**
* The data source of the database.
* @see javax.sql.DataSource
*/
private DataSource ds = null;
private DBProperties dbProperties = null;
private SessionFactory hibernateSessionFactoryCache = null;
private TopicConnectionFactory topConFac = null;
private Topic eventService = null;
/** Cache for QueueConnectionFactory. */
private QueueConnectionFactory qcfCache = null;
/** Cache for event queue. */
private Queue eventQueueCache = null;
/** The cached home interface of the configuration. */
private ConfigurationHome configurationHomeCache = null;
/** The cached home interface of the process definition directory. */
private ProcessDefinitionDirectoryHome processDefinitionDirectoryHomeCache
= null;
/** The cached home interface of the process directory. */
private ProcessDirectoryHome processDirectoryHomeCache = null;
private ProcessDirectoryLocalHome processDirectoryLocalHomeCache = null;
/** The resource assignment service. */
private static boolean rasNotConfigured = false;
private ResourceAssignmentService rasCache = null;
/**
* This operation method delivers a ConfigurationHome
* interface.
* @return home interface of the ConfigurationBean
*/
private ConfigurationHome configurationHome()
throws ResourceNotAvailableException {
if (configurationHomeCache == null) {
configurationHomeCache = (ConfigurationHome)EJBUtil.retrieveEJBHome
(ConfigurationHome.class, "java:comp/env/ejb/Configuration");
}
return configurationHomeCache;
}
/**
* This operation method delivers a ProcessDefinitionDirectoryHome
* interface.
* @return home interface of the ProcessDefinitionDirectoryBean
*/
private ProcessDefinitionDirectoryHome processDefinitionDirectoryHome()
throws ResourceNotAvailableException {
if (processDefinitionDirectoryHomeCache == null) {
processDefinitionDirectoryHomeCache
= (ProcessDefinitionDirectoryHome)EJBUtil.retrieveEJBHome
(ProcessDefinitionDirectoryHome.class,
"java:comp/env/ejb/ProcessDefinitionDirectory");
}
return processDefinitionDirectoryHomeCache;
}
/**
* This operation method delivers a ProcessDirectoryHome interface.
* @return home interface of the ProcessDirectoryBean
*/
private ProcessDirectoryHome processDirectoryHome()
throws ResourceNotAvailableException {
if (processDirectoryHomeCache == null) {
processDirectoryHomeCache
= (ProcessDirectoryHome)EJBUtil.retrieveEJBHome
(ProcessDirectoryHome.class,
"java:comp/env/ejb/ProcessDirectory");
}
return processDirectoryHomeCache;
}
/**
* This operation method delivers a ProcessDirectoryHome interface.
* @return home interface of the ProcessDirectoryBean
*/
private ProcessDirectoryLocalHome processDirectoryLocalHome()
throws ResourceNotAvailableException {
if (processDirectoryLocalHomeCache == null) {
processDirectoryLocalHomeCache
= (ProcessDirectoryLocalHome)EJBUtil.retrieveEJBLocalHome
(ProcessDirectoryLocalHome.class,
"java:comp/env/ejb/ProcessDirectoryLocal");
}
return processDirectoryLocalHomeCache;
}
private QueueConnectionFactory queueConnectionFactory ()
throws ResourceNotAvailableException {
if (qcfCache == null) {
qcfCache = ((QueueConnectionFactory)EJBUtil
.retrieveJNDIEntry ("java:comp/env/jms/QCF"));
}
return qcfCache;
}
private Queue eventQueue ()
throws ResourceNotAvailableException {
if (eventQueueCache == null) {
eventQueueCache = (Queue)EJBUtil.retrieveJNDIEntry
("java:comp/env/jms/InternalEventQueue");
}
return eventQueueCache;
}
/**
* This method returns the cached resource assignment service.
* @return the resource assignment service
*/
private ResourceAssignmentService getRas ()
throws RemoteException {
if (rasCache == null && !rasNotConfigured) {
ResourceAssignmentServiceFactory rasf = null;
try {
rasf = ResourceAssignmentServiceFactory.newInstance();
} catch (de.danet.an.workflow.spis.ras.FactoryConfigurationError
rae) {
logger.warn ("No resource assignment service configured. "
+ "This may be ignored if done intentionally ("
+ "message from factory: " + rae.getMessage() + ").");
logger.debug (rae.getMessage(), rae);
rasNotConfigured = true;
}
try {
rasCache = rasf.newResourceAssignmentService();
} catch (de.danet.an.workflow.spis.ras.FactoryConfigurationError
rae) {
logger.error ("Problem creating resource assignment service: "
+ rae.getMessage(), rae);
rasNotConfigured = true;
}
}
return rasCache;
}
/**
* Set the session context and get new data source.
* @param context the given session context.
* @throws EJBException if problem encountered with getting the data source.
*/
public void setSessionContext(SessionContext context)
throws EJBException {
ctx = context;
configurationHomeCache = null;
processDefinitionDirectoryHomeCache = null;
processDirectoryHomeCache = null;
processDirectoryLocalHomeCache = null;
rasCache = null;
try {
ds = JDBCUtil.refreshDS(null, DB_NAME);
dbProperties = JDBCUtil.dbProperties(ds);
// We should really synchronize on JNDI here, but using the class
// is very close.
synchronized (WorkflowEngineEJB.class) {
try {
hibernateSessionFactoryCache
= (SessionFactory)EJBUtil.lookupJNDIEntry(HSF_NAME);
} catch (NamingException e) {
logger.debug (HSF_NAME + " not bound, creating...");
}
if (hibernateSessionFactoryCache == null) {
org.hibernate.cfg.Configuration cfg
= new org.hibernate.cfg.Configuration();
cfg.setProperty("hibernate.connection.datasource", DB_NAME);
cfg.setProperty("hibernate.session_factory_name", HSF_NAME);
cfg.addInputStream(WfProcessEJB.class
.getResourceAsStream("process.hbm.xml"));
hibernateSessionFactoryCache = cfg.buildSessionFactory();
}
}
} catch (NameAlreadyBoundException e) {
logger.warn("Cannot bind Hibernate session factory (due to "
+ "concurrency, ignored): " + e.getMessage(), e);
} catch (SQLException e) {
throw new EJBException (e);
} catch (NamingException e) {
throw new EJBException (e);
}
}
/**
* Create a new instance of ProcessDefinitonDirectoryBean.
* @throws CreateException Throws if the ProcessDefinitionDirectoryBean
* can not be created.
* @ejb.create-method
* view-type="remote"
*/
public void ejbCreate() throws CreateException {
// getting new data source
try {
topConFac = (TopicConnectionFactory)
EJBUtil.lookupJNDIEntry ("java:comp/env/jms/TCF");
eventService = (Topic)EJBUtil.lookupJNDIEntry
("java:comp/env/jms/EventService");
} catch (NamingException ne) {
throw new EJBException(ne);
}
}
/**
* Not called for stateless EJB.
*/
public void ejbActivate() {
}
/**
* Not called for stateless EJB.
*/
public void ejbPassivate() {
}
/**
* A container invokes this method before it ends the life of the session
* object. This happens as a result of a client's invoking a remove
* operation, or when a container decides to terminate the session object
* after a timeout.
* @see javax.ejb.SessionBean
*/
public void ejbRemove() {
qcfCache = null;
configurationHomeCache = null;
processDefinitionDirectoryHomeCache = null;
processDirectoryHomeCache = null;
processDirectoryLocalHomeCache = null;
rasCache = null;
ds = null;
}
/**
* Return the instance of the Hibernate session factory.
*
* @return the Hibernate session factory
* @ejb.interface-method view-type="local"
*/
public SessionFactory hibernateSessionFactory () {
return hibernateSessionFactoryCache;
}
//
// domain methods
//
/**
* Return the workflow engine configuration.
*
* @return the configuration.
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Supports"
*/
public Configuration configuration () {
try {
return configurationHome().findByPrimaryKey(new Integer(0));
} catch (FinderException fe) {
throw new EJBException (fe);
} catch (RemoteException re) {
throw new EJBException (re);
}
}
/**
* Return the event service connection data. Used by
* <code>StandardWorkflowService</code> only. This allows to
* configure the data needed by the client in the server
* environment and thus eases client configuration.
*
* @return the data.
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Supports"
*/
public Object[] eventServiceData () {
try {
String conFacName = (String)EJBUtil.retrieveJNDIEntry
("java:comp/env/GlobalConnectionFactoryName");
String queueConFacName = (String)EJBUtil.retrieveJNDIEntry
("java:comp/env/GlobalQueueConnectionFactoryName");
String topicConFacName = (String)EJBUtil.retrieveJNDIEntry
("java:comp/env/GlobalTopicConnectionFactoryName");
String evtQueueName = (String)EJBUtil.retrieveJNDIEntry
("java:comp/env/GlobalEventTopicName");
String chanOutName = (String)EJBUtil.retrieveJNDIEntry
("java:comp/env/GlobalChannelOutTopicName");
if (logger.isDebugEnabled()) {
logger.debug
("Returning event service data to client: "
+ conFacName + ", " + evtQueueName + ", " + chanOutName);
}
return new Object[] { conFacName, queueConFacName, topicConFacName,
evtQueueName, chanOutName };
} catch (ResourceNotAvailableException e) {
throw new EJBException (e);
}
}
/**
* Return the process definition directory of the workflow engine.
*
* @return the process definition directory.
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Supports"
*/
public de.danet.an.workflow.api.ProcessDefinitionDirectory
processDefinitionDirectory () {
RequestScope scope = RequestLog.enterScope
(this, "processDefinitionDirectory", new Object[] {});
de.danet.an.workflow.api.ProcessDefinitionDirectory res = null;
try {
res = processDefinitionDirectoryHome().create();
} catch (CreateException ce) {
throw new EJBException (ce);
} catch (RemoteException re) {
throw new EJBException (re);
} finally {
scope.leave (res);
}
return res;
}
/**
* Return the process directory of the workflow engine.
*
* @return the process directory.
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Supports"
*/
public ExtProcessDirectoryLocal processDirectoryLocal () {
try {
return processDirectoryLocalHome().create();
} catch (CreateException ce) {
throw new EJBException (ce);
} catch (RemoteException re) {
throw new EJBException (re);
}
}
/**
* Return the process directory of the workflow engine.
*
* @return the process directory.
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Supports"
*/
public ProcessDirectory processDirectory () {
RequestScope scope = RequestLog.enterScope
(this, "processDirectory", new Object[] {});
ProcessDirectory res = null;
try {
res = processDirectoryHome().create();
} catch (CreateException ce) {
throw new EJBException (ce);
} catch (RemoteException re) {
throw new EJBException (re);
} finally {
scope.leave (res);
}
return res;
}
/**
* Return the resource assignment service used by the
* workflow engine.
*
* @return the resource assignment service
* @ejb.interface-method view-type="local"
* @ejb.transaction type="Supports"
*/
public ResourceAssignmentService resourceAssignmentService () {
RequestScope scope = RequestLog.enterScope
(this, "resourceAssignmentService", new Object[] {});
ResourceAssignmentService res = null;
try {
res = getRas ();
} catch (RemoteException e) {
throw new EJBException (e);
} finally {
scope.leave (res);
}
return res;
}
/**
* Return the assignments of a given resource.
*
* @param resource the resource.
* @return the collection of assigned work items (instances of
* {@link de.danet.an.workflow.omgcore.WfAssignment
* <code>WfAssignment</code>}).
* @throws RemoteException if a system-level error occurs.
* @throws NoSuchResourceException if the resource is invalid.
* As the environment is a concurrent multi user environment,
* <code>WfResource</code> objects may become invalid.
*/
public Collection workItems (WfResource resource)
throws NoSuchResourceException {
RequestScope scope = RequestLog.enterScope
(this, "workItems", new Object[] { resource });
Collection res = null;
try {
res = getRas().workItems(resource);
} catch (RemoteException e) {
throw new EJBException (e);
} finally {
scope.leave (res);
}
return res;
}
/**
* Find out if a given assignment belongs to the work items assigned to
* a particular resource.
*
* @param resource the resource.
* @param assignment the assignment in question.
* @return <code>true</code> if the <code>assignment</code> belongs to
* the work items of the <code>resource</code>.
* @throws RemoteException if a system-level error occurs.
* @throws NoSuchResourceException if the resource is invalid.
* As the environment is a concurrent multi user environment,
* <code>WfResource</code> objects may become invalid.
*/
public boolean isMemberOfWorkItems
(WfResource resource, WfAssignment assignment)
throws NoSuchResourceException {
RequestScope scope = RequestLog.enterScope
(this, "isMemberOfWorkItems",
new Object[] { resource, assignment });
boolean res = false;
try {
res = getRas().isMemberOfWorkItems(resource, assignment);
} catch (RemoteException e) {
throw new EJBException (e);
} finally {
scope.leave (new Boolean(res));
}
return res;
}
/**
* Return the known resources.
*
* @return the known resources
* @throws RemoteException if a communication error occurs.
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Supports"
*/
public Collection knownResources ()
throws RemoteException {
RequestScope scope = RequestLog.enterScope
(this, "knownResources", new Object[] {});
Collection res = null;
try {
ResourceAssignmentService ras = getRas();
if (ras == null) {
throw new UnsupportedOperationException
("No resource assignment service configured.");
}
res = ras.knownResources ();
} finally {
scope.leave (res);
}
return res;
}
/**
* Return the resource associated with the given key.
*
* @param key the key
* @return the resource
* @throws InvalidKeyException if there is no known resource
* with this key
* @throws RemoteException if a communication error occurs.
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Supports"
*/
public WfResource resourceByKey (String key)
throws InvalidKeyException, RemoteException {
RequestScope scope = RequestLog.enterScope
(this, "resourceByKey", new Object[] {});
WfResource res = null;
try {
ResourceAssignmentService ras = getRas();
if (ras == null) {
throw new UnsupportedOperationException
("No resource assignment service configured.");
}
res = ras.resourceByKey(key);
} finally {
scope.leave (res);
}
return res;
}
/**
* Return the authorizers for the given resource.
*
* @param resource the resource
* @return the authorizers
* @throws RemoteException if a communication error occurs.
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Supports"
*/
public Collection authorizers (WfResource resource)
throws RemoteException {
RequestScope scope = RequestLog.enterScope
(this, "authorizers", new Object[] { resource });
Collection res = null;
try {
ResourceAssignmentService ras = getRas();
if (ras == null) {
throw new UnsupportedOperationException
("No resource assignment service configured.");
}
res = ras.authorizers(resource);
} finally {
scope.leave (res);
}
return res;
}
/**
* Return the resource associated with the given principal.
*
* @param principal the principal
* @return the resource
* @throws RemoteException if a communication error occurs.
* @throws InvalidKeyException if there is no known resource
* associated with this principal
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Supports"
*/
public WfResource asResource (Principal principal)
throws RemoteException, InvalidKeyException {
RequestScope scope = RequestLog.enterScope
(this, "asResource", new Object[] { principal });
WfResource res = null;
try {
ResourceAssignmentService ras = getRas();
if (ras == null) {
throw new UnsupportedOperationException
("No resource assignment service configured.");
}
res = ras.asResource(principal);
} finally {
scope.leave (res);
}
return res;
}
/**
* Returns the user currently authenticated as a <code>Principal</code>.
* @return the caller principal.
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Supports"
*/
public Principal caller() {
RequestScope scope = RequestLog.enterScope
(this, "caller", new Object[] {});
Principal res = null;
try {
res = ctx.getCallerPrincipal();
} finally {
scope.leave (res);
}
return res;
}
/**
* Terminate a tool in a new transaction, i.e. the transaction
* attribute of this method is set to <code>RequiresNew</code>.<P>
*
* The implementation simply calls
* <code>appl.terminate(act)</code>.
*
* @param appl the application description of the tool
* @param act the <code>WfActivity</code>
* @throws ApplicationNotStoppedException if termination is not
* possible
* @ejb.interface-method view-type="local"
* @ejb.transaction type="RequiresNew"
*/
public void doTerminateLocal (ExtApplication appl, Activity act)
throws ApplicationNotStoppedException {
try {
appl.terminate(act);
} catch (NoSuchObjectException e) {
logger.warn
("NoSuchObjectException terminating " + appl + " on " + act
+ " (mapped to ApplicationNotStoppedException): "
+ e.getMessage(), e);
throw new ApplicationNotStoppedException
(appl + ": " + e.getMessage());
} catch (RemoteException e) {
throw new EJBException (e);
} catch (TransactionRolledbackLocalException e) {
throw e;
} catch (RuntimeException e) {
logger.warn
("RuntimeException terminating " + appl + " on " + act
+ "(mapped to ApplicationNotStoppedException): "
+ e.getMessage(), e);
throw new ApplicationNotStoppedException
(appl + ": " + e.getMessage());
}
}
/**
* Returns a collection of <code>WfAuditEvent</code>s associated with
* this process describing its history.
* @param execObj the execution object for which to select the audit
* events
* @return the collection of audit events
* @throws HistoryNotAvailableException in case the history is not
* available for any reason
* @ejb.interface-method view-type="local"
* @ejb.transaction type="Supports"
*/
public Collection history(WfExecutionObject execObj)
throws HistoryNotAvailableException {
try {
return selectAuditEvents(execObj);
} catch (SQLException sex) {
throw new EJBException(sex);
} catch (NamingException nex) {
logger.error(nex.getMessage(), nex);
throw new HistoryNotAvailableException();
} catch (IOException iex) {
logger.error(iex.getMessage(), iex);
throw new HistoryNotAvailableException();
}
}
/**
* Set a result and complete an activity in a new transaction,
* i.e. the transaction attribute of this method is set to
* <code>RequiresNew</code>.<P>
*
* @param act the <code>WfActivity</code>
* @param result the tool's result data. If <code>null</code> do
* not call <code>setResult</code>.
* @throws InvalidDataException see {@link
* de.danet.an.workflow.omgcore.WfActivity#setResult
* <code>WfActivity.setResult(...)</code>}
* @throws CannotCompleteException see {@link
* de.danet.an.workflow.omgcore.WfActivity#complete
* <code>WfActivity.complete()</code>}
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="RequiresNew"
*/
public void doFinish (WfActivity act, Map result)
throws InvalidDataException, CannotCompleteException {
try {
if (act instanceof ActivityProxy) {
involveContainerInTx((ActivityProxy)act);
act = ((ActivityProxy)act).unwrap();
}
if (result != null) {
act.setResult (new DefaultProcessData(result));
}
act.complete();
} catch (NoSuchObjectException e) {
logger.warn
(act + " does not exist any more "
+ "(mapped to CannotCompleteException): " + e.getMessage());
logger.debug ("Stacktrace:", e);
throw new CannotCompleteException (act + ": " + e.getMessage());
} catch (RemoteException e) {
throw new EJBException (e);
} catch (RuntimeException e) {
logger.warn
("RuntimeException while finishing " + act
+ "(mapped to CannotCompleteException): " + e.getMessage());
logger.debug ("Stacktrace:", e);
throw new CannotCompleteException (act + ": " + e.getMessage());
}
}
/**
* Abandon an activity in a new transaction,
* i.e. the transaction attribute of this method is set to
* <code>RequiresNew</code>.<P>
*
* @param act the <code>WfActivity</code>
* @param result the exception result to signal
* @throws TransitionNotAllowedException see {@link
* de.danet.an.workflow.api.Activity#abandon(String)
* <code>Activity.abandon()</code>}
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="RequiresNew"
*/
public void doAbandon (Activity act, ExceptionResult result)
throws TransitionNotAllowedException {
try {
if (act instanceof ActivityProxy) {
involveContainerInTx((ActivityProxy)act);
act = ((ActivityProxy)act).unwrap();
}
((ExtActivity)act).abandon(result);
} catch (RemoteException e) {
throw new EJBException (e);
}
}
/**
* Register the failure of a tool invocation.<P>
*
* @param act the <code>ActivityUniqueKey</code>
* @ejb.interface-method view-type="remote"
* @ejb.transaction type="Required"
*/
public void handleToolInvocationFailed (ActivityUniqueKey act) {
try {
QueuerUtils.queue(queueConnectionFactory(), eventQueue(),
new ToolInvocationFailedAuditEvent (act));
} catch (ResourceNotAvailableException e) {
throw new IllegalStateException (e);
}
}
/**
* Process the given event. The transaction attribute of this
* method is set to <code>RequiresNew</code>, so processing will
* be done in its own transaction.
* @param event the event.
* @throws RedeliveryRequiredException if the event should be redelivered
* @ejb.interface-method view-type="local"
* @ejb.transaction type="RequiresNew"
*/
public void processEvent (WfAuditEvent event)
throws RedeliveryRequiredException {
if (logger.isDebugEnabled()) {
logger.debug ("Got audit event: " + event);
}
try {
if (!((DefaultAuditEvent)event).skip ()) {
feedBack (event);
// feedback may have caused the rollback already
// without actually throwing an exception
if (ctx.getRollbackOnly()) {
return;
}
}
// now log and publish
if (!(event instanceof ImplCompleteAuditEvent)
&& !(event instanceof ToolInvocationFailedAuditEvent)) {
int aes = ((DefaultAuditEvent)event).auditEventSelection ();
// Note that if we have
// AUDIT_SELECTION_STATE_EVENTS_ONLY, only state
// events will be processed here, so we do not have
// to look at the events again.
if (aes == ProcessDefinition.AUDIT_SELECTION_ALL_EVENTS
|| aes==ProcessDefinition.AUDIT_SELECTION_STATE_EVENTS_ONLY
|| (aes == (ProcessDefinition
.AUDIT_SELECTION_PROCESS_CLOSED_EVENTS_ONLY)
&& (event.eventType()
.equals (WfAuditEvent.PROCESS_STATE_CHANGED))
&& (((WfStateAuditEvent)event)
.newState().startsWith ("closed")))) {
if (((DefaultAuditEvent)event).store ()) {
storeAuditEvent(event);
}
publishEvent (event);
}
}
} catch (NoSuchObjectException e) {
// if any of the objects involved doesn't exist any more,
// something has gone wrong. But we cannot do anything
// about it (i.e. shouldn't happen).
logger.error (event + " discarded: " + e.getMessage(), e);
} catch (RemoteException e) {
// we assume that this indicates a temporary condition.
throw new EJBException (e);
} catch (EJBException e) {
// we assume that this indicates a temporary condition.
throw e;
} catch (SQLException e) {
// we assume that this indicates a temporary condition.
throw new EJBException (e);
} catch (RedeliveryRequiredException e) {
// we pass this one
throw e;
} catch (Throwable t) {
// as we are called from a MDB, it makes no sense
// to pass the exception to caller.
logger.error (event + " discarded: " + t.getMessage(), t);
ctx.setRollbackOnly();
} finally {
if (logger.isDebugEnabled()) {
logger.debug ("Event has been processed: " + event);
}
}
}
/**
* Queue the given event with a requeued count.
*
* @param evt the <code>WfAuditEvent</code>
* @param requeued the number of times this has been queued
* @ejb.interface-method view-type="local"
*/
public void requeueEvent (DefaultAuditEvent evt, int requeued) {
try {
QueuerUtils.queue
(queueConnectionFactory(), eventQueue(), evt, requeued);
} catch (ResourceNotAvailableException e) {
throw new EJBException (e);
}
}
/**
* Feed the event back to the engine, i.e. to the processes or activities
* that may be interested in it.
* @param event the event.
* @throws RemoteException if a system-level error occurs.
* @throws RedeliveryRequiredException if the event should be redelivered
*/
private void feedBack (WfAuditEvent event)
throws RemoteException, RedeliveryRequiredException {
// feed back only those events that we know the receivers to
// be interested in
boolean processHandles = AbstractProcess.isHandled (event);
boolean activityHandles = (event.activityKey() != null
&& AbstractActivity.isHandled (event));
if (!(processHandles || activityHandles)) {
if (logger.isDebugEnabled()) {
logger.debug ("Not feeding back " + event);
}
return;
}
ExtProcessDirectoryLocal procDir = null;
try {
// Source is not usable internally (remote object). Lookup
// local object in order to get process and (maybe) activity
// in transaction.
procDir = processDirectoryLocal();
ProcessLocal proc = procDir.lookupProcessLocal
(event.processMgrName(), event.processKey());
ExtActivityLocal actLocal = null;
if (event.activityKey() != null) {
actLocal = (ExtActivityLocal)
proc.activityByKeyLocal (event.activityKey());
}
if (activityHandles) {
actLocal.handleAuditEvent (event);
}
if (processHandles) {
((ExtProcessLocal)proc).handleAuditEvent (event);
}
if (logger.isDebugEnabled()) {
logger.debug ("Fed back event: " + event);
}
} catch (InvalidKeyException e) {
// There is a race condition between creation of the
// process and starting it. If both create and start are
// performed within the same transaction, it may happen
// that the event generated by the call to start becomes
// visible before the process, i.e. the data in the
// database. In this case, the thread processing the event
// may not find the process.
if (event instanceof WfStateAuditEvent
&& event.eventType().equals (WfAuditEvent.PROCESS_STATE_CHANGED)
&& (((WfStateAuditEvent)event).newState()
.equals (RunningState.RUNNING.toString ()))) {
throw new RedeliveryRequiredException
("Process not found (yet) for " + event);
}
// Else if we cannot lookup the process, well then this event
// is no good.
logger.warn (event + " discarded, process no longer available.");
} finally {
EJBUtil.removeSession(procDir);
}
}
/**
* Feed the event back to the engine, i.e. to the processes or activities
* that may be interested in it.
* @param event the event.
* @throws RemoteException if a system-level error occurs.
*/
private void publishEvent (WfAuditEvent event)
throws RemoteException {
ProcessDirectory procDir = null;
TopicConnection evtSvcCon = null;
TopicSession evtSvcSession = null;
try {
evtSvcCon = topConFac.createTopicConnection();
evtSvcSession = evtSvcCon.createTopicSession (true, 0);
TopicPublisher evtSvcPublisher
= evtSvcSession.createPublisher (eventService);
evtSvcPublisher.setDisableMessageID (true);
ObjectMessage msg = evtSvcSession.createObjectMessage();
msg.setStringProperty ("processKey", event.processKey());
msg.setStringProperty ("eventType", event.eventType());
msg.setObject ((Serializable)event);
evtSvcPublisher.publish (msg);
if (logger.isDebugEnabled ()) {
logger.debug ("Published event " + event);
}
evtSvcPublisher.close ();
} catch (JMSException e) {
logger.error ("Cannot publish "+event+": "+e.getMessage (), e);
} finally {
EJBUtil.removeSession(procDir);
QueuerUtils.release(evtSvcCon, evtSvcSession);
}
}
//
// Helper methods
//
/**
* Stores the given audit event data to the database.
* @param event the event.
* @throws SQLException in case of database access problems
* @throws IOException in case of i/o problems
*/
private void storeAuditEvent(WfAuditEvent event)
throws SQLException, IOException {
Connection con = null;
UniversalPrepStmt prepStmt = null;
try {
con = ds.getConnection();
// prepare statement
prepStmt = new UniversalPrepStmt
(ds, con, "INSERT INTO AuditEvents "
+ " (DBId, EventTime, EventType, ActivityKey,"
+ " ActivityName, ProcessKey, ProcessName, ProcessMgrName,"
+ " ProcessMgrVersion, EventData1, EventData2,"
+ " EventData3, EventData4, EventData5)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
// primary key
long primKey = EJBUtil.newPrimaryKey("AuditEvents");
int offset = 1;
prepStmt.setLong(offset++, primKey);
// misc. data
setupAuditEventInsertData (event, prepStmt, offset, primKey);
// execute insert statement
int rowCount = prepStmt.executeUpdate();
if (rowCount == 0) {
throw new EJBException
("Inserting audit event >>> " + event + " <<< failed.");
}
// data audit events are stored in different tables
if (event instanceof WfDataAuditEvent){
WfDataAuditEvent ev = (WfDataAuditEvent)event;
storeDataAuditEventProcessData
(con, primKey, ev.oldData(), ev.newData());
}
if (logger.isDebugEnabled ()) {
logger.debug ("Saved event " + event);
}
} catch (ResourceNotAvailableException e) {
throw new SQLException ("Cannot get primary key" + e.getMessage());
} finally {
JDBCUtil.closeAll (null, prepStmt, con);
}
}
/**
* Sets up the entire data fields of the given prepared statement with
* values of the given event.
* @param event the data source event
* @param prepStmt the insert statement
* @param offset an offset of the statement's parameter list
* @param mapId an identifier for a persistant map (in use for data audit
* events, only.)
* @throws SQLException in case of database access problems
* @throws IOException in case of other i/o problems
*/
private void setupAuditEventInsertData
(WfAuditEvent event, UniversalPrepStmt prepStmt, int offset,
long recKey)
throws SQLException, IOException {
// data common to all events
prepStmt.setTimestamp (offset++,
new Timestamp(event.timeStamp().getTime()));
prepStmt.setString (offset++, event.eventType());
prepStmt.setString (offset++, event.activityKey());
prepStmt.setString (offset++, event.activityName());
prepStmt.setString (offset++, event.processKey());
prepStmt.setString (offset++, event.processName());
prepStmt.setString (offset++, event.processMgrName());
prepStmt.setString (offset++, event.processMgrVersion());
// event specific data
String eventData1 = null;
String eventData2 = null;
String eventData3 = null;
String eventData4 = null;
String eventData5 = null;
if (event instanceof WfAssignmentAuditEvent){
WfAssignmentAuditEvent ev = (WfAssignmentAuditEvent)event;
eventData1 = ev.oldResourceKey();
eventData2 = ev.oldResourceName();
eventData3 = ev.newResourceKey();
eventData4 = ev.newResourceName();
} else if (event instanceof WfCreateProcessAuditEvent){
WfCreateProcessAuditEvent ev = (WfCreateProcessAuditEvent)event;
eventData1 = ev.pActivityKey();
eventData2 = ev.pProcessKey();
eventData3 = ev.pProcessName();
eventData4 = ev.pProcessMgrName();
eventData5 = ev.pProcessMgrVersion();
} else if (event instanceof WfDataAuditEvent){
eventData1 = Long.toString (recKey);
} else if (event instanceof WfStateAuditEvent){
WfStateAuditEvent ev = (WfStateAuditEvent)event;
eventData1 = ev.oldState();
eventData2 = ev.newState();
}
prepStmt.setString (offset++, eventData1);
prepStmt.setString (offset++, eventData2);
prepStmt.setString (offset++, eventData3);
prepStmt.setString (offset++, eventData4);
prepStmt.setString (offset++, eventData5);
}
/**
* Stores process data associated to a data audit event as identifed
* by the given map id.
* @param con the database connection to use
* @param mapId the primary key that identifies the assocaited data
* audit event
* @param oldData the old data
* @param newData the new data
* @throws IOException in case of io problems
*/
private void storeDataAuditEventProcessData
(Connection con, long recKey, Map oldData, Map newData)
throws SQLException, IOException {
JDBCPersistentMap m = null;
try {
Long key = new Long(recKey);
// old data
if (oldData != null){
m = new JDBCPersistentMap (ds, key, "DataAuditEventOldData");
m.setConnection(con);
m.setMapId (key);
m.setNewMap ();
m.putAll(oldData);
try {
m.store();
} catch (PersistentMapSQLException e) {
throw (SQLException)e.getCause();
}
m.setConnection(null);
}
// new data
if (newData != null){
m = new JDBCPersistentMap (ds, key, "DataAuditEventNewData");
m.setConnection(con);
m.setMapId (key);
m.setNewMap ();
m.putAll(newData);
try {
m.store();
} catch (PersistentMapSQLException e) {
throw (SQLException)e.getCause();
}
m.setConnection(null);
}
} finally {
m.setConnection(null);
}
}
/**
* Returns a collection of <code>WfAuditEvent</code>s associated with
* this process and stored in the database.
* @param execObj the execution object for which to select the audit
* events
* @return the collection of audit events
*/
private Collection selectAuditEvents(WfExecutionObject execObj)
throws SQLException, IOException, NamingException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
Collection events = new ArrayList();
con = ds.getConnection();
String selectStatement = "SELECT * FROM auditevents ";
if (execObj instanceof WfProcess){
selectStatement
+= " WHERE ProcessKey = ? AND ActivityKey IS NULL";
} else {
selectStatement
+= " WHERE ActivityKey = ?";
}
prepStmt = con.prepareStatement(selectStatement);
prepStmt.setString(1, execObj.key());
rs = prepStmt.executeQuery();
while (rs.next()) {
events.add(restoreAuditEvent(execObj, con, rs));
}
return events;
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
}
/**
* Restores an audit event from the given result set (element).
* @param execObj the execution object for which to select the audit
* events
* @param con a database connection
* @param rs the result set containing the event information
* @return the audit event
* @throws SQLException in case of SQL access problems
*/
private WfAuditEvent restoreAuditEvent(WfExecutionObject execObj,
Connection con, ResultSet rs)
throws SQLException, IOException {
String eventType = rs.getString(3);
WfAuditEvent eventBase = null;
Timestamp ts = rs.getTimestamp(2);
Date evtTime = new Date (ts.getTime() + ts.getNanos() / 1000000000);
if (execObj instanceof WfProcess){
eventBase
= new DefaultAuditEvent
((WfProcess)execObj, eventType, evtTime,
rs.getString(6), rs.getString(7), rs.getString(8),
rs.getString(9),
ProcessDefinition.AUDIT_SELECTION_ALL_EVENTS, false);
} else {
eventBase
= new DefaultAuditEvent
((WfActivity)execObj, eventType, evtTime,
rs.getString(4), rs.getString(5), rs.getString(6),
rs.getString(7), rs.getString(8), rs.getString(9),
ProcessDefinition.AUDIT_SELECTION_ALL_EVENTS, false);
}
if (eventType.equals(WfAuditEvent.PROCESS_CREATED)){
return new DefaultCreateProcessAuditEvent
(eventBase, rs.getString(10), rs.getString(11),
rs.getString(12), rs.getString(13), rs.getString(14));
} else if ((eventType.equals(WfAuditEvent.PROCESS_STATE_CHANGED))
|| (eventType.equals(WfAuditEvent.ACTIVITY_STATE_CHANGED))){
return new DefaultStateAuditEvent
(eventBase, rs.getString(10), rs.getString(11));
} else if ((eventType.equals(WfAuditEvent.PROCESS_CONTEXT_CHANGED))
||(eventType.equals(WfAuditEvent.ACTIVITY_CONTEXT_CHANGED))
||(eventType.equals(WfAuditEvent.ACTIVITY_RESULT_CHANGED))){
return new DefaultDataAuditEvent
(eventBase,
new DefaultProcessData
(selectDataAuditEventData
(con, rs.getString(10), "DataAuditEventOldData")),
new DefaultProcessData
(selectDataAuditEventData
(con, rs.getString(10), "DataAuditEventNewData")));
} else if (eventType.equals(WfAuditEvent.ACTIVITY_ASSIGNMENT_CHANGED)){
return new DefaultAssignmentAuditEvent
(eventBase, rs.getString(10), rs.getString(12),
rs.getString(11), rs.getString(13));
} else {
throw new IllegalStateException
("Invalid audit event selected for history");
}
}
/**
* Returns the process data as identified by the given parameters.
* @param con a database connection
* @param recKey the process data identifier
* @param tableName the table name to look for the process data
* @return the process data
* @throws IOException in case of data access problems
*/
private ProcessData selectDataAuditEventData
(Connection con, String recKey, String tableName)
throws SQLException, IOException {
JDBCPersistentMap m = new JDBCPersistentMap
(ds, Long.valueOf (recKey), tableName);
try {
m.setConnection(con);
m.load();
ProcessData data = new DefaultProcessData();
data.putAll(m);
return data;
} catch (PersistentMapSQLException e) {
throw (SQLException)e.getCause();
} finally {
m.setConnection(null);
}
}
/**
* Clears the history of the process with the given key.
* @param procKey the process key.
* @ejb.interface-method view-type="local"
*/
public void removeAuditEvents (String procKey) {
try {
Connection con = null;
UniversalPrepStmt prepStmt = null;
try {
con = ds.getConnection();
if (dbProperties.isMySQL()
&& dbProperties.driverMajorVersion() == 5) {
prepStmt = new UniversalPrepStmt
(ds, con, "DELETE DATAAUDITEVENTOLDDATA "
+ "FROM DATAAUDITEVENTOLDDATA, "
+ "(SELECT DBID FROM AUDITEVENTS "
+ "WHERE PROCESSKEY = ?) EVENTS "
+ "WHERE DATAAUDITEVENTOLDDATA.MAPID = EVENTS.DBID");
} else {
prepStmt = new UniversalPrepStmt
(ds, con, "DELETE FROM DataAuditEventOldData WHERE "
+ "MapId IN (SELECT DBId FROM AuditEvents "
+ "WHERE ProcessKey = ?)");
}
prepStmt.setString(1, procKey);
prepStmt.executeUpdate();
prepStmt.close ();
prepStmt = null;
if (dbProperties.isMySQL()
&& dbProperties.driverMajorVersion() == 5) {
prepStmt = new UniversalPrepStmt
(ds, con, "DELETE DATAAUDITEVENTNEWDATA "
+ "FROM DATAAUDITEVENTNEWDATA, "
+ "(SELECT DBID FROM AUDITEVENTS "
+ "WHERE PROCESSKEY = ?) EVENTS "
+ "WHERE DATAAUDITEVENTNEWDATA.MAPID = EVENTS.DBID");
} else {
prepStmt = new UniversalPrepStmt
(ds, con, "DELETE FROM DataAuditEventNewData WHERE "
+ "MapId IN (SELECT DBId FROM AuditEvents "
+ "WHERE ProcessKey = ?)");
}
prepStmt.setString(1, procKey);
prepStmt.executeUpdate();
prepStmt.close ();
prepStmt = null;
prepStmt = new UniversalPrepStmt
(ds, con, "DELETE FROM AuditEvents WHERE ProcessKey = ?");
prepStmt.setString(1, procKey);
prepStmt.executeUpdate();
} finally {
JDBCUtil.closeAll(null, prepStmt, con);
}
} catch (SQLException e) {
throw new EJBException (e);
}
}
/**
* Involve the container of the given activity in the transation
* to prevent deadlocks.
*
* @param activity
*/
private void involveContainerInTx (ActivityProxy activity)
throws RemoteException {
try {
processDirectoryLocal().lookupProcessLocal
(activity.uniqueKey().managerName(),
activity.uniqueKey().processKey()).name();
} catch (InvalidKeyException e) {
throw (IllegalArgumentException)
(new IllegalArgumentException
("Container not fouond: " + e.getMessage()).initCause(e));
}
}
/**
* Forward the state change to the given activity calling
* <code>changeState</code>. This method is used by
* <code>ActivityProxy</code> to first involve the process in the
* transaction. This avoids a lot of deadlocks.
*
* @param activity the activity
* @param state the state
* @throws InvalidStateException if the state change is not
* possible
* @throws TransitionNotAllowedException if the state change is
* not possible
* @ejb.interface-method view-type="remote"
*/
public void forwardStateChange (Activity activity, State state)
throws InvalidStateException, TransitionNotAllowedException {
try {
if (activity instanceof ActivityProxy) {
involveContainerInTx((ActivityProxy)activity);
activity = ((ActivityProxy)activity).unwrap();
}
activity.changeState (state);
} catch (RemoteException e) {
throw new EJBException (e);
}
}
/**
* Forward the state change to the given activity. This method is
* used by <code>ActivityProxy</code> to first involve the process
* in the transaction. This avoids a lot of deadlocks.
*
* @param activity the activity
* @param result the exception data
* @throws TransitionNotAllowedException if the state change is
* not possible
* @ejb.interface-method view-type="remote"
*/
public void forwardAbandon (Activity activity, ExceptionResult result)
throws TransitionNotAllowedException {
try {
if (activity instanceof ActivityProxy) {
involveContainerInTx((ActivityProxy)activity);
activity = ((ActivityProxy)activity).unwrap();
}
((ExtActivity)activity).abandon (result);
} catch (RemoteException e) {
throw new EJBException (e);
}
}
/**
* Forward the state change to the given activity. This method is
* used by <code>ActivityProxy</code> to first involve the process
* in the transaction. This avoids a lot of deadlocks.
*
* @param activity the activity
* @throws CannotCompleteException if the state change is
* not possible
* @ejb.interface-method view-type="remote"
*/
public void forwardComplete (Activity activity)
throws CannotCompleteException {
try {
if (activity instanceof ActivityProxy) {
involveContainerInTx((ActivityProxy)activity);
activity = ((ActivityProxy)activity).unwrap();
}
activity.complete ();
} catch (RemoteException e) {
throw new EJBException (e);
}
}
/**
* Forward the state change to the given activity. This method is
* used by <code>ActivityProxy</code> to first involve the process
* in the transaction. This avoids a lot of deadlocks.
*
* @param activity the activity
* @throws CannotStopException if the state change is
* not possible
* @throws NotRunningException if the state change is
* not possible
* @ejb.interface-method view-type="remote"
*/
public void forwardTerminate (Activity activity)
throws CannotStopException, NotRunningException {
try {
if (activity instanceof ActivityProxy) {
involveContainerInTx((ActivityProxy)activity);
activity = ((ActivityProxy)activity).unwrap();
}
activity.terminate ();
} catch (RemoteException e) {
throw new EJBException (e);
}
}
/**
* Forward the state change to the given activity. This method is
* used by <code>ActivityProxy</code> to first involve the process
* in the transaction. This avoids a lot of deadlocks.
*
* @param activity the activity
* @throws CannotStopException if the state change is
* not possible
* @throws NotRunningException if the state change is
* not possible
* @ejb.interface-method view-type="remote"
*/
public void forwardAbort (Activity activity)
throws CannotStopException, NotRunningException {
try {
if (activity instanceof ActivityProxy) {
involveContainerInTx((ActivityProxy)activity);
activity = ((ActivityProxy)activity).unwrap();
}
activity.abort ();
} catch (RemoteException e) {
throw new EJBException (e);
}
}
/**
* Forward the result to the given activity. This method is used
* by <code>ActivityProxy</code> to first involve the process in
* the transaction. This avoids a lot of deadlocks.
*
* @param activity the activity
* @param result the result
* @throws InvalidDataException if the result cannot be set
* @ejb.interface-method view-type="remote"
*/
public void forwardResult (Activity activity, ProcessData result)
throws InvalidDataException {
try {
if (activity instanceof ActivityProxy) {
involveContainerInTx((ActivityProxy)activity);
activity = ((ActivityProxy)activity).unwrap();
}
activity.setResult (result);
} catch (RemoteException e) {
throw new EJBException (e);
}
}
/**
* Forward the request for the activity info to the given
* activity. This method is used by <code>ActivityProxy</code> to
* first involve the process in the transaction (activity info
* includes the process' description). This avoids a lot of
* deadlocks.
*
* @param activity the activity
* @return the activity info
* @ejb.interface-method view-type="remote"
*/
public Activity.Info forwardActivityInfo (Activity activity) {
try {
if (activity instanceof ActivityProxy) {
involveContainerInTx((ActivityProxy)activity);
activity = ((ActivityProxy)activity).unwrap();
}
return activity.activityInfo ();
} catch (RemoteException e) {
throw new EJBException (e);
}
}
/**
* Choose the given activity for further processing in deferred choice.
*
* @param activity the activity
* @return <code>true</code> if the activity could be made the
* effectively chosen one
* @throws TransitionNotAllowedException if the activity is
* neither running nor suspended
* @ejb.interface-method view-type="remote"
*/
public boolean forwardChoose (Activity activity)
throws TransitionNotAllowedException {
try {
if (activity instanceof ActivityProxy) {
involveContainerInTx((ActivityProxy)activity);
activity = ((ActivityProxy)activity).unwrap();
}
return activity.choose ();
} catch (RemoteException e) {
throw new EJBException(e);
}
}
/**
* Execute a batch in the context of the workflow service
* i.e. on the server.<P>
* @param batch the batch to be executed.
* @return the result.
* @throws InvocationTargetException wraps exceptions as defined
* by the implementing class
* @ejb.interface-method view-type="remote"
*/
public Object executeBatch (Batch batch) throws InvocationTargetException {
return batch.execute (new Batch.Context () {
public boolean isRollbackOnly () {
return ctx.getRollbackOnly();
}
});
}
}