/*
 * 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
 *
 * Initial developer(s): Philippe Coq, Philippe Durieux
 * Contributor(s):
 *  Christophe Ney: for making easier Enhydra integration
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.container;


import java.rmi.RemoteException;
import java.security.Identity;
import java.security.Principal;
import java.security.Guard;
import java.util.Properties;
import javax.ejb.EJBContext;
import javax.ejb.EJBHome;
import javax.ejb.EJBLocalHome;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
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.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionRolledbackException;
import javax.transaction.UserTransaction;
import javax.transaction.xa.XAResource;
import org.objectweb.jonas_ejb.container.RequestCtx;
import org.objectweb.jonas_ejb.container.TraceEjb;
import org.objectweb.jonas_ejb.deployment.api.MethodDesc;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

/**
 * 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
 */
public class JMessageDrivenBean implements MessageListener, ServerSession, Runnable, MessageDrivenContext {

    protected Session sess = null;
    protected JMdbFactory bf = null;
    protected MessageDrivenBean mdb = null;
    protected int txattr;	// TX_NOT_SUPPORTED, TX_REQUIRED  or TX_NOT_SET (= bean managed)
    protected TransactionManager tm = null;
    protected ThreadPool thpool = null;
    
    // ----------------------------------------------------------------------------
    // Constructors
    // ----------------------------------------------------------------------------

    public JMessageDrivenBean(JMdbFactory bf, Session sess, MessageDrivenBean mdb, ThreadPool thpool) {
	this.bf = bf;
	this.sess = sess;
	this.mdb = mdb;
	this.thpool = thpool;
	// keep these locally for efficiency.
	txattr = bf.getTransactionAttribute();
	tm = bf.getTransactionManager();
    }

    // ----------------------------------------------------------------------
    // 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.
     */
    public synchronized void onMessage(Message message) {
	TraceEjb.debugJms("JMessageDrivenBean: onMessage");
	
	RequestCtx rctx = null;
	try {
	    rctx = bf.preInvoke(txattr, "");
	    if (rctx.mustCommit) {
		rctx.currTx.enlistResource(((XASession)sess).getXAResource());
	    }
	} catch (Exception e) {
	    TraceEjb.error("onMessage preInvoke failed: ", e);
	    return;
	}
	try {
	    ((MessageListener)mdb).onMessage(message);
	} catch (RuntimeException e) {
	    rctx.sysExc = e;
	    TraceEjb.error("onMessage: RuntimeException thrown by an enterprise Bean", e);
	} catch (Error e) {
	    rctx.sysExc = e;
	    TraceEjb.error("onMessage: Error thrown by an enterprise Bean", e);
	} finally {
	    try {
		if (rctx.mustCommit) {
		    rctx.currTx.delistResource(((XASession)sess).getXAResource(), XAResource.TMSUCCESS);
		}
		bf.postInvoke(rctx);
	    } catch (Exception e) {
		TraceEjb.error("onMessage 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 {
	TraceEjb.debugJms("JMessageDrivenBean: getSession");
	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 {
	TraceEjb.debugJms("JMessageDrivenBean: start");
	thpool.addRunnable(this);
    }

    // ----------------------------------------------------------------------
    // Runnable implementation
    // ----------------------------------------------------------------------

    /**
     * Process messages by calling run method on Session. When finished,
     * return the object in the pool.
     */
    public void run() {
	TraceEjb.debugJms("JMessageDrivenBean: run");

	sess.run();
	bf.releaseServerSession(this);
    }
 
    // ----------------------------------------------------------------------
    // 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. 
     * DEPRECATED
     * disallowed in messagedriven bean method because there is no security context
     * @exception java.lang.IllegalStateException
     */
    public Identity getCallerIdentity() {
	TraceEjb.error("getCallerIdentity()"+ 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
     */
    public Principal getCallerPrincipal() {
	TraceEjb.error("getCallerPrincipal()"+ DISALLOWED_MSG);
	throw new IllegalStateException ("getCallerPrincipal()"+DISALLOWED_MSG);
    }
    
    /**
     * Deprecated. 
     * 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
     */
    public boolean isCallerInRole(Identity role) {
	TraceEjb.error("isCallerInRole()"+DISALLOWED_MSG);
	throw new IllegalStateException ("isCallerInRole()"+DISALLOWED_MSG);
    }

    /**
     * Test if the caller has a given role.
     * 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
     */
    public boolean isCallerInRole(java.lang.String roleLink) {
	TraceEjb.error("isCallerInRole()"+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
     * Must throw java.lang.IllegalStateException if the instance is not associated with a transaction
     *
     */
    public void setRollbackOnly() {

	TraceEjb.debugJms("JContextMessageDriven.setRollbackOnly() ");
	try {
	    tm.setRollbackOnly();
	} catch (IllegalStateException e) {
	    TraceEjb.error("Current thread not associated with transaction");
	    throw e;
	} catch (SystemException e) {
	    TraceEjb.error("setRollbackOnly unexpected exception:", e);
	}
    }
   /** 
     * Tests if the transaction has been marked for rollback only.
     */
    public boolean getRollbackOnly() {
	TraceEjb.debugJms("JContextMessageDriven.getRollbackOnly() ");

	try {
	    if (tm.getTransaction() != null) {
		switch (tm.getStatus()) {
		case Status.STATUS_MARKED_ROLLBACK:
		case Status.STATUS_ROLLEDBACK:
		case Status.STATUS_ROLLING_BACK:
		    return true;
		default:
		    return false;
		} 
	    } else {
		TraceEjb.error("IllegalState Exception 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.error("Cannot get status:", e);
	    return false;
	}
    } 
 

    /**
     * Is disallowed. There is no home for message driven bean.
     */
    public EJBHome getEJBHome() {
	TraceEjb.error(" getEJBHome()"+DISALLOWED_MSG);
	throw new IllegalStateException ("getEJBHome()"+DISALLOWED_MSG);
    }

    /**
     * Is disallowed. There is no local home for message driven bean.
     */
    public EJBLocalHome getEJBLocalHome() {
	TraceEjb.error(" getEJBLocalHome()"+DISALLOWED_MSG);
	throw new IllegalStateException ("getEJBLocalHome()"+DISALLOWED_MSG);
    }

    /**
     * Deprecated. 
     */
    public Properties getEnvironment() {
	TraceEjb.error("getEnvironment is 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 {

	TraceEjb.debugJms("JContextMessageDriven.getUserTransaction() ");

	if (!bf.isTxBeanManaged()) {
	    throw  new IllegalStateException("This bean is not allowed to use UserTransaction interface");
	}
	return (UserTransaction) tm;
    }

}
