/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.ejb.plugins;

import java.rmi.RemoteException;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;

import javax.ejb.SessionBean;
import javax.ejb.RemoveException;
import javax.ejb.EJBException;

import javax.naming.Context;
import javax.naming.InitialContext;

import org.jboss.ejb.Container;
import org.jboss.ejb.StatefulSessionContainer;
import org.jboss.ejb.StatefulSessionPersistenceManager;
import org.jboss.ejb.StatefulSessionEnterpriseContext;
import org.jboss.metadata.ClusterConfigMetaData;

import org.jboss.ha.hasessionstate.interfaces.HASessionState;
import org.jboss.ha.hasessionstate.interfaces.PackagedSession;
import org.jboss.ha.framework.interfaces.HAPartition;

import org.jboss.system.ServiceMBeanSupport;

import org.jboss.util.id.UID;
// import org.jboss.util.collection.CompoundKey;

/**
 *  This persistence manager work with an underlying HASessionState to get
 *  clustered state.
 *
 *  @see HASessionState
 *  @see org.jboss.ha.hasessionstate.server.HASessionStateImpl
 *  
 *  @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>
 *  @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
 *  @version $Revision: 57188 $
 *
 *   <p><b>Revisions:</b>
 */

public class StatefulHASessionPersistenceManager
    extends ServiceMBeanSupport
    implements StatefulSessionPersistenceManager,
	       HASessionState.HASessionStateListener,
	       HAPersistentManager
{
   
   /** Creates new StatefulHASessionPersistenceManager */
   public StatefulHASessionPersistenceManager ()
   {
   }
   
   // Constants -----------------------------------------------------
   
   // Attributes ----------------------------------------------------

   private StatefulSessionContainer con;
   
   private HASessionState sessionState = null;
   
   private String localNodeName = null;
   private String appName = null;
   
   // Static --------------------------------------------------------

   private static String DEFAULT_HASESSIONSTATE_JNDI_NAME = "/HASessionState/Default";
   
   // Constructors --------------------------------------------------
   
   // Public --------------------------------------------------------

   public void setContainer (Container c)
   {
      con = (StatefulSessionContainer)c;
   }

   protected void createService() throws Exception
   {
      // Initialize the dataStore

      // Find HASessionState that we will use
      //
      String sessionStateName = org.jboss.metadata.ClusterConfigMetaData.DEFAULT_SESSION_STATE_NAME;
      ClusterConfigMetaData config = con.getBeanMetaData ().getClusterConfigMetaData ();
      if (config != null)
         sessionStateName = config.getHaSessionStateName ();

      Context ctx = new InitialContext ();
      try {
         this.sessionState = (HASessionState)ctx.lookup (sessionStateName);
      }
      finally {
         ctx.close();
      }
      
      this.localNodeName = this.sessionState.getNodeName ();
      this.appName = this.con.getBeanMetaData ().getJndiName ();
      
      this.sessionState.subscribe (this.appName, this);
   }
   
   protected void stopService ()
   {
      this.sessionState.unsubscribe (this.appName, this);
   }

   public Object createId(StatefulSessionEnterpriseContext ctx)
      throws Exception
   {
      //
      // jason: could probably use org.jboss.util.collection.CompoundKey here
      //        for better uniqueness based on hashCode and such...
      //
      // return new CompoundKey(this.localNodeName, new UID());
      //
      return this.localNodeName + ":" + new UID();
   }

   public void createdSession(StatefulSessionEnterpriseContext ctx)
      throws Exception
   {
      this.sessionState.createSession(this.appName, ctx.getId());
   }
   
   public void activateSession (StatefulSessionEnterpriseContext ctx) throws RemoteException
   {
      try
      {
         ObjectInputStream in;
         
         // Load state
         PackagedSession state = this.sessionState.getStateWithOwnership (this.appName, ctx.getId ());
         
         if (state == null)
            throw new EJBException ("Could not activate; failed to recover session (session has been probably removed by session-timeout)");
         
         in = new SessionObjectInputStream (ctx, new ByteArrayInputStream (state.getState ()));;
         
         ctx.setInstance ( in.readObject () );
         
         in.close ();
         
         //removePassivated (ctx.getId ());

         
         // Instruct the bean to perform activation logic
         ((SessionBean)ctx.getInstance()).ejbActivate();
      }
      catch (ClassNotFoundException e)
      {
         throw new EJBException ("Could not activate", e);
      }
      catch (IOException e)
      {
         throw new EJBException ("Could not activate", e);
      }
   }
   
   public void passivateSession (StatefulSessionEnterpriseContext ctx)
      throws RemoteException
   {
      // do nothing
   }
   
   public void synchroSession (StatefulSessionEnterpriseContext ctx) throws RemoteException
   {
      try
      {
         // Call bean
         ((SessionBean)ctx.getInstance()).ejbPassivate();
         
         // Store state
         ByteArrayOutputStream baos = new ByteArrayOutputStream ();
         ObjectOutputStream out = new SessionObjectOutputStream (baos);
         
         out.writeObject (ctx.getInstance ());
         
         out.close ();
         
         this.sessionState.setState (this.appName, ctx.getId (), baos.toByteArray ());

         ((SessionBean)ctx.getInstance()).ejbActivate(); //needed?         
      }
      catch (IOException e)
      {
         throw new EJBException ("Could not passivate", e);
      }
   }
   
   public void removeSession (StatefulSessionEnterpriseContext ctx)
      throws RemoteException, RemoveException
   {
      try
      {
         // Call bean
         ((SessionBean)ctx.getInstance()).ejbRemove();
      }
      finally
      {
         this.sessionState.removeSession (this.appName, ctx.getId ());         
      }
   }
   
   public void removePassivated (Object key)
   {
      this.sessionState.removeSession (this.appName, key);
   }
   
   // HASessionState.HASessionStateListener methods -----------------
   //
   public void sessionExternallyModified (PackagedSession session)
   {
      // this callback warns us that a session (i.e. a bean) has been externally modified
      // this means that we need to tell to our InstanceCache that this bean is no more under its control
      // and that it should not keep a cached version of it => CACHE MISS
      //
      log.trace ("Invalidating cache for session: " + session.getKey());
      this.con.getInstanceCache ().remove (session.getKey ());
   }
   
   public void newSessionStateTopology (HAPartition haSubPartition)
   {
   }
   
}
