Package de.danet.an.workflow.ejbs.admin

Source Code of de.danet.an.workflow.ejbs.admin.WorkflowEngineEJB

/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* $Id: WorkflowEngineEJB.java 3205 2009-09-27 17:13:47Z mlipp $
*
* $Log$
* Revision 1.29  2007/06/10 05:55:30  drmlipp
* Using local interface for optimization.
*
* Revision 1.28  2007/05/03 21:58:25  mlipp
* Internal refactoring for making better use of local EJBs.
*
*/
package de.danet.an.workflow.ejbs.admin;

import java.io.IOException;
import java.io.Serializable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

import java.lang.reflect.InvocationTargetException;
import java.rmi.NoSuchObjectException;
import java.rmi.RemoteException;
import java.security.Principal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;

import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.FinderException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.TransactionRolledbackLocalException;
import javax.jms.JMSException;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnectionFactory;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.hibernate.SessionFactory;

import de.danet.an.util.EJBUtil;
import de.danet.an.util.JDBCUtil;
import de.danet.an.util.ResourceNotAvailableException;
import de.danet.an.util.UniversalPrepStmt;
import de.danet.an.util.JDBCUtil.DBProperties;
import de.danet.an.util.logging.RequestLog;
import de.danet.an.util.logging.RequestScope;
import de.danet.an.util.persistentmaps.JDBCPersistentMap;
import de.danet.an.util.persistentmaps.PersistentMapSQLException;

import de.danet.an.workflow.localapi.ProcessLocal;
import de.danet.an.workflow.omgcore.CannotCompleteException;
import de.danet.an.workflow.omgcore.CannotStopException;
import de.danet.an.workflow.omgcore.HistoryNotAvailableException;
import de.danet.an.workflow.omgcore.InvalidDataException;
import de.danet.an.workflow.omgcore.InvalidStateException;
import de.danet.an.workflow.omgcore.NotRunningException;
import de.danet.an.workflow.omgcore.ProcessData;
import de.danet.an.workflow.omgcore.TransitionNotAllowedException;
import de.danet.an.workflow.omgcore.WfActivity;
import de.danet.an.workflow.omgcore.WfAssignment;
import de.danet.an.workflow.omgcore.WfAssignmentAuditEvent;
import de.danet.an.workflow.omgcore.WfAuditEvent;
import de.danet.an.workflow.omgcore.WfCreateProcessAuditEvent;
import de.danet.an.workflow.omgcore.WfDataAuditEvent;
import de.danet.an.workflow.omgcore.WfExecutionObject;
import de.danet.an.workflow.omgcore.WfProcess;
import de.danet.an.workflow.omgcore.WfResource;
import de.danet.an.workflow.omgcore.WfStateAuditEvent;
import de.danet.an.workflow.omgcore.WfExecutionObject.State;

import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.ActivityUniqueKey;
import de.danet.an.workflow.api.Batch;
import de.danet.an.workflow.api.Configuration;
import de.danet.an.workflow.api.DefaultProcessData;
import de.danet.an.workflow.api.InvalidKeyException;
import de.danet.an.workflow.api.NoSuchResourceException;
import de.danet.an.workflow.api.ProcessDefinition;
import de.danet.an.workflow.api.ProcessDirectory;

import de.danet.an.workflow.domain.AbstractActivity;
import de.danet.an.workflow.domain.AbstractProcess;
import de.danet.an.workflow.domain.DefaultAssignmentAuditEvent;
import de.danet.an.workflow.domain.DefaultAuditEvent;
import de.danet.an.workflow.domain.DefaultCreateProcessAuditEvent;
import de.danet.an.workflow.domain.DefaultDataAuditEvent;
import de.danet.an.workflow.domain.DefaultStateAuditEvent;
import de.danet.an.workflow.domain.ImplCompleteAuditEvent;
import de.danet.an.workflow.domain.ToolInvocationFailedAuditEvent;

import de.danet.an.workflow.apix.ExtActivity;
import de.danet.an.workflow.ejbs.core.ActivityProxy;
import de.danet.an.workflow.ejbs.core.WfProcessEJB;
import de.danet.an.workflow.ejbs.util.QueuerUtils;
import de.danet.an.workflow.ejbs.util.RedeliveryRequiredException;
import de.danet.an.workflow.internalapi.ExtActivityLocal;
import de.danet.an.workflow.internalapi.ExtApplication;
import de.danet.an.workflow.internalapi.ExtProcessDirectoryLocal;
import de.danet.an.workflow.internalapi.ExtProcessLocal;
import de.danet.an.workflow.internalapi.ExtExecutionObjectLocal.RunningState;
import de.danet.an.workflow.spis.aii.ApplicationNotStoppedException;
import de.danet.an.workflow.spis.aii.ResultProvider.ExceptionResult;
import de.danet.an.workflow.spis.ras.ResourceAssignmentService;
import de.danet.an.workflow.spis.ras.ResourceAssignmentServiceFactory;

