/**
 * 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$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.container;

import java.rmi.RemoteException;
import java.util.ArrayList;

import javax.ejb.EJBException;
import javax.naming.NamingException;
import javax.transaction.SystemException;

import org.objectweb.jonas_ejb.deployment.api.SessionDesc;

import org.objectweb.util.monolog.api.BasicLevel;

/**
 * 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)
 * @author Philippe Durieux
 */
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 free JSessionSwitch objects
     * Used if singleswitch=false.
     */
    protected ArrayList sessionList = new ArrayList();

    /**
     * Unique JSessionSwitch when singleswitch=true
     */
    protected JSessionSwitch uniqueSession = null;

    /**
     * If no timeout, we can manage only 1 JSessionSwitch for all sessions.
     * Only used in case of stateless, for load balancing.
     */
    protected boolean singleswitch;

    /**
     * constructor
     * @param dd The Session Deployment Descriptor
     * @param cont The Container where the bean is defined.
     */
    public JSessionFactory(SessionDesc dd, JContainer cont) {
        super(dd, cont);
        if (TraceEjb.isDebugIc()) {
            TraceEjb.interp.log(BasicLevel.DEBUG, "");
        }

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

        // Create the Home if defined in DD
        Class homeclass = null;
        String clname = dd.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.interp.log(BasicLevel.DEBUG, 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 = dd.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.interp.log(BasicLevel.DEBUG, 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);
            }
        }

    }

    /**
     * Return true if singleswitch option is on.
     * @return
     */
    public boolean singleSwitchOn() {
        return singleswitch;
    }

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

    /**
     * stop this EJB. Mainly unregister it in JNDI.
     */
    public void stop() {
        if (TraceEjb.isDebugIc()) {
            TraceEjb.interp.log(BasicLevel.DEBUG, "");
        }
        try {
            if (home != null) {
                home.unregister();
            }
            if (localhome != null) {
                localhome.unregister();
            }
        } catch (NamingException e) {
        }

        // Must unexport the unique session now.
        if (singleswitch) {
            uniqueSession.noLongerUsed();
        }
        stopContainer();
    }

    /**
     * synchronize bean instances if needed
     */
    public void syncDirty(boolean notused) {
    }

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

    /**
     * @return 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.
     * @return a SessionSwitch
     */
    public synchronized JSessionSwitch createEJB() throws RemoteException {

        if (TraceEjb.isDebugIc()) {
            TraceEjb.interp.log(BasicLevel.DEBUG, "");
        }

        if (singleswitch) {
            if (uniqueSession == null) {
                uniqueSession = createNewSession();
            }
            return uniqueSession;
        }

        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.logger.log(BasicLevel.ERROR, "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 * 1000);
        }
        return bs;
    }

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

        if (TraceEjb.isDebugIc()) {
            TraceEjb.interp.log(BasicLevel.DEBUG, "");
        }

        if (singleswitch) {
            uniqueSession = null;
        } else {
            sessionList.add(bs);
        }
    }

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

    /**
     * Session beans can be container managed or bean managed transaction
     * Session home don't check transactional context.
     * @param rctx The Request Context
     */
    public void checkTransaction(RequestCtx rctx) {
        if (txbeanmanaged) {
            try {
                rctx.clientTx = tm.suspend();
                if (TraceEjb.isDebugTx()) {
                    TraceEjb.tx.log(BasicLevel.DEBUG, "suspending tx:" + rctx.clientTx);
                }
            } catch (SystemException e) {
                throw new EJBException("cannot suspend transaction", e);
            }
        } else {
            checkTransactionContainer(rctx);
        }
    }

    /**
     * @return True if this Session implements SessionSynchronization
     */
    public boolean isSessionSynchro() {
        return isSynchro;
    }

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

    /**
     * set the current timeout value for Jmx
     * @param timeout in seconds
     */
    public void setTimeout(int t) {
        if (TraceEjb.isDebugTx()) {
            TraceEjb.tx.log(BasicLevel.DEBUG, "");
        }
        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;
    }

    /**
     * @return a JSessionContext
     */
    public abstract JSessionContext getJContext(JSessionSwitch ss);

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

    abstract protected JSessionSwitch createNewSession() throws RemoteException;
}
