/*
* 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: WfProcessEJB.java 3205 2009-09-27 17:13:47Z mlipp $
*/
package de.danet.an.workflow.ejbs.core;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.rmi.RemoteException;
import java.security.Principal;
import java.security.ProviderException;
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.EJBLocalObject;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.FinderException;
import javax.ejb.NoSuchEntityException;
import javax.ejb.NoSuchObjectLocalException;
import javax.ejb.ObjectNotFoundException;
import javax.ejb.RemoveException;
import javax.ejb.TimedObject;
import javax.ejb.Timer;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import de.danet.an.util.CollectionsUtil;
import de.danet.an.util.EJBUtil;
import de.danet.an.util.JDBCUtil;
import de.danet.an.util.PersistentMap;
import de.danet.an.util.ResourceNotAvailableException;
import de.danet.an.util.SimplePrincipal;
import de.danet.an.util.UniversalPrepStmt;
import de.danet.an.util.persistentmaps.PersistentMapSQLException;
import de.danet.an.workflow.internalapi.ExtActivityLocal;
import de.danet.an.workflow.internalapi.ExtProcessLocal;
import de.danet.an.workflow.internalapi.ExtTransitionLocal;
import de.danet.an.workflow.localapi.ActivityLocal;
import de.danet.an.workflow.localapi.ProcessDefinitionDirectoryLocal;
import de.danet.an.workflow.localapi.ProcessLocal;
import de.danet.an.workflow.localapi.TransitionLocal;
import de.danet.an.workflow.omgcore.AlreadyRunningException;
import de.danet.an.workflow.omgcore.CannotCompleteException;
import de.danet.an.workflow.omgcore.CannotStartException;
import de.danet.an.workflow.omgcore.HistoryNotAvailableException;
import de.danet.an.workflow.omgcore.InvalidDataException;
import de.danet.an.workflow.omgcore.InvalidPriorityException;
import de.danet.an.workflow.omgcore.InvalidStateException;
import de.danet.an.workflow.omgcore.ProcessData;
import de.danet.an.workflow.omgcore.WfAuditEvent;
import de.danet.an.workflow.omgcore.WfProcessMgr;
import de.danet.an.workflow.omgcore.WfRequester;
import de.danet.an.workflow.omgcore.WfExecutionObject.NotRunningState;
import de.danet.an.workflow.omgcore.WfExecutionObject.State;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.Process;
import de.danet.an.workflow.api.CannotRemoveException;
import de.danet.an.workflow.api.DefaultProcessData;
import de.danet.an.workflow.api.ImportException;
import de.danet.an.workflow.api.InvalidKeyException;
import de.danet.an.workflow.api.ProcessDefinition;
import de.danet.an.workflow.api.Activity.Implementation;
import de.danet.an.workflow.api.Activity.JoinAndSplitMode;
import de.danet.an.workflow.api.Activity.StartFinishMode;
import de.danet.an.workflow.apix.ExtActivity;
import de.danet.an.workflow.domain.AbstractProcess;
import de.danet.an.workflow.domain.Deadline;
import de.danet.an.workflow.domain.DefaultAuditEvent;
import de.danet.an.workflow.domain.DefaultProcessDefinition;
import de.danet.an.workflow.domain.SubProcRequester;
import de.danet.an.workflow.domain.TransitionDefinition;
import de.danet.an.workflow.domain.TransitionDefinitionLocal;
import de.danet.an.workflow.ejbs.admin.ProcessDefinitionDirectoryHome;
import de.danet.an.workflow.ejbs.admin.ProcessDefinitionDirectoryLocalHome;
import de.danet.an.workflow.ejbs.admin.ProcessMgrStub;
import de.danet.an.workflow.ejbs.admin.WorkflowEngineLocal;
import de.danet.an.workflow.ejbs.admin.WorkflowEngineLocalHome;
import de.danet.an.workflow.ejbs.util.QueuerUtils;
/**
* The entity bean <code>WfProcessEJB</code> represent a process
* in the workflow.
*
* @ejb.bean name="ProcessBean" display-name="ProcessBean" type="BMP"
* jndi-name="ejb/@@@_JNDI_Name_Prefix_@@@ProcessBean"
* reentrant="true" view-type="both"
* @jonas.bean ejb-name="ProcessBean"
* @ejb.pk class="java.lang.Long" generate="false"
* @ejb.home remote-class="de.danet.an.workflow.ejbs.core.WfProcessHome"
* local-class="de.danet.an.workflow.ejbs.core.WfProcessLocalHome"
* @ejb.interface
* extends="javax.ejb.EJBObject, de.danet.an.workflow.apix.ExtProcess"
* remote-class="de.danet.an.workflow.ejbs.core.WfProcess"
* local-extends="javax.ejb.EJBLocalObject,
* de.danet.an.workflow.internalapi.ExtProcessLocal"
* local-class="de.danet.an.workflow.ejbs.core.WfProcessLocal"
* @ejb.transaction type="Required"
* @ejb.security-identity run-as="WfMOpenAdmin"
* sunone-principal="WfMCorePrincipalForAdmin"
* @weblogic.run-as-identity-principal WfMCorePrincipalForAdmin
* @ejb.ejb-ref ejb-name="WorkflowEngine" view-type="local"
* @ejb.ejb-ref ejb-name="ActivityBean" view-type="remote"
* @ejb.ejb-ref ejb-name="ActivityBean" view-type="local"
* @ejb.ejb-ref ejb-name="ProcessDefinitionDirectory" view-type="remote"
* @ejb.ejb-ref ejb-name="ProcessDefinitionDirectory" view-type="local"
* @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.permission role-name="WfMOpenAdmin"
* @weblogic.transaction-isolation TRANSACTION_READ_COMMITTED
* @weblogic.cache concurrency-strategy="Exclusive"
* @ejb.resource-ref res-ref-name="jms/QCF"
* res-type="javax.jms.QueueConnectionFactory" res-auth="Container"
* @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/TCF"
* res-type="javax.jms.TopicConnectionFactory" res-auth="Container"
* @jboss.resource-ref res-ref-name="jms/TCF" jndi-name="java:/JmsXA"
* @jonas.resource res-ref-name="jms/TCF" jndi-name="TCF"
* @weblogic.enable-call-by-reference True
* @weblogic.resource-description res-ref-name="jms/TCF"
* 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/ChannelIn"
* res-type="javax.jms.Queue" res-auth="Container"
* @jonas.resource res-ref-name="jms/ChannelIn"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ChannelInMessages"
* @jboss.resource-ref 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"
* @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"
*/
public class WfProcessEJB extends AbstractProcess
implements EntityBean, TimedObject {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog(WfProcessEJB.class);
/** Cache for QueueConnectionFactory. */
private QueueConnectionFactory qcfCache = null;
/** Cache for TopicConnectionFactory. */
private TopicConnectionFactory tcfCache = null;
/** Cache for event queue. */
private Queue eventQueueCache = null;
/** Cache for channel in queue. */
private Queue channelInQueueCache = null;
/** Cache for channel out topic. */
private Topic channelOutTopicCache = null;
private QueueConnectionFactory queueConnectionFactory ()
throws ResourceNotAvailableException {
if (qcfCache == null) {
qcfCache = ((QueueConnectionFactory)EJBUtil
.retrieveJNDIEntry ("java:comp/env/jms/QCF"));
}
return qcfCache;
}
private TopicConnectionFactory topicConnectionFactory ()
throws ResourceNotAvailableException {
if (tcfCache == null) {
tcfCache = ((TopicConnectionFactory)EJBUtil
.retrieveJNDIEntry ("java:comp/env/jms/TCF"));
}
return tcfCache;
}
private Queue eventQueue ()
throws ResourceNotAvailableException {
if (eventQueueCache == null) {
eventQueueCache = (Queue)EJBUtil.retrieveJNDIEntry
("java:comp/env/jms/InternalEventQueue");
}
return eventQueueCache;
}
private Queue channelInQueue ()
throws ResourceNotAvailableException {
if (channelInQueueCache == null) {
channelInQueueCache = (Queue)EJBUtil.retrieveJNDIEntry
("java:comp/env/jms/ChannelIn");
}
return channelInQueueCache;
}
private Topic channelOutTopic ()
throws ResourceNotAvailableException {
if (channelOutTopicCache == null) {
channelOutTopicCache = (Topic)EJBUtil.retrieveJNDIEntry
("java:comp/env/jms/ChannelOut");
}
return channelOutTopicCache;
}
/**
* A lazily loading implementation for deadline storage. Not all
* methods are implemented.
*/
private class DeadlineMap extends HashMap {
/**
* Returns the deadlines for the activity with the given key.
* @param key activity key
* @return the value to which this map maps the specified key,
* or null if the map contains no mapping for this key
* @throws ClassCastException if the key is of an
* inappropriate type for this map.
* @throws NullPointerException key is null
*/
public Object get(Object key)
throws ClassCastException, NullPointerException {
Object res = super.get (key);
if (res == null) {
// try to load from db
try {
res = loadDeadlines (ds, (Long)key);
super.put (key, res);
} catch (SQLException e) {
throw new EJBException (e);
} catch (IOException e) {
throw new EJBException (e);
}
}
return res;
}
/**
* Not supported.
* @param key key whose presence in this map is to be tested.
* @return true if this map contains a mapping for the specified key.
*/
public boolean containsKey(Object key) {
throw new UnsupportedOperationException ();
}
/**
* Not supported.
* @param value value whose presence in this map is to be tested.
* @return true if this map maps one or more keys to the
* specified value.
*/
public boolean containsValue(Object value) {
throw new UnsupportedOperationException ();
}
/**
* Not supported.
* @return set view of the keys contained in this map.
*/
public Set keySet() {
throw new UnsupportedOperationException ();
}
/**
* Not supported.
* @return a collection view of the values contained in this map.
*/
public Collection values() {
throw new UnsupportedOperationException ();
}
/**
* Not supported.
* @return a set view of the mappings contained in this map.
*/
public Set entrySet() {
throw new UnsupportedOperationException ();
}
}
/** Entity context. */
private EntityContext ctx;
/** The data source of the database. */
private DataSource ds = null;
private SessionFactory hibernateSessionFactory = null;
/** Indicates if the database supports select for update. */
private Boolean haveSelForUp = null;
/** Database name. */
private static final String DB_NAME = "java:comp/env/jdbc/WfEngine";
/** Associated activities. */
private Map activityMapCache;
private Map activityLocalMapCache;
/** Used for fast lookup of process definition. */
private String packageId = null;
/** Used for fast lookup of process definition. */
private String processId = null;
/** Used for fast lookup of process definition. */
private Long xpdlRef = null;
/** Creating principal. */
private String processCreator = null;
/** The cached workflow engine. */
private WorkflowEngineLocal engineLocalCache = null;
/** The cached home interface of the activity. */
private WfActivityHome activityHomeCache = null;
private WfActivityLocalHome activityLocalHomeCache = null;
/** The cached home interface of the processDefinitionDirectory. */
private ProcessDefinitionDirectoryHome
processDefinitionDirectoryHomeCache = null;
private ProcessDefinitionDirectoryLocalHome
processDefinitionDirectoryLocalHomeCache = null;
/** Set during call to underlying init() in ejbPostCreate */
private boolean callbackFromInitDuringCreate = false;
/** Delayed events. */
private List delayedEvents = null;
/** Cached transition information. */
private List transitionsCache = null;
/** Cached transition information. */
private List transitionsLocalCache = null;
private Map transLocalByFrom = null;
/** Local process definition as string, to be stored. */
private String myProcDef = null;
/** List of updated transitions (need to be stored). */
private Set updatedTransitions = new HashSet();
//
// Provide the persistent attributes
//
/** Indicates change of a persistent attribute. */
private boolean persistentAttributeModified = false;
/** Indicates change of a persistent attribute description. */
private boolean paDescriptionModified = false;
/** Persistent attribute <code>createTime</code>. */
private Date paCreateTime;
/** Persistent attribute <code>name</code>. */
private String paName;
/** Persistent attribute <code>id</code>. */
private String paId;
/** Persistent attribute <code>description</code>. */
private String paDescription;
/** Persistent attribute <code>priority</code>. */
private Priority paPriority;
/** Persistent attribute <code>typedState</code>. */
private State paTypedState;
/** Persistent attribute <code>lastStateTime</code>. */
private Date paLastStateTime;
/** Persistent attribute <code>requester</code>. */
private WfRequester paRequester;
/** Persistent attribute <code>processMgrName</code>. */
private String paProcessMgrName;
/** Persistent attribute <code>processMgrVersion</code>. */
private String paProcessMgrVersion;
/** Persistent attribute <code>processDef</code>. */
private ProcessDefinition paProcessDef;
/** Alternate store for <code>processDef</code> if individual
process description is used. */
private SoftReference paProcessDefSoft;
/** Persistent attribute <code>processData</code>. */
private ProcessData paProcessData;
/** Persistent attribute <code>blockDeadlines</code>. */
private Map paBlockDeadlines;
/**
* Persistent attribute <code>flags</code>. This attribute is not
* exposed, it serves as space efficient storage for various
* attributes that are exposed.
*/
private int paFlags;
private static final int FLAGS_DEBUG = 1;
private static final int FLAGS_AUDIT_SELECTION_SHIFT = 1;
private static final int FLAGS_AUDIT_SELECTION_MASK = 7 << 1;
private static final int FLAGS_STORE_AUDIT_EVENTS = 16;
/* Transition flags. */
private static int FLAGS_HAS_PENDING_TOKEN = 1;
/**
* Contructor.
*/
public WfProcessEJB () {
}
//
// Accessor methods for cached attributes
//
/**
* The workflow engine.
* @return the workflow engine
*/
private WorkflowEngineLocal engineLocal()
throws ResourceNotAvailableException {
if (engineLocalCache == null) {
engineLocalCache = (WorkflowEngineLocal)EJBUtil.createSession
(WorkflowEngineLocalHome.class,
"java:comp/env/ejb/WorkflowEngineLocal");
}
return engineLocalCache;
}
/**
* The home interface of the WfActivityBean.
* @return home interface of the WfActivityBean
*/
private WfActivityHome activityHome()
throws ResourceNotAvailableException {
if (activityHomeCache == null) {
activityHomeCache = (WfActivityHome)EJBUtil.retrieveEJBHome
(WfActivityHome.class, "java:comp/env/ejb/ActivityBean");
}
return activityHomeCache;
}
/**
* The home interface of the WfActivityBean.
* @return home interface of the WfActivityBean
*/
private WfActivityLocalHome activityLocalHome()
throws ResourceNotAvailableException {
if (activityLocalHomeCache == null) {
activityLocalHomeCache = (WfActivityLocalHome)
EJBUtil.retrieveEJBLocalHome
(WfActivityLocalHome.class,
"java:comp/env/ejb/ActivityBeanLocal");
}
return activityLocalHomeCache;
}
/**
* The home interface of the ProcessDefinitionDirectoryBean.
* @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;
}
/**
* The home interface of the ProcessDefinitionDirectoryBean.
* @return home interface of the ProcessDefinitionDirectoryBean
*/
private ProcessDefinitionDirectoryLocalHome
processDefinitionDirectoryLocalHome()
throws ResourceNotAvailableException {
if (processDefinitionDirectoryLocalHomeCache == null) {
processDefinitionDirectoryLocalHomeCache
= (ProcessDefinitionDirectoryLocalHome)
EJBUtil.retrieveEJBLocalHome
(ProcessDefinitionDirectoryLocalHome.class,
"java:comp/env/ejb/ProcessDefinitionDirectoryLocal");
}
return processDefinitionDirectoryLocalHomeCache;
}
/**
* Returns the associated activities as map by key.
* @return activities for this process by key.
*/
private Map activityMap() throws RemoteException {
if (activityMapCache == null) {
activityMapCache = new HashMap ();
try {
for (Iterator i = activityHome().findByProcess
((Long)ctx.getPrimaryKey()).iterator ();
i.hasNext();) {
ExtActivity a = (ExtActivity)i.next();
cacheActivity (a);
}
} catch (FinderException e) {
throw new EJBException (e);
}
}
return activityMapCache;
}
/**
* Returns the associated activities as map by key.
* @return activities for this process by key.
*/
private Map activityLocalMap() {
if (activityLocalMapCache == null) {
activityLocalMapCache = new HashMap ();
try {
for (Iterator i = activityLocalHome().findByProcess
((Long)ctx.getPrimaryKey()).iterator ();
i.hasNext();) {
ExtActivityLocal a = (ExtActivityLocal)i.next();
cacheActivityLocal (a);
}
} catch (FinderException e) {
throw new EJBException (e);
} catch (ResourceNotAvailableException e) {
throw new EJBException (e);
}
}
return activityLocalMapCache;
}
/**
* Returns the associated activities.
* @return list of activities for this process
*/
private Collection activitiesLocal() {
return activityLocalMap().values();
}
/**
* Put the given activity in the cache. An
* <code>ActivityProxy</code> around the activity ensures that the
* cache is cleared (and thus reloaded when retrying the call) if
* a <code>RemoteException</code> occurs.
*/
private ExtActivity cacheActivity (ExtActivity act)
throws RemoteException {
ExtActivity a = ActivityProxy.wrap (act, (Process)toProcess());
activityMapCache.put (a.key(), a);
return a;
}
/**
* Put the given local activity in the cache. An
* <code>ActivityProxy</code> around the activity ensures that the
* cache is cleared (and thus reloaded when retrying the call) if
* a <code>RemoteException</code> occurs.
*/
private ExtActivityLocal cacheActivityLocal (ExtActivityLocal act) {
activityLocalMap().put (act.key(), act);
return act;
}
/**
* The remote interface of the ProcessDefinitionDirectoryBean.
* @return remote interface of the ProcessDefinitionDirectoryBean
*/
private ProcessDefinitionDirectoryLocal
getProcessDefinitionDirectoryLocal()
throws CreateException, NamingException, RemoteException {
return (ProcessDefinitionDirectoryLocal)
processDefinitionDirectoryLocalHome().create();
}
//
// EJB container contract
//
/**
* Set the associated session context. The container calls this method
* after the instance creation.
* @see javax.ejb.EntityBean
* @throws EJBException Throws on any error.
* @param context - A SessionContext interface for the instance
*/
public void setEntityContext(EntityContext context)
throws EJBException {
try {
ctx = context;
ds = JDBCUtil.refreshDS(null, DB_NAME);
// we can always use the same object for process data.
paProcessData = new ProcessDataStore (ds);
// prepare Hibernate
hibernateSessionFactory = engineLocal().hibernateSessionFactory ();
} catch (Exception ex) {
throw new EJBException(ex);
}
}
/**
* Unset the associated session context.
* @see javax.ejb.EntityBean
*/
public void unsetEntityContext() throws EJBException {
qcfCache = null;
engineLocalCache = null;
activityHomeCache = null;
processDefinitionDirectoryLocalHomeCache = null;
ds = null;
ctx = null;
}
/**
* This method looks up a process EJB by its primary key (dbid).
* @param primaryKey the process primary key.
* @return the process EJB (remote interface).
* @throws FinderException if no process found.
*/
public Long ejbFindByPrimaryKey(Long primaryKey)
throws FinderException {
boolean result = false;
try {
result = selectByPrimaryKey(primaryKey);
} catch (SQLException ex) {
throw new EJBException(ex);
}
if (!result) {
throw new ObjectNotFoundException
("Row for id " + primaryKey + " not found.");
}
return primaryKey;
}
/**
* This method looks up a process by its
* {@link de.danet.an.workflow.omgcore.WfProcess#key key}.
* @param key the process key.
* @return the process EJB (remote interface).
* @throws FinderException if no process found.
*/
public Long ejbFindByProcessKey(String key)
throws FinderException {
Long pk = null;
try {
pk = Long.valueOf (key);
} catch (NumberFormatException nex) {
throw new ObjectNotFoundException
("Invalid key format for key = " + key);
}
return ejbFindByPrimaryKey (pk);
}
/**
* This method looks up a process by the key {@link
* de.danet.an.workflow.omgcore.WfActivity#key key} of one of its
* activities.
* @param key the activity key.
* @return the process EJB (remote interface).
* @throws FinderException if no process found.
*/
public Long ejbFindByActivityKey(String key)
throws FinderException {
Long pk = null;
try {
pk = selectByActivityKey(Long.valueOf(key));
} catch (NumberFormatException nex) {
throw new ObjectNotFoundException
("Invalid key format for key = " + key);
} catch (SQLException ex) {
throw new EJBException(ex);
}
if (pk == null) {
throw new ObjectNotFoundException
("Row for id " + key + " not found.");
}
return pk;
}
/**
* This method looks up all the process by its process type.
* @param processMgrName the process type.
* @return a Collection object with remote interface of WfProcess.
* @throws FinderException because of the EJB syntax.
*/
public Collection ejbFindByProcessMgrName(String processMgrName)
throws FinderException {
Collection col = null;
try {
col = selectByProcessMgrName(processMgrName);
} catch (SQLException ex) {
throw new EJBException(ex);
}
return col;
}
/**
* This method looks up all processes without a local copy of the
* process definition by their process type.
* @param processMgrName the process type.
* @return a Collection object with remote interface of WfProcess.
* @throws FinderException because of the EJB syntax.
*/
public Collection ejbFindWithoutDefinitionByProcessMgrName
(String processMgrName)
throws FinderException {
Collection col = null;
try {
col = selectWithoutDefinitionByProcessMgrName(processMgrName);
} catch (SQLException ex) {
throw new EJBException(ex);
}
return col;
}
/**
* This method looks up all processes that have a requester with
* the given hash value.
* @param hash the hash value.
* @return a Collection object with remote interface of WfProcess.
* @throws FinderException because of the EJB syntax.
*/
public Collection ejbFindByRequesterHash(int hash)
throws FinderException {
Collection col = null;
try {
col = selectByRequesterHash(hash);
} catch (SQLException ex) {
throw new EJBException(ex);
}
return col;
}
/**
* Look up all processes of a given type that have a given value
* in a particular process data item. Note that this method may
* only be used for data items of type string.
*
* @param processMgr the process type
* @param itemName the name of the process data item
* @param itemValue the value of the process data item
* @return a Collection object with remote interface of WfProcess.
* @throws FinderException because of the EJB syntax.
*/
public Collection ejbFindByProcessTypeAndDataItem
(String processMgr, String itemName, String itemValue)
throws FinderException {
Connection con = null;
try {
try {
con = ds.getConnection();
return ((ProcessDataStore)paProcessData)
.findProcessesByTypeAndDataItem
(con, processMgr, itemName, itemValue);
} finally {
JDBCUtil.closeAll (null, null, con);
}
} catch (SQLException ex) {
throw new EJBException(ex);
} catch (IOException ex) {
throw new EJBException(ex);
}
}
/**
* This method looks up all processes.
* @return a Collection object with remote interface of WfProcess.
* @throws FinderException because of the EJB syntax.
*/
public Collection ejbFindAll()
throws FinderException {
Collection col = null;
try {
col = selectAll();
} catch (SQLException ex) {
throw new EJBException(ex);
}
return col;
}
/**
* This method looks up a range of processes.
* @param filterData the filter criteria
* @param orderData the order criteria
* @param start the index of the first item
* @param end the index of the last item
* @return a Collection object with processes primary keys
* @throws FinderException because of the EJB syntax
*/
public Collection ejbFindRange
(byte[] filterData, byte[] orderData, long start, long end)
throws FinderException {
Session session = null;
Criterion filterCriterion = null;
List orderCriteria = null;
try {
ByteArrayInputStream bis
= new ByteArrayInputStream(filterData);
ObjectInputStream ois = new ObjectInputStream (bis);
filterCriterion = (Criterion)ois.readObject();
bis = new ByteArrayInputStream(orderData);
ois = new ObjectInputStream (bis);
orderCriteria = (List)ois.readObject();
} catch (IOException e) {
throw (IllegalArgumentException)
(new IllegalArgumentException()).initCause(e);
} catch (ClassNotFoundException e) {
throw (IllegalArgumentException)
(new IllegalArgumentException()).initCause(e);
}
try {
session = hibernateSessionFactory.openSession();
Criteria curCrit = session.createCriteria(DAO.class);
curCrit.setProjection(Projections.property("dbId"));
if (filterCriterion != null) {
curCrit.add(filterCriterion);
}
if (orderCriteria != null) {
for (Iterator i = orderCriteria.iterator(); i.hasNext();) {
curCrit.addOrder((Order)i.next());
}
}
curCrit.setFirstResult((int)start);
curCrit.setMaxResults((int)(end - start + 1));
List res = curCrit.list();
return res;
} finally {
if (session != null) {
session.close();
}
}
}
/**
* The activate method is called when the instance is activated from its
* "passive" state. The instance should acquire any resource that it has
* released earlier in the ejbPassivate() method.
* This method gets the primary key from container.
* @see javax.ejb.EntityBean
*/
public void ejbActivate() throws EJBException {
// "bind" entity container to primary key
((ProcessDataStore)paProcessData)
.setMapId ((Long)ctx.getPrimaryKey());
activityMapCache = null;
activityLocalMapCache = null;
transitionsCache = null;
transitionsLocalCache = null;
transLocalByFrom = null;
updatedTransitions.clear();
}
/**
* The passivate method is called before the instance enters the
* "passive" state. The instance should release any resources that it
* can re-acquire later in the ejbActivate() method.
* @see javax.ejb.EntityBean
*/
public void ejbPassivate() throws EJBException {
paProcessDef = null;
paProcessDefSoft = null;
activityMapCache = null;
activityLocalMapCache = null;
transitionsCache = null;
transitionsLocalCache = null;
transLocalByFrom = null;
updatedTransitions.clear();
paBlockDeadlines = null;
dispose();
}
/**
* Create an process and save it into database.
* It throws <code>EJBException</code> if any error occurs.
* @param procDef process definition
* @param req requester of the process
* @return Long primary key of the process
* @throws CreateException no
* @ejb.create-method
* view-type="local"
*/
public Long ejbCreate
(Long xpdlRef, ProcessDefinition procDef, WfRequester req)
throws CreateException {
try {
Long processPk = null;
// create new processPk
processPk = new Long (EJBUtil.newPrimaryKey("process"));
// "bind" entity container to primary key
((ProcessDataStore)paProcessData).setMapId (processPk);
((ProcessDataStore)paProcessData).setNewMap ();
return processPk;
} catch (RemoteException ex) {
logger.error (ex.getMessage(), ex);
throw new CreateException(ex.getMessage());
}
}
/**
* Create an process and save it into database. First are created
* activities, transitions and transitions references.
* @param procDef process definition
* @param req requester of the process
* @throws CreateException if the process can not be created.
*/
public void ejbPostCreate
(Long xpdlRef, ProcessDefinition procDef, WfRequester req)
throws CreateException {
try {
// initialize private attributes
packageId = procDef.packageId();
processId = procDef.processId();
this.xpdlRef = xpdlRef;
if (req instanceof SubProcRequester) {
processCreator = ((SubProcRequester)req).creator ();
} else {
processCreator = ctx.getCallerPrincipal().getName();
}
activityLocalMapCache = new HashMap ();
// initialize persistent attributes
paRequester = req;
paCreateTime = new Date();
transitionsLocalCache = new ArrayList ();
transLocalByFrom = new HashMap ();
transitionsCache = null;
updatedTransitions.clear();
paProcessDef = procDef;
paProcessDefSoft = null;
paBlockDeadlines = CollectionsUtil.trackedMap(new DeadlineMap ());
// initialize underlying domain object. Delay events
// because event processing tries to lookup the process in
// the database, and it isn't there yet. This is probably
// only necessary in the JBoss environment (and may even
// not be sufficient) because the creation of the process
// and the queuing of events should be in one transaction
// and become visible to the event handler EJB at the same
// time.
delayedEvents = new ArrayList();
try {
callbackFromInitDuringCreate = true;
init(procDef);
} finally {
callbackFromInitDuringCreate = false;
}
// Is set by init, we initially use xpdlref, however.
Collections.sort (transitionsLocalCache, new Comparator () {
public int compare (Object o1, Object o2) {
return ((TransitionDefinitionLocal)o1).order()
- ((TransitionDefinitionLocal)o2).order();
}
});
refresh();
// save the new process into database
insertProcess();
insertTransitions();
persistentAttributeModified = false;
while (delayedEvents.size() > 0) {
QueuerUtils.queue
(queueConnectionFactory(), eventQueue(),
(DefaultAuditEvent)delayedEvents.remove(0));
}
delayedEvents = null;
} catch (RemoteException re) {
throw new EJBException(re);
} catch (SQLException e) {
throw new EJBException(e);
} catch (IOException ioe) {
logger.error (ioe.getMessage(), ioe);
throw new CreateException (ioe.getMessage());
}
}
/**
* 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.
* @throws RemoveException if the activity is not closed.
* @see javax.ejb.EntityBean
*/
public void ejbRemove() throws RemoveException {
// Note that #removeThis must be adapted if RemoveException is
// thrown due to any other cause. And prepareRemove must be
// adapted if there are any more preconditions to meet.
try {
if (getPaTypedState().workflowState() != State.CLOSED
&& (!getPaTypedState().isSameOrSubState
(NotRunningState.NOT_STARTED))) {
throw new RemoveException
("Cannot remove " + toString() + ": still running.");
}
removeProcess();
dispose();
paProcessDef = null;
paProcessDefSoft = null;
activityMapCache = null;
activityLocalMapCache = null;
transitionsCache = null;
transitionsLocalCache = null;
transLocalByFrom = null;
updatedTransitions.clear();
paBlockDeadlines = null;
engineLocal().removeAuditEvents
(((Long)ctx.getPrimaryKey()).toString());
} catch (RemoteException ex) {
throw new EJBException(ex);
} catch (IOException ex) {
throw new EJBException(ex);
} catch (SQLException ex) {
throw new EJBException(ex);
}
}
/**
* A container invokes this method to synchronize the state of an
* enterprise bean instance with the entity object's state in the database.
* @see javax.ejb.EntityBean
*/
public void ejbLoad() throws EJBException {
try {
// load process attributes from persistent store
loadProcess();
paBlockDeadlines = CollectionsUtil.trackedMap(new DeadlineMap ());
updatedTransitions.clear();
// notify underlying domain object
refresh ();
} catch (SQLException e) {
throw new EJBException(e);
} catch (IOException e) {
throw new EJBException(e);
} catch (FinderException e) {
throw new EJBException(e);
}
}
/**
* A container invokes this method to synchronize the state of the
* entity object in the database with the state of the enterprise bean
* instancee.
* @see javax.ejb.EntityBean
*/
public void ejbStore() throws EJBException {
try {
// moved the check for persistentAttributeModified
// to storeProcess because of paProcessData handling
storeProcess();
persistentAttributeModified = false;
} catch (SQLException ex) {
throw new EJBException(ex);
} catch (IOException ex) {
throw new EJBException(ex);
}
}
/*********************** Database Routines *************************/
/**
* Helper class for Hibernate.
*/
public static class DAO {
private long dbId;
private String name;
private int priority;
private String description;
private String state;
private Date createTime;
private Date lastStateTime;
private String managerName;
/**
* Create a new instance with all attributes initialized
* to defaults or the given values.
*
*/
public DAO() {
}
/**
* @return Returns the dbId.
*/
public long getDbId() {
return dbId;
}
/**
* @param dbId The dbId to set.
*/
public void setDbId(long dbId) {
this.dbId = dbId;
}
/**
* @return Returns the name.
*/
public String getName() {
return name;
}
/**
* @param name The name to set.
*/
public void setName(String name) {
this.name = name;
}
/**
* @return Returns the priority.
*/
public int getPriority() {
return priority;
}
/**
* @param priority The priority to set.
*/
public void setPriority(int priority) {
this.priority = priority;
}
/**
* @return Returns the description.
*/
public String getDescription() {
return description;
}
/**
* @param description The description to set.
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return Returns the state.
*/
public String getState() {
return state;
}
/**
* @param state The state to set.
*/
public void setState(String state) {
this.state = state;
}
/**
* @return Returns the createTime.
*/
public Date getCreateTime() {
return createTime;
}
/**
* @param createTime The createTime to set.
*/
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
/**
* @return Returns the lastStateTime.
*/
public Date getLastStateTime() {
return lastStateTime;
}
/**
* @param lastStateTime The lastStateTime to set.
*/
public void setLastStateTime(Date lastStateTime) {
this.lastStateTime = lastStateTime;
}
/**
* @return Returns the managerName.
*/
public String getManagerName() {
return managerName;
}
/**
* @param managerName The managerName to set.
*/
public void setManagerName(String managerName) {
this.managerName = managerName;
}
}
/**
* This method checks if the process exists.
* @param primaryKey key to check
* @return If it is ok, it returns true.
*/
private boolean selectByPrimaryKey(Long primaryKey) throws SQLException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
String selectStatement
= "SELECT DBId FROM process WHERE DBId = ? ";
prepStmt = con.prepareStatement(selectStatement);
prepStmt.setLong(1, primaryKey.longValue());
rs = prepStmt.executeQuery();
boolean result = rs.next();
return result;
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
}
/**
* This method retrieves an activity's container
* @param key activity key
* @return primary key of process
*/
private Long selectByActivityKey(Long key) throws SQLException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
prepStmt = con.prepareStatement
("SELECT ProcessDBId FROM Activity WHERE DBId = ? ");
prepStmt.setLong(1, key.longValue());
rs = prepStmt.executeQuery();
if (! rs.next()) {
return null;
}
return new Long(rs.getLong(1));
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
}
private Collection selectByProcessMgrName(String processMgrName)
throws SQLException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
Collection processes = new ArrayList();
con = ds.getConnection();
String selectStatement = "SELECT dbid FROM process "
+ " WHERE processmgr = ? "
+ " ORDER BY dbid ";
prepStmt = con.prepareStatement(selectStatement);
prepStmt.setString(1, processMgrName);
rs = prepStmt.executeQuery();
while (rs.next()) {
processes.add(new Long(rs.getLong(1)));
}
return processes;
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
}
private Collection selectWithoutDefinitionByProcessMgrName
(String processMgrName)
throws SQLException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
Collection processes = new ArrayList();
con = ds.getConnection();
String selectStatement = "SELECT dbid FROM process "
+ " WHERE processmgr = ? AND xpdl IS NULL "
+ " ORDER BY dbid ";
prepStmt = con.prepareStatement(selectStatement);
prepStmt.setString(1, processMgrName);
rs = prepStmt.executeQuery();
while (rs.next()) {
processes.add(new Long(rs.getLong(1)));
}
return processes;
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
}
private Collection selectByRequesterHash(int hash) throws SQLException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
Collection processes = new ArrayList();
con = ds.getConnection();
prepStmt = con.prepareStatement
("SELECT dbid FROM process WHERE ReqHash = ? ORDER BY dbid");
prepStmt.setInt(1, hash);
rs = prepStmt.executeQuery();
while (rs.next()) {
processes.add(new Long(rs.getLong(1)));
}
return processes;
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
}
private Collection selectAll() throws SQLException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
Collection processes = new ArrayList();
con = ds.getConnection();
String selectStatement = "SELECT dbid FROM process "
+ " ORDER BY dbid ";
prepStmt = con.prepareStatement(selectStatement);
rs = prepStmt.executeQuery();
while (rs.next()) {
processes.add(new Long(rs.getLong(1)));
}
return processes;
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
}
/**
* Insert a new process info into the database.
*/
private void insertProcess()
throws SQLException, IOException {
Connection con = null;
UniversalPrepStmt prepStmt = null;
try {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con, "INSERT INTO process ("
+ "DBId, PackageId, ProcessId, XPDLREF, Name, State, "
+ "StateTime, Priority"
+ (paRequester != null
? ", Requester, ReqHash" : "")
+ ", Creator, ProcessMgr, "
+ "MgrVersion, CreateTime, Description, Flags) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?"
+ (paRequester != null ? ", ?, ?" : "")
+ ", ?, ?, ?, ?, ?, ?)");
int offset = 1;
Long procKey = (Long)ctx.getPrimaryKey();
prepStmt.setLong(offset++, procKey.longValue());
prepStmt.setString(offset++, packageId);
prepStmt.setString(offset++, processId);
prepStmt.setLong(offset++, xpdlRef);
prepStmt.setString(offset++, paName);
prepStmt.setString(offset++, paTypedState.toString());
prepStmt.setTimestamp
(offset++, new Timestamp (paLastStateTime.getTime()));
prepStmt.setInt(offset++, paPriority.toInt());
if (paRequester != null) {
prepStmt.setBinary(offset++, paRequester);
prepStmt.setInt (offset++, paRequester.hashCode ());
}
prepStmt.setString(offset++, processCreator);
prepStmt.setString(offset++, paProcessMgrName);
prepStmt.setString(offset++, paProcessMgrVersion);
prepStmt.setTimestamp
(offset++, new Timestamp (paCreateTime.getTime()));
prepStmt.setString(offset++, paDescription);
prepStmt.setInt(offset++, paFlags);
prepStmt.executeUpdate();
((ProcessDataStore)paProcessData).setConnection (con);
try {
((PersistentMap)paProcessData).store();
} catch (PersistentMapSQLException e) {
throw (SQLException)e.getCause();
}
for (Iterator i = CollectionsUtil.modifiedEntries(paBlockDeadlines)
.iterator (); i.hasNext ();) {
Long key = (Long)i.next ();
updateDeadlines (ds, con, (Long)ctx.getPrimaryKey(), key,
(List)paBlockDeadlines.get(key), false);
}
} catch (RemoteException e) {
throw new EJBException (e);
} catch (NumberFormatException e) {
throw new IOException ("Activity key not a long:" + e.getMessage());
} finally {
((ProcessDataStore)paProcessData).setConnection (null);
JDBCUtil.closeAll (null, prepStmt, con);
}
}
/**
* Store process info in the database. The
* transitions of a process does not need
* to be stored.
*/
private void storeProcess() throws SQLException, IOException {
Connection con = null;
UniversalPrepStmt prepStmt = null;
try {
if (persistentAttributeModified) {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con, "UPDATE process SET Name = ?, State = ?, "
+ "StateTime = ?, Priority = ?, Flags = ?"
+ (paDescriptionModified ? ", Description = ? " : "")
+ (myProcDef != null ? ", Xpdl = ? " : "")
+ " WHERE DBId = ? ");
int offset = 1;
prepStmt.setString(offset++, paName);
prepStmt.setString(offset++, paTypedState.toString());
prepStmt.setTimestamp
(offset++, new Timestamp (paLastStateTime.getTime()));
prepStmt.setInt(offset++, paPriority.toInt());
prepStmt.setInt(offset++, paFlags);
if (paDescriptionModified) {
prepStmt.setString(offset++, paDescription);
paDescriptionModified = false;
}
if (myProcDef != null) {
prepStmt.setLargeString(offset++, myProcDef);
}
// for where clause
prepStmt.setLong
(offset++, ((Long)ctx.getPrimaryKey()).longValue());
int rowCount = prepStmt.executeUpdate();
myProcDef = null;
if (rowCount == 0) {
throw new NoSuchEntityException
("Storing row for DBId "
+ (Long)ctx.getPrimaryKey() + " failed.");
}
}
if (((ProcessDataStore)paProcessData).isModified ()) {
if (con == null) {
con = ds.getConnection();
}
((ProcessDataStore)paProcessData).setConnection (con);
try {
((PersistentMap)paProcessData).store();
} catch (PersistentMapSQLException e) {
throw (SQLException)e.getCause();
}
}
if (CollectionsUtil.hasBeenModified(paBlockDeadlines)) {
if (con == null) {
con = ds.getConnection();
}
for (Iterator i = CollectionsUtil
.modifiedEntries(paBlockDeadlines).iterator ();
i.hasNext ();) {
Long key = (Long)i.next ();
updateDeadlines (ds, con, (Long)ctx.getPrimaryKey(), key,
(List)paBlockDeadlines.get(key), true);
}
}
if (updatedTransitions.size() > 0) {
if (con == null) {
con = ds.getConnection();
}
updateTransitions (con);
}
} catch (RemoteException re) {
throw new EJBException (re);
} finally {
((ProcessDataStore)paProcessData).setConnection (null);
JDBCUtil.closeAll (null, prepStmt, con);
}
// transitions does not need to be stored
}
/**
* Load process info from the database. Loads also
* the transitions of the process.
* After sucessful loading, the process attribute structure
* is set with the values just loaded.
*/
private void loadProcess()
throws SQLException, FinderException, IOException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
if (haveSelForUp == null) {
haveSelForUp = new Boolean(JDBCUtil.dbProperties(ds, con)
.supportsSelectForUpdate ());
}
prepStmt = con.prepareStatement
("SELECT PackageId, ProcessId, Name, State, StateTime, "
+ "Priority, Requester, Creator, ProcessMgr, MgrVersion, "
+ "CreateTime, Description, Flags, XpdlRef "
+ "FROM process WHERE DBId = ? "
+ (haveSelForUp.booleanValue() ? " FOR UPDATE" : ""));
Long procKey = (Long)ctx.getPrimaryKey();
prepStmt.setLong(1, procKey.longValue());
rs = prepStmt.executeQuery();
if (!rs.next()) {
throw new NoSuchEntityException
("Row for id " + (Long)ctx.getPrimaryKey()
+ " not found in database.");
}
// read process from db
int offset = 1;
packageId = rs.getString(offset++);
processId = rs.getString(offset++);
paName = rs.getString(offset++);
paTypedState = State.fromString(rs.getString(offset++));
paLastStateTime = rs.getTimestamp(offset++);
paPriority = Priority.fromInt(rs.getInt(offset++));
paRequester = (WfRequester)JDBCUtil.getBinary(rs, offset++);
processCreator = rs.getString(offset++);
paProcessMgrName = rs.getString(offset++);
paProcessMgrVersion = rs.getString(offset++);
paCreateTime = rs.getTimestamp(offset++);
paDescription = rs.getString(offset++);
paDescriptionModified = false;
paFlags = rs.getInt(offset++);
xpdlRef = JDBCUtil.getLong(rs, offset++);
((ProcessDataStore)paProcessData).setConnection (con);
try {
((PersistentMap)paProcessData).load ();
} catch (PersistentMapSQLException e) {
throw (SQLException)e.getCause();
}
} catch (InvalidPriorityException ipe){
logger.error (ipe.getMessage (), ipe);
throw new SQLException(ipe.getMessage ());
} catch (InvalidStateException ise) {
logger.error (ise.getMessage (), ise);
throw new SQLException(ise.getMessage ());
} catch (ClassNotFoundException e) {
logger.error (e.getMessage (), e);
throw new SQLException(e.getMessage ());
} finally {
((ProcessDataStore)paProcessData).setConnection (null);
JDBCUtil.closeAll (rs, prepStmt, con);
}
}
/**
* Returns the process definition of this process.
* @return the definition for this process in Document.
*/
private String loadProcDef() throws SQLException, NamingException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
String selectStatement
= "SELECT Xpdl FROM process WHERE DBId = ? ";
prepStmt = con.prepareStatement(selectStatement);
prepStmt.setLong(1, ((Long)ctx.getPrimaryKey()).longValue());
rs = prepStmt.executeQuery();
if (!rs.next()) {
throw new NoSuchEntityException
("No process with DBId = " + ctx.getPrimaryKey());
}
return JDBCUtil.getString(ds, rs, 1);
} catch (IOException e) {
throw new EJBException (e);
} finally {
try {
JDBCUtil.closeAll (rs, prepStmt, con);
} catch (SQLException e) {
logger.warn (e.getMessage(), e);
}
}
}
/**
* Remove process info from the database.
*/
private void removeProcess()
throws SQLException, IOException, RemoveException {
Connection con = null;
PreparedStatement prepStmt = null;
try {
removeActivities();
long procId = ((Long)ctx.getPrimaryKey()).longValue();
con = ds.getConnection();
removeTransitions(con, procId);
((ProcessDataStore)paProcessData).setConnection (con);
paProcessData.clear();
try {
((PersistentMap)paProcessData).store();
} catch (PersistentMapSQLException e) {
throw (SQLException)e.getCause();
}
String deleteStatement = "DELETE FROM process WHERE DBId = ? ";
prepStmt = con.prepareStatement(deleteStatement);
prepStmt.setLong(1, procId);
prepStmt.executeUpdate();
prepStmt.close ();
prepStmt = null;
prepStmt = new UniversalPrepStmt
(ds, con, "DELETE FROM Deadline WHERE Process = ?");
prepStmt.setLong(1, ((Long)ctx.getPrimaryKey()).longValue());
prepStmt.executeUpdate();
prepStmt.close ();
prepStmt = null;
prepStmt = new UniversalPrepStmt
(ds, con, "DELETE FROM SimpleAppl WHERE ProcessKey = ?");
prepStmt.setString(1, ((Long)ctx.getPrimaryKey()).toString());
prepStmt.executeUpdate();
if (xpdlRef != null) {
processDefinitionDirectoryLocal()
.removeXpdlIfOrphaned(xpdlRef.longValue());
}
} finally {
((ProcessDataStore)paProcessData).setConnection (null);
JDBCUtil.closeAll (null, prepStmt, con);
}
}
/**
* Remove transitions info as associated to the process from the database.
* @param procKey the process key for to remove the transitions
* @throws SQLException in case of any SQL errors
* @throws IOException in case of any IO errors
*/
private void removeTransitions(Connection con, long procKey)
throws SQLException, IOException {
PreparedStatement prepStmt = null;
try {
String deleteStatement = "DELETE FROM transition "
+ "WHERE ProcessDBId = ? ";
prepStmt = con.prepareStatement(deleteStatement);
prepStmt.setLong(1, procKey);
prepStmt.executeUpdate();
} finally {
JDBCUtil.closeAll (null, prepStmt, null);
}
}
/**
* Remove activities as associated to the process.
* @param procKey the process key for to remove the transitions
* @throws SQLException in case of any SQL errors
* @throws IOException in case of any IO errors
*/
private void removeActivities() throws RemoveException, RemoteException {
// remove the activities from the system
for (Iterator i = activitiesLocal().iterator(); i.hasNext();){
EJBLocalObject activity = ((EJBLocalObject)i.next());
activity.remove();
}
activityMapCache = null;
}
/**
* Load the deadlines for the activity with the given primary key
* from the given datasource.<P>
*
* This is provided as a static package visible method as it is
* used by <code>WfActivityEJB</code> as well. The deadline table
* is considered a shared resource. As a process and its
* activities operate on a distinct set of deadlines, there is no
* need for activities to access the deadlines via the process
* (and we can save the overhead).
*
* @param ds the data source to use
* @param pk the key of the activity
* @return the list of deadlines
* @throws SQLException if an error occurs
* @throws IOException if an error occurs
*/
static List loadDeadlines(DataSource ds, Long pk)
throws SQLException, IOException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
List res = new ArrayList ();
con = ds.getConnection();
String selectStatement ="SELECT "
+ "DeadlineCond, ExceptionName, Execution "
+ "FROM Deadline WHERE Activity = ? ORDER BY Entry";
prepStmt = con.prepareStatement(selectStatement);
prepStmt.setLong(1, pk.longValue());
rs = prepStmt.executeQuery();
while (rs.next()) {
res.add (new Deadline
(rs.getInt (3), JDBCUtil.getString(ds, rs, 1),
rs.getString (2)));
}
return res;
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
}
/**
* Updates the deadlines for the activity with the given primary key
* using the given connection.<P>
*
* This is provided as a static package visible method as it is
* used by <code>WfActivityEJB</code> as well. The deadline table
* is considered a shared resource. As a process and its
* activities operate on a distinct set of deadlines, there is no
* need for activities to access the deadlines via the process
* (and we can save the overhead).
*
* @param ds the data source
* @param con the connection to the database
* @param pk the key of the process
* @param ak the key of the activity
* @param dls the list of deadlines
* @param doDelete if old values should be deleted (should only be
* set to <code>false</code> if updating the deadlines for a newly
* created activity
* @throws SQLException if an error occurs
* @throws IOException if an error occurs
*/
public static void updateDeadlines
(DataSource ds, Connection con,
Long pk, Long ak, List dls, boolean doDelete)
throws SQLException, IOException {
UniversalPrepStmt prepStmt = null;
try {
if (doDelete) {
prepStmt = new UniversalPrepStmt
(ds, con, "DELETE FROM Deadline WHERE Activity = ?");
prepStmt.setLong(1, ak.longValue());
prepStmt.executeUpdate();
prepStmt.close ();
prepStmt = null;
}
if (dls != null && dls.size () > 0) {
prepStmt = new UniversalPrepStmt
(ds, con, "INSERT INTO Deadline (Process, Activity, Entry, "
+ "DeadlineCond, ExceptionName, Execution, State) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?)",
new String[] {"Activity", "Entry"});
int entry = 0;
for (Iterator i = dls.iterator(); i.hasNext();) {
Deadline dl = (Deadline)i.next ();
int offset = 1;
prepStmt.setLong(offset++, pk.longValue());
prepStmt.setLong(offset++, ak.longValue());
prepStmt.setInt(offset++, entry++);
prepStmt.setLargeString(offset++, dl.getCondition());
prepStmt.setString (offset++, dl.getExceptionName());
prepStmt.setInt(offset++, dl.getExecution());
prepStmt.setInt(offset++, dl.getState());
prepStmt.executeUpdate();
}
}
} finally {
JDBCUtil.closeAll (null, prepStmt, null);
}
}
//
// Domain methods and helpers
//
/**
* Indicates if some other object is equal to this one. <P>
*
* Note that <code>obj</code> must implement the process's
* remote interface. Comparing a domain object usually
* indicates some violation of the constraints of our
* implementation environment.
*
* @param obj the object to compare with.
* @return <code>true</code> if the other object is equal.
*/
public boolean equals (Object obj) {
try {
return ctx.getPrimaryKey().toString()
.equals (((WfProcess)obj).key());
} catch (RemoteException e) {
throw new EJBException (e);
}
}
/**
* Return the hash code.<P>
* @return the result.
*/
public int hashCode () {
return ctx.getPrimaryKey().toString().hashCode();
}
/**
* Starts a process.
* @throws CannotStartException when the process cannot be started (e.g.,
* because it is not properly initialized).
* @throws AlreadyRunningException when the process has already been
* started.
* @ejb.interface-method view-type="remote"
*/
public void start()
throws CannotStartException, AlreadyRunningException {
super.start ();
}
/**
* Returns a collection of activities of the process.
* @return collection of activities of the process
*/
public Collection steps() {
// HashMap.values() returns a Collection implementation that
// is not serializable. Besides we really do not need to
// transfer the whole map, so let's create a new
// collection-container.
try {
return new ArrayList (activityMap().values());
} catch (RemoteException e) {
throw new EJBException(e);
}
}
/**
* Returns a collection of activities of the process.
* @return collection of activities of the process
*/
public Collection stepsLocal() {
// HashMap.values() returns a Collection implementation that
// is not serializable. Besides we really do not need to
// transfer the whole map, so let's create a new
// collection-container.
return new ArrayList (activitiesLocal());
}
/**
* Retrieve the base event information about a requesting activity.
* @param req the requester.
* @return the base info using an audit event as DTO.
*/
protected WfAuditEvent activityRequesterInfo(WfRequester req) {
if (!(req instanceof ExtActivity)) {
return null;
}
try {
ExtActivity act = (ExtActivity)req;
return act.auditEventBase();
} catch (RemoteException e) {
throw new EJBException (e);
}
}
/**
* Set a new name of the process. The change must be forwarded
* to the activity EJBs as they cache the name.
* @param newValue new name.
*/
public void setName (String newValue) {
super.setName(newValue);
for (Iterator i = activitiesLocal().iterator(); i.hasNext();) {
ExtActivityLocal a = (ExtActivityLocal)i.next();
a.updateProcessName (newValue);
}
}
/**
* Returns the process manager which created this process.
* @return the associated process manager.
*/
public WfProcessMgr manager () {
ProcessDefinition def = getPaProcessDef();
try {
return new ProcessMgrStub
(def.packageId(), def.processId(), def.mgrName(), key(),
processDefinitionDirectoryHome(),
(WfProcessHome)ctx.getEJBHome());
} catch (ResourceNotAvailableException e) {
throw new EJBException (e);
}
}
/**
* Factory method that create new persistent objects of type
* <code>WfActivity</code>. Must be implement by the persistence
* layer.
*
* @param blockActId if the activity is part of a block activity,
* else <code>null</code>
* @param priority a <code>Priority</code> value
* @param name the activity's name
* @param description activity description
* @param startMode the start mode
* @param finishMode the finish mode
* @param joinMode the join mode
* @param splitMode the split mode
* @param implementation the implementation description
* @param performer the performer
* @param deadlines the deadlines
* @param deferChoiceOnSplit if true, choice is to be deferred
* @param auditEventSelection the audit event selection
* @param storeAuditEvents if true, audit events are stored in the
* database
* @return the created activity.
*/
protected de.danet.an.workflow.localcoreapi.WfActivityLocal
createActivity
(String blockActId, Priority priority, String name, String description,
StartFinishMode startMode, StartFinishMode finishMode,
JoinAndSplitMode joinMode, JoinAndSplitMode splitMode,
Implementation[] implementation, String performer, List deadlines,
boolean deferChoiceOnSplit, int auditEventSelection,
boolean storeAuditEvents) {
try {
ExtActivityLocal act = activityLocalHome().create
((WfProcessLocal)ctx.getEJBLocalObject(),
(Long)ctx.getPrimaryKey(), auditEventBase(),
blockActId == null ? null : new Long (blockActId),
priority, name, description, startMode, finishMode,
joinMode, splitMode, implementation, performer, deadlines,
deferChoiceOnSplit, auditEventSelection,
storeAuditEvents);
return cacheActivityLocal (act);
} catch (CreateException ce) {
logger.error (ce.getMessage (), ce);
throw new IllegalStateException (ce.getMessage());
} catch (RemoteException re) {
logger.error (re.getMessage (), re);
throw new IllegalStateException (re.getMessage());
}
}
/**
* Provide a new unique activity key.
*
* @return the key.
*/
protected Long createActivityKey () {
try {
return new Long(EJBUtil.newPrimaryKey("activity"));
} catch (ResourceNotAvailableException e) {
throw new EJBException (e);
}
}
/**
* Create a new transition with given id, from-activity,
* to-activity.
* @param id the transition id
* @param group the transition group id
* @param order the transition priority
* @param fromAct the from Activity
* @param toAct the to Activity
* @param condType type of the condition
* @param condition condition of this transition
* @return the created transition.
*/
protected TransitionLocal doCreateTransition
(String id, String group, int order,
ActivityLocal fromAct, ActivityLocal toAct,
int condType, String condition) {
TransitionLocal trans = new InternalTransitionDefinition
(this, ((Long)ctx.getPrimaryKey()).toString(),
id, group, order, fromAct, toAct, condType, condition, false);
transitionsLocalCache.add(trans);
String fromKey = fromAct.key();
if (!transLocalByFrom.containsKey(fromKey)) {
transLocalByFrom.put(fromKey, new ArrayList());
}
((List)transLocalByFrom.get(fromKey)).add(trans);
return trans;
}
/**
* Gets a list of transitions for this process.
* @return list of transitions for this process
*/
public List transitions() {
if (transitionsCache == null) {
List ts = new ArrayList ();
for (Iterator i = transitionsLocal().iterator(); i.hasNext();) {
TransitionDefinitionLocal def
= (TransitionDefinitionLocal)i.next();
ts.add (new TransitionDefinition(def));
}
transitionsCache = ts;
}
return transitionsCache;
}
/**
* Gets a list of transitions for this process.
* The method is public, because the transition manager needs
* access to the transitions of a process.
* @return list of transitions for this process
*/
public List transitionsLocal() {
if (transitionsLocalCache == null) {
try {
loadTransitions();
} catch (RemoteException e) {
throw new EJBException (e);
} catch (SQLException e) {
throw new EJBException(e);
}
}
return transitionsLocalCache;
}
/**
* Gets a list of transitions for this process that start
* at the given activity.
* @return list of transitions for this process
*/
public List transitionsLocalFrom(String fromActivityKey) {
if (transLocalByFrom == null) {
try {
loadTransitions();
} catch (RemoteException e) {
throw new EJBException (e);
} catch (SQLException e) {
throw new EJBException(e);
}
}
List res = (List)transLocalByFrom.get(fromActivityKey);
if (res == null) {
res = new ArrayList();
}
return res;
}
/**
* Return the process definition directory.
* While there is no immediate relation between a process
* instance and the known process definitions, the definitions
* nevertheless make up part of the environment of a process.
* Specifically, this method is needed to start a sub-process
* in an activity.
* @return the process definition directory
*/
public ProcessDefinitionDirectoryLocal processDefinitionDirectoryLocal () {
try {
return getProcessDefinitionDirectoryLocal ();
} catch (RemoteException e) {
throw new EJBException (e);
} catch (NamingException e) {
throw new EJBException (e);
} catch (CreateException e) {
throw new EJBException (e);
}
}
/**
* Returns the {@link Activity <code>Activity</code>} with the given key.
* The OMG interface only defines a {@link WfProcess#steps
* method for listing all the activities} associated with the process.
* While, of course, one could select the activity with a certain key
* from that list, this would be rather insufficient.
* @param key the
* {@link de.danet.an.workflow.omgcore.WfActivity#key key} of the process.
* @return the activity associated with the key.
* @throws InvalidKeyException if no activity with the given key
* exists.
*/
public Activity activityByKey (String key)
throws InvalidKeyException {
try {
Activity act = (Activity)activityMap().get(key);
if (act != null) {
return act;
}
} catch (RemoteException e) {
throw new EJBException(e);
}
throw new InvalidKeyException ("Key " + key + " is invalid.");
}
/**
* Returns the {@link Activity <code>ActivityLocal</code>} with the given
* key. The OMG interface only defines a {@link WfProcess#steps
* method for listing all the activities} associated with the process.
* While, of course, one could select the activity with a certain key
* from that list, this would be rather insufficient.
* @param key the
* {@link de.danet.an.workflow.omgcore.WfActivity#key key} of the process.
* @return the activity associated with the key.
* @throws InvalidKeyException if no activity with the given key
* exists.
*/
public ActivityLocal activityByKeyLocal (String key)
throws InvalidKeyException {
ActivityLocal act = (ActivityLocal)activityLocalMap().get(key);
if (act != null) {
return act;
}
throw new InvalidKeyException ("Key " + key + " is invalid.");
}
/**
* Loads the transitions of the process from the db.
* @param con db connection
* @return list of transitions for this process.
*/
private void loadTransitions()
throws SQLException, RemoteException {
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
String selectStatement ="SELECT "
+ "TransitionId, FromActivity, ToActivity, TransGroup, "
+ "TransOrder, CondType, TransCond, Flags "
+ "FROM transition WHERE ProcessDBId = ? "
+ "ORDER BY TransOrder ";
prepStmt = con.prepareStatement(selectStatement);
Long pk = (Long)ctx.getPrimaryKey();
prepStmt.setLong(1, pk.longValue());
rs = prepStmt.executeQuery();
transitionsLocalCache = new ArrayList();
transLocalByFrom = new HashMap ();
String pks = pk.toString();
while (rs.next()) {
String id = rs.getString(1);
Long from = new Long(rs.getLong(2));
Long to = new Long(rs.getLong(3));
String group = rs.getString(4);
int order = rs.getInt(5);
int condType = rs.getInt(6);
String condition = JDBCUtil.getString(ds, rs, 7);
int flags = rs.getInt (8);
// add transition
InternalTransitionDefinition transDef
= new InternalTransitionDefinition
(this, pks, id, group, order,
from.longValue() < 0
? blockActivityRepresentation (from.toString())
: activityByKeyLocal(from.toString()),
activityByKeyLocal(to.toString()), condType, condition,
(flags & FLAGS_HAS_PENDING_TOKEN) != 0);
transitionsLocalCache.add (transDef);
if (!transLocalByFrom.containsKey(from.toString())) {
transLocalByFrom.put(from.toString(), new ArrayList());
}
((List)transLocalByFrom.get(from.toString())).add(transDef);
}
} catch (IOException e) {
throw new EJBException (e);
} catch (InvalidKeyException e) {
throw new EJBException (e);
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
}
/**
* insert the transitions into the db.
*/
private void insertTransitions() throws SQLException {
Connection con = null;
UniversalPrepStmt prepStmt = null;
try {
con = ds.getConnection();
Iterator it = transitionsLocalCache.iterator();
prepStmt = new UniversalPrepStmt
(ds, con, "INSERT INTO transition ("
+ "TransitionId, ProcessDBId, FromActivity, ToActivity, "
+ "TransGroup, TransOrder, CondType, TransCond) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
while (it.hasNext()) {
TransitionLocal t = (TransitionLocal)it.next();
TransitionDefinitionLocal td = (TransitionDefinitionLocal)t;
try {
int offset = 1;
prepStmt.setString(offset++, t.id());
prepStmt.setLong(offset++,
((Long)ctx.getPrimaryKey()).longValue());
prepStmt.setLong
(offset++, Long.parseLong(t.from().key()));
prepStmt.setLong
(offset++, Long.parseLong(t.to().key()));
prepStmt.setString(offset++, td.group());
prepStmt.setInt(offset++, td.order());
prepStmt.setInt(offset++, td.conditionType());
prepStmt.setString(offset++, td.condition());
} catch (NumberFormatException re) {
throw new SQLException(re.getMessage());
}
prepStmt.executeUpdate();
}
} finally {
try {
JDBCUtil.closeAll (null, prepStmt, con);
} catch (SQLException e) {
logger.error("Error while closing: " + e.getMessage (), e);
}
}
}
private void updateTransitions (Connection con)
throws SQLException, RemoteException {
UniversalPrepStmt prepStmt = null;
try {
Iterator it = updatedTransitions.iterator();
prepStmt = new UniversalPrepStmt
(ds, con, "UPDATE transition SET Flags = ?"
+ " WHERE ProcessDBId = ? AND TransitionId = ?");
while (it.hasNext()) {
ExtTransitionLocal t = (ExtTransitionLocal)it.next();
if (logger.isDebugEnabled ()) {
logger.debug ("Storing " + t);
}
int offset = 1;
prepStmt.setInt(offset++,
((t.hasPendingToken() ? FLAGS_HAS_PENDING_TOKEN : 0)));
prepStmt.setLong(offset++,
((Long)ctx.getPrimaryKey()).longValue());
prepStmt.setString(offset++, t.id());
prepStmt.executeUpdate();
}
updatedTransitions.clear();
} finally {
try {
JDBCUtil.closeAll (null, prepStmt, null);
} catch (SQLException e) {
logger.error("Error while closing: " + e.getMessage (), e);
}
}
}
/**
* Notify about the change of a transition.
* @param transition the changed transition
*/
public void transitionUpdated (ExtTransitionLocal transition) {
updatedTransitions.add (transition);
}
/**
* Return the remote version of this object. This is the client side
* implementation of the remote interface (i.e. the EJB object).
*
* @return the remote stub.
*/
public Process toProcess () {
return (Process)ctx.getEJBObject();
}
/* (non-Javadoc)
* Comment copied from interface or superclass.
*/
public ExtProcessLocal toProcessLocal() {
ExtProcessLocal procLocal = (ExtProcessLocal)ctx.getEJBLocalObject();
return procLocal;
}
/**
* Remove this process.
* @throws CannotRemoveException if the process is still in progress
*/
protected void removeThis () throws CannotRemoveException {
try {
ctx.getEJBObject().remove();
} catch (RemoteException e) {
throw new EJBException (e);
} catch (RemoveException e) {
// the process being still in process is the only
// prossible cause
throw new CannotRemoveException (e.getMessage ());
}
}
/**
* Return an arbitrary activity with the given key. The activity
* need not belong to this process.
* @param key activity key
* @return the activity
* @throws InvalidKeyException if the activity cannot be found
*/
protected ExtActivityLocal lookupActivityLocal (String key)
throws InvalidKeyException {
// Search via process to get process into transaction first
try {
ProcessLocal proc = ((WfProcessLocalHome)ctx.getEJBLocalHome())
.findByActivityKey (key);
return (ExtActivityLocal)proc.activityByKeyLocal(key);
} catch (FinderException e) {
logger.debug (e.getMessage (), e);
throw new InvalidKeyException (e.getMessage());
}
}
/**
* Return the Principal that created this process.
* @return the principal.
* @ejb.interface-method
* view-type="remote"
*/
public Principal processCreator () {
return new SimplePrincipal (processCreator);
}
/**
* Returns an audit event object with process relevant information
* initialized.
* @return the process related information.
* @ejb.interface-method
* view-type="remote"
*/
public WfAuditEvent auditEventBase() {
return auditEventBase(null);
}
/**
* Process newly generated event.
* @param evt Event
*/
protected void fireAuditEvent(WfAuditEvent evt) {
if (delayedEvents != null) {
delayedEvents.add (evt);
} else {
try {
QueuerUtils.queue
(queueConnectionFactory(), eventQueue(),
(DefaultAuditEvent)evt);
} catch (RemoteException e) {
throw new EJBException (e);
}
}
}
/**
* Returns a collection of <code>WfAuditEvent</code>s associated with
* this process describing its history.
* @return the collection of audit events
* @throws HistoryNotAvailableException in case the history is not
* available for any reason
*/
public Collection history()
throws HistoryNotAvailableException {
try {
return engineLocal().history (toProcess());
} catch (RemoteException rex){
throw new EJBException (rex);
}
}
/**
* Deliver a message on the given channel to a receiver tool
* listening on that channel.
*
* @param channel the channel name
* @param message the message
* @return <code>true</code> if the mesage could be delivered,
* <code>false</code> if no activity was listening
* @throws InvalidDataException if the message contains invalid data
* @ejb.interface-method view-type="remote"
*/
public boolean deliverChannelMessage (String channel, Map message)
throws InvalidDataException {
ActivityLocal act = null;
try {
act = activityLocalHome().findByWaitingOn
((Long)ctx.getPrimaryKey(), channel);
if (logger.isDebugEnabled ()) {
logger.debug
("Delivering message " + CollectionsUtil.toString(message)
+ " from channel " + channel
+ " to waiting activity " + act);
}
try {
act.setResult (new DefaultProcessData (message));
act.complete ();
} catch (CannotCompleteException e) {
logger.error
("Activity " + act + " listening on channel "
+ "but cannot be completed?!: " + e.getMessage (), e);
throw new IllegalStateException
("Receiving activity cannot be completed");
}
return true;
} catch (ObjectNotFoundException e) {
return false;
} catch (RemoteException rex){
throw new EJBException (rex);
} catch (FinderException e) {
throw new EJBException (e);
}
}
/**
* Deliver a message on the given channel to a receiver tool
* listening on that channel. If no tool is listening, store the
* message.
*
* @param channel the channel name
* @param message the message
* @throws InvalidDataException if the message contains invalid data
* @ejb.interface-method view-type="remote"
*/
public void submitChannelMessage (String channel, Map message)
throws InvalidDataException {
if (! deliverChannelMessage (channel, message)) {
if (logger.isDebugEnabled ()) {
logger.debug ("Putting message from channel " + channel
+ " on queue");
}
queueChannelMessage (channel, message);
}
}
/**
* Queue the given channel message.
*
* @param channel the channel
* @param message the message
*/
private void queueChannelMessage (String channel, Map message) {
QueueConnection qc = null;
QueueSession qs = null;
try {
qc = queueConnectionFactory().createQueueConnection();
qs = qc.createQueueSession (true, 0);
QueueSender snd = qs.createSender (channelInQueue());
snd.setDisableMessageID (true);
snd.setDisableMessageTimestamp (true);
ObjectMessage msg = qs.createObjectMessage((Serializable)message);
String processKey = ((Long)ctx.getPrimaryKey()).toString();
msg.setStringProperty ("processKey", processKey);
msg.setStringProperty ("channelName", channel);
snd.send (msg);
snd.close ();
} catch (JMSException e) {
throw new EJBException (e);
} catch (RemoteException e) {
throw new EJBException (e);
} finally {
QueuerUtils.release(qc, qs);
}
}
/**
* Looks for a message on the given channel and if found returns it.
*
* @param channel the channel name
* @return the message or <code>null</code>
* @ejb.interface-method view-type="remote"
*/
public Map lookForMessage (String channel) {
QueueConnection qc = null;
QueueSession qs = null;
try {
qc = queueConnectionFactory().createQueueConnection();
qc.start();
qs = qc.createQueueSession (true, 0);
QueueReceiver rec = qs.createReceiver
(channelInQueue(), "processKey = '"
+ ((Long)ctx.getPrimaryKey()).toString() + "'"
+ " AND channelName = '" + channel + "'");
Message msg = rec.receiveNoWait();
Map result = null;
if (msg != null && (msg instanceof ObjectMessage)) {
result = (Map)((ObjectMessage)msg).getObject();
}
rec.close ();
return result;
} catch (JMSException e) {
throw new EJBException (e);
} catch (ResourceNotAvailableException e) {
throw new EJBException (e);
} finally {
QueuerUtils.release(qc, qs);
}
}
/**
* Broadcast the given channel message.
*
* @param channel the channel
* @param message the message
* @ejb.interface-method view-type="remote"
*/
public void broadcastChannelMessage (String channel, Map data) {
TopicConnection tc = null;
TopicSession ts = null;
QueueConnection qc = null;
QueueSession qs = null;
try {
tc = topicConnectionFactory().createTopicConnection();
ts = tc.createTopicSession (true, 0);
TopicPublisher sndr = ts.createPublisher (channelOutTopic());
sndr.setDisableMessageID (true);
ObjectMessage msg
= ts.createObjectMessage ((Serializable)data);
String processKey = ((Long)ctx.getPrimaryKey()).toString();
msg.setStringProperty ("processKey", processKey);
msg.setStringProperty ("channelName", channel);
msg.setStringProperty ("messageType", "DATA");
sndr.publish(msg);
sndr.close();
// Now clean up in queue
qc = queueConnectionFactory().createQueueConnection();
qs = qc.createQueueSession (true, 0);
QueueReceiver rec = qs.createReceiver
(channelInQueue(), "processKey = '" + processKey + "'");
while (rec.receiveNoWait() != null) {
}
rec.close ();
} catch (JMSException e) {
throw new EJBException (e);
} catch (RemoteException e) {
throw new EJBException (e);
} finally {
QueuerUtils.release(tc, ts);
QueuerUtils.release(qc, qs);
}
}
/**
* Closes all channels.
*/
protected void closeChannels () {
TopicConnection tc = null;
TopicSession ts = null;
QueueConnection qc = null;
QueueSession qs = null;
try {
tc = topicConnectionFactory().createTopicConnection();
ts = tc.createTopicSession (true, 0);
TopicPublisher sndr = ts.createPublisher (channelOutTopic());
sndr.setDisableMessageID (true);
Message msg = ts.createMessage();
String processKey = ((Long)ctx.getPrimaryKey()).toString();
msg.setStringProperty ("processKey", processKey);
msg.setStringProperty ("messageType", "CLOSE_NOTIFICATION");
sndr.publish(msg);
sndr.close();
// Now clean up in queue
qc = queueConnectionFactory().createQueueConnection();
qs = qc.createQueueSession (true, 0);
QueueReceiver rec = qs.createReceiver
(channelInQueue(), "processKey = '" + processKey + "'");
while (rec.receiveNoWait() != null) {
}
rec.close ();
} catch (JMSException e) {
throw new EJBException (e);
} catch (ResourceNotAvailableException e) {
throw new EJBException (e);
} finally {
QueuerUtils.release(tc, ts);
QueuerUtils.release(qc, qs);
}
}
//
// Timers
//
/**
* Start a timer that will call <code>handleTimeout</code> at the
* given date with the given info.
* @param due target date
* @param info the context
*/
public void startTimer (Date due, Serializable info) {
// Adjust duration
long duration = due.getTime() - System.currentTimeMillis();
if (duration <= 0) {
logger.warn("Deadline with zero or negative duration ("
+ ((double)duration / 1000) + "s) for "
+ toString() + " will be triggered immediately.");
duration = 1;
}
if (ctx.getClass().getName().startsWith("org.jboss.ejb.")) {
// create a timer with interval to circumvent JBoss bug JBAS-2274
ctx.getTimerService().createTimer (duration, 3600 * 1000, info);
} else {
ctx.getTimerService().createTimer (duration, info);
}
}
/**
* Stop all timers that this object has created.
*/
public void stopTimers () {
for (Iterator i = ctx.getTimerService().getTimers().iterator ();
i.hasNext ();) {
Timer timer = (Timer)i.next();
timer.cancel ();
}
}
/**
* Handle the timeout of a timer.
* @param timer the timer that has expired.
*/
public void ejbTimeout (Timer timer) {
try {
if (logger.isDebugEnabled()) {
logger.debug ("Received timeout from " + timer
+ " for " + toString());
}
try {
handleTimeout (timer.getInfo());
if (ctx.getClass().getName().startsWith("org.jboss.ejb.")) {
// Because of JBoss bug JBAS-2274 we have an interval and
// must therefore cancel the timer
timer.cancel();
}
} catch (NoSuchObjectLocalException e) {
// JBoss may call this method with a canceled timer
if (timer.getClass().getName()
.equals("org.jboss.ejb.txtimer.TimerImpl")) {
logger.debug("Exception \"" + e.getMessage()
+ "\" ignored, caused by JBoss bug.");
return;
}
throw e;
}
} catch (EJBException e) {
throw e;
} catch (Throwable e) {
logger.error ("Problem handling timer event " + timer
+ "(discarded): " + e.getMessage(), e);
}
}
//
// Implement accessor methods for the persistent attributes
//
/**
* The getter method implementation for the persistent
* attribute <code>key</code>.
*
* @return the value of key.
*/
protected String getPaKey() {
return ((Long)ctx.getPrimaryKey()).toString();
}
/**
* The getter method implementation for the persistent
* attribute <code>createTime</code>.
*
* @return the value of createTime.
*/
protected Date getPaCreateTime() {
return paCreateTime;
}
/**
* The getter method implementation for the persistent
* attribute <code>name</code>.
*
* @see #setPaName
* @return the value of name.
*/
protected String getPaName() {
return paName;
}
/**
* The setter method implementation for the persistent
* attribute <code>name</code>.
*
* @param newName the new value of name.
* @see #getPaName
*/
protected void setPaName(String newName) {
paName = newName;
persistentAttributeModified = true;
}
/**
* The getter method implementation for the persistent
* attribute <code>description</code>.
*
* @see #setPaDescription
* @return the value of description.
*/
protected String getPaDescription() {
return paDescription;
}
/**
* The setter method implementation for the persistent
* attribute <code>description</code>.
*
* @param newDescription the new value of description.
* @see #getPaDescription
*/
protected void setPaDescription(String newDescription) {
paDescription = newDescription;
paDescriptionModified = true;
persistentAttributeModified = true;
}
/**
* The getter method implementation for the persistent
* attribute <code>priority</code>.
*
* @see #setPaPriority
* @return the value of priority.
*/
protected Priority getPaPriority() {
return paPriority;
}
/**
* The setter method implementation for the persistent
* attribute <code>priority</code>.
*
* @param newPriority the new value of priority.
* @see #getPaPriority
*/
protected void setPaPriority(Priority newPriority) {
paPriority = newPriority;
persistentAttributeModified = true;
}
/**
* The getter method implementation for the persistent
* attribute <code>typedState</code>.
*
* @see #setPaTypedState
* @return the value of typedState.
*/
protected State getPaTypedState() {
return paTypedState;
}
/**
* The getter method implementation for the persistent
* attribute <code>lastStateTime</code>.
*
* @see #setPaLastStateTime
* @return the value of lastStateTime.
*/
protected Date getPaLastStateTime() {
return paLastStateTime;
}
/**
* The setter method implementation for the persistent
* attribute <code>lastStateTime</code>.
*
* @param newLastStateTime the new value of lastStateTime.
* @see #getPaLastStateTime
*/
protected void setPaLastStateTime(Date newLastStateTime) {
paLastStateTime = newLastStateTime;
persistentAttributeModified = true;
}
/**
* The setter method implementation for the persistent
* attribute <code>typedState</code>.
*
* @param newTypedState the new value of typedState.
* @see #getPaTypedState
*/
protected void setPaTypedState(State newTypedState) {
paTypedState = newTypedState;
persistentAttributeModified = true;
}
/**
* The getter method implementation for the persistent
* attribute <code>processMgrName</code>.
*
* @see #setPaProcessMgrName
* @return the value of processMgrName.
*/
protected String getPaProcessMgrName() {
return paProcessMgrName;
}
/**
* The setter method implementation for the persistent
* attribute <code>processMgrName</code>.
*
* @param newProcessMgrName the new value of processMgrName.
* @see #getPaProcessMgrName
*/
protected void setPaProcessMgrName(String newProcessMgrName) {
paProcessMgrName = newProcessMgrName;
persistentAttributeModified = true;
}
/**
* The getter method implementation for the persistent
* attribute <code>processMgrVersion</code>.
*
* @see #setPaProcessMgrVersion
* @return the value of processMgrVersion.
*/
protected String getPaProcessMgrVersion() {
return paProcessMgrVersion;
}
/**
* The setter method implementation for the persistent
* attribute <code>processMgrVersion</code>.
*
* @param newProcessMgrVersion the new value of processMgrVersion.
* @see #getPaProcessMgrVersion
*/
protected void setPaProcessMgrVersion(String newProcessMgrVersion) {
paProcessMgrVersion = newProcessMgrVersion;
persistentAttributeModified = true;
}
/**
* The getter method implementation for the persistent
* attribute <code>requester</code>.
*
* @return the value of requester.
*/
protected WfRequester getPaRequester() {
return paRequester;
}
/**
* The setter method implementation for the persistent
* attribute <code>id</code>.
*
* @param newId the new value of process Id.
* @see #getPaId
*/
protected void setPaId(String newId) {
paId = newId;
persistentAttributeModified = true;
}
/**
* The getter method implementation for the persistent
* attribute <code>id</code>.
*
* @see #setPaId
* @return the value of process Id.
*/
protected String getPaId() {
return paId;
}
/**
* The getter method implementation for the persistent attribute
* <code>processDef</code>. Three fields in the database
* contribute to the persistence implementation for the process
* definition: PackageId, ProcessId and Xpdl. If the
* process definition for the process has not been changed
* specifically and if the process definition still exists in the
* process definition directory, then packageId and processId are
* used to lookup the process definition.<P>
*
* If the definition of the process has changed, e.g. by inserting
* an additional activity or the process definition has been
* removed from the process definition directory. the string in
* field xpdl is used to reconstruct a process definition.
*
* @see #setPaProcessDef
* @return the value of processDef.
*/
protected ProcessDefinition getPaProcessDef() {
if (paProcessDef != null) {
return paProcessDef;
}
if (paProcessDefSoft != null) {
ProcessDefinition pd = (ProcessDefinition)paProcessDefSoft.get();
if (pd != null) {
return pd;
}
}
ProcessDefinitionDirectoryLocal pdd = null;
try {
String def = loadProcDef();
if (def != null) {
ProcessDefinition pd = new DefaultProcessDefinition (def);
paProcessDefSoft = new SoftReference(pd);
return pd;
}
pdd = getProcessDefinitionDirectoryLocal();
if (xpdlRef != null) {
paProcessDef = pdd.lookupArchivedProcessDefinition
(xpdlRef.longValue());
return paProcessDef;
}
paProcessDef = pdd.lookupProcessDefinition (packageId, processId);
return paProcessDef;
} catch (SQLException sex) {
logger.error (sex.getMessage(), sex);
throw new ProviderException (sex.getMessage());
} catch (IOException ioe) {
logger.error (ioe.getMessage(), ioe);
throw new ProviderException (ioe.getMessage());
} catch (ImportException pe) {
logger.error (pe.getMessage(), pe);
throw new ProviderException (pe.getMessage());
} catch (NamingException nex) {
logger.error (nex.getMessage(), nex);
throw new ProviderException (nex.getMessage());
} catch (CreateException cex) {
logger.error (cex.getMessage(), cex);
throw new ProviderException (cex.getMessage());
} catch (InvalidKeyException kex) {
logger.error (kex.getMessage(), kex);
throw new IllegalStateException (kex.getMessage());
} finally {
EJBUtil.removeSession(pdd);
}
}
/**
* The setter method implementation for the persistent
* attribute <code>processDef</code>.
*
* @param newProcessDef the new value of processDef.
* @see #getPaProcessDef
*/
protected void setPaProcessDef(ProcessDefinition newProcessDef) {
if (callbackFromInitDuringCreate) {
paProcessDef = newProcessDef;
} else {
paProcessDef = null;
paProcessDefSoft = new SoftReference(newProcessDef);
myProcDef = newProcessDef.toXPDL();
}
persistentAttributeModified = true;
}
/**
* The getter method implementation for the persistent
* attribute <code>processData</code>.
*
* @return the value of processData.
*/
protected ProcessData getPaProcessData() {
return paProcessData;
}
/**
* The getter method implementation for the persistent
* attribute <code>blockDeadlines</code>.
*
* @return the value of blockDeadlines.
*/
protected Map getPaBlockDeadlines() {
return paBlockDeadlines;
}
/**
* The getter method implementation for the persistent
* attribute <code>debug</code>.
*
* @see #setPaDebug
* @return the value of debug.
*/
protected boolean getPaDebug() {
return (paFlags & FLAGS_DEBUG) != 0;
}
/**
* The setter method implementation for the persistent
* attribute <code>debug</code>.
*
* @param newDebug the new value of debug.
* @see #getPaDebug
*/
protected void setPaDebug(boolean newDebug) {
paFlags = (paFlags & FLAGS_DEBUG)
| (newDebug ? FLAGS_DEBUG : 0);
persistentAttributeModified = true;
}
/**
* The getter method implementation for the persistent
* attribute <code>auditEventSelection</code>.
*
* @see #setPaAuditEventSelection
* @return the value of auditEventSelection.
*/
protected int getPaAuditEventSelection() {
return (paFlags & FLAGS_AUDIT_SELECTION_MASK)
>> FLAGS_AUDIT_SELECTION_SHIFT;
}
/**
* The setter method implementation for the persistent
* attribute <code>auditEventSelection</code>.
*
* @param newValue the new value of auditEventSelection.
* @see #getPaAuditEventSelection
*/
protected void setPaAuditEventSelection (int newValue) {
paFlags = (paFlags & ~FLAGS_AUDIT_SELECTION_MASK)
| (newValue << FLAGS_AUDIT_SELECTION_SHIFT);
persistentAttributeModified = true;
}
/**
* The getter method implementation for the persistent
* attribute <code>storeAuditEvents</code>.
*
* @see #setPaStoreAuditEvents
* @return the value of storeAuditEvents.
*/
protected boolean getPaStoreAuditEvents() {
return (paFlags & FLAGS_STORE_AUDIT_EVENTS) != 0;
}
/**
* The setter method implementation for the persistent
* attribute <code>storeAuditEvents</code>.
*
* @param newValue the new value of storeAuditEvents.
* @see #getPaStoreAuditEvents
*/
protected void setPaStoreAuditEvents(boolean newValue) {
paFlags = (paFlags & ~FLAGS_STORE_AUDIT_EVENTS)
| (newValue ? FLAGS_STORE_AUDIT_EVENTS : 0);
persistentAttributeModified = true;
}
}