/**
* 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.interceptor.HACurrent;
import org.ow2.jonas.lib.ejb21.JEntityContext;
import org.ow2.jonas.lib.ejb21.JEntityFactory;
import org.ow2.jonas.lib.ejb21.JEntityRemote;
import org.ow2.jonas.lib.ejb21.RequestCtx;
import org.ow2.jonas.lib.util.Log;

public abstract class JRepEntityRemote extends JEntityRemote implements Remote {

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

    public JRepEntityRemote(final JEntityFactory 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 JEntityContext bctx, final Object response) throws RemoteException {
        /* ******************REPLICATION CODE STARTS HERE*********************** */
        try {
            postInvokeHook(bctx, response); // Replication algorithm link
        /* **********************END OF REPLICATION CODE************************ */
        } catch (Exception e) {
            // COMPLETE: Handle errors
            e.printStackTrace();
            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();
        }
    }

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

    /**
     * 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 JEntityContext bctx, final Object response) {
        HACurrent current = HACurrent.getHACurrent();

        Stack<RequestId> requests = current.getRequests();
        if (requests != null && !requests.isEmpty()) {
            RequestId rootId = requests.get(0);
    //        JRepUtil.addEntityBean(bctx, rootId);


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

}