/**
* The session bean <code>WorkflowEngineEJB</code> gives access to the
* workflow engine. The remote and home interfaces of this EJB are
* declared in package <code>de.danet.an.workflow.ejbs.util</code> as
* {@link de.danet.an.workflow.ejbs.WorkflowEngine
* <code>WorkflowEngine</code>} and {@link
* de.danet.an.workflow.ejbs.WorkflowEngineHome
* <code>WorkflowEngineHome</code>} because the interfaces are needed
* in all sub-packages and putting the interfaces in a subpackage will
* result in cyclic dependencies. Implementation is in {@link
* de.danet.an.workflow.ejbs.admin this package}, however, because it
* relies on other EJBs from this package. <P>
*
* Setting log level to debug for this class results in progress
* messages during event handling, adds stack traces to warning
* messages and logs the event queue data delivered to clients.
*
* @ejbHome <{de.danet.an.workflow.api.ejbhomes.WorkflowEngineHome}>
* @ejbRemote <{de.danet.an.workflow.ejbs.admin.WorkflowEngine}>
*
* @ejb.bean name="WorkflowEngine" display-name="WorkflowEngine"
* jndi-name="ejb/@@@_JNDI_Name_Prefix_@@@WorkflowEngine"
* type="Stateless" transaction-type="Container" view-type="both"
* @jonas.bean ejb-name="WorkflowEngine"
* jndi-name="ejb/@@@_JNDI_Name_Prefix_@@@WorkflowEngine"
* @ejb.home
* remote-class="de.danet.an.workflow.ejbs.WorkflowEngineHome"
* local-class="de.danet.an.workflow.ejbs.admin.WorkflowEngineLocalHome"
* @ejb.interface remote-class="de.danet.an.workflow.ejbs.WorkflowEngine"
* local-class="de.danet.an.workflow.ejbs.admin.WorkflowEngineLocal"
* extends="javax.ejb.EJBObject, de.danet.an.workflow.spis.rms.ResourceAssignmentContext"
* @ejb.transaction type="Required"
* @ejb.permission role-name="WfMOpenAdmin"
* @ejb.ejb-ref ejb-name="ConfigurationBean" view-type="remote"
* ref-name="ejb/Configuration"
* @ejb.ejb-ref ejb-name="ProcessDefinitionDirectory" view-type="remote"
* @ejb.ejb-ref ejb-name="ProcessDirectory" view-type="remote"
* @ejb.ejb-ref ejb-name="ProcessDirectory" view-type="local"
* @ejb.ejb-ref ejb-name="WorkflowEngine" view-type="remote"
* @ejb.ejb-ref ejb-name="SimpleApplicationDirectory" view-type="local"
* @ejb.ejb-ref ejb-name="SimpleApplicationDirectory" view-type="remote"
* @ejb.ejb-ref ejb-name="TimerHandler" view-type="local"
* ref-name="ejb/TimerHandlerLocal"
* @ejb.ejb-external-ref ref-name="ejb/JdbcKeyGenLocal" link="KeyGen"
* type="Session" view-type="local" home="de.danet.an.util.KeyGenLocalHome"
* business="de.danet.an.util.KeyGenLocal"
* @ejb.resource-ref res-ref-name="jdbc/WfEngine"
* res-type="javax.sql.DataSource" res-auth="Container"
* @jonas.resource res-ref-name="jdbc/WfEngine" jndi-name="jdbc_1"
* @weblogic.enable-call-by-reference True
* @weblogic.resource-description res-ref-name="jdbc/WfEngine"
* jndi-name="DefaultDS"
* @ejb.resource-ref res-ref-name="jms/TCFRemote"
* res-type="javax.jms.TopicConnectionFactory" res-auth="Application"
* @jboss.resource-ref res-ref-name="jms/TCFRemote"
* jndi-name="ConnectionFactory"
* @jonas.resource res-ref-name="jms/TCFRemote" jndi-name="JCF"
* @weblogic.resource-description res-ref-name="jms/TCFRemote"
* jndi-name="weblogic.jms.ConnectionFactory"
* @ejb.resource-ref res-ref-name="jms/TCF"
* res-type="javax.jms.TopicConnectionFactory" res-auth="Container"
* jndi-name="TCF"
* @jboss.resource-ref res-ref-name="jms/TCF" jndi-name="java:/JmsXA"
* @jonas.resource res-ref-name="jms/TCF" jndi-name="TCF"
* @weblogic.resource-description res-ref-name="jms/TCF"
* jndi-name="weblogic.jms.XAConnectionFactory"
* @ejb.resource-ref res-ref-name="jms/QCF"
* res-type="javax.jms.QueueConnectionFactory" res-auth="Container"
* jndi-name="QCF"
* @jboss.resource-ref res-ref-name="jms/QCF" jndi-name="java:/JmsXA"
* @jonas.resource res-ref-name="jms/QCF" jndi-name="QCF"
* @weblogic.resource-description res-ref-name="jms/QCF"
* jndi-name="weblogic.jms.XAConnectionFactory"
* @ejb.resource-ref res-ref-name="jms/InternalEventQueue"
* res-type="javax.jms.Queue" res-auth="Container"
* @jboss.resource-ref res-ref-name="jms/InternalEventQueue"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@InternalEventQueue"
* @jonas.resource res-ref-name="jms/InternalEventQueue"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@InternalEventQueue"
* @weblogic.resource-description res-ref-name="jms/InternalEventQueue"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@InternalEventQueue"
* @ejb.resource-ref res-ref-name="jms/EventService"
* res-type="javax.jms.Topic" res-auth="Container"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@EventService"
* @jboss.resource-ref res-ref-name="jms/EventService"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@EventService"
* @jonas.resource res-ref-name="jms/EventService"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@EventService"
* @weblogic.resource-description res-ref-name="jms/EventService"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@EventService"
* @ejb.resource-ref res-ref-name="jms/ChannelIn"
* res-type="javax.jms.Queue" res-auth="Container"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ChannelInMessages"
* @jboss.resource-ref res-ref-name="jms/ChannelIn"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ChannelInMessages"
* @jonas.resource res-ref-name="jms/ChannelIn"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ChannelInMessages"
* @weblogic.resource-description res-ref-name="jms/ChannelIn"
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ChannelInMessages"
* @ejb.resource-ref res-ref-name="jms/ChannelOut"
* res-type="javax.jms.Topic" res-auth="Container"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@ChannelOutMessages"
* @jboss.resource-ref res-ref-name="jms/ChannelOut"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@ChannelOutMessages"
* @jonas.resource res-ref-name="jms/ChannelOut"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@ChannelOutMessages"
* @weblogic.resource-description res-ref-name="jms/ChannelOut"
* jndi-name="topic/@@@_JNDI_Name_Prefix_@@@ChannelOutMessages"
* @ejb.env-entry type="java.lang.String"
* name="GlobalConnectionFactoryName" value="null"
* @ejb.env-entry type="java.lang.String"
* name="GlobalQueueConnectionFactoryName" value="null"
* @ejb.env-entry type="java.lang.String"
* name="GlobalTopicConnectionFactoryName" value="null"
* @ejb.env-entry type="java.lang.String" name="GlobalEventTopicName"
* value="topic/@@@_JNDI_Name_Prefix_@@@EventService"
* @ejb.env-entry type="java.lang.String" name="GlobalChannelOutTopicName"
* value="topic/@@@_JNDI_Name_Prefix_@@@ChannelOutMessages"
* @ejb.env-entry type="java.lang.String"
* name="de.danet.an.workflow.assignment.assignmentService"
* value="ejb/@@@_JNDI_Name_Prefix_@@@AssignmentService"
* @weblogic.transaction-isolation TRANSACTION_READ_COMMITTED
*/
public class WorkflowEngineEJB implements SessionBean {

    private static final org.apache.commons.logging.Log logger
  = org.apache.commons.logging.LogFactory.getLog
  (WorkflowEngineEJB.class);

    /** Database name */
    private static final String DB_NAME = "java:comp/env/jdbc/WfEngine";

    /** Hibernate session factory savepoint. */
    private static final String HSF_NAME
        = "java:comp/env/hibernate/SessionFactory";

    /** The SessionContext interface of the instance. */
    private SessionContext ctx;

    /**
     * The data source of the database.
     * @see javax.sql.DataSource
     */
    private DataSource ds = null;
    private DBProperties dbProperties = null;

    private SessionFactory hibernateSessionFactoryCache = null;
       
    private TopicConnectionFactory topConFac = null;
    private Topic eventService = null;

    /** Cache for QueueConnectionFactory. */
    private QueueConnectionFactory qcfCache = null;
   
    /** Cache for event queue. */
    private Queue eventQueueCache = null;
   
    /** The cached home interface of the configuration. */
    private ConfigurationHome configurationHomeCache = null;

    /** The cached home interface of the process definition directory. */
    private ProcessDefinitionDirectoryHome processDefinitionDirectoryHomeCache
  = null;

    /** The cached home interface of the process directory. */
    private ProcessDirectoryHome processDirectoryHomeCache = null;
    private ProcessDirectoryLocalHome processDirectoryLocalHomeCache = null;

    /** The resource assignment service. */
    private static boolean rasNotConfigured = false;
    private ResourceAssignmentService rasCache = null;

    /**
     * This operation method delivers a ConfigurationHome
     * interface.
     * @return home interface of the ConfigurationBean
     */
    private ConfigurationHome configurationHome()
  throws ResourceNotAvailableException {
  if (configurationHomeCache == null) {
      configurationHomeCache = (ConfigurationHome)EJBUtil.retrieveEJBHome
    (ConfigurationHome.class, "java:comp/env/ejb/Configuration");
  }
  return configurationHomeCache;
    }

    /**
     * This operation method delivers a ProcessDefinitionDirectoryHome
     * interface.
     * @return home interface of the ProcessDefinitionDirectoryBean
     */
    private ProcessDefinitionDirectoryHome processDefinitionDirectoryHome()
  throws ResourceNotAvailableException {
  if (processDefinitionDirectoryHomeCache == null) {
      processDefinitionDirectoryHomeCache
    = (ProcessDefinitionDirectoryHome)EJBUtil.retrieveEJBHome
    (ProcessDefinitionDirectoryHome.class,
     "java:comp/env/ejb/ProcessDefinitionDirectory");
  }
  return processDefinitionDirectoryHomeCache;
    }

