/* **************************************************************************

  $Archive: /njcl_v2/src/com/novell/service/session/SessionEnv.java $
  $Revision: 22 $
  $Modtime: 1/18/01 2:23p $

  Copyright (c) 1997 Novell, Inc.  All Rights Reserved.

  THIS WORK IS  SUBJECT  TO  U.S.  AND  INTERNATIONAL  COPYRIGHT  LAWS  AND
  TREATIES.   NO  PART  OF  THIS  WORK MAY BE  USED,  PRACTICED,  PERFORMED
  COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED,  ABRIDGED, CONDENSED,
  EXPANDED,  COLLECTED,  COMPILED,  LINKED,  RECAST, TRANSFORMED OR ADAPTED
  WITHOUT THE PRIOR WRITTEN CONSENT OF NOVELL, INC. ANY USE OR EXPLOITATION
  OF THIS WORK WITHOUT AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO
  CRIMINAL AND CIVIL LIABILITY.

****************************************************************************/
package com.novell.service.session;

import com.novell.service.jncpv2.clx.*;
import com.novell.service.session.*;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import com.novell.service.session.util.Debug;

/**
 * Provides a collection of key/value pairs describing a session
 * environment. SessionEnv affects the behavior of the session and
 * its children.
 *
 * <p>SessionEnv has been optimized to be cloneable without requiring
 * a new copy of it's internal data until that data is modified. This
 * is a performance and memory usage optimization. Until the internal
 * hashtable is modified, clones of this object share the hashtable.
 */
public class SessionEnv implements Cloneable, Serializable
{
   // Always access debug via final static...hopefully a final static of
   // false will be optimized out by the compiler
   final static private boolean DEBUG = false;
   final static private boolean I_DEBUG = false; // Ignore exception
   final static private boolean S_DEBUG = false; // May have side effects

   // Configuration

   /**
    * Key describing initial sessions and their load order in a
    * colon-separated list.
    *
    * <p>(INITIAL_SESSION_FACTORY =
    * "com.novell.service.session.spi.InitialSessionFactory")
    *
    * <p>Following is a code example:
    * <pre><code>(SessionEnv)e.add(
    *    INITIAL_SESSION_FACTORY,
    *    "com.novell.service.session.nds.NDSInitialSessionFactory:" +
    *    "com.novell.service.session.bindery.BinderyInitialSessionFactory");
    * </pre></code></p>
    */
   public final static String INITIAL_SESSION_FACTORY
      = "com.novell.service.session.spi.InitialSessionFactory";

   /**
    * Key indicating whether to run local or in RMI mode
    * Set this key to the location of the remote SessionManager to run RMI
    *
    * <p>(SESSION_MANAGER_URL =
    * "com.novell.service.session.spi.SessionManagerService")
    *
    * <p>Following is a code example:
    * <pre><code>SessionEnv env = new SessionEnv();
    *    env.add(SessionEnv.SESSION_MANAGER_URL, rmiServerName);
    *    SessionManager sm = SessionManagerFactory.getPrivate(env);
    * </pre></code></p>
    */
   public final static String SESSION_MANAGER_URL
      = "com.novell.service.session.spi.SessionManagerService";

   /**@deprecated Use SESSION_MANAGER_URL
   *
   */
   public final static String SESSION_MANAGER_SERVICE
      = "com.novell.service.session.spi.SessionManagerService";

   /**
    * Key describing whether or not a separate thread will be spawned by the
    * SessionManager to keep Sessions validated.
    *
    * <p>(ALLOW_BACKGROUND_VALIDATION =
    * "com.novell.service.session.spi.SessionManagerServiceDaemon")
    *
    * <p>Following is a code example:
    * <pre><code>(SessionEnv)e.add(
    *    ALLOW_BACKGROUND_VALIDATION,
    *    new Boolean(true));
    * </pre></code></p>
    */
   public final static String ALLOW_BACKGROUND_VALIDATION
      = "com.novell.service.session.spi.SessionManagerServiceDaemon";

   /**@internal
    *
    * Key describing whether or not a call to SessionManagerFactory.
    * getSessionManager() returns a private SessionManager.
    *
    * <p>(PRIVATE_SCOPE = "com.novell.service.session.PrivateScope")
    *
    * <p>Following is a code example:
    * <pre><code>(SessionEnv)e.add(
    *    PRIVATE_SCOPE,
    *    new Boolean(true));
    * </pre></code></p>
    */
   public final static String PRIVATE_SCOPE
      = "com.novell.service.session.PrivateScope";

   // Behavior

   /**
    * The environment used for key/value storage.
    */
   private Hashtable environment;

   /** @internal
    *
    * A boolean flag indicating whether or not the environment is shared.
    */
   protected boolean shared;


   /**
    * Constructs a new, empty session environment that is shared.
    */
   public SessionEnv ()
   {
      shared = true;
      setEnvironment(
         new Hashtable());
   }

