/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2006 Bull S.A.S.
 * Contact: jonas-team@objectweb.org
 * Copyright (C) 2006 Distributed Systems Lab.
 * Universidad Polit??cnica 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
 *
 * --------------------------------------------------------------------------
 *
 * --------------------------------------------------------------------------
 */
package org.objectweb.jonas_ejb.container;

import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;

import org.objectweb.carol.cmi.ClusterStub;
import org.objectweb.carol.cmi.CmiOutputStream;
import org.objectweb.carol.cmi.DistributedEquiv;
import org.objectweb.carol.cmi.LowerOrb;
import org.objectweb.carol.cmi.ObjectId;
import org.objectweb.carol.cmi.ServerConfigException;
import org.objectweb.carol.cmi.StubConfig;
import org.objectweb.carol.cmi.StubConfigException;
import org.objectweb.carol.cmi.StubData;
import org.objectweb.carol.cmi.configuration.TraceCmi;
import org.objectweb.carol.cmi.ha.EntityBeanReference;
import org.objectweb.carol.cmi.ha.StatefulBeanReference;
import org.objectweb.carol.cmi.ha.ReplicationManager;
import org.objectweb.carol.cmi.ha.RequestId;
import org.objectweb.carol.util.configuration.ConfigurationRepository;
import org.objectweb.jonas.ha.EntityBeanRefImpl;
import org.objectweb.jonas.ha.StatefulBeanRefImpl;
import org.objectweb.jonas.ha.HaService;
import org.objectweb.jonas.ha.interceptor.HACurrentDelegateImpl;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.jonas.service.ServiceManager;
import org.objectweb.jonas_ejb.deployment.api.BeanDesc;

/**
 * This class implements common function for SFSB replication.
 * @author Francisco Perez-Sorrosal (fpsorrosal@no-spam@fi.upm.es)
 * @author Alberto Paz-Jimenez (apaz@no-spam@fi.upm.es)
 */
class JRepUtil {
    
    static boolean hasBackupResponse(RequestId requestId) {
        boolean result = false;
        
        if (TraceCmi.isDebugCmiHA()) {
            TraceCmi.debugCmiHA("---------------------------------------");
            TraceCmi.debugCmiHA("In hasBackupResponse for: " + requestId);
        }
        
        try {
            // Only injects state during failover
            if (HACurrentDelegateImpl.getCurrent().isOnFailover()) {
                result = getRepMgr().hasBackupResponse(requestId);
            }
        } catch (Exception e) {
            TraceCmi.error("Unable to obtain the info", e);
        }

        if (TraceCmi.isDebugCmiHA()) {
            TraceCmi.debugCmiHA("---------------------------------------");
        }        
        
        return result;
    }
    
    /**
     * Injects the state of the bean if the cluster object id received in the
     * request is in the current node.
     * Only injects the state if we are on failover
     */
    static void injectState(ObjectId clusterOId, JStatefulSwitch bs) {
        if (TraceCmi.isDebugCmiHA()) {
            TraceCmi.debugCmiHA("---------------------------------------");
            TraceCmi.debugCmiHA("In injectState for: " + clusterOId);
        }
        
        try {
            // Only injects state during failover
            if (HACurrentDelegateImpl.getCurrent().isOnFailover()) {
                StatefulBeanReference bean = new StatefulBeanRefImpl(bs);
                getRepMgr().restoreBeanChanges(clusterOId, bean);
            }
        } catch (Exception e) {
            TraceCmi.error("Unable to replicate the state", e);
        }

        if (TraceCmi.isDebugCmiHA()) {
            TraceCmi.debugCmiHA("---------------------------------------");
        }        
    }
    
    /**
     * Sets the clusterOId in the context, and if the bean does not implement 
     * isModifiedMethod stores the state in the context
     * @param bs the session switch
     * @param clusterOId the cluster object Id
     * @param rctx the request context
     */
    static void preInvokeHook(JSessionSwitch bs, ObjectId clusterOId, RequestCtx rctx) {
        JRepUtil.setClusterOId(bs, clusterOId, rctx);
    }
    
