/**
* High Availability Service (HA) for JOnAS
*
* Copyright (C) 2007,2008 Bull S.A.S.
* Copyright (C) 2006 Distributed Systems Lab.
* Universidad Politecnica de Madrid (Spain)
* Contact: http://lsd.ls.fi.upm.es/lsd
*
* 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.ha;


import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Stack;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.cmi.controller.common.ClusterViewManager;
import org.ow2.cmi.controller.factory.ClusterViewManagerFactory;
import org.ow2.cmi.ha.RequestId;
import org.ow2.cmi.ha.SessionId;
import org.ow2.cmi.ha.interceptor.HACurrent;
import org.ow2.jonas.lib.ejb21.JSessionFactory;
import org.ow2.jonas.lib.ejb21.JSessionRemote;
import org.ow2.jonas.lib.ejb21.JStatefulSwitch;
import org.ow2.jonas.lib.ejb21.RequestCtx;
import org.ow2.jonas.lib.util.Log;

/**
 * Generic part of the EJBObject implementation for replicated SFSBs
 * @author Francisco Perez-Sorrosal (fpsorrosal@no-spam@fi.upm.es)
 * @author Alberto Paz-Jimenez (apaz@no-spam@fi.upm.es)
 */
public abstract class JRepStatefulRemote extends JSessionRemote implements Remote {

    /**
     * Logger for traces
     */
    protected static Logger cmilogger = Log.getLogger("org.ow2.jonas.lib.ejb21.ha");

    /**
     * Constructor
     * @param bf
     * @throws RemoteException
     */
    public JRepStatefulRemote(final JSessionFactory bf) throws RemoteException {
        super(bf);
        cmilogger.log(BasicLevel.DEBUG, "");
    }

    /**
     * preInvoke is called before any request.
     * @param txa Transaction Attribute (Supports, Required, ...)
     * @return A RequestCtx object
     * @throws RemoteException Thrown when the method failed due to a
     *         system-level failure.
     */
    @Override
    public RequestCtx preInvoke(final int txa) throws RemoteException {
        RequestCtx rctx = super.preInvoke(txa);
        /* ******************REPLICATION CODE STARTS HERE*********************** */
        preInvokeHook(rctx); // Replication algorithm link
        /* **********************END OF REPLICATION CODE************************ */
        return rctx;
    }

    /**
     * postInvoke is called after any request.
     * @param rctx The RequestCtx that was returned at preInvoke()
     * @param remove TODO
     * @param response The response that is going to be returned to the client
     *        (Needed by HA)
     * @throws RemoteException Thrown when the method failed due to a
     *         system-level failure.
     */
    public void postInvoke(
            final RequestCtx rctx, final Object response, final boolean remove)
    throws RemoteException {
        /* ******************REPLICATION CODE STARTS HERE*********************** */
        try {
            postInvokeHook(rctx, response, remove); // Replication algorithm link
        /* **********************END OF REPLICATION CODE************************ */
        } catch (Exception e) {
            // COMPLETE: Handle errors
            cmilogger.log(BasicLevel.ERROR, "Error calling postInvokeHook in sfsb.", e);
        } finally {
            super.postInvoke(rctx);
            // We don't need the request anymore so remove it
            JRepUtil.removeCurrentRequest();
        }
    }

    /* ******************REPLICATION CODE STARTS HERE*********************** */

    // ------------------------------------------------------------------------
    // Protected methods ------------------------------------------------------
    // ------------------------------------------------------------------------
    /**
     * Injects the state of the bean if the cluster object id received in the
     * request is in the current node. It extracts the object id passed through
     * the interceptors from the request id included in the current HA context.
     */
    protected void injectState() {
        HACurrent current = HACurrent.getHACurrent();
        // We still need the request so do not remove it
        Stack<RequestId> requests = current.getRequests();
        if (requests != null && !requests.isEmpty()) {
            RequestId reqId = requests.peek();
            if (reqId != null) {
                JRepUtil.injectState(reqId.getObjectId(), (JStatefulSwitch) this.bs);
            }
        }
    }

