/*
* 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: TimerHandlerEJB.java 2413 2007-06-10 05:55:30Z drmlipp $
*
* $Log$
* Revision 1.13 2006/12/18 13:07:31 drmlipp
* Fixed typo.
*
* Revision 1.12 2006/12/18 12:45:55 drmlipp
* Handle exception to avoid loop.
*
* Revision 1.10 2006/10/11 09:43:11 drmlipp
* Removed not used non-local name.
*
* Revision 1.9 2006/10/11 09:05:54 drmlipp
* Fixed EJB naming.
*
* Revision 1.8 2006/10/10 15:37:17 drmlipp
* Made TimerHandlerEJB local.
*
* Revision 1.7 2006/10/07 21:35:30 mlipp
* Continuing migration to J2EE 1.4.
*
* Revision 1.6 2006/10/07 20:41:34 mlipp
* Merged J2EE 1.4 adaptions from test branch.
*
* Revision 1.5 2006/09/29 12:32:10 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.4 2005/04/08 11:28:05 drmlipp
* Merged changes from 1.3 branch up to 1.3p6.
*
* Revision 1.3.4.1 2005/04/04 20:09:21 drmlipp
* Changed WLS transaction isolation.
*
* Revision 1.3 2004/10/20 20:40:32 drmlipp
* Made simple WaitTool terminatable.
*
* Revision 1.2 2004/09/10 12:44:30 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.4 2004/02/27 12:01:48 lipp
* Fixed permissions.
*
* Revision 1.3 2004/02/23 13:59:31 lipp
* Added simple waittool invocation.
*
* Revision 1.2 2004/02/21 21:31:01 lipp
* Some more refactoring to resolve cyclic dependencies.
*
* Revision 1.1 2004/02/20 18:56:35 lipp
* Renamed package waittool to timing (much better ;-)).
*
* Revision 1.3 2004/02/20 15:58:22 lipp
* Several WaitTool fixes.
*
* Revision 1.2 2004/02/19 21:14:48 lipp
* Several WaitTool fixes.
*
* Revision 1.1 2004/02/19 17:55:55 lipp
* Initial version of waittool.
*
*/
package de.danet.an.workflow.tools.timing;
import java.util.Date;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.FinderException;
import javax.ejb.NoSuchObjectLocalException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.TimedObject;
import javax.ejb.Timer;
import javax.ejb.TimerHandle;
import de.danet.an.util.EJBUtil;
import de.danet.an.util.ResourceNotAvailableException;
import de.danet.an.workflow.localapi.ActivityLocal;
import de.danet.an.workflow.omgcore.CannotCompleteException;
import de.danet.an.workflow.omgcore.CannotStopException;
import de.danet.an.workflow.omgcore.InvalidDataException;
import de.danet.an.workflow.omgcore.NotRunningException;
import de.danet.an.workflow.omgcore.ProcessData;
import de.danet.an.workflow.omgcore.WfExecutionObject.State;
import de.danet.an.workflow.api.ActivityUniqueKey;
import de.danet.an.workflow.api.DefaultProcessData;
import de.danet.an.workflow.api.InvalidKeyException;
import de.danet.an.workflow.ejbs.core.WfActivityLocalHome;
import de.danet.an.workflow.tools.util.SimpleApplicationDirectoryLocal;
import de.danet.an.workflow.tools.util.SimpleApplicationDirectoryLocalHome;
import de.danet.an.workflow.tools.util.SimpleApplicationInfo;
/**
* This EJB provides a directory for simple applications. Applications
* are considered simple in this context if their associated
* persistent state information 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 the assigned
* resource may be saved in this directory as well.
*
* @author <a href="mailto:lipp@danet.de">Michael Lipp</a>
* @version $Revision: 2413 $
* @ejb.bean name="TimerHandler" display-name="Timer Handler"
* local-jndi-name="ejb/@@@_JNDI_Name_Prefix_@@@TimerHandlerLocal"
* type="Stateless" transaction-type="Container" view-type="local"
* @ejb.transaction type="Required"
* @ejb.permission role-name="WfMOpenAdmin"
* @ejb.security-identity run-as="WfMOpenAdmin"
* sunone-principal="WfMOpenPrincipalForAdmin"
* @weblogic.run-as-identity-principal WfMOpenPrincipalForAdmin
* @ejb.ejb-ref ejb-name="SimpleApplicationDirectory" view-type="local"
* @ejb.ejb-ref ejb-name="ActivityBean" 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"
* @weblogic.enable-call-by-reference True
*/
public class TimerHandlerEJB implements SessionBean, TimedObject {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory
.getLog(TimerHandlerEJB.class);
/**
* The SessionContext interface of the instance.
*/
private SessionContext ctx;
/** The cached home interface of the activity. */
private WfActivityLocalHome activityLocalHomeCache = null;
/** Application directory. */
private SimpleApplicationDirectoryLocal applDirCache = null;
/**
* Creates an instance of <code>TimerHandlerEJB</code>
* with all attributes initialized to default values.
*/
public TimerHandlerEJB () {
}
/**
* The application directory.
* @return the application directory
*/
private SimpleApplicationDirectoryLocal applicationDirectory()
throws ResourceNotAvailableException {
if (applDirCache == null) {
applDirCache = (SimpleApplicationDirectoryLocal)
EJBUtil.createSession
(SimpleApplicationDirectoryLocalHome.class,
"java:comp/env/ejb/SimpleApplicationDirectoryLocal");
}
return applDirCache;
}
/**
* 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;
}
/**
* Save the session context asigned by the container.
* @param context the context
*/
public void setSessionContext(SessionContext context) {
ctx = context;
}
/**
* Create a new EJB.
* @throws CreateException if creation fails
*/
public void ejbCreate() throws CreateException {
}
/**
* Remove this EJB.
*/
public void ejbRemove() {
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 {
}
/**
* Create the timer for a waiting application.
*
* @param applId the application id
* @param waitUntil the time to wait for
* @return the timer
* @ejb.interface-method view-type="local"
*/
public Object createTimer (long applId, Date waitUntil) {
return ctx.getTimerService().createTimer(waitUntil, new Long (applId))
.getHandle ();
}
/**
* Create a timer for a an activity
*
* @param auk the activity's unique key
* the result
* @param waitUntil the time to wait for
* @ejb.interface-method view-type="local"
*/
public void createTimer (ActivityUniqueKey auk, Date waitUntil) {
ctx.getTimerService().createTimer (waitUntil, auk);
}
/**
* Remove a previously created timer.
*
* @param timer the timer
* @ejb.interface-method view-type="local"
*/
public void removeTimer (Object timer) {
try {
((TimerHandle)timer).getTimer().cancel ();
} catch (NoSuchObjectLocalException e) {
logger.warn ("Trying to cancel no longer existing timer "
+ "(may be due to a race condition): "
+ e.getMessage(), e);
}
}
/**
* Handle the timeout of a timer.
* @param timer the timer that has expired.
*/
public void ejbTimeout (Timer timer) {
// Note that a RemoteException will lead to a rollback and an
// automatic re-invocation of this method.
Object info = timer.getInfo();
ActivityUniqueKey auk = null;
String resParam = null;
long applId = ((Long)info).longValue ();
SimpleApplicationInfo applInfo = null;
try {
try {
applInfo = applicationDirectory().instanceInfo(applId);
} catch (InvalidKeyException e) {
logger.warn ("Application " + applId + " removed without removing "
+ "associated timer (may be race condition).");
return;
}
auk = applInfo.activityUniqueKey();
if (logger.isDebugEnabled ()) {
logger.debug
("Handling timeout for application " + applId
+ (auk == null ? "" : (", " + auk)));
}
applicationDirectory().removeInstance (applId);
if (auk == null) {
if (logger.isDebugEnabled ()) {
logger.debug ("Nothing to do for application " + applId
+ " (no associated activity)");
}
return;
}
resParam = (String)((Object[])applInfo.state())[1];
ActivityLocal act = null;
try {
Long pk = Long.valueOf (auk.activityKey());
act = activityLocalHome().findByPrimaryKey(pk);
} catch (FinderException nex) {
logger.warn (auk + " has disappeared (doing nothing)");
return;
}
if (logger.isDebugEnabled ()) {
logger.debug ("Setting result for " + auk);
}
try {
if (resParam != null) {
ProcessData res = new DefaultProcessData ();
res.put (resParam, "EXPIRED");
act.setResult (res);
}
act.complete ();
if (logger.isDebugEnabled ()) {
logger.debug ("Result set for " + auk + " and completed");
}
return;
} catch (InvalidDataException e) {
logger.error
("Cannot set result of wait tool ("
+ auk + " will be terminated): " + e.getMessage ());
} catch (CannotCompleteException e) {
logger.error ("Cannot complete " + auk
+ " (will be terminated): " + e.getMessage ());
}
if (act.typedState().workflowState() == State.OPEN) {
try {
act.terminate ();
} catch (CannotStopException e) {
logger.error
("Cannot terminate " + auk + ": " + e.getMessage ());
} catch (NotRunningException e) {
logger.warn (auk + " not running although state is open?");
}
}
} catch (RemoteException e) {
throw new EJBException (e);
}
}
}