    /**
     * This operation method delivers a ProcessDirectoryHome interface.
     * @return home interface of the ProcessDirectoryBean
     */
    private ProcessDirectoryHome processDirectoryHome()
  throws ResourceNotAvailableException {
  if (processDirectoryHomeCache == null) {
      processDirectoryHomeCache
    = (ProcessDirectoryHome)EJBUtil.retrieveEJBHome
    (ProcessDirectoryHome.class,
     "java:comp/env/ejb/ProcessDirectory");
  }
  return processDirectoryHomeCache;
    }

    /**
     * This operation method delivers a ProcessDirectoryHome interface.
     * @return home interface of the ProcessDirectoryBean
     */
    private ProcessDirectoryLocalHome processDirectoryLocalHome()
  throws ResourceNotAvailableException {
  if (processDirectoryLocalHomeCache == null) {
      processDirectoryLocalHomeCache
    = (ProcessDirectoryLocalHome)EJBUtil.retrieveEJBLocalHome
    (ProcessDirectoryLocalHome.class,
     "java:comp/env/ejb/ProcessDirectoryLocal");
  }
  return processDirectoryLocalHomeCache;
    }

    private QueueConnectionFactory queueConnectionFactory ()
        throws ResourceNotAvailableException {
        if (qcfCache == null) {
            qcfCache = ((QueueConnectionFactory)EJBUtil
                    .retrieveJNDIEntry ("java:comp/env/jms/QCF"));
        }
        return qcfCache;
    }

    private Queue eventQueue ()
        throws ResourceNotAvailableException {
        if (eventQueueCache == null) {
            eventQueueCache = (Queue)EJBUtil.retrieveJNDIEntry
                ("java:comp/env/jms/InternalEventQueue");
        }
        return eventQueueCache;
    }

    /**
     * This method returns the cached resource assignment service.
     * @return the resource assignment service
     */
    private ResourceAssignmentService getRas ()
  throws RemoteException {
  if (rasCache == null && !rasNotConfigured) {
            ResourceAssignmentServiceFactory rasf = null;
      try {
    rasf = ResourceAssignmentServiceFactory.newInstance();
      } catch (de.danet.an.workflow.spis.ras.FactoryConfigurationError
         rae) {
    logger.warn ("No resource assignment service configured. "
                         + "This may be ignored if done intentionally ("
                         + "message from factory: " + rae.getMessage() + ").");
                logger.debug (rae.getMessage(), rae);
    rasNotConfigured = true;
      }
            try {
                rasCache = rasf.newResourceAssignmentService();
            } catch (de.danet.an.workflow.spis.ras.FactoryConfigurationError
                     rae) {
                logger.error ("Problem creating resource assignment service: "
                              + rae.getMessage(), rae);
                rasNotConfigured = true;
            }
  }
  return rasCache;
    }

    /**
     * Set the session context and get new data source. 
     * @param context the given session context.
     * @throws EJBException if problem encountered with getting the data source.
     */
    public void setSessionContext(SessionContext context)
  throws EJBException {
        ctx = context;
  configurationHomeCache = null;
  processDefinitionDirectoryHomeCache = null;
  processDirectoryHomeCache = null;
  processDirectoryLocalHomeCache = null;
  rasCache = null;
  try {
      ds = JDBCUtil.refreshDS(null, DB_NAME);
            dbProperties = JDBCUtil.dbProperties(ds);
      // We should really synchronize on JNDI here, but using the class
      // is very close.
      synchronized (WorkflowEngineEJB.class) {
          try {
                    hibernateSessionFactoryCache
                        = (SessionFactory)EJBUtil.lookupJNDIEntry(HSF_NAME);
                } catch (NamingException e) {
                    logger.debug (HSF_NAME + " not bound, creating...");
                }
                if (hibernateSessionFactoryCache == null) {
                    org.hibernate.cfg.Configuration cfg
                        = new org.hibernate.cfg.Configuration();
                    cfg.setProperty("hibernate.connection.datasource", DB_NAME);
                    cfg.setProperty("hibernate.session_factory_name", HSF_NAME);
                    cfg.addInputStream(WfProcessEJB.class
                            .getResourceAsStream("process.hbm.xml"));
                    hibernateSessionFactoryCache = cfg.buildSessionFactory();
                }
            }
        } catch (NameAlreadyBoundException e) {
            logger.warn("Cannot bind Hibernate session factory (due to "
                        + "concurrency, ignored): " + e.getMessage(), e);
        } catch (SQLException e) {
            throw new EJBException (e);
  } catch (NamingException e) {
      throw new EJBException (e);
  }
    }
   
    /**
     * Create a new instance of ProcessDefinitonDirectoryBean.
     * @throws CreateException Throws if the ProcessDefinitionDirectoryBean
     * can not be created.
     * @ejb.create-method
     * view-type="remote"
     */
    public void ejbCreate() throws CreateException {
  // getting new data source
  try {
      topConFac = (TopicConnectionFactory)
    EJBUtil.lookupJNDIEntry ("java:comp/env/jms/TCF");
      eventService = (Topic)EJBUtil.lookupJNDIEntry
    ("java:comp/env/jms/EventService");
  } catch (NamingException ne) {
      throw new EJBException(ne);
  }
    }

    /**
     * Not called for stateless EJB.
     */
    public void ejbActivate() {
    }

    /**
     * Not called for stateless EJB.
     */
    public void ejbPassivate() {
    }

    /**
     * A container invokes this method before it ends the life of the session
     * object. This happens as a result of a client's invoking a remove
     * operation, or when a container decides to terminate the session object
     * after a timeout.
     * @see javax.ejb.SessionBean
     */
    public void ejbRemove() {
        qcfCache = null;
  configurationHomeCache = null;
  processDefinitionDirectoryHomeCache = null;
  processDirectoryHomeCache = null;
  processDirectoryLocalHomeCache = null;
  rasCache = null;
  ds = null;
    }

    /**
     * Return the instance of the Hibernate session factory.
     *
     * @return the Hibernate session factory
     * @ejb.interface-method view-type="local"
     */
    public SessionFactory hibernateSessionFactory () {
       return hibernateSessionFactoryCache;
    }
   
    //
    // domain methods
    //

    /**
     * Return the workflow engine configuration.
     *
     * @return the configuration.
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Supports"
     */
    public Configuration configuration () {
  try {
      return configurationHome().findByPrimaryKey(new Integer(0));
  } catch (FinderException fe) {
      throw new EJBException (fe);
  } catch (RemoteException re) {
      throw new EJBException (re);
  }
    }

    /**
     * Return the event service connection data. Used by
     * <code>StandardWorkflowService</code> only. This allows to
     * configure the data needed by the client in the server
     * environment and thus eases client configuration.
     *
     * @return the data.
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Supports"
     */
    public Object[] eventServiceData () {
  try {
      String conFacName = (String)EJBUtil.retrieveJNDIEntry
    ("java:comp/env/GlobalConnectionFactoryName");
      String queueConFacName = (String)EJBUtil.retrieveJNDIEntry
    ("java:comp/env/GlobalQueueConnectionFactoryName");
      String topicConFacName = (String)EJBUtil.retrieveJNDIEntry
    ("java:comp/env/GlobalTopicConnectionFactoryName");
      String evtQueueName = (String)EJBUtil.retrieveJNDIEntry
    ("java:comp/env/GlobalEventTopicName");
      String chanOutName = (String)EJBUtil.retrieveJNDIEntry
    ("java:comp/env/GlobalChannelOutTopicName");
      if (logger.isDebugEnabled()) {
    logger.debug
        ("Returning event service data to client: "
         + conFacName + ", " + evtQueueName + ", " + chanOutName);
      }
      return new Object[] { conFacName, queueConFacName, topicConFacName,
          evtQueueName, chanOutName };
  } catch (ResourceNotAvailableException e) {
      throw new EJBException (e);
  }
    }

