/*
 * 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 Durieux
 * Contributor(s):
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.container;

import java.rmi.NoSuchObjectException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EmptyStackException;
import java.util.List;
import java.util.ListIterator;
import javax.ejb.EJBException;
import javax.ejb.EJBLocalObject;
import javax.ejb.EJBObject;
import javax.ejb.SessionBean;
import javax.naming.NamingException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import org.objectweb.jonas_ejb.deployment.api.MethodDesc;
import org.objectweb.jonas_ejb.deployment.api.SessionDesc;

/**
 * This class is a factory for a Session Bean. It is responsible for
 * - managing Home and LocalHome.
 * - keeping the JNDI context for this component (java:comp/env)
 */
public abstract class JSessionFactory extends JFactory {

    // Timeout for this session
    int timeout = 0;

    protected JSessionHome home = null;
    protected JSessionLocalHome localhome = null;
    protected boolean isSynchro = false;
    protected boolean isStateful; // set after constructor call

    // Pool of JSessionSwitch
    protected ArrayList sessionList = new ArrayList();

    /**
     * constructor
     */
    public JSessionFactory(SessionDesc dd, JContainer cont) {
	super(dd, cont);
	TraceEjb.debugIc("JSessionFactory constructor");

	txbeanmanaged = dd.isBeanManagedTransaction();
	timeout = dd.getSessionTimeout();

	// Create the Home if defined in DD
	Class homeclass = null;
	String clname = bn.getFullWrpHomeName();
	if (clname != null) {
	    try {
		homeclass = cont.getClassLoader().loadClass(clname);
	    } catch (ClassNotFoundException e) {
		throw new EJBException(ejbname+" Cannot load "+ clname, e);
	    }
	    if (TraceEjb.isDebugIc())
		TraceEjb.debugIc(ejbname+": "+clname+" loaded");
	    try {
		// new JSessionHome(dd, this)
		int nbp = 2;
		Class ptype[] = new Class[nbp];
		Object pobj[] = new Object[nbp];
		ptype[0] = org.objectweb.jonas_ejb.deployment.api.SessionDesc.class;
		pobj[0] = (Object) dd;
		ptype[1] = org.objectweb.jonas_ejb.container.JSessionFactory.class;
		pobj[1] = (Object) this;
		home = (JSessionHome) homeclass.getConstructor(ptype).newInstance(pobj);
	    } catch (Exception e) {
		throw new EJBException(ejbname+" Cannot create home ", e);
	    }
	    // register it in JNDI
	    try {
		home.register();
	    } catch (Exception e) {
		throw new EJBException(ejbname+" Cannot register home ", e);
	    }
	}

	// Create the LocalHome if defined in DD
	clname = bn.getFullWrpLocalHomeName();
	if (clname != null) {
	    try {
		homeclass = cont.getClassLoader().loadClass(clname);
	    } catch (ClassNotFoundException e) {
		throw new EJBException(ejbname+" Cannot load "+ clname, e);
	    }
	    if (TraceEjb.isDebugIc())
		TraceEjb.debugIc(ejbname+": "+clname+" loaded");
	    try {
		// new JSessionLocalHome(dd, this)
		int nbp = 2;
		Class ptype[] = new Class[nbp];
		Object pobj[] = new Object[nbp];
		ptype[0] = org.objectweb.jonas_ejb.deployment.api.SessionDesc.class;
		pobj[0] = (Object) dd;
		ptype[1] = org.objectweb.jonas_ejb.container.JSessionFactory.class;
		pobj[1] = (Object) this;
		localhome = (JSessionLocalHome) homeclass.getConstructor(ptype).newInstance(pobj);
	    } catch (Exception e) {
		throw new EJBException(ejbname+" Cannot create localhome ", e);
	    }
	    // register it in JNDI
	    try {
		localhome.register();
	    } catch (Exception e) {
		throw new EJBException(ejbname+" Cannot register localhome ", e);
	    }
	}

    }

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

    /**
     * stop this EJB.
     * Mainly unregister it in JNDI.
     */
    public void stop() {
	TraceEjb.debugIc("JSessionFactory stop");
	try {
	    if (home != null) {
		home.unregister();
	    }
	    if (localhome != null) {
		localhome.unregister();
	    }
	} catch (NamingException e) {
	}
    }

    /**
     * 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 home;
    }

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

    // ---------------------------------------------------------------
    // JSessionSwitch pool management
    // ---------------------------------------------------------------

    /**
     * Create a new Session
     * Find one in the pool, or create a new object.
     */
    public synchronized JSessionSwitch createEJB() throws RemoteException {

	TraceEjb.debugIc("JSessionFactory: createEJB");

	JSessionSwitch bs = null;
	if (sessionList.size() > 0) {
	    bs = (JSessionSwitch) sessionList.remove(0);
	    // must re-export the remote object in the Orb since EJBObjects
	    // should be un exported when put in the pool.
	    JSessionRemote remote = bs.getRemote();
	    if (remote != null) {
		if (remote.exportObject() == false) {
		    TraceEjb.error("createEJB: Bad JSessionSwitch found in pool.");
		    return null;
		}
	    }
	} else {
	    // This depend on subclass (stateless or stateful)
	    // no need to export the EJBObject because it is basically a RemoteObject
	    bs = createNewSession();
	}

	// Start a timer for this new session
	if (timeout > 0) {
	    bs.startTimer(timeout);
	}
	return bs;
    }

    /**
     * remove a Session. This may be called also on timeout.
     * put it in the pool for later use.
     */
    public synchronized void removeEJB(JSessionSwitch bs) {

	TraceEjb.debugIc("JSessionFactory: removeEJB");

	sessionList.add(bs);
    }
    
    // ---------------------------------------------------------------
    // other public methods
    // ---------------------------------------------------------------
	
    /**
     * Session beans can be container managed or bean managed transaction
     * Session home don't check transactional context.
     */
    public void checkTransaction(RequestCtx rctx) throws SystemException {
	TraceEjb.debugTx("JSessionFactory checkTransaction");
	if (txbeanmanaged) {
	    rctx.clientTx = tm.suspend();
	} else {
	    checkTransactionContainer(rctx);
	}
    }

    public boolean isSessionSynchro() {
	return isSynchro;
    }

    /**
     * return the current timeout value
     * for Jmx
     */
    public int getTimeout() {
	return timeout;
    }

    /**
     * set the current timeout value
     * for Jmx
     */
    public void setTimeout(int t) {
	TraceEjb.debugTx("setTimeout");
	timeout = t;
    }

    /**
     * Return true if this Session is Stateful. Will be used internally by
     * EJBObject or EJBLocalObject because they are common to both types.
     */
    public boolean isStateful() {
	return isStateful;
    }

    abstract public JSessionContext getJContext();

    // ---------------------------------------------------------------
    // private methods
    // ---------------------------------------------------------------

    abstract protected JSessionSwitch createNewSession() throws RemoteException;
}
