/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@ow2.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.ow2.jonas.lib.ejb21;

import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;

import javax.ejb.RemoveException;
import javax.ejb.SessionBean;
import javax.ejb.SessionSynchronization;
import javax.ejb.TimerService;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.xml.rpc.handler.MessageContext;

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

/**
 * This class extends JSessionContext in case of Stateful Session Bean.
 * @author Philippe Coq, Philippe Durieux
 */
public class JStatefulContext extends JSessionContext implements Synchronization {

    private static final long serialVersionUID = 4070522102466667753L;
    /**
     * the bean instance implements SessionSynchronization interface
     * @serial
     */
    boolean synchro = false;

    boolean timedout = false;

    // ------------------------------------------------------------------
    // constructors
    // ------------------------------------------------------------------

    /**
     * Constructs a JStatefulContext
     * @param bf - the Session Factory
     * @param sb - the Enterprise Bean instance
     * @param sync - True if implements SessionSymchronization.
     */
    public JStatefulContext(final JSessionFactory bf, final SessionBean sb, final boolean sync) {
        super(bf, sb);
        if (TraceEjb.isDebugIc()) {
            TraceEjb.interp.log(BasicLevel.DEBUG, "");
        }
        synchro = sync;
        // Register this Context in java:comp (used by JTimerHandle)
        try {
            Context c = bf.naming.getComponentContext();
            c.rebind("MY_SF_CONTEXT", this);
        } catch (NamingException ne) {
            TraceEjb.logger.log(BasicLevel.ERROR, "Cannot bind EJBContext", ne);
        }
    }

    /**
     * set instance. Used for passivation/activation.
     * @param sb
     */
    public void setInstance(final SessionBean sb) {
        TraceEjb.logger.log(BasicLevel.DEBUG, "Set to " + sb);
        myinstance = sb;
    }

    // ------------------------------------------------------------------
    // EJBContext implementation
    // ------------------------------------------------------------------

    /**
     * Get access to the EJB Timer Service.
     * @return the EJB Timer Service
     * @throws IllegalStateException Thrown if the instance is not allowed to
     *         use this method
     */
    @Override
    public TimerService getTimerService() throws IllegalStateException {
        throw new IllegalStateException("getTimerService Not Allowed on Stateful Session Beans");
    }

    @Override
    public Map<String, Object> getContextData() {
        throw new UnsupportedOperationException("EJBs 2.1 do not support this operation.");
    }

    /**
     * Obtain a reference to the JAX-RPC MessageContext.
     * @return The MessageContext for this web service invocation.
     * @throws java.lang.IllegalStateException - the instance is in a state that
     *         does not allow access to this method.
     */
    @Override
    public MessageContext getMessageContext() throws java.lang.IllegalStateException {
        throw new IllegalStateException("getMessageContext Not Allowed on Stateful Session Beans");
    }

    @Override
    public boolean wasCancelCalled() throws IllegalStateException {
        throw new UnsupportedOperationException("EJBs 2.1 do not support this operation.");
    }

    // -------------------------------------------------------------------
    // Synchronization implementation
    // -------------------------------------------------------------------

    /**
     * This beforeCompletion method is called by the transaction manager prior
     * to the start of the transaction completion process. This method executes
     * in the transaction context of the calling thread.
     */
    public void beforeCompletion() {
        if (TraceEjb.isDebugTx()) {
            TraceEjb.tx.log(BasicLevel.DEBUG, " Starting transaction completion process");
        }

        if (synchro) {
            // Set classloader for getting the right component context
            ClassLoader old = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(bf.myClassLoader());
            Context bnctx = bf.setComponentContext();
            try {
                SessionSynchronization ss = (SessionSynchronization) getInstance();
                ss.beforeCompletion();
            } catch (RemoteException e) {
                TraceEjb.logger.log(BasicLevel.ERROR, "exception:", e);
            } finally {
                bf.resetComponentContext(bnctx);
                Thread.currentThread().setContextClassLoader(old);
            }
        }
    }

    /**
     * The afterCompletion method is called by the transaction manager after the
     * transaction is committed or rolled back. This method executes without a
     * transaction context.
     * @param status The status of the transaction completion.
     */
    public void afterCompletion(final int status) {
        if (TraceEjb.isDebugTx()) {
            TraceEjb.tx.log(BasicLevel.DEBUG, "");
        }

        if (synchro) {
            // Set classloader for getting the right component context
            ClassLoader old = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(bf.myClassLoader());
            Context bnctx = bf.setComponentContext();
            try {
                SessionSynchronization ss = (SessionSynchronization) getInstance();
                setCommitting();
                ss.afterCompletion(status == Status.STATUS_COMMITTED);
                setActive();
            } catch (RemoteException e) {
                TraceEjb.logger.log(BasicLevel.ERROR, "exception:", e);
            } finally {
                bf.resetComponentContext(bnctx);
                Thread.currentThread().setContextClassLoader(old);
            }
        }

        // Let the StatefulSwitch know that transaction is over and that it can
        // now dispose of this Context for another transaction.
        // no need to be in the correct component context ?
        JStatefulSwitch jss = (JStatefulSwitch) bs;
        jss.txCompleted();

        // The timeout has expired during the transaction. We can now remove
        // this session.
        if (timedout) {
            TraceEjb.logger.log(BasicLevel.WARN, "timeout expired during the transaction");
            timedout = false;
            // TODO
            // ((JStatefulSession)ejbObject).forceSessionRemove();
        }
    }

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

    /**
     * set this instance as removed
     */
    @Override
    public void setRemoved() throws RemoteException, RemoveException {
        if (TraceEjb.isDebugIc()) {
            TraceEjb.interp.log(BasicLevel.DEBUG, "");
        }

        // Cannot remove a stateful session inside a transaction
        JStatefulSwitch jss = (JStatefulSwitch) bs;
        if (jss.isInTransaction()) {
            TraceEjb.interp.log(BasicLevel.WARN, "Cannot remove a statefull session inside a transaction");
            throw new RemoveException("Cannot remove a statefull session inside a transaction");
        }

        // Call ejbRemove on instance
        // Assume that if this method is transacted (container transaction
        // only), this
        // transaction will be committed by the container (always true normally)
        SessionBean sb = (SessionBean) myinstance;
        if (sb != null) {
            sb.ejbRemove();
        } else {
            TraceEjb.interp.log(BasicLevel.WARN, "remove a passivated instance ?");
        }

        // Set a flag to finish remove at postInvoke.
        // getEJBObject should work in ejbRemove.
        // => we do this only after ejbRemove call.
        ismarkedremoved = true;
    }

    /**
     * Set the connection list for this instance.
     */
    @Override
    public void setConnectionList(final List conlist) {
        JStatefulSwitch jss = (JStatefulSwitch) bs;
        jss.setConnectionList(conlist);
    }

}