    /**
     * Return the process definition directory of the workflow engine.
     *
     * @return the process definition directory.
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Supports"
     */
    public de.danet.an.workflow.api.ProcessDefinitionDirectory
  processDefinitionDirectory () {
        RequestScope scope = RequestLog.enterScope
            (this, "processDefinitionDirectory", new Object[] {});
        de.danet.an.workflow.api.ProcessDefinitionDirectory res = null;
        try {
            res = processDefinitionDirectoryHome().create();
  } catch (CreateException ce) {
      throw new EJBException (ce);
  } catch (RemoteException re) {
      throw new EJBException (re);
  } finally {
      scope.leave (res);
  }
  return res;
    }

    /**
     * Return the process directory of the workflow engine.
     *
     * @return the process directory.
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Supports"
     */
    public ExtProcessDirectoryLocal processDirectoryLocal () {
  try {
      return processDirectoryLocalHome().create();
  } catch (CreateException ce) {
      throw new EJBException (ce);
  } catch (RemoteException re) {
      throw new EJBException (re);
  }
    }

    /**
     * Return the process directory of the workflow engine.
     *
     * @return the process directory.
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Supports"
     */
    public ProcessDirectory processDirectory () {
        RequestScope scope = RequestLog.enterScope
            (this, "processDirectory", new Object[] {});
        ProcessDirectory res = null;
  try {
      res = processDirectoryHome().create();
  } catch (CreateException ce) {
      throw new EJBException (ce);
  } catch (RemoteException re) {
      throw new EJBException (re);
  } finally {
      scope.leave (res);
  }
  return res;
    }

    /**
     * Return the resource assignment service used by the
     * workflow engine.
     *
     * @return the resource assignment service
     * @ejb.interface-method view-type="local"
     * @ejb.transaction type="Supports"
     */
    public ResourceAssignmentService resourceAssignmentService () {
        RequestScope scope = RequestLog.enterScope
            (this, "resourceAssignmentService", new Object[] {});
        ResourceAssignmentService res = null;
        try {
            res = getRas ();
        } catch (RemoteException e) {
            throw new EJBException (e);
        } finally {
            scope.leave (res);
        }
        return res;
    }

    /**
     * Return the assignments of a given resource.
     *
     * @param resource the resource.
     * @return the collection of assigned work items (instances of
     * {@link de.danet.an.workflow.omgcore.WfAssignment
     * <code>WfAssignment</code>}).
     * @throws RemoteException if a system-level error occurs.
     * @throws NoSuchResourceException if the resource is invalid.
     * As the environment is a concurrent multi user environment,
     * <code>WfResource</code> objects may become invalid.
     */
    public Collection workItems (WfResource resource)
        throws NoSuchResourceException {
        RequestScope scope = RequestLog.enterScope
            (this, "workItems", new Object[] { resource });
        Collection res = null;
        try {
            res = getRas().workItems(resource);
        } catch (RemoteException e) {
            throw new EJBException (e);
        } finally {
            scope.leave (res);
        }
        return res;
    }

    /**
     * Find out if a given assignment belongs to the work items assigned to
     * a particular resource.
     *
     * @param resource the resource.
     * @param assignment the assignment in question.
     * @return <code>true</code> if the <code>assignment</code> belongs to
     * the work items of the <code>resource</code>.
     * @throws RemoteException if a system-level error occurs.
     * @throws NoSuchResourceException if the resource is invalid.
     * As the environment is a concurrent multi user environment,
     * <code>WfResource</code> objects may become invalid.
     */
    public boolean isMemberOfWorkItems
        (WfResource resource, WfAssignment assignment)
        throws NoSuchResourceException {
        RequestScope scope = RequestLog.enterScope
            (this, "isMemberOfWorkItems",
             new Object[] { resource, assignment });
        boolean res = false;
        try {
            res = getRas().isMemberOfWorkItems(resource, assignment);
        } catch (RemoteException e) {
            throw new EJBException (e);
        } finally {
            scope.leave (new Boolean(res));
        }
        return res;
    }
   
   
    /**
     * Return the known resources.
     *
     * @return the known resources
     * @throws RemoteException if a communication error occurs.
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Supports"
     */
    public Collection knownResources ()
  throws RemoteException {
        RequestScope scope = RequestLog.enterScope
            (this, "knownResources", new Object[] {});
        Collection res = null;
        try {
            ResourceAssignmentService ras = getRas();
            if (ras == null) {
                throw new UnsupportedOperationException
                    ("No resource assignment service configured.");
            }
            res = ras.knownResources ();
        } finally {
            scope.leave (res);
        }
        return res;
    }

    /**
     * Return the resource associated with the given key.
     *
     * @param key the key
     * @return the resource
     * @throws InvalidKeyException if there is no known resource
     * with this key
     * @throws RemoteException if a communication error occurs.
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Supports"
     */
    public WfResource resourceByKey (String key)
  throws InvalidKeyException, RemoteException {
        RequestScope scope = RequestLog.enterScope
            (this, "resourceByKey", new Object[] {});
        WfResource res = null;
        try {
            ResourceAssignmentService ras = getRas();
            if (ras == null) {
                throw new UnsupportedOperationException
                    ("No resource assignment service configured.");
            }
            res = ras.resourceByKey(key);
        } finally {
            scope.leave (res);
        }
        return res;
    }

    /**
     * Return the authorizers for the given resource.
     *
     * @param resource the resource
     * @return the authorizers
     * @throws RemoteException if a communication error occurs.
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Supports"
     */
    public Collection authorizers (WfResource resource)
  throws RemoteException {
        RequestScope scope = RequestLog.enterScope
            (this, "authorizers", new Object[] { resource });
        Collection res = null;
        try {
            ResourceAssignmentService ras = getRas();
            if (ras == null) {
                throw new UnsupportedOperationException
                    ("No resource assignment service configured.");
            }
            res = ras.authorizers(resource);
        } finally {
            scope.leave (res);
        }
        return res;
    }

    /**
     * Return the resource associated with the given principal.
     *
     * @param principal the principal
     * @return the resource
     * @throws RemoteException if a communication error occurs.
     * @throws InvalidKeyException if there is no known resource
     * associated with this principal
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Supports"
     */
    public WfResource asResource (Principal principal)
  throws RemoteException, InvalidKeyException {
        RequestScope scope = RequestLog.enterScope
            (this, "asResource", new Object[] { principal });
        WfResource res = null;
        try {
            ResourceAssignmentService ras = getRas();
            if (ras == null) {
                throw new UnsupportedOperationException
                    ("No resource assignment service configured.");
            }
            res = ras.asResource(principal);
        } finally {
            scope.leave (res);
        }
        return res;
    }

    /**
     * Returns the user currently authenticated as a <code>Principal</code>.
     * @return the caller principal.
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Supports"
     */
    public Principal caller() {
        RequestScope scope = RequestLog.enterScope
            (this, "caller", new Object[] {});
        Principal res = null;
        try {
            res = ctx.getCallerPrincipal();
        } finally {
            scope.leave (res);
        }
        return res;
    }

    /**
     * Terminate a tool in a new transaction, i.e. the transaction
     * attribute of this method is set to <code>RequiresNew</code>.<P>
     *
     * The implementation simply calls
     * <code>appl.terminate(act)</code>.
     *
     * @param appl the application description of the tool
     * @param act the <code>WfActivity</code>
     * @throws ApplicationNotStoppedException if termination is not
     * possible
     * @ejb.interface-method view-type="local"
     * @ejb.transaction type="RequiresNew"
     */
    public void doTerminateLocal (ExtApplication appl, Activity act)
  throws ApplicationNotStoppedException {
  try {
      appl.terminate(act);
  } catch (NoSuchObjectException e) {
      logger.warn
    ("NoSuchObjectException terminating " + appl + " on " + act
     + " (mapped to ApplicationNotStoppedException): "
     + e.getMessage(), e);
      throw new ApplicationNotStoppedException
    (appl + ": " + e.getMessage());
        } catch (RemoteException e) {
            throw new EJBException (e);
  } catch (TransactionRolledbackLocalException e) {
      throw e;
  } catch (RuntimeException e) {
      logger.warn
    ("RuntimeException terminating " + appl + " on " + act
     + "(mapped to ApplicationNotStoppedException): "
     + e.getMessage(), e);
      throw new ApplicationNotStoppedException
    (appl + ": " + e.getMessage());
  }
    }

