/*
 * The contents of this file are subject to the JOnAS Public License Version 
 * 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License on the JOnAS web site.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 * See the License for the specific terms governing rights and limitations under 
 * the License.
 *
 * The Original Code is JOnAS application server code released July 1999.
 *
 * The Initial Developer of the Original Code is Bull S.A.
 * The Original Code and portions created by Bull S.A. are
 *    Copyright (C) 1999 Bull S.A. All Rights Reserved.
 *
 * Contributor(s):
 * Jun Inamori: Unreferenced interface for  poolling of EJBObjects - JOnAS 2.1
 * Markus Karg: Novell port - jonas-2-2-4
 *
 * --------------------------------------------------------------------------
 * $Id: JBeanSession.java,v 1.1.1.2 2001/02/27 13:38:27 cney Exp $
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.container;

import java.rmi.RemoteException;
import java.rmi.NoSuchObjectException;
import java.rmi.server.Unreferenced;
import java.util.Vector;
import javax.ejb.Handle;
import javax.ejb.SessionBean;
import javax.ejb.EJBObject;
import javax.naming.Context;
import javax.transaction.Transaction;
import javax.transaction.SystemException;

import org.objectweb.jonas.thds.ThreadData;
import org.objectweb.jonas_ejb.deployment.api.MethodDesc;

/**
 * JBeanSession is the base class for all Session Beans
 */
public abstract class JBeanSession extends JBean implements Unreferenced {


    /**
     * Saved connectionList for this instance. The real connectionList
     * is maintained in a ThreadLocal variable in ThreadData.
     * Used only for session stateful methods, if they keep
     * connection along several calls, not always in same thread.
     * This list must not be shared between all instances.
     * @serial
     */
    protected Vector connectionList = new Vector();

    /**
     * true is the corresponding instance implements SessionSynchronization interface
     * @serial
     */
    protected boolean isSessionSynchro = false;

    /**
     * unique SessionContext for this Session bean
     * @serial
     */
    protected SessionContextImpl sc = null;

    /**
     * Constructor
     */
    public JBeanSession(JSessionHome home, boolean sync) throws RemoteException {
	super(home); 
	isSessionSynchro = sync;
    }

    // --------------------------------------------------------------------------
    // EJBObject implementation
    //
    // getEJBHome	in JBean
    // getHandle	ok
    // getPrimaryKey	ok
    // isIdentical	ok
    // remove		generated
    // --------------------------------------------------------------------------

    /**
     * Obtains a handle for the EJB object. The handle can be used at later time to re-obtain
     * a reference to the EJB object, possibly in a different JVM.
     *
     * @return    A handle for the EJB object. 
     * @exception RemoteException: Thrown when the method failed due to a system-level failure.
     */
    public Handle getHandle() throws RemoteException {
	TraceEjb.debugInvoke("JBeanSession.getHandle");

	if (nosuchobject)
	    throw new NoSuchObjectException("EJBObject already removed");

	if (handleEJBObject == null) {
	    handleEJBObject = new SessionHandleImpl(this);
	}
	return (Handle) handleEJBObject;
    }

    /**
     * Obtains the primary key of the EJB Object
     * must throw RemoteException for sessionBean
     * (§5.5 Spec EJB1.1)
     */
    public Object getPrimaryKey() throws RemoteException {
	TraceEjb.error("getPrimaryKey: No primary Key for Session Bean");
	throw new RemoteException("No primary Key for Session Bean");
    }

    /**
     * Tests if a given EJB is identical to the invoked EJB object.
     *
     * @param     EJBObject obj - An object to test for identity with the invoked object. 
     * @return    True if the given EJB object is identical to the invoked object. 
     * @throws RemoteException: Thrown when the method failed due to a system-level failure.
     */
    public boolean isIdentical(EJBObject obj) throws RemoteException {

	if (nosuchobject) {
	    throw new NoSuchObjectException("EJBObject already removed");
	}

	boolean ret = false;
	if (obj != null) {
	    if (isStateFul()) {
		// In case of Statefull session bean, compares the two EJBObjects
		ret = obj.equals(this);
	    } else {
		// In case of Stateless session bean, compares the EJBHome associated to
		// the two EJBObjects
		ret = obj.getEJBHome().equals(this.getEJBHome());
	    }
	}
	return ret;
    }

    // -------------------------------------------------------------------
    // Unreferenced implementation
    // -------------------------------------------------------------------

