001: /**
002: * JOnAS: Java(TM) Open Application Server
003: * Copyright (C) 1999 Bull S.A.
004: * Contact: jonas-team@objectweb.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library 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 GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: JMessageDrivenBean.java 6673 2005-04-28 16:53:00Z benoitf $
023: * --------------------------------------------------------------------------
024: */package org.objectweb.jonas_ejb.container;
025:
026: import java.security.Identity;
027: import java.security.Principal;
028: import java.util.Properties;
029:
030: import javax.ejb.EJBException;
031: import javax.ejb.EJBHome;
032: import javax.ejb.EJBLocalHome;
033: import javax.ejb.MessageDrivenBean;
034: import javax.ejb.MessageDrivenContext;
035: import javax.ejb.TimedObject;
036: import javax.ejb.Timer;
037: import javax.ejb.TimerService;
038: import javax.jms.JMSException;
039: import javax.jms.Message;
040: import javax.jms.MessageListener;
041: import javax.jms.ServerSession;
042: import javax.jms.Session;
043: import javax.jms.XASession;
044: import javax.resource.spi.work.Work;
045: import javax.resource.spi.work.WorkException;
046: import javax.resource.spi.work.WorkManager;
047: import javax.transaction.Status;
048: import javax.transaction.SystemException;
049: import javax.transaction.UserTransaction;
050: import javax.transaction.xa.XAResource;
051:
052: import org.objectweb.transaction.jta.TransactionManager;
053:
054: import org.objectweb.util.monolog.api.BasicLevel;
055:
056: /**
057: * Generic interposed class for Message Driven Beans This class presents these
058: * interfaces, depending on object reached: ServerSession interface to the
059: * ServerSessionPool MessageDrivenContext interface to the bean instance
060: * MessageListener interface to the JMS Session Runnable interface to the
061: * ThreadPool
062: * @author Philippe Coq, Philippe Durieux
063: * @author Christophe Ney (Easier Enhydra integration)
064: */
065: public class JMessageDrivenBean implements MessageListener,
066: ServerSession, Work, MessageDrivenContext {
067:
068: protected Session sess = null;
069:
070: protected JMdbFactory bf = null;
071:
072: protected MessageDrivenBean mdb = null;
073:
074: /**
075: * Transactional attribute for onMessage method.
076: * TX_NOT_SUPPORTED, TX_REQUIRED or TX_NOT_SET (= bean managed)
077: */
078: protected int txattr;
079:
080: /**
081: * Transactional attribute for ejbTimeout method.
082: * default is TX_REQUIRES_NEW
083: */
084: protected int timerTxAttr;
085:
086: protected TransactionManager tm = null;
087:
088: protected WorkManager wm = null;
089:
090: /**
091: * constructor
092: * @param bf The MDB Factory
093: * @param sess The JMS Session
094: * @param mdb The Message Driven Bean
095: * @param wm The Work Manager
096: */
097: public JMessageDrivenBean(JMdbFactory bf, Session sess,
098: MessageDrivenBean mdb, WorkManager wm) {
099: this .bf = bf;
100: this .sess = sess;
101: this .mdb = mdb;
102: this .wm = wm;
103: // keep these locally for efficiency.
104: txattr = bf.getTransactionAttribute();
105: timerTxAttr = bf.getTimerTxAttribute();
106: tm = bf.getTransactionManager();
107: }
108:
109: // ------------------------------------------------------------------
110: // EJBContext implementation
111: // ------------------------------------------------------------------
112:
113: /**
114: * Get access to the EJB Timer Service.
115: * @return the EJB Timer Service
116: * @throws IllegalStateException Thrown if the instance is not allowed to
117: * use this method
118: */
119: public TimerService getTimerService() throws IllegalStateException {
120: if (TraceEjb.isDebugIc()) {
121: TraceEjb.interp.log(BasicLevel.DEBUG, "");
122: }
123: return bf.getTimerService();
124: }
125:
126: // ----------------------------------------------------------------------
127: // javax.jms.MessageListener implementation
128: // ----------------------------------------------------------------------
129:
130: /**
131: * A message has been received by the Session. Basically, we have to do:
132: * preInvoke + onMessage + postInvoke. No exception should be returned to
133: * the caller.
134: * @param message The received message to handle.
135: */
136: public synchronized void onMessage(Message message) {
137: if (TraceEjb.isDebugJms()) {
138: TraceEjb.mdb.log(BasicLevel.DEBUG, "");
139: }
140:
141: RequestCtx rctx = null;
142: try {
143:
144: if (tm.getTransaction() != null) {
145: // This should not occur (DEBUG)
146: TraceEjb.logger.log(BasicLevel.ERROR,
147: "Transaction already OPENED");
148: TraceEjb.logger.log(BasicLevel.ERROR, "Transaction = "
149: + tm.getTransaction());
150: Thread.dumpStack();
151: return;
152: }
153:
154: rctx = bf.preInvoke(txattr);
155: bf.checkSecurity(null);
156: if (rctx.mustCommit) {
157: if (TraceEjb.isDebugTx()) {
158: TraceEjb.tx.log(BasicLevel.DEBUG, "enlistResource");
159: }
160: rctx.currTx.enlistResource(((XASession) sess)
161: .getXAResource());
162: }
163: } catch (Exception e) {
164: TraceEjb.logger.log(BasicLevel.ERROR, "preInvoke failed: ",
165: e);
166: return;
167: }
168: try {
169: if (TraceEjb.isDebugJms()) {
170: TraceEjb.mdb.log(BasicLevel.DEBUG, "Call MDB");
171: }
172: ((MessageListener) mdb).onMessage(message);
173: if (TraceEjb.isDebugJms()) {
174: TraceEjb.mdb.log(BasicLevel.DEBUG, "Return from MDB");
175: }
176: } catch (RuntimeException e) {
177: rctx.sysExc = e;
178: TraceEjb.logger
179: .log(
180: BasicLevel.ERROR,
181: "runtime exception thrown by an enterprise Bean",
182: e);
183: } catch (Error e) {
184: rctx.sysExc = e;
185: TraceEjb.logger.log(BasicLevel.ERROR,
186: "error thrown by an enterprise Bean", e);
187: } finally {
188: try {
189: if (rctx.mustCommit) {
190: if (TraceEjb.isDebugTx()) {
191: TraceEjb.tx.log(BasicLevel.DEBUG,
192: "delistResource");
193: }
194: rctx.currTx.delistResource(((XASession) sess)
195: .getXAResource(), XAResource.TMSUCCESS);
196: }
197: bf.postInvoke(rctx);
198: } catch (Exception e) {
199: TraceEjb.logger.log(BasicLevel.ERROR,
200: "exception on postInvoke: ", e);
201: }
202: }
203: }
204:
205: // ----------------------------------------------------------------------
206: // javax.jms.ServerSession implementation
207: // ----------------------------------------------------------------------
208:
209: /**
210: * Return the ServerSession's Session. This must be a Session created by the
211: * same Connection which will be dispatching messages to it. The provider
212: * will assign one or more messages to the Session and then call start on
213: * the ServerSession.
214: * @return the server session's session.
215: * @exception JMSException - if a JMS fails to get associated session for
216: * this serverSession due to some internal error.
217: */
218: public Session getSession() throws JMSException {
219: if (TraceEjb.isDebugJms()) {
220: TraceEjb.mdb.log(BasicLevel.DEBUG, "");
221: }
222: return sess;
223: }
224:
225: /**
226: * Cause the session's run method to be called to process messages that were
227: * just assigned to it.
228: * @exception JMSException - if a JMS fails to start the server session to
229: * process messages.
230: */
231: public void start() throws JMSException {
232: if (TraceEjb.isDebugJms()) {
233: TraceEjb.mdb.log(BasicLevel.DEBUG, "");
234: }
235: try {
236: wm.scheduleWork(this );
237: } catch (WorkException e) {
238: JMSException jmsE = new JMSException("Cannot schedule work");
239: jmsE.initCause(e);
240: throw jmsE;
241: }
242: }
243:
244: // ----------------------------------------------------------------------
245: // Work implementation
246: // ----------------------------------------------------------------------
247:
248: /**
249: * Process messages by calling run method on Session. When finished, return
250: * the object in the pool.
251: */
252: public void run() {
253: if (TraceEjb.isDebugJms()) {
254: TraceEjb.mdb.log(BasicLevel.DEBUG, "");
255: }
256:
257: // this thread must have the classloader of this container to
258: // be able to retrieve beans called from a MDB.
259: Thread.currentThread()
260: .setContextClassLoader(bf.myClassLoader());
261:
262: sess.run();
263: bf.releaseServerSession(this );
264: }
265:
266: public void release() {
267: TraceEjb.mdb.log(BasicLevel.WARN, "Ignored");
268: }
269:
270: // ----------------------------------------------------------------------
271: // javax.ejb.MessageDrivenContext implementation
272: // ----------------------------------------------------------------------
273:
274: private static final String DISALLOWED_MSG = " is disallowed in a message driven bean";
275:
276: /**
277: * Obtains the java.security.Identity of the caller. disallowed in
278: * messagedriven bean method because there is no security context
279: * @deprecated @exception java.lang.IllegalStateException always
280: */
281: public Identity getCallerIdentity() {
282: TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
283: throw new IllegalStateException("getCallerIdentity()"
284: + DISALLOWED_MSG);
285: }
286:
287: /**
288: * Obtain the java.security.Principal that identifies the caller. throws a
289: * java.lang.IllegalStateException for message driven bean because there is
290: * no security context available (EJB v2.0, chapter 14.5.1)
291: * @exception java.lang.IllegalStateException always
292: */
293: public Principal getCallerPrincipal() {
294: TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
295: throw new IllegalStateException("getCallerPrincipal()"
296: + DISALLOWED_MSG);
297: }
298:
299: /**
300: * Test if the caller has a given role.
301: * @deprecated @throws java.lang.IllegalStateException for message driven
302: * bean because there is no security context available
303: */
304: public boolean isCallerInRole(Identity role) {
305: TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
306: throw new IllegalStateException("isCallerInRole()"
307: + DISALLOWED_MSG);
308: }
309:
310: /**
311: * Test if the caller has a given role.
312: * @throws java.lang.IllegalStateException for message driven bean because
313: * there is no security context available
314: */
315: public boolean isCallerInRole(java.lang.String roleLink) {
316: TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
317: throw new IllegalStateException("isCallerInRole()"
318: + DISALLOWED_MSG);
319: }
320:
321: /**
322: * Marks the current transaction for rollback. Should be used only if the
323: * instance is associated with a transaction
324: * @throws java.lang.IllegalStateException if the instance is not associated
325: * with a transaction
326: */
327: public void setRollbackOnly() {
328:
329: if (TraceEjb.isDebugJms()) {
330: TraceEjb.mdb.log(BasicLevel.DEBUG, "");
331: }
332: try {
333: tm.setRollbackOnly();
334: } catch (IllegalStateException e) {
335: TraceEjb.logger.log(BasicLevel.ERROR,
336: "current thread not associated with transaction");
337: throw e;
338: } catch (SystemException e) {
339: TraceEjb.logger.log(BasicLevel.ERROR,
340: "unexpected exception:", e);
341: }
342: }
343:
344: /**
345: * Tests if the transaction has been marked for rollback only.
346: * @return True if transaction has been marked for rollback.
347: */
348: public boolean getRollbackOnly() {
349: if (TraceEjb.isDebugJms()) {
350: TraceEjb.mdb.log(BasicLevel.DEBUG, "");
351: }
352:
353: try {
354: if (tm.getTransaction() != null) {
355: switch (tm.getStatus()) {
356: case Status.STATUS_MARKED_ROLLBACK:
357: case Status.STATUS_ROLLEDBACK:
358: case Status.STATUS_ROLLING_BACK:
359: return true;
360: case Status.STATUS_NO_TRANSACTION:
361: throw new IllegalStateException("No transaction");
362: default:
363: return false;
364: }
365: } else {
366: TraceEjb.logger.log(BasicLevel.ERROR,
367: "the bean is not associated in a transaction");
368: throw new IllegalStateException(
369: "the message driven bean is not associated in a transaction");
370: }
371: } catch (SystemException e) {
372: TraceEjb.logger.log(BasicLevel.ERROR, "cannot get status:",
373: e);
374: return false;
375: }
376: }
377:
378: /**
379: * Is disallowed. There is no home for message driven bean.
380: * @throws IllegalStateException Always.
381: */
382: public EJBHome getEJBHome() {
383: TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
384: throw new IllegalStateException("getEJBHome()" + DISALLOWED_MSG);
385: }
386:
387: /**
388: * Is disallowed. There is no local home for message driven bean.
389: * @throws IllegalStateException Always.
390: */
391: public EJBLocalHome getEJBLocalHome() {
392: TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
393: throw new IllegalStateException("getEJBLocalHome()"
394: + DISALLOWED_MSG);
395: }
396:
397: /**
398: * @deprecated Use the JNDI naming context java:comp/env instead.
399: * @return properties for the bean.
400: */
401: public Properties getEnvironment() {
402: TraceEjb.logger
403: .log(BasicLevel.ERROR,
404: "deprecated use : Use the JNDI naming context java:comp/env");
405: return new java.util.Properties();
406: }
407:
408: /**
409: * Obtains the transaction demarcation interface.
410: * @return The UserTransaction interface that the enterprise bean instance
411: * can use for transaction demarcation.
412: * @exception IllegalStateException: Thrown if the instance container does
413: * not make the UserTransaction interface available to the
414: * instance.
415: */
416: public UserTransaction getUserTransaction()
417: throws IllegalStateException {
418:
419: if (TraceEjb.isDebugJms()) {
420: TraceEjb.mdb.log(BasicLevel.DEBUG, "");
421: }
422:
423: if (!bf.isTxBeanManaged()) {
424: throw new IllegalStateException(
425: "This bean is not allowed to use UserTransaction interface");
426: }
427: return (UserTransaction) tm;
428: }
429:
430: // -----------------------------------------------------------------------
431: // other public methods
432: // -----------------------------------------------------------------------
433:
434: /**
435: * Deliver a timeout to the bean
436: * @param timer timer whose expiration caused this notification.
437: */
438: public void deliverTimeout(Timer timer) {
439: if (TraceEjb.isDebugJms()) {
440: TraceEjb.mdb.log(BasicLevel.DEBUG, "");
441: }
442:
443: RequestCtx rctx = null;
444: try {
445: rctx = bf.preInvoke(timerTxAttr);
446: } catch (Exception e) {
447: TraceEjb.logger.log(BasicLevel.ERROR, "preInvoke failed: ",
448: e);
449: return;
450: }
451: try {
452: bf.checkSecurity(null);
453: if (mdb instanceof TimedObject) {
454: ((TimedObject) mdb).ejbTimeout(timer);
455: } else {
456: throw new EJBException(
457: "The bean does not implement the `TimedObject` interface");
458: }
459: } catch (EJBException e) {
460: rctx.sysExc = e;
461: TraceEjb.logger.log(BasicLevel.ERROR,
462: "EJB exception thrown by an enterprise Bean", e);
463: } catch (RuntimeException e) {
464: rctx.sysExc = e;
465: TraceEjb.logger
466: .log(
467: BasicLevel.ERROR,
468: "runtime exception thrown by an enterprise Bean",
469: e);
470: } catch (Error e) {
471: rctx.sysExc = e;
472: TraceEjb.logger.log(BasicLevel.ERROR,
473: "error thrown by an enterprise Bean", e);
474: } finally {
475: try {
476: bf.postInvoke(rctx);
477: } catch (Exception e) {
478: TraceEjb.logger.log(BasicLevel.ERROR,
479: "exception on postInvoke: ", e);
480: }
481: }
482: }
483:
484: }
|