    /**
     * Returns a collection of <code>WfAuditEvent</code>s associated with
     * this process describing its history.
     * @param execObj the execution object for which to select the audit
     * events
     * @return the collection of audit events
     * @throws HistoryNotAvailableException in case the history is not
     * available for any reason
     * @ejb.interface-method view-type="local"
     * @ejb.transaction type="Supports"
     */
    public Collection history(WfExecutionObject execObj)
  throws HistoryNotAvailableException {
  try {
            return selectAuditEvents(execObj);
  } catch (SQLException sex) {
      throw new EJBException(sex);
  } catch (NamingException nex) {
      logger.error(nex.getMessage(), nex);
      throw new HistoryNotAvailableException();
  } catch (IOException iex) {
      logger.error(iex.getMessage(), iex);
      throw new HistoryNotAvailableException();
  }
    }

    /**
     * Set a result and complete an activity in a new transaction,
     * i.e. the transaction attribute of this method is set to
     * <code>RequiresNew</code>.<P>
     *
     * @param act the <code>WfActivity</code>
     * @param result the tool's result data. If <code>null</code> do
     * not call <code>setResult</code>.
     * @throws InvalidDataException see {@link
     * de.danet.an.workflow.omgcore.WfActivity#setResult
     * <code>WfActivity.setResult(...)</code>}
     * @throws CannotCompleteException see {@link
     * de.danet.an.workflow.omgcore.WfActivity#complete
     * <code>WfActivity.complete()</code>}
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="RequiresNew"
     */
    public void doFinish (WfActivity act, Map result)
  throws InvalidDataException, CannotCompleteException {
  try {
      if (act instanceof ActivityProxy) {
          involveContainerInTx((ActivityProxy)act);
          act = ((ActivityProxy)act).unwrap();
      }
      if (result != null) {
    act.setResult (new DefaultProcessData(result));
      }
      act.complete();
  } catch (NoSuchObjectException e) {
      logger.warn
    (act + " does not exist any more "
     + "(mapped to CannotCompleteException): " + e.getMessage());
      logger.debug ("Stacktrace:", e);
      throw new CannotCompleteException (act + ": " + e.getMessage());
  } catch (RemoteException e) {
      throw new EJBException (e);
  } catch (RuntimeException e) {
      logger.warn
    ("RuntimeException while finishing " + act
     + "(mapped to CannotCompleteException): " + e.getMessage());
      logger.debug ("Stacktrace:", e);
      throw new CannotCompleteException (act + ": " + e.getMessage());
  }
    }

    /**
     * Abandon an activity in a new transaction,
     * i.e. the transaction attribute of this method is set to
     * <code>RequiresNew</code>.<P>
     *
     * @param act the <code>WfActivity</code>
     * @param result the exception result to signal
     * @throws TransitionNotAllowedException see {@link
     * de.danet.an.workflow.api.Activity#abandon(String)
     * <code>Activity.abandon()</code>}
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="RequiresNew"
     */
    public void doAbandon (Activity act, ExceptionResult result)
  throws TransitionNotAllowedException {
  try {
            if (act instanceof ActivityProxy) {
                involveContainerInTx((ActivityProxy)act);
                act = ((ActivityProxy)act).unwrap();
            }
      ((ExtActivity)act).abandon(result);
  } catch (RemoteException e) {
      throw new EJBException (e);
  }
    }

    /**
     * Register the failure of a tool invocation.<P>
     *
     * @param act the <code>ActivityUniqueKey</code>
     * @ejb.interface-method view-type="remote"
     * @ejb.transaction type="Required"
     */
    public void handleToolInvocationFailed (ActivityUniqueKey act) {
        try {
            QueuerUtils.queue(queueConnectionFactory(), eventQueue(),
                              new ToolInvocationFailedAuditEvent (act));
        } catch (ResourceNotAvailableException e) {
            throw new IllegalStateException (e);
        }
    }

    /**
     * Process the given event. The transaction attribute of this
     * method is set to <code>RequiresNew</code>, so processing will
     * be done in its own transaction.
     * @param event the event.
     * @throws RedeliveryRequiredException if the event should be redelivered
     * @ejb.interface-method view-type="local"
     * @ejb.transaction type="RequiresNew"
     */
    public void processEvent (WfAuditEvent event)
  throws RedeliveryRequiredException {
  if (logger.isDebugEnabled()) {
      logger.debug ("Got audit event: " + event);
  }
  try {
      if (!((DefaultAuditEvent)event).skip ()) {
    feedBack (event);
    // feedback may have caused the rollback already
    // without actually throwing an exception
    if (ctx.getRollbackOnly()) {
        return;
    }
      }
      // now log and publish
      if (!(event instanceof ImplCompleteAuditEvent)
    && !(event instanceof ToolInvocationFailedAuditEvent)) {
    int aes = ((DefaultAuditEvent)event).auditEventSelection ();
    // Note that if we have
    // AUDIT_SELECTION_STATE_EVENTS_ONLY, only state
    // events will be processed here, so we do not have
    // to look at the events again.
    if (aes == ProcessDefinition.AUDIT_SELECTION_ALL_EVENTS
        || aes==ProcessDefinition.AUDIT_SELECTION_STATE_EVENTS_ONLY
        || (aes == (ProcessDefinition
        .AUDIT_SELECTION_PROCESS_CLOSED_EVENTS_ONLY)
      && (event.eventType()
          .equals (WfAuditEvent.PROCESS_STATE_CHANGED))
      && (((WfStateAuditEvent)event)
          .newState().startsWith ("closed")))) {
        if (((DefaultAuditEvent)event).store ()) {
      storeAuditEvent(event);
        }
        publishEvent (event);
    }
      }
  } catch (NoSuchObjectException e) {
      // if any of the objects involved doesn't exist any more,
      // something has gone wrong. But we cannot do anything
      // about it (i.e. shouldn't happen).
      logger.error (event + " discarded: " + e.getMessage(), e);
  } catch (RemoteException e) {
      // we assume that this indicates a temporary condition.
      throw new EJBException (e);
        } catch (EJBException e) {
            // we assume that this indicates a temporary condition.
            throw e;
  } catch (SQLException e) {
      // we assume that this indicates a temporary condition.
      throw new EJBException (e);
  } catch (RedeliveryRequiredException e) {
      // we pass this one
      throw e;
  } catch (Throwable t) {
      // as we are called from a MDB, it makes no sense
      // to pass the exception to caller.
      logger.error (event + " discarded: " + t.getMessage(), t);
      ctx.setRollbackOnly();
  } finally {
      if (logger.isDebugEnabled()) {
    logger.debug ("Event has been processed: " + event);
      }
  }
    }

    /**
     * Queue the given event with a requeued count.
     *
     * @param evt the <code>WfAuditEvent</code>
     * @param requeued the number of times this has been queued
     * @ejb.interface-method view-type="local"
     */
    public void requeueEvent (DefaultAuditEvent evt, int requeued) {
        try {
            QueuerUtils.queue
                (queueConnectionFactory(), eventQueue(), evt, requeued);
        } catch (ResourceNotAvailableException e) {
            throw new EJBException (e);
        }
    }
   
