/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2004 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: SimpleApplicationDirectoryEJB.java 2738 2008-04-04 09:16:34Z drmlipp $
*
* $Log$
* Revision 1.14 2007/01/31 12:24:07 drmlipp
* Design revisited.
*
* Revision 1.13 2006/12/12 10:54:38 drmlipp
* Added method for retrieving infos by application.
*
* Revision 1.11 2006/10/25 21:42:34 mlipp
* Handling duplicate keys.
*
* Revision 1.10 2006/10/25 15:46:04 drmlipp
* Added support for looking up entries by key.
*
* Revision 1.9 2006/10/11 09:05:54 drmlipp
* Fixed EJB naming.
*
* Revision 1.8 2006/10/07 20:41:34 mlipp
* Merged J2EE 1.4 adaptions from test branch.
*
* Revision 1.7 2006/09/29 12:32:09 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.6 2006/09/22 08:17:14 drmlipp
* Removed strange import.
*
* Revision 1.5 2006/09/21 12:20:38 drmlipp
* Added lookup by resource.
*
* Revision 1.4 2005/04/22 15:11:05 drmlipp
* Merged changes from 1.3 branch up to 1.3p15.
*
* Revision 1.2.6.2 2005/04/13 16:14:08 drmlipp
* Optimized db access.
*
* Revision 1.3 2005/04/08 11:28:05 drmlipp
* Merged changes from 1.3 branch up to 1.3p6.
*
* Revision 1.2.6.1 2005/04/04 20:09:22 drmlipp
* Changed WLS transaction isolation.
*
* Revision 1.2 2004/09/10 12:44:31 drmlipp
* Enabled call by reference for weblogic by default.
*
* Revision 1.1.1.1 2004/08/18 15:17:39 drmlipp
* Update to 1.2
*
* Revision 1.6 2004/07/04 17:36:03 lipp
* Added JOnAS support.
*
* Revision 1.5 2004/04/08 09:34:54 lipp
* Clarified documentation of package structure.
*
* Revision 1.4 2004/02/27 12:01:48 lipp
* Fixed permissions.
*
* Revision 1.3 2004/02/20 15:58:22 lipp
* Several WaitTool fixes.
*
* Revision 1.2 2004/02/19 18:06:42 lipp
* Fixed application name handling.
*
* Revision 1.1 2004/02/19 17:55:54 lipp
* Initial version of waittool.
*
*/
package de.danet.an.workflow.tools.util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import de.danet.an.util.EJBUtil;
import de.danet.an.util.JDBCUtil;
import de.danet.an.util.UniversalPrepStmt;
import de.danet.an.workflow.omgcore.WfAssignment;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.ActivityUniqueKey;
import de.danet.an.workflow.api.InvalidKeyException;
/**
* This EJB provides a directory for simple applications. Applications
* are considered simple in this context if their state can
* efficiently be represented (and stored) using a single serializable
* object.<P>
*
* This directory maps a unique id to the activity unique key
* information of the executing activity and an associated state (and
* vice versa). Optionally, the assignment time and an assigned
* resource may be saved in this directory as well.
*
* @see de.danet.an.workflow.util
*
* @author <a href="mailto:lipp@danet.de">Michael Lipp</a>
* @version $Revision: 2738 $
* @ejb.bean name="SimpleApplicationDirectory"
* jndi-name="ejb/@@@_JNDI_Name_Prefix_@@@SimpleApplicationDirectory"
* local-jndi-name="ejb/@@@_JNDI_Name_Prefix_@@@SimpleApplicationDirectoryLocal"
* display-name="Simple Application Directory EJB"
* type="Stateless" transaction-type="Container" view-type="both"
* @jonas.bean ejb-name="SimpleApplicationDirectory"
* @ejb.transaction type="Required"
* @ejb.permission role-name="WfMOpenAdmin"
* @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"
* @weblogic.transaction-isolation TRANSACTION_READ_COMMITTED
*/
public class SimpleApplicationDirectoryEJB implements SessionBean {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory
.getLog(SimpleApplicationDirectoryEJB.class);
/**
* The SessionContext interface of the instance.
*/
private SessionContext ctx;
/**
* The data source of the database.
* @see javax.sql.DataSource
*/
private DataSource ds = null;
/** Database name. */
private static final String DB_NAME = "java:comp/env/jdbc/WfEngine";
/**
* Creates an instance of <code>SimpleApplicationDirectoryEJB</code>
* with all attributes initialized to default values.
*/
public SimpleApplicationDirectoryEJB () {
}
/**
* Save the session context asigned by the container.
* @param context the context
*/
public void setSessionContext(SessionContext context) {
ctx = context;
// getting new data source
try {
ds = JDBCUtil.refreshDS(null, DB_NAME);
} catch (NamingException ne) {
throw new EJBException(ne);
}
}
/**
* Create a new EJB.
* @throws CreateException if creation fails
*/
public void ejbCreate() throws CreateException {
}
/**
* Remove this EJB.
*/
public void ejbRemove() {
ds = null;
ctx = null;
}
/**
* 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 {
}
/**
* Register a new application instance. This method returns a unique
* key that can be used to {@link #instanceInfo(long) retrieve},
* {@link #updateState(long, Object) update} and
* {@link #removeInstance(long)} remove the instance information.<P>
*
* @param applName the application name
* @param activity the invoking activity
* @param state the application state
* @param saveAssignment if <code>true</code> the assigned
* resource will be saved to allow searching application instances
* with a particular assignee
* @return the instance id
* @ejb.interface-method view-type="both"
*/
public long registerInstance (String applName, Activity activity,
Object state, boolean saveAssignment) {
return registerInstance
(applName, null, activity, state, saveAssignment);
}
/**
* Register a new application instance. This method returns a unique
* key that may be used to
*
* @param applName the application name
* @param applInstKey an arbitrary key for this instance, up to 1000
* characters long
* @param activity the invoking activity
* @param state the application state
* @param saveAssignment if <code>true</code> the assigned
* resource will be saved to allow searching application instances
* with a particular assignee
* @return the instance id
* @ejb.interface-method view-type="both"
*/
public long registerInstance
(String applName, String applInstKey,
Activity activity, Object state, boolean saveAssignment) {
try {
Connection con = null;
UniversalPrepStmt prepStmt = null;
try {
ActivityUniqueKey auk = activity.uniqueKey();
con = ds.getConnection();
long instId = EJBUtil.newPrimaryKey
("SimpleApplicationDirectory");
prepStmt = new UniversalPrepStmt
(ds, con, "INSERT INTO SimpleAppl (DBId, ApplName, "
+ "InstKey, ActivityKey, ProcessKey, ProcessMgr"
+ (saveAssignment ? ", ResourceKey" : "")
+ ", AssignedAt, InstData) VALUES (?, ?, ?, ?, ?, ?"
+ (saveAssignment ? ", ?" : "")
+ ", ?, ?)");
int offset = 1;
prepStmt.setLong (offset++, instId);
prepStmt.setString (offset++, applName);
prepStmt.setString (offset++, applInstKey);
prepStmt.setString (offset++, auk.activityKey());
prepStmt.setString (offset++, auk.processKey());
prepStmt.setString (offset++, auk.managerName());
if (saveAssignment) {
String resourceKey = null;
Iterator i = activity.assignments().iterator();
if (i.hasNext ()) {
WfAssignment assignment = (WfAssignment)i.next();
resourceKey = assignment.assignee().resourceKey();
}
prepStmt.setString (offset++, resourceKey);
}
prepStmt.setTimestamp
(offset++, new Timestamp (System.currentTimeMillis()));
prepStmt.setBinary(offset++, state);
prepStmt.executeUpdate();
if (logger.isDebugEnabled()) {
logger.debug
("Created application " + instId + " for " + auk);
}
return instId;
} finally {
JDBCUtil.closeAll (null, prepStmt, con);
}
} catch (SQLException e) {
throw new EJBException(e);
} catch (IOException e) {
throw new EJBException(e);
}
}
/**
* Remove an application instance.
*
* @param instId the application instance id previously assigned
* by {@link #registerInstance <code>registerInstance</code>}
* @ejb.interface-method view-type="both"
*/
public void removeInstance (long instId) {
try {
Connection con = null;
UniversalPrepStmt prepStmt = null;
try {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con, "DELETE FROM SimpleAppl WHERE DBId = ?");
prepStmt.setLong (1, instId);
prepStmt.executeUpdate();
if (logger.isDebugEnabled()) {
logger.debug ("Removed application " + instId);
}
} finally {
JDBCUtil.closeAll (null, prepStmt, con);
}
} catch (SQLException e) {
throw new EJBException(e);
}
}
/**
* Return the information associated with the application instance.
*
* @param instId the application instance id previously assigned
* by {@link #registerInstance <code>registerInstance</code>}
* @throws InvalidKeyException if there is no data available for
* the given id
* @return the info
* @ejb.interface-method view-type="both"
*/
public SimpleApplicationInfo instanceInfo (long instId)
throws InvalidKeyException {
try {
Connection con = null;
UniversalPrepStmt prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con, "SELECT ActivityKey, ProcessKey, ProcessMgr, "
+ "ResourceKey, AssignedAt, InstData "
+ "FROM SimpleAppl WHERE DBId = ?");
prepStmt.setLong (1, instId);
rs = prepStmt.executeQuery();
if (! rs.next()) {
throw new InvalidKeyException
("No application instance with key = " + instId);
}
String actKey = rs.getString (1);
String prcKey = rs.getString (2);
String mgrKey = rs.getString (3);
String resKey = rs.getString (4);
Date asndAt = rs.getTimestamp(5);
Object state = JDBCUtil.getBinary(rs, 6);
return new SimpleApplicationInfo
(instId, actKey == null? null
: new ActivityUniqueKey (mgrKey, prcKey, actKey),
asndAt, resKey, state);
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
} catch (ClassNotFoundException e) {
throw new EJBException(e);
} catch (IOException e) {
throw new EJBException(e);
} catch (SQLException e) {
throw new EJBException(e);
}
}
/**
* Return the information associated with the activity.
*
* @param auk the unique key of the activity an application
* instance is expected to be registered for.
* @throws InvalidKeyException if there is no data available for
* the given activity
* @return the info
* @ejb.interface-method view-type="both"
*/
public SimpleApplicationInfo infoByActivity (ActivityUniqueKey auk)
throws InvalidKeyException {
try {
Connection con = null;
UniversalPrepStmt prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con, "SELECT DBId, ResourceKey, AssignedAt, InstData "
+ "FROM SimpleAppl WHERE ActivityKey = ? "
+ "AND ProcessKey = ? AND ProcessMgr = ?");
prepStmt.setString (1, auk.activityKey());
prepStmt.setString (2, auk.processKey());
prepStmt.setString (3, auk.managerName());
rs = prepStmt.executeQuery();
if (! rs.next()) {
throw new InvalidKeyException
("No application instance for activity = " + auk);
}
long instId = rs.getLong(1);
String resKey = rs.getString (2);
Date asndAt = rs.getTimestamp (3);
Object state = JDBCUtil.getBinary(rs, 4);
return new SimpleApplicationInfo
(instId, auk, asndAt, resKey, state);
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
} catch (ClassNotFoundException e) {
throw new EJBException(e);
} catch (IOException e) {
throw new EJBException(e);
} catch (SQLException e) {
throw new EJBException(e);
}
}
/**
* Return infos associated with a given application.
*
* @param applName the application name
* @return the infos as collection
* @ejb.interface-method view-type="both"
*/
public Collection infosByApplication (String applName) {
Collection res = new ArrayList ();
try {
Connection con = null;
UniversalPrepStmt prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con, "SELECT DBId, ActivityKey, ProcessKey, "
+ "ProcessMgr, ResourceKey, AssignedAt, InstData "
+ "FROM SimpleAppl WHERE ApplName = ? ");
prepStmt.setString (1, applName);
rs = prepStmt.executeQuery();
while (rs.next()) {
int pos = 1;
long instId = rs.getLong(pos++);
String actKey = rs.getString(pos++);
String procKey = rs.getString(pos++);
String procMgr = rs.getString(pos++);
String resourceKey = rs.getString(pos++);
Date asndAt = rs.getTimestamp (pos++);
Object state = JDBCUtil.getBinary(rs, pos++);
ActivityUniqueKey auk
= new ActivityUniqueKey(procMgr, procKey, actKey);
res.add(new SimpleApplicationInfo
(instId, auk, asndAt, resourceKey, state));
}
return res;
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
} catch (ClassNotFoundException e) {
throw new EJBException(e);
} catch (IOException e) {
throw new EJBException(e);
} catch (SQLException e) {
throw new EJBException(e);
}
}
/**
* Return the infos associated with the given application name and key.
*
* @param applName the application name
* @param applInstKey the key associated with the instance
* @return the infos or an empty collection if no infos
* with the given application name and key exist
* @ejb.interface-method view-type="both"
*/
public Collection infosByKey (String applName, String applInstKey)
throws InvalidKeyException {
try {
Collection res = new ArrayList ();
Connection con = null;
UniversalPrepStmt prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con, "SELECT DBId, ActivityKey, ProcessKey, "
+ "ProcessMgr, ResourceKey, AssignedAt, InstData "
+ "FROM SimpleAppl WHERE ApplName = ? AND InstKey = ?");
prepStmt.setString (1, applName);
prepStmt.setString (2, applInstKey);
rs = prepStmt.executeQuery();
while (rs.next()) {
int pos = 1;
long instId = rs.getLong(pos++);
String actKey = rs.getString(pos++);
String procKey = rs.getString(pos++);
String procMgr = rs.getString(pos++);
String resKey = rs.getString (pos++);
Date asndAt = rs.getTimestamp (pos++);
Object state = JDBCUtil.getBinary(rs, pos++);
ActivityUniqueKey auk
= new ActivityUniqueKey(procMgr, procKey, actKey);
res.add(new SimpleApplicationInfo
(instId, auk, asndAt, resKey, state));
}
return res;
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
} catch (ClassNotFoundException e) {
throw new EJBException(e);
} catch (IOException e) {
throw new EJBException(e);
} catch (SQLException e) {
throw new EJBException(e);
}
}
/**
* Return infos associated with a given application and resource.
*
* @param applName the application name
* @param resourceKey the resource's key
* @return the infos as collection
* @ejb.interface-method view-type="both"
*/
public Collection infosByResource
(String applName, String resourceKey) {
Collection res = new ArrayList ();
try {
Connection con = null;
UniversalPrepStmt prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con, "SELECT DBId, ActivityKey, ProcessKey, "
+ "ProcessMgr, AssignedAt, InstData "
+ "FROM SimpleAppl "
+ "WHERE ApplName = ? AND ResourceKey = ? ");
prepStmt.setString (1, applName);
prepStmt.setString (2, resourceKey);
rs = prepStmt.executeQuery();
while (rs.next()) {
int pos = 1;
long instId = rs.getLong(pos++);
String actKey = rs.getString(pos++);
String procKey = rs.getString(pos++);
String procMgr = rs.getString(pos++);
Date asndAt = rs.getTimestamp (pos++);
Object state = JDBCUtil.getBinary(rs, pos++);
ActivityUniqueKey auk
= new ActivityUniqueKey(procMgr, procKey, actKey);
res.add(new SimpleApplicationInfo
(instId, auk, asndAt, resourceKey, state));
}
return res;
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
} catch (ClassNotFoundException e) {
throw new EJBException(e);
} catch (IOException e) {
throw new EJBException(e);
} catch (SQLException e) {
throw new EJBException(e);
}
}
/**
* Update the state information associated with the given
* application instance id.
*
* @param instId the application instance id previously assigned
* by {@link #registerInstance <code>registerInstance</code>}
* @param state the new state
* @throws InvalidKeyException if there is no application instance
* with the given id
* @ejb.interface-method view-type="both"
*/
public void updateState (long instId, Object state)
throws InvalidKeyException {
try {
Connection con = null;
UniversalPrepStmt prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con,
"UPDATE SimpleAppl SET InstData = ? WHERE DBId = ? ");
prepStmt.setBinary (1, state);
prepStmt.setLong (2, instId);
if (prepStmt.executeUpdate() == 0) {
throw new InvalidKeyException
("No application instance with key = " + instId);
}
if (logger.isDebugEnabled()) {
logger.debug
("Updated state for application " + instId);
}
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
} catch (IOException e) {
throw new EJBException(e);
} catch (SQLException e) {
throw new EJBException(e);
}
}
/**
* Update the resource associated with the given
* application instance id.
*
* @param instId the application instance id previously assigned
* by {@link #registerInstance <code>registerInstance</code>}
* @param resourceKey the associated resource
* @throws InvalidKeyException if there is no application instance
* with the given id
* @ejb.interface-method view-type="both"
*/
public void updateResourceKey (long instId, String resourceKey)
throws InvalidKeyException {
try {
Connection con = null;
UniversalPrepStmt prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con,
"UPDATE SIMPLEAPPL SET RESOURCEKEY = ? WHERE DBID = ? ");
prepStmt.setString (1, resourceKey);
prepStmt.setLong (2, instId);
if (prepStmt.executeUpdate() == 0) {
throw new InvalidKeyException
("No application instance with key = " + instId);
}
if (logger.isDebugEnabled()) {
logger.debug
("Updated resourceKey for application " + instId);
}
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
} catch (SQLException e) {
throw new EJBException(e);
}
}
/**
* Update the activity associated with the given application
* instance. This is useful if an application instance is started
* by one tool (agent) invocation and stopped by another.<P>
*
* Be careful to ensure the eventual termination of the
* application. If the creating activity has completed, the
* terminate method of the tool agent that started the application
* will not be called on abnormal process completion. So, if a
* process is terminated abnormally and the starting activity is
* closed and the stopping activity has not yet been started (and
* associated with the application) the application will not be
* stopped. This should normally not be a problem for simple
* applications.<P>
*
* As a convenience, any application information that is still
* registered after a process completion will automatically be
* deleted.<P>
*
* The new activity must belong to the same process as the
* activity that initially created the application instance.
*
* @param instId the application instance id previously assigned
* by {@link #registerInstance <code>registerInstance</code>}
* @param auk the new activity's unique key. May be
* <code>null</code> if the application instance is temporarily
* not associated with an activity.
* @throws InvalidKeyException if there is no application instance
* with the given id
* @ejb.interface-method view-type="both"
*/
public void updateInvokingActivity (long instId, ActivityUniqueKey auk)
throws InvalidKeyException {
try {
Connection con = null;
UniversalPrepStmt prepStmt = null;
ResultSet rs = null;
try {
con = ds.getConnection();
prepStmt = new UniversalPrepStmt
(ds, con, "UPDATE SimpleAppl "
+ "SET ActivityKey = ? WHERE DBId = ?");
prepStmt.setString (1, auk == null ? null : auk.activityKey());
prepStmt.setLong (2, instId);
if (prepStmt.executeUpdate() == 0) {
throw new InvalidKeyException
("No application instance with key = " + instId);
}
if (logger.isDebugEnabled()) {
logger.debug ("Application " + instId
+ " has been associated with " + auk);
}
} finally {
JDBCUtil.closeAll (rs, prepStmt, con);
}
} catch (SQLException e) {
throw new EJBException(e);
}
}
}