/*
* 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: ApplInvokerEJB.java 3205 2009-09-27 17:13:47Z mlipp $
*
* $Log$
* Revision 1.14 2007/05/03 21:58:25 mlipp
* Internal refactoring for making better use of local EJBs.
*
*/
package de.danet.an.workflow.ejbs.client;
import java.util.HashMap;
import java.util.Map;
import java.rmi.RemoteException;
import javax.ejb.EJBException;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import de.danet.an.util.EJBUtil;
import de.danet.an.util.logging.RequestLog;
import de.danet.an.util.logging.RequestScope;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.ActivityUniqueKey;
import de.danet.an.workflow.api.DefaultProcessData;
import de.danet.an.workflow.api.WorkflowService;
import de.danet.an.workflow.api.WorkflowServiceFactory;
import de.danet.an.workflow.apix.ExtActivity;
import de.danet.an.workflow.ejbs.WorkflowEngine;
import de.danet.an.workflow.ejbs.core.ActivityProxy;
import de.danet.an.workflow.internalapi.ExtApplication;
import de.danet.an.workflow.internalapi.ToolInvocationException;
import de.danet.an.workflow.spis.aii.CannotExecuteException;
import de.danet.an.workflow.spis.aii.ResultProvider.ExceptionResult;
/**
* This class provides the message driven bean that handles tool
* invocations.<P>
* The following diagram provides an overview of the invocation.<br></br>
* <img src="doc-files/ApplicationInvocation.gif"/>
*
* Setting log level to <code>DEBUG</code> will additionally output
* the stack traces for the issued warning messages.
*
* @author <a href="mailto:lipp@danet.de">Michael N. Lipp</a>
* @version $Revision: 3205 $
*
* @ejb.bean name="ApplicationInvoker"
* transaction-type="Container" destination-type="javax.jms.Queue"
* message-selector="FALSE"
* @jonas.bean ejb-name="ApplicationInvoker"
* @ejb.security-identity run-as="WfMOpenAdmin"
* @weblogic.run-as-identity-principal WfMOpenPrincipalForAdmin
* @ejb.transaction type="Required"
* @ejb.permission role-name="WfMOpenAdmin"
* @jboss.destination-jndi-name
* name="queue/@@@_JNDI_Name_Prefix_@@@ApplicationInvocations"
* @jonas.message-driven-destination
* jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ApplicationInvocations"
* @weblogic.enable-call-by-reference True
* @weblogic.message-driven
* destination-jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ApplicationInvocations"
* @jboss.container-configuration name="Standard Message Driven Bean"
* @ejb.ejb-ref ejb-name="InvocationHelper" view-type="local"
*/
public class ApplInvokerEJB implements MessageDrivenBean, MessageListener {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog (ApplInvokerEJB.class);
private MessageDrivenContext ctx;
private InvocationHelperLocal invocationHelperCache = null;
/**
* The invocation helper must be created lazily as we need permissions that
* we have in onMessage only.
*/
private InvocationHelperLocal invocationHelper() {
if (invocationHelperCache == null) {
try {
invocationHelperCache = (InvocationHelperLocal)
EJBUtil.createSession
(InvocationHelperLocalHome.class,
"java:comp/env/ejb/InvocationHelper");
} catch (RemoteException e) {
throw new EJBException (e);
}
}
return invocationHelperCache;
}
/**
* Called by container to set context.
* @param context the context.
*/
public void setMessageDrivenContext(MessageDrivenContext context) {
ctx = context;
}
/**
* Called by container to create EJB.
*/
public void ejbCreate() {
}
/**
* Called by container to remove EJB.
*/
public void ejbRemove() {
// TODO: unsure if this is a JBoss bug, but ejbRemove is
// called without a principal although run-as has been
// specified.
logger.debug ("Removed.");
}
/**
* Called by container when a new message is to be processed.
* @param message the message.
*/
public void onMessage(Message message) {
RequestScope scope = RequestLog.enterScope
(this, "onMessage", new Object[] { message });
try {
if (logger.isDebugEnabled() && message.getJMSRedelivered()) {
String msgId = "(unkown)";
try {
msgId = message.getJMSMessageID();
} catch (JMSException ee) {
logger.debug
("Cannot obtain message id: " + ee.getMessage ());
}
logger.debug ("Handling redelivered message " + msgId);
}
Map args = new HashMap((Map)((ObjectMessage)message).getObject());
Activity act = ActivityProxy.wrap
((ActivityUniqueKey)args.get("activityUniqueKey"));
args.put ("activity", act);
args.remove("activityUniqueKey");
ExtApplication appl = (ExtApplication)args.get("appl");
Map params = (Map)args.get("params");
doInvoke (null, invocationHelper(), act, appl, params, true);
} catch (RemoteException e) {
// a RemoteException should be a temporary condition,
// i.e. message may be redelivered.
String msgId = "(unkown)";
try {
msgId = message.getJMSMessageID();
} catch (JMSException ee) {
logger.debug ("Cannot obtain message id: " + ee.getMessage ());
}
logger.debug
("Temporary problem invoking application for message "
+ msgId + " (retrying): " + e.getMessage (), e);
// WLS seems to have problems with invoking setRollbackOnly twice
if (!ctx.getRollbackOnly()) {
ctx.setRollbackOnly();
}
} catch (Throwable t) {
// avoid redelivery of message.
logger.error (t.getMessage(), t);
} finally {
scope.leave();
}
}
/**
* Execute a tool invocation using the map retrieved from the
* queue. Is also called from {@link EventHandlerEJB
* <code>EventHandlerEJB</code>}.
* @param wfe the workflow engine
* @param act the activity
* @param appl the application
* @param params the invocation arguments
* @param useNew if <code>true</code> new transactions are used to
* set the activity result
* @throws RemoteException if a system-level error occurs.
*/
public static void doInvoke
(WorkflowEngine wfe, InvocationHelperLocal invHlp, Activity act,
ExtApplication appl, Map params, boolean useNew)
throws RemoteException {
ExtApplication.InvocationResult res = null;
RequestScope scope = RequestLog.enterScope
(ApplInvokerEJB.class, "doInvoke",
new Object[] { wfe,invHlp,act,appl,params,new Boolean(useNew) });
if (RequestLog.isEnabled()) {
scope.logMessage
("Invoking " + appl.toString() + "from " + act.toString()
+ " with parameters " + params.toString());
}
try {
// Invoke in separate transaction. Else all exceptions
// thrown by EJB methods invoked from the application
// cause a roll back of this transaction and no
// ToolInvocationFailedAuditEvent will be recorded and
// the invocation will be repeated (maybe endlessly)
res = invHlp.doInvoke(wfe, appl, act, params);
if (res != null) {
Object resVal = res.result();
if (useNew) {
while (true) {
try {
if (resVal instanceof ExceptionResult) {
invHlp.doAbandon
(wfe, act, (ExceptionResult)resVal);
break;
}
invHlp.doFinish (wfe, act, (Map)resVal);
break;
} catch (EJBException e) {
// Probably deadlock
logger.warn ("Error while finishing, repeating: "
+ e.getMessage());
}
}
} else {
if (resVal instanceof ExceptionResult) {
((ExtActivity)act).abandon((ExceptionResult)resVal);
} else {
if (act instanceof ActivityProxy) {
Activity aun = ((ActivityProxy)act).unwrap();
aun.setResult(new DefaultProcessData ((Map)resVal));
aun.complete ();
}
}
}
}
if (RequestLog.isEnabled()) {
scope.logMessage
("Result is: " + (res == null ? "null" : res.toString()));
}
} catch (RemoteException e) {
// Indicates that the invocation should be retried,
// simply re-throw.
logger.warn (appl + " has requested re-invocation: "
+ e.getMessage());
logger.debug ("Stack trace:", e);
throw e;
} catch (EJBException e) {
// Indicates that the invocation should be retried,
// simply re-throw.
logger.warn (appl + " has requested re-invocation: "
+ e.getMessage());
logger.debug ("Stack trace:", e);
throw (RemoteException)
(new RemoteException (e.getMessage(), e));
} catch (Throwable e) {
if ((e instanceof ToolInvocationException)
&& e.getCause() != null
&& (e.getCause() instanceof CannotExecuteException)) {
Throwable t = e.getCause();
if (t.getCause() != null) {
t = t.getCause();
}
logger.warn(appl + " reports " + t.getClass().getName()
+ " during invocation (" + act + " will be terminated): "
+ t.getMessage(), t);
} else {
logger.error
("Problem invoking " + appl
+ " (" + act + " will be terminated): "
+ e.getClass().getName() + ": " + e.getMessage(), e);
}
invHlp.handleToolInvocationFailed(wfe, act.uniqueKey());
return;
} finally {
scope.leave();
}
}
}