    /**
     * Feed the event back to the engine, i.e. to the processes or activities
     * that may be interested in it.
     * @param event the event.
     * @throws RemoteException if a system-level error occurs.
     * @throws RedeliveryRequiredException if the event should be redelivered
     */
    private void feedBack (WfAuditEvent event)
  throws RemoteException, RedeliveryRequiredException {
  // feed back only those events that we know the receivers to
  // be interested in
  boolean processHandles = AbstractProcess.isHandled (event);
  boolean activityHandles = (event.activityKey() != null
           && AbstractActivity.isHandled (event));
  if (!(processHandles || activityHandles)) {
      if (logger.isDebugEnabled()) {
    logger.debug ("Not feeding back " + event);
      }
      return;
  }
  ExtProcessDirectoryLocal procDir = null;
  try {
      // Source is not usable internally (remote object). Lookup
            // local object in order to get process and (maybe) activity
            // in transaction.
      procDir = processDirectoryLocal();
      ProcessLocal proc = procDir.lookupProcessLocal
    (event.processMgrName(), event.processKey());
            ExtActivityLocal actLocal = null;
      if (event.activityKey() != null) {
                actLocal = (ExtActivityLocal)
                    proc.activityByKeyLocal (event.activityKey());
      }
      if (activityHandles) {
    actLocal.handleAuditEvent (event);
      }
      if (processHandles) {
    ((ExtProcessLocal)proc).handleAuditEvent (event);
      }
      if (logger.isDebugEnabled()) {
    logger.debug ("Fed back event: " + event);
      }
  } catch (InvalidKeyException e) {
      // There is a race condition between creation of the
      // process and starting it. If both create and start are
      // performed within the same transaction, it may happen
      // that the event generated by the call to start becomes
      // visible before the process, i.e. the data in the
      // database. In this case, the thread processing the event
      // may not find the process.
      if (event instanceof WfStateAuditEvent
    && event.eventType().equals (WfAuditEvent.PROCESS_STATE_CHANGED)
    && (((WfStateAuditEvent)event).newState()
        .equals (RunningState.RUNNING.toString ()))) {
    throw new RedeliveryRequiredException
        ("Process not found (yet) for " + event);
      }
      // Else if we cannot lookup the process, well then this event
      // is no good.
      logger.warn (event + " discarded, process no longer available.");
  } finally {
      EJBUtil.removeSession(procDir);
  }
    }

    /**
     * Feed the event back to the engine, i.e. to the processes or activities
     * that may be interested in it.
     * @param event the event.
     * @throws RemoteException if a system-level error occurs.
     */
    private void publishEvent (WfAuditEvent event)
  throws RemoteException {
  ProcessDirectory procDir = null;
  TopicConnection evtSvcCon = null;
  TopicSession evtSvcSession = null;
  try {
      evtSvcCon = topConFac.createTopicConnection();
      evtSvcSession = evtSvcCon.createTopicSession (true, 0);
      TopicPublisher evtSvcPublisher
                = evtSvcSession.createPublisher (eventService);
      evtSvcPublisher.setDisableMessageID (true);
      ObjectMessage msg = evtSvcSession.createObjectMessage();
      msg.setStringProperty ("processKey", event.processKey());
      msg.setStringProperty ("eventType", event.eventType());
      msg.setObject ((Serializable)event);
      evtSvcPublisher.publish (msg);
      if (logger.isDebugEnabled ()) {
    logger.debug ("Published event " + event);
      }
      evtSvcPublisher.close ();
  } catch (JMSException e) {
      logger.error ("Cannot publish "+event+": "+e.getMessage (), e);
  } finally {
      EJBUtil.removeSession(procDir);
      QueuerUtils.release(evtSvcCon, evtSvcSession);
  }
    }

    //
    // Helper methods
    //