    /*
     * Called by the RMI runtime sometime after the runtime determines that
     * the reference list, the list of clients referencing the remote object, becomes
     * empty.
     * If the container has been removed by JonasAdmin (jsh.isunregistered()) do nothing
     */
    public void unreferenced() {
	TraceEjb.debug("JBeanSession.unreferenced");
	try{
	    JSessionHome jsh = (JSessionHome)home;
	    if (!jsh.isunregistered()) {
		removeInstance();
		jsh.freeSessionEJBObject(this);
	    }
	} catch (Exception ex){
	    TraceEjb.error("Unreferenced unexpected exception : "+ex);
	}
    }

    // --------------------------------------------------------------------------
    // Other methods
    // --------------------------------------------------------------------------

    /**
     * Tests if a bean implements SessionSynchronization interface
     */
    public boolean isSynchronized() {
	return isSessionSynchro;
    }

    /**
     * returns the unique SessionContext. If not yet associated,
     * find one in the pool.
     */
    public SessionContextImpl getContext() throws RemoteException {
	if (sc == null) {
	    sc = getContextFromPool();
	}
	return sc;
    }

 

    /**
     * returns true is Session Stateful
     */
    public boolean isStateFul() {
	return ((JSessionHome)home).isStateFul;
    }

    /**
     * Get a context from the pool
     */
    public SessionContextImpl getContextFromPool() throws RemoteException {
	TraceEjb.debugInvoke("JBeanSession: getContextFromPool");
	// patched by cney to unactivate the pooling of stateful session
	// SessionContextImpl ec = (SessionContextImpl)home.fromThePool();	
	SessionContextImpl ec = null;
	if (!isStateFul()) {
	    ec = (SessionContextImpl)home.fromThePool();	
	} 
	// end patch cney
	if (ec == null) {
	    ec = createContext();
	} else {
	    ec.initSessionContext(this);
	}
	return ec;
    }

    /**
     * put back the context in the pool
     */
    public void putContextInPool() {
	TraceEjb.debugInvoke("JBeanSession: putContextInPool"); 
	if (sc!= null){
	    sc.razSessionContext();
	    // patched by cney to unactivate the pooling of stateful session
	    if (!isStateFul()) {
            home.toThePool(sc);
	    }
	    // end patch cney
	    sc = null;
	}
    }


    /**
     * Creates the SessionContext and the enterprise bean instance
     * by getContextFromPool() when the pool is empty
     * @return the SessionContextImpl object
     */
    public synchronized SessionContextImpl createContext() throws RemoteException {
	TraceEjb.debugInvoke("JBeanSession.createContext");

	String cn = home.getEnterpriseBeanClassName();
	Class  c  = null;
	SessionContextImpl sci = null;
	try {
	    // load the bean with the same class loader of the Home
	    ClassLoader loader = container.getClassLoader();
	    c = loader.loadClass(cn);

	} catch (ClassNotFoundException e) {
	    TraceEjb.error("createInstance: Failed to find class " +cn);
	    throw new RemoteException(" Container fails to find class " +cn, e);
	}

	try {
	    SessionBean sb = (SessionBean)c.newInstance();
	    
	    sci = new SessionContextImpl(this, home, sb, isSessionSynchro);

	    // Give session context to the instance.
	    sb.setSessionContext(sci);
	    if (!isStateFul()){
		// for the stateless Session bean call to ejbCreate()
		JSessionHome h = (JSessionHome)home;
		h.callejbCreate(sb);
	    }
	} catch(RemoteException ex) {
	    TraceEjb.error("Remote Exception caught on setSessionContext");
	    throw ex;
	} catch (Exception e) {
	    TraceEjb.error("createInstance: "+ cn+"Fails to create a newInstance ", e);
	    throw new RemoteException("Container cannot create a new instance of " +cn, e);
	}
	return sci;
    }


