/*
 * 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): 
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.container;

 
import java.util.Stack;
import java.util.EmptyStackException;
import java.util.Properties;
import javax.ejb.EJBException;
import javax.ejb.MessageDrivenBean; 
import javax.ejb.MessageDrivenContext;
import javax.ejb.RemoveException; 
import javax.jms.Connection;
import javax.jms.ConnectionConsumer;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.ServerSession;
import javax.jms.ServerSessionPool;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.XAQueueConnection;
import javax.jms.XAQueueConnectionFactory;
import javax.jms.XATopicConnection;
import javax.jms.XATopicConnectionFactory;
import javax.naming.Context;
import org.objectweb.jonas_ejb.deployment.api.BeanDesc;
import org.objectweb.jonas_ejb.deployment.api.MessageDrivenDesc;
import org.objectweb.jonas_ejb.deployment.api.MethodDesc;
import org.objectweb.jonas_jms.api.JmsManager;

/**
 * This class is a factory for a Message Driven Bean
 * There is one such class per MDB class.
 * Contains all information related to the bean and set up all JMS environment for the bean
 * It manages a ServerSession pool to server MDB requests.
 */
public class JMdbFactory extends JFactory implements ServerSessionPool {

    /**
     * JMS Manager
     */
    private JmsManager jms = null;

    /**
     * Connection Consumer for this message driven bean
     */
    ConnectionConsumer cc = null;

    /**
     * pool of ServerSession objects
     */
    private Stack sspool = new Stack();	// Stack is already synchronized

    /**
     * Threadpool used to run ServerSessions
     */
    protected ThreadPool threadpool = null;

    /**
     * JMS Connection (Topic or Queue)
     * always use XA Connections for transactions.
     */
    protected XATopicConnection tconn = null;
    protected XAQueueConnection qconn = null;

    /**
     * Constructor 
     */
    public JMdbFactory(MessageDrivenDesc dd, JContainer cont, ThreadPool thp) {
	super(dd, cont);
	threadpool = thp;

	// Check if tx managed by the bean or the container
	txbeanmanaged = dd.isBeanManagedTransaction();

	// Check that JMS Service has been run in the server
	jms = cont.getJmsManager();
	if (jms == null) {
	    TraceEjb.error("Cannot deploy a Message Driven Bean without the JMS Service");
	    throw new EJBException("JMS Service must be run");
	}

	// Create a Connection Consumer, depending on Deployment Descriptor
	String selector = dd.getSelector();
	String dest = dd.getDestinationJndiName();

	// Get the number max of messages sent on a Session at one time.
	// There is a bug in Joram: If the number is greater than 1, Joram
	// will wait until this nb is reached!
	// We set the number max to 1 to work around this bug.
	// LATER: maxMessage = dd.getMaxMessages(), with this value configurable.
	int maxMessages = 1;

	try {
	    if (dd.isTopicDestination()) {
		// topic
		XATopicConnectionFactory tcf = jms.getXATopicConnectionFactory();
		tconn = tcf.createXATopicConnection();
		Topic t = jms.getTopic(dest);
		if (dd.isSubscriptionDurable()) {
		    TraceEjb.debugJms("JMdbFactory createDurableConnectionConsumer for "+ejbname);
		    cc  = tconn.createDurableConnectionConsumer(t, ejbname, selector, this, maxMessages);
		} else {
		    TraceEjb.debugJms("JMdbFactory createConnectionConsumer for "+dest);
		    cc  = tconn.createConnectionConsumer(t, selector, this, maxMessages);
		}
		tconn.start();
	    } else {
		// queue
		XAQueueConnectionFactory qcf = jms.getXAQueueConnectionFactory();
		qconn = qcf.createXAQueueConnection();
		Queue q = jms.getQueue(dest);
		TraceEjb.debugJms("JMdbFactory createConnectionConsumer for "+dest);
		cc = qconn.createConnectionConsumer(q, selector, this, maxMessages);
		qconn.start();
	    }
	} catch (Exception e) {
	    throw new EJBException("Cannot create connection consumer:", e);
	}
    }

    // ---------------------------------------------------------------
    // Specific BeanFactory implementation
    // ---------------------------------------------------------------

    /**
     * @return the size of the ServerSessionPool
     */
    public int getPoolSize() {
	return sspool.size();
    }

