/*
* 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: ProcessDirectoryEJB.java 2945 2009-02-19 20:09:16Z mlipp $
*
* $Log$
* Revision 1.8 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.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.FinderException;
import javax.ejb.ObjectNotFoundException;
import javax.ejb.RemoveException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
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.EJBUtil;
import de.danet.an.util.JDBCUtil;
import de.danet.an.util.ResourceNotAvailableException;
import de.danet.an.util.logging.RequestLog;
import de.danet.an.util.logging.RequestScope;
import de.danet.an.workflow.localapi.ProcessLocal;
import de.danet.an.workflow.omgcore.WfRequester;
import de.danet.an.workflow.util.HibernateUtil;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.ActivityUniqueKey;
import de.danet.an.workflow.api.CannotRemoveException;
import de.danet.an.workflow.api.InvalidKeyException;
import de.danet.an.workflow.api.Process;
import de.danet.an.workflow.api.RangeAccess;
import de.danet.an.workflow.api.query.AscendingOrder;
import de.danet.an.workflow.api.query.FilterCriterion;
import de.danet.an.workflow.api.query.SortCriterion;
import de.danet.an.workflow.apix.ExtProcessDirectory;
import de.danet.an.workflow.ejbs.core.WfActivityHome;
import de.danet.an.workflow.ejbs.core.WfProcess;
import de.danet.an.workflow.ejbs.core.WfProcessHome;
import de.danet.an.workflow.ejbs.core.WfProcessLocalHome;
import de.danet.an.workflow.ejbs.core.WfProcessEJB.DAO;
/**
* The session bean <code>ProcessDirectoryEJB</code> manage the
* processes and activities of a contributor.
*
* @ejb.bean name="ProcessDirectory" display-name="ProcessDirectory"
* jndi-name="ejb/@@@_JNDI_Name_Prefix_@@@ProcessDirectory"
* type="Stateless" transaction-type="Container" view-type="both"
* @jonas.bean ejb-name="ProcessDirectory"
* @ejb.home
* remote-class="de.danet.an.workflow.ejbs.admin.ProcessDirectoryHome"
* local-class="de.danet.an.workflow.ejbs.admin.ProcessDirectoryLocalHome"
* @ejb.interface
* extends="javax.ejb.EJBObject, de.danet.an.workflow.apix.ExtProcessDirectory"
* remote-class="de.danet.an.workflow.ejbs.admin.ProcessDirectory"
* local-extends="javax.ejb.EJBLocalObject, de.danet.an.workflow.internalapi.ExtProcessDirectoryLocal"
* local-class="de.danet.an.workflow.ejbs.admin.ProcessDirectoryLocal"
* @ejb.ejb-ref ejb-name="WorkflowEngine" view-type="local"
* @ejb.ejb-ref ejb-name="ProcessDefinitionDirectory"
* view-type="remote"
* @ejb.ejb-ref ejb-name="ActivityBean" view-type="remote"
* @ejb.ejb-ref ejb-name="ProcessBean" view-type="remote"
* @ejb.ejb-ref ejb-name="ProcessBean" view-type="local"
* @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.resource-description
* res-ref-name="jdbc/WfEngine" jndi-name="DefaultDS"
* @ejb.transaction type="Required"
* @ejb.permission role-name="WfMOpenAdmin"
* @weblogic.enable-call-by-reference True
* @weblogic.transaction-isolation TRANSACTION_READ_COMMITTED
*/
public class ProcessDirectoryEJB implements SessionBean {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog
(ProcessDirectoryEJB.class);
/** The SessionContext interface of the instance. */
private SessionContext ctx;
/**
* The data source of the database.
* @see javax.sql.DataSource
*/
private DataSource ds = null;
private SessionFactory hibernateSessionFactory = null;
/** Database name. */
private static final String DB_NAME = "java:comp/env/jdbc/WfEngine";
/** The cached home interface of the activity EJB. */
private WfActivityHome activityHomeCache = null;
/** The cached home interface of the process EJB. */
private WfProcessHome processHomeCache = null;
private WfProcessLocalHome processLocalHomeCache = null;
/**
* This operation method delivers a WfActivity home interface.
* @return home of ActivityBean
* {@link de.danet.an.workflow.omgcore.WfProcess <code>WfActivity</code>}
*/
private WfActivityHome activityHome()
throws ResourceNotAvailableException {
if (activityHomeCache == null) {
activityHomeCache
= (WfActivityHome)EJBUtil.retrieveEJBHome
(WfActivityHome.class, "java:comp/env/ejb/ActivityBean");
}
return activityHomeCache;
}
/**
* This operation method delivers a WFProcessHome interface.
* @return home interface of WfProcessBean
* {@link de.danet.an.workflow.omgcore.WfProcess <code>WfProcess</code>}
*/
private WfProcessHome processHome() throws ResourceNotAvailableException {
if (processHomeCache == null) {
processHomeCache = (WfProcessHome)EJBUtil.retrieveEJBHome
(WfProcessHome.class, "java:comp/env/ejb/ProcessBean");
}
return processHomeCache;
}
/**
* This operation method delivers a WFProcessHome interface.
* @return home interface of WfProcessBean
* {@link de.danet.an.workflow.omgcore.WfProcess <code>WfProcess</code>}
*/
private WfProcessLocalHome processLocalHome()
throws ResourceNotAvailableException {
if (processLocalHomeCache == null) {
processLocalHomeCache = (WfProcessLocalHome)
EJBUtil.retrieveEJBLocalHome
(WfProcessLocalHome.class,
"java:comp/env/ejb/ProcessBeanLocal");
}
return processLocalHomeCache;
}
public void setSessionContext(SessionContext context)
throws EJBException {
ctx = context;
activityHomeCache = null;
processHomeCache = null;
try {
// getting new data source
ds = JDBCUtil.refreshDS(null, DB_NAME);
// prepare Hibernate
hibernateSessionFactory
= ((WorkflowEngineLocal)EJBUtil.createSession
(WorkflowEngineLocalHome.class,
"java:comp/env/ejb/WorkflowEngineLocal"))
.hibernateSessionFactory();
} catch (NamingException e) {
throw new EJBException(e);
} catch (ResourceNotAvailableException e) {
throw new EJBException(e);
}
}
/**
* Not called for stateless session beans.
* @see javax.ejb.SessionBean
*/
public void ejbActivate() throws EJBException {
}
/**
* Not called for stateless session beans.
* @see javax.ejb.SessionBean
*/
public void ejbPassivate() throws EJBException {
}
public void ejbRemove() {
ds = null;
activityHomeCache = null;
processHomeCache = null;
ctx = null;
}
/**
* Create an new instance of ProcessDirectoryBean for the Contributor.
* @throws CreateException Throws if the ProcessDirectoryBean can not
* be create.
*/
public void ejbCreate() throws CreateException {
// Write your code here
}
/**
* This operation method delivers a collection of process types
* of created processes or an empty collection if no processes
* are found.
*
* @return collection of process definition types as String objects
* or an empty collection if no processes are found.
* @ejb.interface-method
* view-type="remote"
*/
public Collection processMgrNames() {
RequestScope scope = RequestLog.enterScope
(this, "processMgrNames", (Object[])null);
Collection definitionTypes = null;
try {
definitionTypes = new ArrayList();
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try {
try {
con = ds.getConnection();
String selectStatement
= "SELECT distinct processMgr FROM process";
stmt = con.createStatement();
rs = stmt.executeQuery(selectStatement);
while (rs.next()) {
String type = rs.getString(1);
definitionTypes.add(type);
}
} finally {
JDBCUtil.closeAll (rs, stmt, con);
}
} catch (SQLException se) {
throw new EJBException(se);
}
} finally {
scope.leave(definitionTypes);
}
return definitionTypes;
}
/**
* This operation method delivers a collection of process names
* of created processes or an empty collection if no processes
* are found.
*
* @return collection of process names as String objects
* or an empty collection if no processes are found.
* @ejb.interface-method
* view-type="remote"
*/
public Collection processNames() {
Collection processNames = new ArrayList();
RequestScope scope = RequestLog.enterScope
(this, "processNames", (Object[])null);
try {
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
String selectStatement
= "SELECT DISTINCT NAME FROM PROCESS ORDER BY NAME";
stmt = con.createStatement();
rs = stmt.executeQuery(selectStatement);
while (rs.next()) {
String name = rs.getString(1);
processNames.add(name);
}
} finally {
JDBCUtil.closeAll (rs, stmt, con);
}
} catch (SQLException se) {
throw new EJBException(se);
} finally {
scope.leave (processNames);
}
return processNames;
}
/**
* This operation method creates and delivers a collection of all
* known processes. The objects of the collection are
* remote interface of type
* {@link de.danet.an.workflow.omgcore.WfProcess <code>WfProcess</code>}.
* @return a Collection object
* @ejb.interface-method
* view-type="remote"
*/
public Collection processes() {
RequestScope scope = RequestLog.enterScope
(this, "processes", (Object[])null);
Collection res = null;
try {
res = processHome().findAll();
} catch (ResourceNotAvailableException re) {
throw new EJBException(re);
} catch (FinderException ne) {
throw new EJBException(ne);
} catch (RemoteException re) {
throw new EJBException(re);
} finally {
scope.leave (res);
}
return res;
}
public static class ProcessDirectoryRangeAccess
implements RangeAccess, Serializable {
private ExtProcessDirectory processDirectory;
private byte[] filterData;
private byte[] orderData;
/**
* Create a new instance with all attributes initialized
* to defaults or the given values.
* @param processDirectory
* @param criteria
* @param orderCriteria TODO
*/
public ProcessDirectoryRangeAccess
(ExtProcessDirectory processDirectory, byte[] filterData,
byte[] orderData) {
this.processDirectory = processDirectory;
this.filterData = filterData;
this.orderData = orderData;
}
/* (non-Javadoc)
* Comment copied from interface or superclass.
*/
public long itemCount() throws RemoteException {
return processDirectory.processCount(filterData);
}
/* (non-Javadoc)
* Comment copied from interface or superclass.
*/
public List items(long start, long end) throws RemoteException {
return processDirectory.processes
(filterData, orderData, start, end);
}
}
/**
* This method returns an access object to an ordered set of
* processes. The objects in the result are
* remote interface of type
* {@link de.danet.an.workflow.api.Process
* <code>Process</code>}.
*
* @param filter a filter for the result
* @param order the sort order for the result
* @return access object to
* {@link de.danet.an.workflow.api.Process <code>Processes</code>}
* @throws RemoteException if a system-level error occurs.
*/
public RangeAccess processes (FilterCriterion filter, SortCriterion order)
throws RemoteException {
RequestScope scope = RequestLog.enterScope
(this, "processes", new Object[] { filter, order });
ProcessDirectoryRangeAccess res = null;
try {
Criterion filterCriteria = HibernateUtil.convertFilterCriterion(filter);
List orderCriteria = new ArrayList();
while (order != null) {
String sortProp = order.getSortProperty();
if (sortProp.equals("key")) {
sortProp = "dbId";
}
if (order instanceof AscendingOrder) {
orderCriteria.add(Order.asc(sortProp));
} else {
orderCriteria.add(Order.desc(sortProp));
}
order = order.getSubCriterion();
}
// serialize (1) because criteria can only be used once and
// (2) to avoid hibernate classes on client side
byte[] filterData = null;
byte[] orderData = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(filterCriteria);
oos.flush();
oos.close();
filterData = bos.toByteArray();
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(orderCriteria);
oos.flush();
oos.close();
orderData = bos.toByteArray();
} catch (IOException e) {
throw (IllegalStateException)
(new IllegalStateException()).initCause(e);
}
res = new ProcessDirectoryRangeAccess
((ExtProcessDirectory)ctx.getEJBObject(),
filterData, orderData);
} finally {
scope.leave (res);
}
return res;
}
/**
* Return the number of available processes considering the given
* criteria.
*
* @param criteria the criteria for filtering
* @return the result
*/
public long processCount (byte[] filterData) {
Criterion filterCriterion = null;
try {
ByteArrayInputStream bis
= new ByteArrayInputStream(filterData);
ObjectInputStream ois = new ObjectInputStream (bis);
filterCriterion = (Criterion)ois.readObject();
} catch (IOException e) {
throw (IllegalArgumentException)
(new IllegalArgumentException()).initCause(e);
} catch (ClassNotFoundException e) {
throw (IllegalArgumentException)
(new IllegalArgumentException()).initCause(e);
}
RequestScope scope = RequestLog.enterScope
(this, "processCount", new Object[] { filterCriterion });
Session session = null;
long result = 0;
try {
session = hibernateSessionFactory.openSession();
Criteria curCrit = session.createCriteria(DAO.class);
curCrit.setProjection(Projections.rowCount());
if (filterCriterion != null) {
curCrit.add(filterCriterion);
}
Object res = curCrit.uniqueResult();
result = ((Number)res).longValue();
} finally {
if (session != null) {
session.close();
}
scope.leave(new Long(result));
}
return result;
}
/**
* Return a collection of processes based on the given criteria and limits.
*
* @param criteria the criteria
* @param start the index of the first item
* @param end the index of the last item
* @return the collection
*/
public List processes
(byte[] filterData, byte[] orderData, long start, long end) {
RequestScope scope = RequestLog.enterScope
(this, "processes", new Object[]
{ filterData, orderData, new Long(start), new Long(end) });
List res = null;
try {
res = (List)processHome().findRange
(filterData, orderData, start, end);
} catch (ResourceNotAvailableException re) {
throw new EJBException(re);
} catch (FinderException fe) {
throw new EJBException(fe);
} catch (RemoteException re) {
throw new EJBException(re);
} finally {
scope.leave(res);
}
return res;
}
/**
* This method finds the process identified by the given
* type and key.
*
* @param processMgrName type of the given process.
* @param processKey key of the process.
* @return the process found.
* @throws InvalidKeyException if no such process can be
* found.
* @ejb.interface-method
* view-type="remote"
*/
public de.danet.an.workflow.api.Process lookupProcess
(String processMgrName, String processKey)
throws InvalidKeyException {
RequestScope scope = RequestLog.enterScope
(this,"lookupProcess",new Object[] { processMgrName, processKey });
de.danet.an.workflow.api.Process res = null;
try {
res = processHome().findByProcessKey(processKey);
} catch (ObjectNotFoundException onfe) {
ctx.setRollbackOnly();
throw new InvalidKeyException
("No process of type = " + processMgrName + ", key = "
+ processKey + ": " + onfe.getMessage());
} catch (ResourceNotAvailableException re) {
throw new EJBException(re);
} catch (FinderException fe) {
throw new EJBException(fe);
} catch (RemoteException re) {
throw new EJBException(re);
} finally {
scope.leave(res);
}
return res;
}
/**
* This method finds the process identified by the given
* type and key.
*
* @param processMgrName type of the given process.
* @param processKey key of the process.
* @return the process found.
* @throws InvalidKeyException if no such process can be
* found.
* @ejb.interface-method
* view-type="remote"
*/
public ProcessLocal lookupProcessLocal
(String processMgrName, String processKey)
throws InvalidKeyException {
try {
return processLocalHome().findByProcessKey(processKey);
} catch (ObjectNotFoundException onfe) {
ctx.setRollbackOnly();
throw new InvalidKeyException
("No process of type = " + processMgrName + ", key = "
+ processKey + ": " + onfe.getMessage());
} catch (ResourceNotAvailableException re) {
throw new EJBException(re);
} catch (FinderException fe) {
throw new EJBException(fe);
} catch (RemoteException re) {
throw new EJBException(re);
}
}
/**
* Return the processes requested by the given requester. This is
* a helper method intended to be used when implementing a
* <code>WfRequester</code>. Applications should use {@link
* de.danet.an.workflow.omgcore.WfRequester#performers
* <code>WfRequester.performers</code>} instead.
* @param req the requester.
* @return the processes created with the given requester.
* @ejb.interface-method view-type="remote"
*/
public Collection requestedBy (WfRequester req) {
Collection res = new ArrayList ();
try {
Collection hashEqual
= processHome().findByRequesterHash (req.hashCode());
for (Iterator i = hashEqual.iterator (); i.hasNext();) {
Process p = (Process)i.next ();
if (p.requester().equals (req)) {
res.add (p);
}
}
return res;
} catch (ResourceNotAvailableException re) {
throw new EJBException(re);
} catch (FinderException ne) {
throw new EJBException(ne);
} catch (RemoteException re) {
throw new EJBException(re);
}
}
/**
* Removes the given process. The process can be removed, only and if only
* it is in state "closed".
*
* @param process the process to remove.
* @throws CannotRemoveException if the process cannot be removed,
* because it is still in progress.
* @ejb.interface-method view-type="remote"
*/
public void removeProcess(de.danet.an.workflow.omgcore.WfProcess process)
throws CannotRemoveException {
RequestScope scope = RequestLog.enterScope
(this, "remoceProcess", new Object[] { process });
try {
((WfProcess)process).remove();
} catch (RemoteException re) {
throw new CannotRemoveException(re.getMessage());
} catch (RemoveException re) {
throw new CannotRemoveException(re.getMessage());
} finally {
scope.leave();
}
}
/**
* This method returns the activity identified be the given unique
* key.
*
* @param key denotes the activity to be looked up.
* @return the corresponding <code>Activity</code> value
* @exception InvalidKeyException if the activity specified by
* <code>key</code> cannot be found.
* @ejb.interface-method
* view-type="remote"
*/
public Activity lookupActivity (ActivityUniqueKey key)
throws InvalidKeyException {
RequestScope scope = RequestLog.enterScope
(this, "lookupActivity", new Object[] { key });
Activity res = null;
try {
Long pk = Long.valueOf (key.activityKey());
res = activityHome().findByPrimaryKey(pk);
} catch (NumberFormatException nex) {
ctx.setRollbackOnly();
throw new InvalidKeyException ("Cause: " + nex.getMessage ());
} catch (FinderException nex) {
throw new InvalidKeyException ("Cause: " + nex.getMessage ());
} catch (RemoteException nex) {
throw new EJBException (nex);
} finally {
scope.leave (res);
}
return res;
}
/**
* This method finds the activity identified be the given unique
* key and returns all available information about it.
*
* @param key denotes the activity to be looked up.
* @return the corresponding <code>Activity.Info</code> value
* @exception InvalidKeyException if the activity specified by
* <code>key</code> cannot be found.
* @ejb.interface-method
* view-type="remote"
*/
public de.danet.an.workflow.api.Activity.Info lookupActivityInfo
(ActivityUniqueKey key) throws InvalidKeyException {
RequestScope scope = RequestLog.enterScope
(this, "lookupActivityInfo", new Object[] { key });
de.danet.an.workflow.api.Activity.Info res = null;
try {
Long pk = Long.valueOf (key.activityKey());
res = activityHome().findByPrimaryKey(pk).activityInfo();
} catch (NumberFormatException nex) {
ctx.setRollbackOnly();
throw new InvalidKeyException ("Cause: " + nex.getMessage ());
} catch (FinderException nex) {
throw new EJBException (nex);
} catch (RemoteException nex) {
throw new EJBException (nex);
} finally {
scope.leave (scope);
}
return res;
}
}