   /**
     * This method makes the container's job before the invocation of the business method.
     * Here are treated transaction attributes in order to execute the business.
     *  method in the correct transactional context. A new transaction may be created.
     *
     * @param     transAttribute Transaction attribute of the method that will be invoked.
     * @exception RemoteException: Thrown when the method failed due to a system-level failure. 
     *
     * @return    the context of the SessionBean 
     *
     */
    public RequestCtx preinvoke(int txattr, String methodSignature) throws RemoteException {
	TraceEjb.debugInvoke("JBeanSession.preinvoke");

	if (nosuchobject)
	    throw new NoSuchObjectException("Object Removed");

	// security controls
	home.checkSecurity(methodSignature);

	// Build a RequestCtx to save information about this request
	// until the postinvoke time.
	RequestCtx rctx = new RequestCtx(txattr);

	// Set bean context for JNDI
	rctx.setOldJNDICtx(home.setComponentContext());

	// Set the connectionList in threadLocal
	// and save old value in RequestContext
	Vector oldList = ThreadData.getInstance().setConnectionList(connectionList);
	rctx.setOldConnectionList(oldList);

	sc = getContext();
	rctx.setEJBContext(sc);

	if (txattr == MethodDesc.TX_BEAN_MANAGED) {
	    // Bean managed transactions
	    // Suspend client transaction if exists
	    try {
		Transaction cltx = tm.suspend();
		if (cltx != null) {
		    TraceEjb.debugInvoke("JBeanSession.preinvoke: suspending tx");
		    rctx.setClientTx(cltx);
		}
	    } catch (SystemException e) {
		TraceEjb.error("Exception in suspend:", e);
		throw new RemoteException("preinvoke failed");
	    }
	    // Resume transaction started previously in the bean
	    // Only for stateful session beans
	    try {
		if (sc.beanAssociatedTransaction != null) {
		    TraceEjb.debugInvoke("JBeanSession.preinvoke: resuming tx");
		    tm.resume(sc.beanAssociatedTransaction);
		} 
	    } catch (SystemException e) {
		TraceEjb.error("Exception in resume:", e);
		// We should resume cltx here ???
		throw new RemoteException("preinvoke failed");
	    }
	} else {
	    // Container managed transactions
	    container.checkTransactionIn(rctx);
	}

	sc.isInContainerTransaction = rctx.mustCommit();

	// Synchronization 
	// Only stateful Session bean with container-managed transaction may implements
	// the SessionSynchronization interface.
	// A stateless Session bean must not implement the SessionSynchronization interface.
	// 
	if (isSynchronized()) 
	    sc.considerRequest();

	rctx.setEJBContext(sc);
	return rctx;
    }


    /**
     * This method is called after the invocation of the business method.
     *
     * @param     rctx context of the current request
     * @exception RemoteException: Thrown when the method failed due to a system-level failure.
     */
    public void postinvoke(RequestCtx rctx) throws RemoteException {
	TraceEjb.debugInvoke("JBeanSession.postinvoke");

	if (rctx.getTxAttr() == MethodDesc.TX_BEAN_MANAGED) {
	    // tx initiated by the bean must be suspended in any case
	    Transaction tx = null;
	    try {
		tx = tm.suspend();
	    } catch(SystemException e) {
		TraceEjb.error("postinvoke: cannot suspend transaction:", e);
	    }
	    if (isStateFul()) {
		// keep it for resume in next request
		// or reinit if committed.
		sc.beanAssociatedTransaction = tx;
	    }
	    if (tx != null && !isStateFul()) {
		// Stateless Session beans are not allowed to retain a
		// transaction between 2 requests.
		try {
		    TraceEjb.error("Rollback stateless session bean transaction");
		    tx.rollback();
		} catch (SystemException e) {
		    TraceEjb.error("postinvoke: cannot rollback transaction:", e);
		}
	    }
	}
	// cney : putContext is equivalent to a raz for a stateful
	if (!isStateFul() || (nosuchobject)) {
	    putContextInPool();
	}

	// common part
	doPostInvoke(rctx);

	// Reset bean context for JNDI
	// - must be done AFTER storeIfModified() (i.e. after ejbStore)
	home.naming.setComponentContext(rctx.getOldJNDICtx());

	// Save connectionList and reset the ThreadLocal
	Vector oldList = rctx.getOldConnectionList();
	connectionList = ThreadData.getInstance().setConnectionList(oldList);
    }

    /*
     * called by SessionHome.unregister or unreferenced
     */
    public void removeInstance() {
	TraceEjb.debugInvoke("JBeanSession.removeInstance");

	if (!nosuchobject) {
	    // JNDI context must be OK. (see EJB spec. p.60)
	    Context ctx = home.setComponentContext();
	    try {
		SessionContextImpl sci = getContext();
		sci.removeInstance();
	    } catch(RemoteException e) {
		TraceEjb.error("Get the following exception on ejbRemove", e);
	    } catch(RuntimeException e) {
		TraceEjb.error("Get the following exception on ejbRemove", e);
	    } catch(Error e) {
		TraceEjb.error("Get the following exception on ejbRemove", e);
	    }
	}
	noMoreInUse();
	putContextInPool();
    }

    /*
     * exportObject
     */
    public void exportObject() throws RemoteException {
	TraceEjb.debugInvoke("JBeanSession.exportObject");
	exportObject(this, getPort());
	nosuchobject = false;
    }
}