    /**
     * stop this EJB.
     * call ejbRemove on all MDB
     * close the connection consumer
     * Stop the threads and remove the beans
     */
    public void stop() {
	TraceEjb.debugJms("JMdbFactory: stop");
	try {
	    cc.close();
	} catch(javax.jms.JMSException e) {
	    TraceEjb.verbose("unregister: Cannot close Connection Consumer");
	}
    }

    /**
     * synchronize bean instances if needed
     */
    public void sync() {
    }

    /**
     * Reduce number of instances in memory
     */
    public void reduceCache() {
    }

    /**
     * returns the home if exist
     */
    public JHome getHome() {
	return null;
    }

    /**
     * returns the local home if exist
     */
    public JLocalHome getLocalHome() {
	return null;
    }

    // ---------------------------------------------------------------
    // ServerSessionPool Implementation
    // ---------------------------------------------------------------

    /**
     * Return a server session from the pool.
     *
     * @return	a server session from the pool.
     * @exception    JMSException - if an application server fails to return a Server Session
     *   out of its server session pool.
     */
    public ServerSession getServerSession() throws JMSException {
	TraceEjb.debugJms("JMdbFactory: getServerSession");

	// try to get one from the Server Session Pool
	ServerSession ss = null;
	try {
	    ss = (ServerSession) sspool.pop();
	    TraceEjb.debugJms("JMdbFactory pop: pool size = "+sspool.size());
	} catch (EmptyStackException e) {
	    TraceEjb.debugJms("JMdbFactory pop: empty pool");
	}

	// If pool is empty, creates a new one.
	if (ss == null) {
	    // Creates the Session object
	    Session sess = null;
	    MessageDrivenDesc mdd = (MessageDrivenDesc) dd;
	    if (tconn != null) {
		if (mdd.isRequired()) {
		    sess = tconn.createXATopicSession();
		} else {
		    sess = tconn.createTopicSession(false, mdd.getAcknowledgeMode());
		}
	    } else if (qconn != null) {
		if (mdd.isRequired()) {
		    sess = qconn.createXAQueueSession();
		} else {
		    sess = qconn.createQueueSession(false, mdd.getAcknowledgeMode());
		}
	    } else {
		TraceEjb.error("JMdbFactory: Connection not initialized");
		return ss;
	    }
	    // Creates the new instance
	    MessageDrivenBean mdb = null;
	    try {
		mdb  = (MessageDrivenBean) beanclass.newInstance();
	    } catch (Exception e) {
		TraceEjb.error("JMdbFactory: failed to create instance:"+e);
		throw new EJBException("Container failed to create instance of Message Driven Bean");
	    }
	    
	    // Instanciates a new JMessageDrivenBean object
	    // and set it as the MessageListener for this Session.
	    ss = new JMessageDrivenBean(this, sess, mdb, threadpool);
	    sess.setMessageListener((MessageListener)ss);

	    // starts the bean instance: setMessageDrivenContext() + ejbCreate()
	    // see EJB spec. 2.0 page 322.
	    // Both operations must be called with the correct ComponentContext
	    Context ctxsave = setComponentContext();
	    mdb.setMessageDrivenContext((MessageDrivenContext)ss);
	    try {
		beanclass.getMethod("ejbCreate", null).invoke(mdb, null);
	    } catch (Exception e) {
		TraceEjb.error("JMdbFactory:  cannot call ejbCreate on message driven bean instance "+e);
		throw new EJBException(" Container fails to call ejbCreate on message driven bean instance");
	    } finally {
		resetComponentContext(ctxsave);
	    }
	}
	return ss;
    }

    // ---------------------------------------------------------------
    // Other methods
    // ---------------------------------------------------------------

    /**
     * return the ServerSession to the pool
     */
    public void releaseServerSession(ServerSession ss) {
	sspool.push(ss);	
	TraceEjb.debugJms("JMdbFactory push: pool size = "+sspool.size());
    }

    /**
     * return the Transaction Attribute
     */
    public int getTransactionAttribute() {
	return ((MessageDrivenDesc)dd).getTxAttribute();
    }

    // ---------------------------------------------------------------
    // other public methods
    // ---------------------------------------------------------------

    /**
     * For Message Driven Beans, only 2 cases are possible.
     */
    public void checkTransaction(RequestCtx rctx) {
	if (rctx.txAttr == MethodDesc.TX_REQUIRED) {
	    try {
		tm.begin();
		rctx.mustCommit = true;
		rctx.currTx = tm.getTransaction();
	    } catch (Exception e) {
		// No exception raised in case of MDB
		TraceEjb.error("JMdbFactory: cannot start tx:"+e);
		return;
	    }
	}
    }

}