    /**
     * Checks if there is a response associated with the current request id
     * @return true if the current request id have an associated response
     */
    protected boolean hasResponse() {
        HACurrent current = HACurrent.getHACurrent();
        // We still need the request so do not remove it
        Stack<RequestId> requests = current.getRequests();
        if (requests != null && !requests.isEmpty()) {
            RequestId reqId = requests.peek();
            if (reqId != null) {
                try {
                    return JRepUtil.hasBackupResponse(reqId);
                } catch (Exception e) {
                    cmilogger.log(BasicLevel.ERROR, "Cannot check response : " + reqId, e);
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * Obtain the response associated with the current request id.
     * Returns null if there is not response associated with the current requestId
     * IMPORTANT: This method remove the request id from the requests heap
     * @return the response associated with the current request id
     */
    protected Object getResponse() {
        Object response = null;

        HACurrent current = HACurrent.getHACurrent();
        // We still need the request so do not remove it
        Stack<RequestId> requests = current.getRequests();
        if (requests != null && !requests.isEmpty()) {
            RequestId reqId = requests.peek();
            if (reqId != null) {
                try {
                    response = JRepUtil.getRepMgr().getBackupResponse(reqId);
                } catch (Exception e) {
                    cmilogger.log(BasicLevel.ERROR, "Cannot obtain response : " + reqId, e);
                    response = null;
                }
            } else {
                response = null;
            }

            // Remove the request
            requests.pop();
        }
        return response;
    }

    // ------------------------------------------------------------------------
    // Private methods --------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * preInvokeHook implements the hook to perform the required replication
     * tasks before the bean context has been established. In this case, it
     * saves the current state of the bean before perform the invocation, in the
     * request context.
     * @param rctx The RequestCtx that was returned at preInvoke()
     * @throws RemoteException
     */
    private void preInvokeHook(final RequestCtx rctx) throws RemoteException {
        ClusterViewManager clusterViewManager =
            ClusterViewManagerFactory.getFactory().getClusterViewManager();
        if(clusterViewManager != null) {
            // If the call is done from other JVM this is the root request
            HACurrent current = HACurrent.getHACurrent();
            // We still need the request so do not remove it
            Stack<RequestId> requests = current.getRequests();
            if (requests != null && !requests.isEmpty()) {
                RequestId reqId = requests.peek();
                // Discard external request ids
                if (!reqId.getObjectId().getClientId().equals(
                        clusterViewManager.getUUID())) {
                    requests.clear();
                    requests.push(reqId);
                }

                JRepUtil.preInvokeHook(bs, reqId.getObjectId(), rctx);
            }
        }
    }

    /**
     * postInvokeHook implements the hook to perform the required replication
     * tasks after the bean context has been established
     * @param rctx The RequestCtx that was returned at preInvoke()
     * @param response The response that is going to be returned to the client
     * @param remove true if it is an invocation to a remove method
     */
    private void postInvokeHook(
            final RequestCtx rctx, final Object response, final boolean remove) {
        HACurrent current = HACurrent.getHACurrent();
        RequestId reqId = null;
        SessionId clusterOId = null;

        Stack<RequestId> requests = current.getRequests();
        if (requests != null && !requests.isEmpty()) {
            reqId = requests.peek();
            clusterOId = reqId.getObjectId();

            JRepUtil.postInvokeHook(rctx, clusterOId, bs, remove);

            // If this is the root request, then associate response with rootId
            if (requests.size() == 1) {
                try {
                    JRepUtil.getRepMgr().addResponse(reqId, response);
                } catch (Exception e) {
                    e.printStackTrace();
                    cmilogger.log(BasicLevel.ERROR, "unable to obtain replication manager.", e);
                }
            }
        }
    }

    /* **********************END OF REPLICATION CODE************************ */
}