   /** @internal
    *
    * Constructs a session environment that is not shared. It is only
    * intended to be used for subclassing SessionEnv in cases where the
    * implementation cannot handle sharing of environment variables.
    *
    * @param h  The hashtable on which to base this session environment.
    */
   protected SessionEnv (Hashtable h)
   {
      shared = false;
      setEnvironment(h);
   }

   /**
    * Adds a new entry to this environment. If key already exists,
    * EntryInUseException will be thrown.
    *
    * @param key  The key to be added.
    * @param val  The value to be added.
    *
    * @exception SessionException Top-level exception extended by all Session
    *            exceptions and thrown by Session objects.
    * @exception EntryInUseException Thrown if a key already exists.
    */
   public void add
   (
      String key,
      Object val
   )
   throws SessionException
   {
      copyWhenShared ();
      if (getEnvironment().
          get(key) != null)
         throw new EntryInUseException(key.
            toString());
      getEnvironment().
         put(key, val);
   }

   /**
    * Clears this environment so it contains no keys.
    */
   public void clear
   (
   )
   {
      getEnvironment().
         clear();
   }

   /**
    * Creates a new copy of this environment. The environment is
    * shared until a modification is made to the clone.
    *
    * <p>For more information on this Object, see Sun's java.lang.Cloneable
    * class.
    *
    * @return  A clone of this session environment.
    */
   public Object clone ()
   {
      try
      {
         SessionEnv env = (SessionEnv) super.clone ();

         env.setEnvironment(
            getEnvironment());
         setShared();
         env.setShared();
         return (env);
      }
      catch (CloneNotSupportedException e)
      {
         // this shouldn't happen, since we are Cloneable
         throw (new InternalError ());
      }
   }

   /**
    * Determines if some value maps into the specified value of this
    * environment.
    *
    * @param value  The value for which to search.
    *
    * @return A boolean set to TRUE if the target value maps into
    *         the specified value of this environment, otherwise
    *         set to FALSE.
    */
   public boolean contains
   (
      Object value
   )
   {
      return getEnvironment().
         contains(value);
   }

   /**
    * Determines if a key maps into the specified key of this environment.
    *
    * @param key  The possible key for which to search.
    *
    * @return A boolean set to TRUE if the target key maps into
    *         the specified key of this environment, otherwise
    *         set to FALSE.
    */
   public boolean containsKey
   (
      String key
   )
   {
      return getEnvironment().
         containsKey(key);
   }

   /**
    * Returns an enumeration of the values in this environment.
    *
    * @return An enumeration of the values in this environment.
    */
   public Enumeration elements
   (
   )
   {
      return getEnvironment().
         elements();
   }

   /**
    * Returns the value to which the specified key is mapped in this
    * environment.
    *
    * @param key  A key in the environment.
    *
    * @return An Object containing the value to which the key is mapped.
    */
   public Object get
   (
      String key
   )
   {
      return getEnvironment().
         get(key);
   }

   /**
    * Determines if this environment is empty.
    *
    * @return A boolean set to TRUE if this environment is empty,
    *         otherwise set to FALSE.
    */
   public boolean isEmpty
   (
   )
   {
      return getEnvironment().
         isEmpty();
   }

   /**
    * Returns an enumeration of the keys in this environment.
    *
    * @return An enumeration of the keys in this environment.
    */
   public Enumeration keys
   (
   )
   {
      return getEnvironment().
         keys();
   }

   /**
    * Determines if the passed-in subEnv is a subset of this set
    * and all attributes match.
    *
    * @param subEnv The environment to match.
    *
    * @return A boolean set to TRUE if the passed-in subEnv is a
    *         subset of this session environment, otherwise set
    *         to FALSE.
    */
   public boolean matchesSubset
   (
      SessionEnv subEnv
   )
   {
      boolean matches = true;

      Enumeration subEnum = subEnv.keys();
       // Iterate through attributes to match
      try
      {
         while (subEnum.hasMoreElements())
         {
            String subKey = (String)subEnum.nextElement();
            Object subObj = subEnv.get(subKey);
             // Possible null return...caught in catch
            Object curObj = getEnvironment().
               get(subKey);

             // Any failure is a complete failure.
            if (!subObj.equals(curObj))
            {
               matches = false;
               break;
            }
         }
      }
      catch (Exception e)
      {
         matches = false;
      }
      return matches;
   }

   /**
    * Merge two session environments together. Keys in this environment
    * that are matched by the subSet will not be overridden.
    *
    * @param subSet  The environment to merge into this environment.
    *
    * @exception SessionException Top-level exception extended by all Session
    *            exceptions and thrown by Session objects.
    */
   public void merge
   (
      SessionEnv subSet
   )
   throws SessionException
   {
      copyWhenShared ();
      Enumeration enum = subSet.keys();

      while (enum.hasMoreElements())
      {
         String key = (String)enum.nextElement();
         try
         {
            add(
               key,
               subSet.get(key));
         }
         catch (EntryInUseException e)
         {
            // Do nothing
            if (I_DEBUG)
            {
               Debug.ignoreException(
                  "merge failure", e);
            }
         }
      }
   }

