/**
 * High Availability Service (HA) for JOnAS
 *
 * Copyright (C) 2007,2008 Bull S.A.S.
 * 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
 *
 * --------------------------------------------------------------------------
 * $Id: StatefulBeanRefImpl.java 15428 2008-10-07 11:20:29Z sauthieg $
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.lib.ejb21.ha;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import javax.ejb.SessionBean;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.cmi.ha.ReplicationException;
import org.ow2.cmi.ha.StatefulBeanReference;
import org.ow2.jonas.deployment.ejb.SessionStatefulDesc;
import org.ow2.jonas.lib.ejb21.JStatefulContext;
import org.ow2.jonas.lib.ejb21.JStatefulSwitch;
import org.ow2.jonas.lib.util.Log;

/**
 * This class implements reference to beans
 * @author Francisco Perez-Sorrosal (fpsorrosal@no-spam@fi.upm.es)
 * @author Alberto Paz-Jimenez (apaz@no-spam@fi.upm.es)
 */
public class StatefulBeanRefImpl implements StatefulBeanReference {

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

    private JStatefulSwitch bs;

    public StatefulBeanRefImpl(final JStatefulSwitch bs) {
        this.bs = bs;
    }

    public byte[] getState() throws ReplicationException {
        return getStatefulState(bs);
    }

    public boolean isModified() throws ReplicationException {
        SessionStatefulDesc ssd = (SessionStatefulDesc) bs.getBeanFactory().getDeploymentDescriptor();
        if (ssd.hasIsModifiedMethod()) {
            Method isModifiedMethod = ssd.getIsModifiedMethod();
            try {
                SessionBean sb = (bs).getStatefulContext().getInstance();
                return ((Boolean) isModifiedMethod.invoke(sb, (Object[]) null)).booleanValue();
            } catch (Exception e) {
                cmilogger.log(BasicLevel.ERROR, "Cannot invoke method " + isModifiedMethod.getName(), e);
                throw new ReplicationException("Cannot invoke method " + isModifiedMethod.getName(), e);
            }
        } else {
            return true;
        }
    }

    /* (non-Javadoc)
     * @see org.ow2.cmi.ha.BeanReference#injectState(byte[])
     */
     public void injectState(final byte[] state) throws ReplicationException {
        if (bs != null) {
            try {
                injectState(bs, state);
            } catch (Exception e) {
                cmilogger.log(BasicLevel.ERROR, "Cannot inject state for bean " + bs.getBeanFactory().getEJBName(), e);
                throw new ReplicationException("Cannot inject state for bean " + bs.getBeanFactory().getEJBName(), e);
            }
        }
     }

     /**
      * Extracts the state of the stateful session bean
      * @param jss the related switch of the stateful session bean to access the
      *        SFBS's real instance
     * @throws ReplicationException
      */
     static private byte[] getStatefulState(final JStatefulSwitch jss) throws ReplicationException {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         JStatefulContext ctx = jss.getStatefulContext();
         try {
             SessionBean instance = ctx.getInstance();
             JRepStatefulOutputStream out = new JRepStatefulOutputStream(bos);
//			 ObjectOutputStream out = new ObjectOutputStream(bos);
             out.writeObject(instance);
             out.close();
         } catch (Exception e) {
             cmilogger.log(BasicLevel.ERROR, "Can't get state of " + jss.getBeanFactory().getEJBName() + " inst.", e);
             throw new ReplicationException("Can't get state of " + jss.getBeanFactory().getEJBName() + " inst.", e);
         }
         return bos.toByteArray();
     }

     /**
      * Injects the serialized state of the bean in the corresponding beanSwitch
      * @param bean target bean
      * @param beanState state to inject
      * @throws IOException
      * @throws ClassNotFoundException
      * @throws IllegalAccessException
      * @throws IllegalArgumentException
      */
     static private void injectState(final JStatefulSwitch bean, final byte[] beanState)
     throws IOException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException {

         JStatefulContext bctx = bean.getStatefulContext();
         ByteArrayInputStream bais = new ByteArrayInputStream(beanState);
         JRepStatefulInputStream in = new JRepStatefulInputStream(bais, bean);
         ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(bean.getBeanFactory().myClassLoader());
//		 ObjectInputStream in = new ObjectInputStream(bais);
         Object obj;
         try {
             obj = in.readObject();
         } finally {
             Thread.currentThread().setContextClassLoader(oldClassLoader);
         }
         in.close();
         SessionBean sb = (SessionBean) obj;

         // Needed for the new serialization
//		 sb.setSessionContext(bean.getStatefulContext());

         // Deal with transient values
         SessionBean oldInstance = bctx.getInstance();
         for (Field field : sb.getClass().getDeclaredFields()) {
             if (Modifier.isTransient(field.getModifiers())) {
                 field.setAccessible(true);
                 field.set(sb,field.get(oldInstance));
             }
         }

         bctx.setInstance(sb);
         sb.ejbActivate();
     }
}
