/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt 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.web.tomcat.tc5.session;

import org.apache.catalina.Manager;
import org.jboss.ha.httpsession.interfaces.SerializableHttpSession;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import org.apache.catalina.util.Enumerator;
import org.apache.catalina.util.StringManager;
import org.apache.catalina.session.StandardSession;
import org.jboss.metadata.WebMetaData;
import org.jboss.logging.Logger;

/** Implementation of a clustered session for the ClusteredHttpSession-Service
 *  @see org.jboss.ha.httpsession.server.ClusteredHTTPSessionService
 *
 *  It is based on the standard catalina class org.apache.catalina.session.StandardSession
 *  (Revision: 1.25.2.1 Release: 4.0.3)
 *
 * <p><b>Revisions:</b>
 *
 * <p><b>23.11.2003 Sacha Labourey:</b>
 * <ul>
 * <li> Avoid unnecessary session replication (3 policies)</li>
 * </ul>

 @see org.jboss.ha.httpsession.server.ClusteredHTTPSessionService

 @author Thomas Peuss <jboss@peuss.de>
 @author Sacha Labourey <sacha.labourey@jboss.org>
 @version $Revision: 1.2.2.2 $
 */
class ClusteredSessionCMP
    extends StandardSession
    implements SerializableHttpSession
{
    private static final long serialVersionUID = -758573655613558722L;
    private static Logger log = Logger.getLogger(ClusteredSessionCMP.class);

   // ----------------------------------------------------- Instance Variables


   /**
    * Descriptive information describing this Session implementation.
    */
   protected static final String info = "ClusteredSessionCMP/1.0";


   /**
    * The Manager with which this Session is associated.
    */
   private transient Manager manager = null;


   /**
    * The string manager for this package.
    */
   private static StringManager sm =
         StringManager.getManager("org.jboss.web.tomcat.session");

   protected transient boolean isSessionModifiedSinceLastSave = true;
   protected transient int replicationType = WebMetaData.REPLICATION_TYPE_SYNC;
   protected transient boolean replicationTypeAlreadySet = false;

   public ClusteredSessionCMP(JBossManagerCMP manager)
   {
      super(manager);
      this.manager = manager;
   }

   /**
    * Initialize fields marked as transient after loading this session
    * from the distributed store
    * @param manager the manager for this session
    */
   protected void initAfterLoad(JBossManagerCMP manager)
   {
      setManager(manager);
      debug = 0;
      expiring = false;
      listeners = new ArrayList();
      notes = new HashMap();
      support = new PropertyChangeSupport(this);
      this.replicationType = manager.getReplicationType ();
      this.replicationTypeAlreadySet = false;


      // Notify all attributes of type HttpSessionActivationListener (SRV 7.7.2)
      this.activate();
   }
   // SerializableHttpSession-methods --------------------------------------------

   /**
    * Are attributes modified?
    * @param previousVersion The version to diff with
    */
   public boolean areAttributesModified(SerializableHttpSession previousVersion)
   {
     // return true;
      if (this == previousVersion)
      {
         if (log.isDebugEnabled())
            log.debug ("Are current session " + this.id + " attributes modified? : " + isSessionModifiedSinceLastSave);
         return isSessionModifiedSinceLastSave;
      }
      else
      {
         return true;
      }
   }

   /**
    * Get the creation time of this session
    */
   public long getContentCreationTime()
   {
      return this.getCreationTime();
   }

   /**
    * Get the last access time for this session
    */
   public long getContentLastAccessTime()
   {
      return this.getLastAccessedTime();
   }

   public void sessionHasBeenStored ()
   {
      isSessionModifiedSinceLastSave = false;
      replicationTypeAlreadySet = false;
   }

   public int getReplicationTypeForSession ()
   {
      return this.replicationType;
   }

   public void setReplicationTypeForSession (int type)
   {
      this.replicationType = type;
   }

   public boolean isReplicationTypeAlreadySet()
   {
      return replicationTypeAlreadySet;
   }
   // ----------------------------------------------------- Session Properties


   /**
    * Set the creation time for this session.  This method is called by the
    * Manager when an existing Session instance is reused.
    *
    * @param time The new creation time
    */
   public void setCreationTime(long time)
   {
       super.setCreationTime(time);
       sessionIsDirty();
   }


   /**
    * Set the authenticated Principal that is associated with this Session.
    * This provides an <code>Authenticator</code> with a means to cache a
    * previously authenticated Principal, and avoid potentially expensive
    * <code>Realm.authenticate()</code> calls on every request.
    *
    * @param principal The new Principal, or <code>null</code> if none
    */
   public void setPrincipal(Principal principal)
   {

      Principal oldPrincipal = this.principal;
      this.principal = principal;
      support.firePropertyChange("principal", oldPrincipal, this.principal);

      if ( (oldPrincipal != null && ! oldPrincipal.equals(principal) ) ||
           (oldPrincipal == null && principal != null) )
         sessionIsDirty();

   }

   /**
    * Set the Manager within which this Session is valid.
    *
    * @param manager The new Manager
    */
   public void setManager(Manager manager) {

     super.setManager(manager);

     this.manager=manager;
   }

   // ------------------------------------------------- Session Public Methods


   /**
    * Return a string representation of this object.
    */
   public String toString()
   {

      StringBuffer sb = new StringBuffer();
      sb.append("ClusteredSessionCMP[");
      sb.append(id);
      sb.append("]");
      return (sb.toString());

   }


   // ------------------------------------------------ Session Package Methods



   // ----------------------------------------------HttpSession Public Methods

   public void access()
   {
      super.access();

      // If we do not use the local cache the session is dirty
      // after every access.
      if (!((JBossManagerCMP)manager).isUseLocalCache())
      {
         this.sessionIsDirty();
      }
   }

   /**
    * Return the object bound with the specified name in this session, or
    * <code>null</code> if no object is bound with that name.
    *
    * @param name Name of the attribute to be returned
    *
    * @exception IllegalStateException if this method is called on an
    *  invalidated session
    */
   public Object getAttribute(String name)
   {

      if (!isValid)
         throw new IllegalStateException
               (sm.getString("clusteredSession.getAttribute.ise"));

      synchronized (attributes)
      {
         Object result = attributes.get(name);

         if (result != null)
         {
            int invalidationPolicy = ((AbstractJBossManager)manager).getInvalidateSessionPolicy();

            if (invalidationPolicy == WebMetaData.SESSION_INVALIDATE_SET_AND_GET)
            {
               sessionIsDirty();
            }
            else if (invalidationPolicy == WebMetaData.SESSION_INVALIDATE_SET_AND_NON_PRIMITIVE_GET)
            {
               if ( ! (result instanceof String ||
                  result instanceof Integer ||
                  result instanceof Long ||
                  result instanceof Byte ||
                  result instanceof Short ||
                  result instanceof Float ||
                  result instanceof Double ||
                  result instanceof Character ||
                  result instanceof Boolean)
                  )
               {
                  sessionIsDirty();
               }
            }
         }

         return result;
      }

   }


   /**
    * Remove the object bound with the specified name from this session.  If
    * the session does not have an object bound with this name, this method
    * does nothing.
    * <p>
    * After this method executes, and if the object implements
    * <code>HttpSessionBindingListener</code>, the container calls
    * <code>valueUnbound()</code> on the object.
    *
    * @param name Name of the object to remove from this session.
    *
    * @exception IllegalStateException if this method is called on an
    *  invalidated session
    */
   public void removeAttribute(String name, boolean notify)
   {
      // Remove this attribute from our collection
      boolean found = false;
      synchronized (attributes)
      {
         found = attributes.containsKey(name);
         if (found)
         {
            // Session is now dirty
            sessionIsDirty ();
         }
         else
         {
            return;
         }
      }

      super.removeAttribute(name, notify);
   }

   /** Override to copy the attributes key set to avoid ConcurrentModification
    * exceptions.
    * 
    * Return an <code>Enumeration</code> of <code>String</code> objects
    * containing the names of the objects bound to this session.
    *
    * @exception IllegalStateException if this method is called on an
    *  invalidated session
    */
   public Enumeration getAttributeNames()
   {
       if (!isValid())
       {
           throw new IllegalStateException
               (sm.getString("standardSession.getAttributeNames.ise"));
   }

      synchronized (attributes)
      {
         HashSet keys = new HashSet(attributes.keySet());
         Enumeration iter = new Enumerator(keys);
         return iter;
      }
   }

   /**
    * Bind an object to this session, using the specified name.  If an object
    * of the same name is already bound to this session, the object is
    * replaced.
    * <p>
    * After this method executes, and if the object implements
    * <code>HttpSessionBindingListener</code>, the container calls
    * <code>valueBound()</code> on the object.
    *
    * @param name Name to which the object is bound, cannot be null
    * @param value Object to be bound, cannot be null
    *
    * @exception IllegalArgumentException if an attempt is made to add a
    *  non-serializable object in an environment marked distributable.
    * @exception IllegalStateException if this method is called on an
    *  invalidated session
    */
   public void setAttribute(String name, Object value)
   {

       super.setAttribute(name, value);

       // Session is dirty
       sessionIsDirty ();
   }


   /**
    * Log a message on the Logger associated with our Manager (if any).
    *
    * @param message Message to be logged
    */
   protected void log(String message)
   {
      log.debug(message);
   }


   /**
    * Log a message on the Logger associated with our Manager (if any).
    *
    * @param message Message to be logged
    * @param throwable Associated exception
    */
   protected void log(String message, Throwable throwable)
   {
      log.error(message, throwable);
   }


    protected void writeObject(java.io.ObjectOutputStream out)
        throws IOException
    {
       synchronized (attributes)
       {
          out.defaultWriteObject();
       }
    }
    protected void readObject(java.io.ObjectInputStream in)
        throws IOException, ClassNotFoundException
    {
       in.defaultReadObject();
    }

   protected void sessionIsDirty ()
   {
      // Session is dirty
      isSessionModifiedSinceLastSave = true;
   }
}


// -------------------------------------------------------------- Private Class


/**
 * This class is a dummy implementation of the <code>HttpSessionContext</code>
 * interface, to conform to the requirement that such an object be returned
 * when <code>HttpSession.getSessionContext()</code> is called.
 *
 * @author Craig R. McClanahan
 *
 * @deprecated As of Java Servlet API 2.1 with no replacement.  The
 *  interface will be removed in a future version of this API.
 */

final class ClusteredSessionContext implements HttpSessionContext
{


   private HashMap dummy = new HashMap();

   /**
    * Return the session identifiers of all sessions defined
    * within this context.
    *
    * @deprecated As of Java Servlet API 2.1 with no replacement.
    *  This method must return an empty <code>Enumeration</code>
    *  and will be removed in a future version of the API.
    */
   public Enumeration getIds()
   {

      return (new Enumerator(dummy));

   }


   /**
    * Return the <code>HttpSession</code> associated with the
    * specified session identifier.
    *
    * @param id Session identifier for which to look up a session
    *
    * @deprecated As of Java Servlet API 2.1 with no replacement.
    *  This method must return null and will be removed in a
    *  future version of the API.
    */
   public HttpSession getSession(String id)
   {

      return (null);

   }


}
