001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
004: * All rights reserved.
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * $Id: ApplInvokerEJB.java,v 1.15 2007/07/08 20:35:02 mlipp Exp $
021: *
022: * $Log: ApplInvokerEJB.java,v $
023: * Revision 1.15 2007/07/08 20:35:02 mlipp
024: * Even more usage of local EJB interfaces.
025: *
026: * Revision 1.14 2007/05/03 21:58:25 mlipp
027: * Internal refactoring for making better use of local EJBs.
028: *
029: */
030: package de.danet.an.workflow.ejbs.util;
031:
032: import java.util.HashMap;
033: import java.util.Map;
034:
035: import java.rmi.RemoteException;
036:
037: import javax.ejb.EJBException;
038: import javax.ejb.MessageDrivenBean;
039: import javax.ejb.MessageDrivenContext;
040: import javax.jms.JMSException;
041: import javax.jms.Message;
042: import javax.jms.MessageListener;
043: import javax.jms.ObjectMessage;
044:
045: import de.danet.an.util.EJBUtil;
046:
047: import de.danet.an.workflow.api.Activity;
048: import de.danet.an.workflow.api.ActivityUniqueKey;
049: import de.danet.an.workflow.api.DefaultProcessData;
050:
051: import de.danet.an.workflow.domain.ToolInvocationFailedAuditEvent;
052:
053: import de.danet.an.workflow.apix.ExtActivity;
054: import de.danet.an.workflow.ejbs.admin.WorkflowEngineLocal;
055: import de.danet.an.workflow.ejbs.admin.WorkflowEngineLocalHome;
056: import de.danet.an.workflow.ejbs.core.ActivityProxy;
057: import de.danet.an.workflow.internalapi.ExtApplication;
058: import de.danet.an.workflow.internalapi.ToolInvocationException;
059: import de.danet.an.workflow.spis.aii.CannotExecuteException;
060: import de.danet.an.workflow.spis.aii.ResultProvider.ExceptionResult;
061:
062: /**
063: * This class provides the message driven bean that handles tool
064: * invocations.<P>
065: * The following diagram provides an overview of the invocation.<br></br>
066: * <img src="doc-files/ApplicationInvocation.gif"/>
067: *
068: * Setting log level to <code>DEBUG</code> will additionally output
069: * the stack traces for the issued warning messages.
070: *
071: * @author <a href="mailto:lipp@danet.de">Michael N. Lipp</a>
072: * @version $Revision: 1.15 $
073: *
074: * @ejb.bean name="ApplicationInvoker"
075: * transaction-type="Container" destination-type="javax.jms.Queue"
076: * @jonas.bean ejb-name="ApplicationInvoker"
077: * @ejb.ejb-ref ejb-name="WorkflowEngine" view-type="local"
078: * @ejb.ejb-ref ejb-name="Queuer" view-type="local"
079: * @ejb.security-identity run-as="WfMOpenAdmin"
080: * @weblogic.run-as-identity-principal WfMOpenPrincipalForAdmin
081: * @ejb.transaction type="Required"
082: * @ejb.permission role-name="WfMOpenAdmin"
083: * @jboss.destination-jndi-name
084: * name="queue/@@@_JNDI_Name_Prefix_@@@ApplicationInvocations"
085: * @jonas.message-driven-destination
086: * jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ApplicationInvocations"
087: * @weblogic.enable-call-by-reference True
088: * @weblogic.message-driven
089: * destination-jndi-name="queue/@@@_JNDI_Name_Prefix_@@@ApplicationInvocations"
090: * @jboss.container-configuration name="Standard Message Driven Bean"
091: */
092: public class ApplInvokerEJB implements MessageDrivenBean,
093: MessageListener {
094:
095: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
096: .getLog(ApplInvokerEJB.class);
097:
098: private MessageDrivenContext ctx;
099:
100: private QueuerLocal queuerCache = null;
101:
102: /**
103: * Called by container to set context.
104: * @param context the context.
105: */
106: public void setMessageDrivenContext(MessageDrivenContext context) {
107: ctx = context;
108: }
109:
110: /**
111: * The queuer must be created lazily as we need permissions that
112: * we have in onMessage only.
113: */
114: private QueuerLocal queuer() {
115: if (queuerCache == null) {
116: try {
117: queuerCache = (QueuerLocal) EJBUtil.createSession(
118: QueuerLocalHome.class,
119: "java:comp/env/ejb/Queuer");
120: } catch (RemoteException e) {
121: throw new EJBException(e);
122: }
123: }
124: return queuerCache;
125: }
126:
127: /**
128: * Called by container to create EJB.
129: */
130: public void ejbCreate() {
131: }
132:
133: /**
134: * Called by container to remove EJB.
135: */
136: public void ejbRemove() {
137: // TODO: unsure if this is a JBoss bug, but ejbRemove is
138: // called without a principal although run-as has been
139: // specified. Anyway, not removing the Queuer is not that bad.
140: // EJBUtil.removeSession(queuerCache);
141: queuerCache = null;
142: logger.debug("Removed.");
143: }
144:
145: /**
146: * Called by container when a new message is to be processed.
147: * @param message the message.
148: */
149: public void onMessage(Message message) {
150: WorkflowEngineLocal wfe = null;
151: try {
152: if (logger.isDebugEnabled() && message.getJMSRedelivered()) {
153: String msgId = "(unkown)";
154: try {
155: msgId = message.getJMSMessageID();
156: } catch (JMSException ee) {
157: logger.debug("Cannot obtain message id: "
158: + ee.getMessage());
159: }
160: logger.debug("Handling redelivered message " + msgId);
161: }
162: Map args = new HashMap((Map) ((ObjectMessage) message)
163: .getObject());
164: args.put("activity", new ActivityProxy((Object[]) args
165: .get("activity"), (ActivityUniqueKey) args
166: .get("activityUniqueKey")));
167: args.remove("activityUniqueKey");
168: Activity act = (Activity) args.get("activity");
169: ExtApplication appl = (ExtApplication) args.get("appl");
170: Map params = (Map) args.get("params");
171: wfe = (WorkflowEngineLocal) EJBUtil.createSession(
172: WorkflowEngineLocalHome.class,
173: "java:comp/env/ejb/WorkflowEngineLocal");
174: doInvoke(wfe, queuer(), act, appl, params, true);
175: } catch (RemoteException e) {
176: // a RemoteException should be a temporary condition,
177: // i.e. message may be redelivered.
178: String msgId = "(unkown)";
179: try {
180: msgId = message.getJMSMessageID();
181: } catch (JMSException ee) {
182: logger.debug("Cannot obtain message id: "
183: + ee.getMessage());
184: }
185: logger.debug(
186: "Temporary problem invoking application for message "
187: + msgId + " (retrying): " + e.getMessage(),
188: e);
189: // WLS seems to have problems with invoking setRollbackOnly twice
190: if (!ctx.getRollbackOnly()) {
191: ctx.setRollbackOnly();
192: }
193: } catch (Throwable t) {
194: // avoid redelivery of message.
195: logger.error(t.getMessage(), t);
196: } finally {
197: EJBUtil.removeSession(wfe);
198: }
199: }
200:
201: /**
202: * Execute a tool invocation using the map retrieved from the
203: * queue. Is also called from {@link EventHandlerEJB
204: * <code>EventHandlerEJB</code>}. Note that caller must have
205: * "<code>java:comp/env/ejb/WorkflowEngine</code>" in its
206: * environment.
207: * @param wfe the workflow engine
208: * @param aeq used to queue problem events from synchronous invocation
209: * @param act the activity
210: * @param appl the application
211: * @param params the invocation arguments
212: * @param useNew if <code>true</code> new transactions are used to
213: * set the activity result
214: * @throws RemoteException if a system-level error occurs.
215: */
216: public static void doInvoke(WorkflowEngineLocal wfe,
217: QueuerLocal aeq, Activity act, ExtApplication appl,
218: Map params, boolean useNew) throws RemoteException {
219: ExtApplication.InvocationResult res = null;
220: try {
221: // Invoke in separate transaction. Else all exceptions
222: // thrown by EJB methods invoked from the application
223: // cause a roll back of this transaction and no
224: // ToolInvocationFailedAuditEvent will be recorded and
225: // the invocation will be repeated (maybe endlessly)
226: res = wfe.doInvokeLocal(appl, act, params);
227: if (res != null) {
228: Object resVal = res.result();
229: if (useNew) {
230: while (true) {
231: try {
232: if (resVal instanceof ExceptionResult) {
233: wfe.doAbandonLocal(act,
234: (ExceptionResult) resVal);
235: break;
236: }
237: wfe.doFinishLocal(act, (Map) resVal);
238: break;
239: } catch (EJBException e) {
240: // Probably deadlock
241: logger
242: .warn("Error while finishing, repeating: "
243: + e.getMessage());
244: }
245: }
246: } else {
247: if (resVal instanceof ExceptionResult) {
248: ((ExtActivity) act)
249: .abandon((ExceptionResult) resVal);
250: } else {
251: act.setResult(new DefaultProcessData(
252: (Map) resVal));
253: act.complete();
254: }
255: }
256: }
257: } catch (EJBException e) {
258: // Indicates that the invocation should be retried,
259: // simply rethrow.
260: logger.warn(appl + " has requested re-invocation: "
261: + e.getMessage());
262: logger.debug("Stack trace:", e);
263: throw (RemoteException) (new RemoteException(e.getMessage()))
264: .initCause(e);
265: } catch (Throwable e) {
266: if ((e instanceof ToolInvocationException)
267: && e.getCause() != null
268: && (e.getCause() instanceof CannotExecuteException)) {
269: Throwable t = e.getCause();
270: if (t.getCause() != null) {
271: t = t.getCause();
272: }
273: logger.warn(appl + " reports " + t.getClass().getName()
274: + " during invocation (" + act
275: + " will be terminated): " + t.getMessage(), t);
276: } else {
277: logger.error("Problem invoking " + appl + " (" + act
278: + " will be terminated): "
279: + e.getClass().getName() + ": "
280: + e.getMessage(), e);
281: }
282: aeq.queue(new ToolInvocationFailedAuditEvent(act
283: .uniqueKey()));
284: return;
285: }
286: }
287: }
|