/**
* JOnAS: Java(TM) Open Application Server
* Copyright (C) 1999 Bull S.A.
* Contact: jonas-team@objectweb.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* --------------------------------------------------------------------------
* $Id: JMessageDrivenBean.java 6673 2005-04-28 16:53:00Z benoitf $
* --------------------------------------------------------------------------
*/
package org.objectweb.jonas_ejb.container;
import java.security.Identity;
import java.security.Principal;
import java.util.Properties;
import javax.ejb.EJBException;
import javax.ejb.EJBHome;
import javax.ejb.EJBLocalHome;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.ejb.TimedObject;
import javax.ejb.Timer;
import javax.ejb.TimerService;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ServerSession;
import javax.jms.Session;
import javax.jms.XASession;
import javax.resource.spi.work.Work;
import javax.resource.spi.work.WorkException;
import javax.resource.spi.work.WorkManager;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import javax.transaction.xa.XAResource;
import org.objectweb.transaction.jta.TransactionManager;
import org.objectweb.util.monolog.api.BasicLevel;
/**
* Generic interposed class for Message Driven Beans This class presents these
* interfaces, depending on object reached: ServerSession interface to the
* ServerSessionPool MessageDrivenContext interface to the bean instance
* MessageListener interface to the JMS Session Runnable interface to the
* ThreadPool
* @author Philippe Coq, Philippe Durieux
* @author Christophe Ney (Easier Enhydra integration)
*/
public class JMessageDrivenBean implements MessageListener, ServerSession, Work, MessageDrivenContext {
protected Session sess = null;
protected JMdbFactory bf = null;
protected MessageDrivenBean mdb = null;
/**
* Transactional attribute for onMessage method.
* TX_NOT_SUPPORTED, TX_REQUIRED or TX_NOT_SET (= bean managed)
*/
protected int txattr;
/**
* Transactional attribute for ejbTimeout method.
* default is TX_REQUIRES_NEW
*/
protected int timerTxAttr;
protected TransactionManager tm = null;
protected WorkManager wm = null;
/**
* constructor
* @param bf The MDB Factory
* @param sess The JMS Session
* @param mdb The Message Driven Bean
* @param wm The Work Manager
*/
public JMessageDrivenBean(JMdbFactory bf, Session sess, MessageDrivenBean mdb, WorkManager wm) {
this.bf = bf;
this.sess = sess;
this.mdb = mdb;
this.wm = wm;
// keep these locally for efficiency.
txattr = bf.getTransactionAttribute();
timerTxAttr = bf.getTimerTxAttribute();
tm = bf.getTransactionManager();
}
// ------------------------------------------------------------------
// EJBContext implementation
// ------------------------------------------------------------------
/**
* Get access to the EJB Timer Service.
* @return the EJB Timer Service
* @throws IllegalStateException Thrown if the instance is not allowed to
* use this method
*/
public TimerService getTimerService() throws IllegalStateException {
if (TraceEjb.isDebugIc()) {
TraceEjb.interp.log(BasicLevel.DEBUG, "");
}
return bf.getTimerService();
}
// ----------------------------------------------------------------------
// javax.jms.MessageListener implementation
// ----------------------------------------------------------------------
/**
* A message has been received by the Session. Basically, we have to do:
* preInvoke + onMessage + postInvoke. No exception should be returned to
* the caller.
* @param message The received message to handle.
*/
public synchronized void onMessage(Message message) {
if (TraceEjb.isDebugJms()) {
TraceEjb.mdb.log(BasicLevel.DEBUG, "");
}
RequestCtx rctx = null;
try {
if (tm.getTransaction() != null) {
// This should not occur (DEBUG)
TraceEjb.logger.log(BasicLevel.ERROR, "Transaction already OPENED");
TraceEjb.logger.log(BasicLevel.ERROR, "Transaction = " + tm.getTransaction());
Thread.dumpStack();
return;
}
rctx = bf.preInvoke(txattr);
bf.checkSecurity(null);
if (rctx.mustCommit) {
if (TraceEjb.isDebugTx()) {
TraceEjb.tx.log(BasicLevel.DEBUG, "enlistResource");
}
rctx.currTx.enlistResource(((XASession) sess).getXAResource());
}
} catch (Exception e) {
TraceEjb.logger.log(BasicLevel.ERROR, "preInvoke failed: ", e);
return;
}
try {
if (TraceEjb.isDebugJms()) {
TraceEjb.mdb.log(BasicLevel.DEBUG, "Call MDB");
}
((MessageListener) mdb).onMessage(message);
if (TraceEjb.isDebugJms()) {
TraceEjb.mdb.log(BasicLevel.DEBUG, "Return from MDB");
}
} catch (RuntimeException e) {
rctx.sysExc = e;
TraceEjb.logger.log(BasicLevel.ERROR, "runtime exception thrown by an enterprise Bean", e);
} catch (Error e) {
rctx.sysExc = e;
TraceEjb.logger.log(BasicLevel.ERROR, "error thrown by an enterprise Bean", e);
} finally {
try {
if (rctx.mustCommit) {
if (TraceEjb.isDebugTx()) {
TraceEjb.tx.log(BasicLevel.DEBUG, "delistResource");
}
rctx.currTx.delistResource(((XASession) sess).getXAResource(), XAResource.TMSUCCESS);
}
bf.postInvoke(rctx);
} catch (Exception e) {
TraceEjb.logger.log(BasicLevel.ERROR, "exception on postInvoke: ", e);
}
}
}
// ----------------------------------------------------------------------
// javax.jms.ServerSession implementation
// ----------------------------------------------------------------------
/**
* Return the ServerSession's Session. This must be a Session created by the
* same Connection which will be dispatching messages to it. The provider
* will assign one or more messages to the Session and then call start on
* the ServerSession.
* @return the server session's session.
* @exception JMSException - if a JMS fails to get associated session for
* this serverSession due to some internal error.
*/
public Session getSession() throws JMSException {
if (TraceEjb.isDebugJms()) {
TraceEjb.mdb.log(BasicLevel.DEBUG, "");
}
return sess;
}
/**
* Cause the session's run method to be called to process messages that were
* just assigned to it.
* @exception JMSException - if a JMS fails to start the server session to
* process messages.
*/
public void start() throws JMSException {
if (TraceEjb.isDebugJms()) {
TraceEjb.mdb.log(BasicLevel.DEBUG, "");
}
try {
wm.scheduleWork(this);
} catch (WorkException e) {
JMSException jmsE = new JMSException("Cannot schedule work");
jmsE.initCause(e);
throw jmsE;
}
}
// ----------------------------------------------------------------------
// Work implementation
// ----------------------------------------------------------------------
/**
* Process messages by calling run method on Session. When finished, return
* the object in the pool.
*/
public void run() {
if (TraceEjb.isDebugJms()) {
TraceEjb.mdb.log(BasicLevel.DEBUG, "");
}
// this thread must have the classloader of this container to
// be able to retrieve beans called from a MDB.
Thread.currentThread().setContextClassLoader(bf.myClassLoader());
sess.run();
bf.releaseServerSession(this);
}
public void release() {
TraceEjb.mdb.log(BasicLevel.WARN, "Ignored");
}
// ----------------------------------------------------------------------
// javax.ejb.MessageDrivenContext implementation
// ----------------------------------------------------------------------
private static final String DISALLOWED_MSG = " is disallowed in a message driven bean";
/**
* Obtains the java.security.Identity of the caller. disallowed in
* messagedriven bean method because there is no security context
* @deprecated @exception java.lang.IllegalStateException always
*/
public Identity getCallerIdentity() {
TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
throw new IllegalStateException("getCallerIdentity()" + DISALLOWED_MSG);
}
/**
* Obtain the java.security.Principal that identifies the caller. throws a
* java.lang.IllegalStateException for message driven bean because there is
* no security context available (EJB v2.0, chapter 14.5.1)
* @exception java.lang.IllegalStateException always
*/
public Principal getCallerPrincipal() {
TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
throw new IllegalStateException("getCallerPrincipal()" + DISALLOWED_MSG);
}
/**
* Test if the caller has a given role.
* @deprecated @throws java.lang.IllegalStateException for message driven
* bean because there is no security context available
*/
public boolean isCallerInRole(Identity role) {
TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
throw new IllegalStateException("isCallerInRole()" + DISALLOWED_MSG);
}
/**
* Test if the caller has a given role.
* @throws java.lang.IllegalStateException for message driven bean because
* there is no security context available
*/
public boolean isCallerInRole(java.lang.String roleLink) {
TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
throw new IllegalStateException("isCallerInRole()" + DISALLOWED_MSG);
}
/**
* Marks the current transaction for rollback. Should be used only if the
* instance is associated with a transaction
* @throws java.lang.IllegalStateException if the instance is not associated
* with a transaction
*/
public void setRollbackOnly() {
if (TraceEjb.isDebugJms()) {
TraceEjb.mdb.log(BasicLevel.DEBUG, "");
}
try {
tm.setRollbackOnly();
} catch (IllegalStateException e) {
TraceEjb.logger.log(BasicLevel.ERROR, "current thread not associated with transaction");
throw e;
} catch (SystemException e) {
TraceEjb.logger.log(BasicLevel.ERROR, "unexpected exception:", e);
}
}
/**
* Tests if the transaction has been marked for rollback only.
* @return True if transaction has been marked for rollback.
*/
public boolean getRollbackOnly() {
if (TraceEjb.isDebugJms()) {
TraceEjb.mdb.log(BasicLevel.DEBUG, "");
}
try {
if (tm.getTransaction() != null) {
switch (tm.getStatus()) {
case Status.STATUS_MARKED_ROLLBACK:
case Status.STATUS_ROLLEDBACK:
case Status.STATUS_ROLLING_BACK:
return true;
case Status.STATUS_NO_TRANSACTION:
throw new IllegalStateException("No transaction");
default:
return false;
}
} else {
TraceEjb.logger.log(BasicLevel.ERROR, "the bean is not associated in a transaction");
throw new IllegalStateException("the message driven bean is not associated in a transaction");
}
} catch (SystemException e) {
TraceEjb.logger.log(BasicLevel.ERROR, "cannot get status:", e);
return false;
}
}
/**
* Is disallowed. There is no home for message driven bean.
* @throws IllegalStateException Always.
*/
public EJBHome getEJBHome() {
TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
throw new IllegalStateException("getEJBHome()" + DISALLOWED_MSG);
}
/**
* Is disallowed. There is no local home for message driven bean.
* @throws IllegalStateException Always.
*/
public EJBLocalHome getEJBLocalHome() {
TraceEjb.logger.log(BasicLevel.ERROR, DISALLOWED_MSG);
throw new IllegalStateException("getEJBLocalHome()" + DISALLOWED_MSG);
}
/**
* @deprecated Use the JNDI naming context java:comp/env instead.
* @return properties for the bean.
*/
public Properties getEnvironment() {
TraceEjb.logger.log(BasicLevel.ERROR, "deprecated use : Use the JNDI naming context java:comp/env");
return new java.util.Properties();
}
/**
* Obtains the transaction demarcation interface.
* @return The UserTransaction interface that the enterprise bean instance
* can use for transaction demarcation.
* @exception IllegalStateException: Thrown if the instance container does
* not make the UserTransaction interface available to the
* instance.
*/
public UserTransaction getUserTransaction() throws IllegalStateException {
if (TraceEjb.isDebugJms()) {
TraceEjb.mdb.log(BasicLevel.DEBUG, "");
}
if (!bf.isTxBeanManaged()) {
throw new IllegalStateException("This bean is not allowed to use UserTransaction interface");
}
return (UserTransaction) tm;
}
// -----------------------------------------------------------------------
// other public methods
// -----------------------------------------------------------------------
/**
* Deliver a timeout to the bean
* @param timer timer whose expiration caused this notification.
*/
public void deliverTimeout(Timer timer) {
if (TraceEjb.isDebugJms()) {
TraceEjb.mdb.log(BasicLevel.DEBUG, "");
}
RequestCtx rctx = null;
try {
rctx = bf.preInvoke(timerTxAttr);
} catch (Exception e) {
TraceEjb.logger.log(BasicLevel.ERROR, "preInvoke failed: ", e);
return;
}
try {
bf.checkSecurity(null);
if (mdb instanceof TimedObject) {
((TimedObject) mdb).ejbTimeout(timer);
} else {
throw new EJBException("The bean does not implement the `TimedObject` interface");
}
} catch (EJBException e) {
rctx.sysExc = e;
TraceEjb.logger.log(BasicLevel.ERROR, "EJB exception thrown by an enterprise Bean", e);
} catch (RuntimeException e) {
rctx.sysExc = e;
TraceEjb.logger.log(BasicLevel.ERROR, "runtime exception thrown by an enterprise Bean", e);
} catch (Error e) {
rctx.sysExc = e;
TraceEjb.logger.log(BasicLevel.ERROR, "error thrown by an enterprise Bean", e);
} finally {
try {
bf.postInvoke(rctx);
} catch (Exception e) {
TraceEjb.logger.log(BasicLevel.ERROR, "exception on postInvoke: ", e);
}
}
}
}
|