    static void postInvokeHook(RequestCtx rctx, ObjectId clusterOId, JSessionSwitch bs, boolean remove) {
        if (TraceCmi.isDebugCmiHA()) {
            TraceCmi.debugCmiHA("---------------------------------------");
            TraceCmi.debugCmiHA("In PostInvoke hook for: " + clusterOId);
        }

        // Create the rootId from the current HACtx/Thread association
        HACurrentDelegateImpl current = HACurrentDelegateImpl.getCurrent();
        // Obtain the root request
        RequestId rootId = null;
        try { 
            rootId = (RequestId) current.getRequests().get(0);

            if (remove) {
                JRepUtil.addRemovedBean(clusterOId, rootId);
            } else {
                JRepUtil.addModifiedBean((JStatefulSwitch) bs, clusterOId, rootId);
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            TraceCmi.debugCmiHA("Don't need to be replicate.");
        }
        
        if (TraceCmi.isDebugCmiHA()) {
            TraceCmi.debugCmiHA("---------------------------------------");
        }        
    }
    
    /**
     * Adds a bean to the beans associated with rootId
     * @param bs the bean switch
     * @param clusterOId the cluster object id
     * @param rootId the root request id
     */
    static void addModifiedBean(JStatefulSwitch bs, ObjectId clusterOId, RequestId rootId) {
        StatefulBeanReference bean = new StatefulBeanRefImpl(bs);
        
        try {
            getRepMgr().addModifiedBean(rootId, clusterOId, bean);
        } catch (Exception e) {
            TraceCmi.error("Unable to add modified bean: " + clusterOId, e);
        }
    }
    
    /**
     * Adds a bean to the beans associated with rootId marked for remove
     * @param clusterOId the cluster object id
     * @param rootId the root request id
     */
    public static void addRemovedBean(ObjectId clusterOId, RequestId rootId) {
        try {
            getRepMgr().addModifiedBean(rootId, clusterOId, null);
        } catch (Exception e) {
            TraceCmi.error("Unable to add removed bean: " + clusterOId, e);
        }
    }
    
    /**
     * Adds an entity bean to the beans associated with rootId
     * @param bctx The entity context
     * @param rootId the root request id
     */
    public static void addEntityBean(JEntityContext bctx, RequestId rootId) {
        EntityBeanReference bean = new EntityBeanRefImpl(bctx);

        try {
            getRepMgr().addEntityBean(rootId, bean);
        } catch (Exception e) {
            try {
                TraceCmi.error("Unable to add entity bean: " + bean.getPrimaryKey(), e);
            } catch (Exception e1) {
                ;
            }
        }
    }
    
    /**
     * Get the remote object as a stub object for HA remote object LB
     * @param remote remote object
     * @param dd the deployment descriptor
     * @return get remote object
     * @throws RemoteException
     * @throws ServerConfigException
     * @throws NoSuchObjectException if the clustered object is not found in the
     *         CMI registry
     */
    public static Remote getHARemoteStub(Remote remote, BeanDesc dd) throws RemoteException {
        String protocol = ConfigurationRepository.getCurrentConfiguration().getProtocol().getName();
        if (protocol.equals("cmi")) {
            StubData sd;
            ClusterStub cs;
            try {
                Remote remObject = LowerOrb.getPRODelegate().toStub(remote);
                byte[] ser = CmiOutputStream.serialize(dd.getJndiName(), remObject);
                // Need to obtain an ObjectId from DistributedEquivSystem.
                sd = new StubData(DistributedEquiv.getOidFactory().getId(), ser, 100);
                sd.setKey(dd.getJndiName());
                cs = StubConfig.instanciateClusterStub(remote.getClass(), sd);
            } catch (RemoteException e) {
                e.printStackTrace();
                throw e;
            } catch (ServerConfigException e) {
                e.printStackTrace();
                throw new StubConfigException(e.getMessage());
            }
            return cs;
        }

        return remote;
    }

    /**
     * Replication manager
     */
    private static ReplicationManager repMgr = null;

     /**
     * Get the replication manager
     * @return replication manager
     * @throws Exception if the replication manager is not set
     */
    static public ReplicationManager getRepMgr() throws Exception {
        // Get a reference to the replication manager from the HA service
        if (repMgr == null) {
            try {
                HaService svc = null;
                try {
                    // Access the high-availability service (HA)
                    svc = (HaService) ServiceManager.getInstance().getService("ha");
                } catch (ServiceException se) {
                    se.printStackTrace();
                    throw se;
                } catch (Exception e) {
                    e.printStackTrace();
                    throw e;
                }
                // Get the replication manager
                repMgr = svc.getReplicationManager();

                if (repMgr == null) {
                    TraceCmi.error("unable to obtain Replication Manager (not set)");
                    throw new Exception("unable to obtain Replication Manager");
                }
            } catch (Exception e) {
                TraceCmi.error("unable to obtain Replication Manager", e);
                throw new Exception("unable to obtain Replication Manager");
            }
        }
        return repMgr;
    }

    /**
     * Sets the clusterOId in the context
     * @param bs the session switch
     * @param clusterOId the cluster object Id
     * @param rctx the request context
     */
    static private void setClusterOId(JSessionSwitch bs, ObjectId clusterOId, RequestCtx rctx) {
        JRepStatefulContext jrsc;
        try {
            jrsc = (JRepStatefulContext) bs.getICtx(rctx.currTx);
            jrsc.setClusterOId(clusterOId);
        } catch (RemoteException e) {
            e.printStackTrace();
            TraceCmi.error("Unable to set clusterOId in context", e);
        }
    }
    
}