    /**
     * Stores the given audit event data to the database.
     * @param event the event.
     * @throws SQLException in case of database access problems
     * @throws IOException in case of i/o problems
     */
    private void storeAuditEvent(WfAuditEvent event)
  throws SQLException, IOException {
  Connection con = null;
  UniversalPrepStmt prepStmt = null;
  try {
      con = ds.getConnection();
      // prepare statement
      prepStmt = new UniversalPrepStmt
    (ds, con, "INSERT INTO AuditEvents "
    + " (DBId, EventTime, EventType, ActivityKey,"
    + " ActivityName, ProcessKey, ProcessName, ProcessMgrName,"
    + " ProcessMgrVersion, EventData1, EventData2,"
    + " EventData3, EventData4, EventData5)"
    + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
      // primary key
      long primKey = EJBUtil.newPrimaryKey("AuditEvents");
      int offset = 1;
      prepStmt.setLong(offset++, primKey);
     
      // misc. data
      setupAuditEventInsertData (event, prepStmt, offset, primKey);
     
      // execute insert statement
      int rowCount = prepStmt.executeUpdate();
      if (rowCount == 0) {
    throw new EJBException
        ("Inserting audit event >>> " + event + " <<< failed.");
      }
     
      // data audit events are stored in different tables
      if (event instanceof WfDataAuditEvent){
    WfDataAuditEvent ev = (WfDataAuditEvent)event;
    storeDataAuditEventProcessData
        (con, primKey, ev.oldData(), ev.newData());
      }
      if (logger.isDebugEnabled ()) {
    logger.debug ("Saved event " + event);
      }
  } catch (ResourceNotAvailableException e) {
      throw new SQLException ("Cannot get primary key" + e.getMessage());
  } finally {
      JDBCUtil.closeAll (null, prepStmt, con);
  }
    }
   
    /**
     * Sets up the entire data fields of the given prepared statement with
     * values of the given event.
     * @param event the data source event
     * @param prepStmt the insert statement
     * @param offset an offset of the statement's parameter list
     * @param mapId an identifier for a persistant map (in use for data audit
     * events, only.)
     * @throws SQLException in case of database access problems
     * @throws IOException in case of other i/o problems
     */
    private void setupAuditEventInsertData
  (WfAuditEvent event, UniversalPrepStmt prepStmt, int offset,
   long recKey)
  throws SQLException, IOException {
 
  // data common to all events
  prepStmt.setTimestamp (offset++,
             new Timestamp(event.timeStamp().getTime()));
  prepStmt.setString (offset++, event.eventType());
  prepStmt.setString (offset++, event.activityKey());
  prepStmt.setString (offset++, event.activityName());
  prepStmt.setString (offset++, event.processKey());
  prepStmt.setString (offset++, event.processName());
  prepStmt.setString (offset++, event.processMgrName());
  prepStmt.setString (offset++, event.processMgrVersion());
     
  // event specific data
  String eventData1 = null;
  String eventData2 = null;
  String eventData3 = null;
  String eventData4 = null;
  String eventData5 = null;
  if (event instanceof WfAssignmentAuditEvent){
      WfAssignmentAuditEvent ev = (WfAssignmentAuditEvent)event;
      eventData1 = ev.oldResourceKey();
      eventData2 = ev.oldResourceName();
      eventData3 = ev.newResourceKey();
      eventData4 = ev.newResourceName();
  } else if (event instanceof WfCreateProcessAuditEvent){
      WfCreateProcessAuditEvent ev = (WfCreateProcessAuditEvent)event;
      eventData1 = ev.pActivityKey();
      eventData2 = ev.pProcessKey();
      eventData3 = ev.pProcessName();
      eventData4 = ev.pProcessMgrName();
      eventData5 = ev.pProcessMgrVersion();
  } else if (event instanceof WfDataAuditEvent){
      eventData1 = Long.toString (recKey);
  } else if (event instanceof WfStateAuditEvent){
      WfStateAuditEvent ev = (WfStateAuditEvent)event;
      eventData1 = ev.oldState();
      eventData2 = ev.newState();
  }
  prepStmt.setString (offset++, eventData1);
  prepStmt.setString (offset++, eventData2);
  prepStmt.setString (offset++, eventData3);
        prepStmt.setString (offset++, eventData4);
  prepStmt.setString (offset++, eventData5);
    }

    /**
     * Stores process data associated to a data audit event as identifed
     * by the given map id.
     * @param con the database connection to use
     * @param mapId the primary key that identifies the assocaited data
     * audit event
     * @param oldData the old data
     * @param newData the new data
     * @throws IOException in case of io problems
     */
    private void storeDataAuditEventProcessData
  (Connection con, long recKey, Map oldData, Map newData)
  throws SQLException, IOException {
  JDBCPersistentMap m = null;
  try {
      Long key = new Long(recKey);
      // old data
      if (oldData != null){
    m = new JDBCPersistentMap (ds, key, "DataAuditEventOldData");
    m.setConnection(con);
    m.setMapId (key);
    m.setNewMap ();
    m.putAll(oldData);
    try {
        m.store();
    } catch (PersistentMapSQLException e) {
        throw (SQLException)e.getCause();
    }
    m.setConnection(null);
      }
     
      // new data
      if (newData != null){
    m = new JDBCPersistentMap (ds, key, "DataAuditEventNewData");
    m.setConnection(con);
    m.setMapId (key);
    m.setNewMap ();
    m.putAll(newData);
    try {
        m.store();
    } catch (PersistentMapSQLException e) {
        throw (SQLException)e.getCause();
    }
    m.setConnection(null);
      }
  } finally {
      m.setConnection(null);
  }
    }

    /**
     * Returns a collection of <code>WfAuditEvent</code>s associated with
     * this process and stored in the database.
     * @param execObj the execution object for which to select the audit
     * events
     * @return the collection of audit events
     */
    private Collection selectAuditEvents(WfExecutionObject execObj)
  throws SQLException, IOException, NamingException {
  Connection con = null;
  PreparedStatement prepStmt = null;
  ResultSet rs = null;
  try {
      Collection events = new ArrayList();
      con = ds.getConnection();
            String selectStatement = "SELECT * FROM auditevents ";
            if (execObj instanceof WfProcess){
    selectStatement
        += " WHERE ProcessKey = ? AND ActivityKey IS NULL";
            } else {
    selectStatement
        += " WHERE ActivityKey = ?";
            }
      prepStmt = con.prepareStatement(selectStatement);
      prepStmt.setString(1, execObj.key());
      rs = prepStmt.executeQuery();
      while (rs.next()) {
    events.add(restoreAuditEvent(execObj, con, rs));
      }
      return events;
  } finally {
      JDBCUtil.closeAll (rs, prepStmt, con);
  }
    }
   
    /**
     * Restores an audit event from the given result set (element).
     * @param execObj the execution object for which to select the audit
     * events
     * @param con a database connection
     * @param rs the result set containing the event information
     * @return the audit event
     * @throws SQLException in case of SQL access problems
     */   
    private WfAuditEvent restoreAuditEvent(WfExecutionObject execObj,
             Connection con, ResultSet rs)
  throws SQLException, IOException {
  String eventType = rs.getString(3);
 
        WfAuditEvent eventBase = null;
  Timestamp ts = rs.getTimestamp(2);
  Date evtTime = new Date (ts.getTime() + ts.getNanos() / 1000000000);
  if (execObj instanceof WfProcess){
      eventBase
    = new DefaultAuditEvent
    ((WfProcess)execObj, eventType, evtTime,
     rs.getString(6), rs.getString(7), rs.getString(8),
     rs.getString(9),
     ProcessDefinition.AUDIT_SELECTION_ALL_EVENTS, false);
  } else {
      eventBase
    = new DefaultAuditEvent
    ((WfActivity)execObj, eventType, evtTime,
     rs.getString(4), rs.getString(5), rs.getString(6),
     rs.getString(7), rs.getString(8), rs.getString(9),
     ProcessDefinition.AUDIT_SELECTION_ALL_EVENTS, false);
  }
       
        if (eventType.equals(WfAuditEvent.PROCESS_CREATED)){
      return new DefaultCreateProcessAuditEvent
    (eventBase, rs.getString(10), rs.getString(11),
     rs.getString(12), rs.getString(13), rs.getString(14));
  } else if ((eventType.equals(WfAuditEvent.PROCESS_STATE_CHANGED))
       || (eventType.equals(WfAuditEvent.ACTIVITY_STATE_CHANGED))){
      return new DefaultStateAuditEvent
    (eventBase, rs.getString(10), rs.getString(11));
  } else if ((eventType.equals(WfAuditEvent.PROCESS_CONTEXT_CHANGED))
       ||(eventType.equals(WfAuditEvent.ACTIVITY_CONTEXT_CHANGED))
       ||(eventType.equals(WfAuditEvent.ACTIVITY_RESULT_CHANGED))){
      return new DefaultDataAuditEvent
    (eventBase,
     new DefaultProcessData
     (selectDataAuditEventData
      (con, rs.getString(10), "DataAuditEventOldData")),
     new DefaultProcessData
     (selectDataAuditEventData
      (con, rs.getString(10), "DataAuditEventNewData")));
        } else if (eventType.equals(WfAuditEvent.ACTIVITY_ASSIGNMENT_CHANGED)){
      return new DefaultAssignmentAuditEvent
    (eventBase, rs.getString(10), rs.getString(12),
     rs.getString(11), rs.getString(13));
  } else {
      throw new IllegalStateException
    ("Invalid audit event selected for history");
  }
    }
   
    /**
     * Returns the process data as identified by the given parameters.
     * @param con a database connection
     * @param recKey the process data identifier
     * @param tableName the table name to look for the process data
     * @return the process data
     * @throws IOException in case of data access problems
     */
    private ProcessData selectDataAuditEventData
  (Connection con, String recKey, String tableName)
  throws SQLException, IOException {
  JDBCPersistentMap m = new JDBCPersistentMap
      (ds, Long.valueOf (recKey), tableName);
  try {
      m.setConnection(con);
      m.load();
      ProcessData data = new DefaultProcessData();
      data.putAll(m);
      return data;
  } catch (PersistentMapSQLException e) {
      throw (SQLException)e.getCause();
  } finally {
      m.setConnection(null);
  }
    }

    /**
     * Clears the history of the process with the given key.
     * @param procKey the process key.
     * @ejb.interface-method view-type="local"
     */
    public void removeAuditEvents (String procKey) {
  try {
      Connection con = null;
      UniversalPrepStmt prepStmt = null;
      try {
    con = ds.getConnection();
    if (dbProperties.isMySQL()
        && dbProperties.driverMajorVersion() == 5) {
                    prepStmt = new UniversalPrepStmt
                        (ds, con, "DELETE DATAAUDITEVENTOLDDATA "
                         + "FROM DATAAUDITEVENTOLDDATA, "
                         + "(SELECT DBID FROM AUDITEVENTS "
                         + "WHERE PROCESSKEY = ?) EVENTS "
                         + "WHERE DATAAUDITEVENTOLDDATA.MAPID = EVENTS.DBID");
    } else {
        prepStmt = new UniversalPrepStmt
            (ds, con, "DELETE FROM DataAuditEventOldData WHERE "
             + "MapId IN (SELECT DBId FROM AuditEvents "
             + "WHERE ProcessKey = ?)");
    }
    prepStmt.setString(1, procKey);
    prepStmt.executeUpdate();
    prepStmt.close ();
    prepStmt = null;

                if (dbProperties.isMySQL()
                    && dbProperties.driverMajorVersion() == 5) {
                    prepStmt = new UniversalPrepStmt
                        (ds, con, "DELETE DATAAUDITEVENTNEWDATA "
                         + "FROM DATAAUDITEVENTNEWDATA, "
                         + "(SELECT DBID FROM AUDITEVENTS "
                         + "WHERE PROCESSKEY = ?) EVENTS "
                         + "WHERE DATAAUDITEVENTNEWDATA.MAPID = EVENTS.DBID");
                } else {
                    prepStmt = new UniversalPrepStmt
                        (ds, con, "DELETE FROM DataAuditEventNewData WHERE "
             + "MapId IN (SELECT DBId FROM AuditEvents "
             + "WHERE ProcessKey = ?)");
                }
    prepStmt.setString(1, procKey);
    prepStmt.executeUpdate();
    prepStmt.close ();
    prepStmt = null;

    prepStmt = new UniversalPrepStmt
        (ds, con, "DELETE FROM AuditEvents WHERE ProcessKey = ?");
    prepStmt.setString(1, procKey);
    prepStmt.executeUpdate();
      } finally {
    JDBCUtil.closeAll(null, prepStmt, con);
      }
  } catch (SQLException e) {
      throw new EJBException (e);
  }
    }
   
    /**
     * Involve the container of the given activity in the transation
     * to prevent deadlocks.
     *
     * @param activity
     */
    private void involveContainerInTx (ActivityProxy activity)
        throws RemoteException {
        try {
            processDirectoryLocal().lookupProcessLocal
                (activity.uniqueKey().managerName(),
                 activity.uniqueKey().processKey()).name();
        } catch (InvalidKeyException e) {
            throw (IllegalArgumentException)
                (new IllegalArgumentException
                 ("Container not fouond: " + e.getMessage()).initCause(e));
        }
       
    }
   
    /**
     * Forward the state change to the given activity calling
     * <code>changeState</code>. This method is used by
     * <code>ActivityProxy</code> to first involve the process in the
     * transaction. This avoids a lot of deadlocks.
     *
     * @param activity the activity
     * @param state the state
     * @throws InvalidStateException if the state change is not
     * possible
     * @throws TransitionNotAllowedException if the state change is
     * not possible
     * @ejb.interface-method view-type="remote"
     */
    public void forwardStateChange (Activity activity, State state)
        throws InvalidStateException, TransitionNotAllowedException {
        try {
            if (activity instanceof ActivityProxy) {
                involveContainerInTx((ActivityProxy)activity);
                activity = ((ActivityProxy)activity).unwrap();
            }
            activity.changeState (state);
        } catch (RemoteException e) {
            throw new EJBException (e);
        }
    }

    /**
     * Forward the state change to the given activity. This method is
     * used by <code>ActivityProxy</code> to first involve the process
     * in the transaction. This avoids a lot of deadlocks.
     *
     * @param activity the activity
     * @param result the exception data
     * @throws TransitionNotAllowedException if the state change is
     * not possible
     * @ejb.interface-method view-type="remote"
     */
    public void forwardAbandon (Activity activity, ExceptionResult result)
        throws TransitionNotAllowedException {
        try {
            if (activity instanceof ActivityProxy) {
                involveContainerInTx((ActivityProxy)activity);
                activity = ((ActivityProxy)activity).unwrap();
            }
            ((ExtActivity)activity).abandon (result);
        } catch (RemoteException e) {
            throw new EJBException (e);
        }
    }

    /**
     * Forward the state change to the given activity. This method is
     * used by <code>ActivityProxy</code> to first involve the process
     * in the transaction. This avoids a lot of deadlocks.
     *
     * @param activity the activity
     * @throws CannotCompleteException if the state change is
     * not possible
     * @ejb.interface-method view-type="remote"
     */
    public void forwardComplete (Activity activity)
        throws CannotCompleteException {
        try {
            if (activity instanceof ActivityProxy) {
                involveContainerInTx((ActivityProxy)activity);
                activity = ((ActivityProxy)activity).unwrap();
            }
            activity.complete ();
        } catch (RemoteException e) {
            throw new EJBException (e);
        }
    }

    /**
     * Forward the state change to the given activity. This method is
     * used by <code>ActivityProxy</code> to first involve the process
     * in the transaction. This avoids a lot of deadlocks.
     *
     * @param activity the activity
     * @throws CannotStopException if the state change is
     * not possible
     * @throws NotRunningException if the state change is
     * not possible
     * @ejb.interface-method view-type="remote"
     */
    public void forwardTerminate (Activity activity)
        throws CannotStopException, NotRunningException {
        try {
            if (activity instanceof ActivityProxy) {
                involveContainerInTx((ActivityProxy)activity);
                activity = ((ActivityProxy)activity).unwrap();
            }
            activity.terminate ();
        } catch (RemoteException e) {
            throw new EJBException (e);
        }
    }

    /**
     * Forward the state change to the given activity. This method is
     * used by <code>ActivityProxy</code> to first involve the process
     * in the transaction. This avoids a lot of deadlocks.
     *
     * @param activity the activity
     * @throws CannotStopException if the state change is
     * not possible
     * @throws NotRunningException if the state change is
     * not possible
     * @ejb.interface-method view-type="remote"
     */
    public void forwardAbort (Activity activity)
        throws CannotStopException, NotRunningException {
        try {
            if (activity instanceof ActivityProxy) {
                involveContainerInTx((ActivityProxy)activity);
                activity = ((ActivityProxy)activity).unwrap();
            }
            activity.abort ();
        } catch (RemoteException e) {
            throw new EJBException (e);
        }
    }

    /**
     * Forward the result to the given activity. This method is used
     * by <code>ActivityProxy</code> to first involve the process in
     * the transaction. This avoids a lot of deadlocks.
     *
     * @param activity the activity
     * @param result the result
     * @throws InvalidDataException if the result cannot be set
     * @ejb.interface-method view-type="remote"
     */
    public void forwardResult (Activity activity, ProcessData result)
        throws InvalidDataException {
        try {
            if (activity instanceof ActivityProxy) {
                involveContainerInTx((ActivityProxy)activity);
                activity = ((ActivityProxy)activity).unwrap();
            }
            activity.setResult (result);
        } catch (RemoteException e) {
            throw new EJBException (e);
        }
    }

    /**
     * Forward the request for the activity info to the given
     * activity. This method is used by <code>ActivityProxy</code> to
     * first involve the process in the transaction (activity info
     * includes the process' description). This avoids a lot of
     * deadlocks.
     *
     * @param activity the activity
     * @return the activity info
     * @ejb.interface-method view-type="remote"
     */
    public Activity.Info forwardActivityInfo (Activity activity) {
        try {
            if (activity instanceof ActivityProxy) {
                involveContainerInTx((ActivityProxy)activity);
                activity = ((ActivityProxy)activity).unwrap();
            }
            return activity.activityInfo ();
        } catch (RemoteException e) {
            throw new EJBException (e);
        }
    }

    /**
     * Choose the given activity for further processing in deferred choice.
     *
     * @param activity the activity
     * @return <code>true</code> if the activity could be made the
     * effectively chosen one
     * @throws TransitionNotAllowedException if the activity is
     * neither running nor suspended
     * @ejb.interface-method view-type="remote"
     */
    public boolean forwardChoose (Activity activity)
        throws TransitionNotAllowedException {
        try {
            if (activity instanceof ActivityProxy) {
                involveContainerInTx((ActivityProxy)activity);
                activity = ((ActivityProxy)activity).unwrap();
            }
            return activity.choose ();
        } catch (RemoteException e) {
            throw new EJBException(e);
        }
    }
   
    /**
     * Execute a batch in the context of the workflow service
     * i.e. on the server.<P>
     * @param batch the batch to be executed.
     * @return the result.
     * @throws InvocationTargetException wraps exceptions as defined
     * by the implementing class
     * @ejb.interface-method view-type="remote"
     */
    public Object executeBatch (Batch batch) throws InvocationTargetException {
  return batch.execute (new Batch.Context () {
    public boolean isRollbackOnly () {
        return ctx.getRollbackOnly();
    }
      });
    }
}
TOP

Related Classes of de.danet.an.workflow.ejbs.admin.WorkflowEngineEJB

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.