   /**
    * Merge properties into this session environment. Keys in this
    * environment that are matched by the subSet will not be overridden.
    *
    * @param subSet  The properties to merge.
    *
    * @exception SessionException Top-level exception extended by all Session
    *            exceptions and thrown by Session objects.
    */
   public void merge
   (
      Properties subSet
   )
   throws SessionException
   {
      copyWhenShared ();
      Enumeration enum = subSet.keys();

      while (enum.hasMoreElements())
      {
         String key = (String)enum.nextElement();
         try
         {
            add(
               key,
               subSet.get(key));
         }
         catch (EntryInUseException e)
         {
            if (I_DEBUG)
            {
               Debug.ignoreException("merge failure", e);
            }
         }
      }
   }

   /**
    * Replaces the entry identified by key with val, or adds it
    * if it does not exist.
    *
    * @param key The key to be modified.
    * @param val The value to modify.
    *
    * @return The Object containing the modified environment.
    */
   public Object modify
   (
      String key,
      Object val
   )
   {
      copyWhenShared ();
      return getEnvironment().
         put(key, val);
   }

   /**
    * Replaces the attributes in this set by those in newEnv, or
    * adds any that do not exist.
    *
    * @param newEnv The environment to use for modification.
    */
   public void modify
   (
      SessionEnv newEnv
   )
   {
      String key;

      Enumeration enum = newEnv.keys();

      while (enum.hasMoreElements())
      {
         key = (String)enum.nextElement();
         this.modify(
            key,
            newEnv.get(key));
      }
   }

   /**
    * Replaces the attributes in this set by those in subSet, or
    * adds any that do not exist.
    *
    * @param subSet The attributes to use for modification.
    */
   public void modify
   (
      Properties subSet
   )
   {
      String key;

      Enumeration enum = subSet.keys();

      while (enum.hasMoreElements())
      {
         key = (String)enum.nextElement();
         this.modify(
            key,
            subSet.get(key));
      }
   }

   /**
    * Returns the value to which the specified key is mapped in this
    * environment.
    *
    * @param key  The specified key in this environment.
    *
    * @return The Object containing the environment.
    *
    * @exception EntryNotFoundException Thrown if no value matching the key.
    */
   public Object mustGet
   (
      String key
   )
   throws EntryNotFoundException
   {
      Object o = getEnvironment().
         get(key);
      if (null == o)
      {
         throw new EntryNotFoundException(key);
      }
      return o;
   }

   /**
    * Maps the specified key to the specified value in this environment.
    *
    * @param key   The key to map.
    * @param value The value to map.
    *
    * @return The Object containing the modified environment.
    */
   public Object put
   (
      String key,
      Object value
   )
   {
      copyWhenShared ();
      return getEnvironment().
         put(key, value);
   }

   /**
    * Removes the key and it's value from this environment.
    *
    * @param key The key to remove.
    *
    * @return The Object containing the modified environment.
    */
   public Object remove
   (
      String key
   )
   {
      copyWhenShared ();
      return getEnvironment().
         remove(key);
   }

   /**
    * Returns the number keys in this environment.
    *
    * @return The number of keys in this environment as an int.
    */
   public int size
   (
   )
   {
      return getEnvironment().
         size();
   }

   /**
    * Returns a string that textually represents this environment.
    *
    * @return A string representation of the environment.
    */
   public String toString
   (
   )
   {
      return getEnvironment().
         toString();
   }

   /** @internal
    *
    * Allows a SessionEnv to be efficiently share. it should be
    * called when a sub-class shares the same Hashtable.
    */
   synchronized protected final void setShared ()
   {
      shared = true;
   }

   /*
    * Copies the environment if it is shared.
    */
   private final void copyWhenShared ()
   {
      if (shared)
      {
         setEnvironment(
            (Hashtable)getEnvironment().
               clone ());
         shared = false;
      }

   } /* copyWhenShared () */

   /** @internal
    *
    * Sets the environment hashtable.
    *
    * @param environment The environment hashtable to be set.
    */
   // Make sure get/set synchronized...avoid read/write synch problems.
   synchronized protected void setEnvironment(Hashtable environment)
   {
      this.environment = environment;
   }

   /** @internal
    *
    * Returns the environment hashtable.
    *
    * @return The Hashtable for the environment.
    */
   // Make sure get/set synchronized...avoid read/write synch problems.
   synchronized protected Hashtable getEnvironment()
   {
      return this.environment;
   }
}

