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

  $Archive: /njcl_v2/src/com/novell/service/session/spi/SessionState.java $
  $Revision: 32 $
  $Modtime: 1/08/02 9:51a $

  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.spi;

import com.novell.service.jncpv2.clx.*;
import com.novell.service.session.*;
import com.novell.service.session.util.Debug;
import com.novell.service.session.util.JVMUID;
import com.novell.java.security.*;
import java.rmi.server.UID;
import java.util.Vector;

/** @internal
 * SessionState is the true implementation for SessionImpl, while
 * SessionImpl is the implementation for the Session interface.
 *
 *  - knows all information specific to a session, including names
 * and attributes.
 *
 * - provides operations for session management, excluding session
 * traversal.
 *
 */
public abstract class SessionState implements Session
{
   // 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 FINALIZER_DEBUG = false;
   final static private boolean I_DEBUG = false; // Ignore exception
   final static private boolean S_DEBUG = false; // May have side effects
   final static private boolean JVMUID_DEBUG = false;
   final static private boolean CLOSE_DEBUG = false;
   final static private boolean CREATION_DEBUG = false;
   final static private boolean SERVICE_DEBUG = false;
   final static private boolean FIND_DEBUG = false;

   // Print message if deprecated API is used
   final static private boolean ERROR_ON_DEPRECATED = false;

   protected SessionImpl attachedSession;
   protected SessionManagerImpl sessionManager;
   protected UID uid;
   protected String userName = null;

   public SessionState
   (
      SessionImpl attachedSession
   )
   throws SessionException
   {
      if (DEBUG || CREATION_DEBUG)
      {
         Debug.println("Creating state..." + attachedSession);
      }

      this.attachedSession = attachedSession;

      // Get session manager
      if (null == attachedSession)
         sessionManager = null;
      else
      {
         Session sm = attachedSession;
         while (sm.hasParent())
            sm = sm.getParent();
         sessionManager = (SessionManagerImpl)sm;
      }

      if (JVMUID_DEBUG)
      {
         // Assign unique id to this session state
         JVMUID jvmUid = JVMUID.getInstance();
         uid = jvmUid.createUID();
//         jvmUid.write(uid, this);
      }

      // We're ready to be used!
//      if (null != this.attachedSession)
//         this.attachedSession.setState(this);
   }

   /**
    * Inherited from Session
    *
    * @see Session#getDomainName
    */
   public String getDomainName
   (
   )
   throws SessionException
   {
      return this.attachedSession.getDomainName();
   }

   /**
    * Inherited from Session
    */
   public SessionEnv getEnvironment
   (
   )
   throws SessionException
   {
      return attachedSession.getEnvironment();
   }

   /**
    * Inherited from Session
    */
   public SessionEnv getEffectiveEnvironment
   (
   )
   throws SessionException
   {
      return attachedSession.getEffectiveEnvironment();
   }

   /**
    * Inherited from Session
    */
   public SessionEnv setEnvironment
   (
      SessionEnv environment
   )
   throws SessionException
   {
      return attachedSession.setEnvironment(
         (SessionEnv)environment.clone());
   }

   /**
    * Inherited from Session
    *
    * @see Session#getAttributes()
    */
   public SessionAttrs getAttributes
   (
   )
   throws SessionException
   {
      // Top-level state...no inherited attributes
      SessionAttrs attributes = new SessionAttrs();

      // Override with this session's
      String attrIds[] =
      {
         Session.DOMAIN_NAME_ATTR_ID,
         Session.SESSION_STATE_ATTR_ID,
         Session.IS_REAL_ATTR_ID,
         Authenticatable.USER_NAME_ATTR_ID
      };

      attributes.modify(
         getStateAttrs(attrIds));

      return attributes;
   }

   /**
    * Inherited from Session
    *
    * @see Session#getAttributes(String[])
    */
   public SessionAttrs getAttributes
   (
      String attrIds[]
   )
   throws SessionException
   {
      // Top-level state...no inherited attributes
      SessionAttrs attributes = getStateAttrs(attrIds);

      return attributes;
   }

   private SessionAttrs getStateAttrs
   (
      String attrIds[]
   )
   throws SessionException
   {
      // Get inherited attributes
      SessionAttrs attributes = new SessionAttrs();

      // Override with this session's
      for (int i = 0; i < attrIds.length; i++)
      {
         if (Session.DOMAIN_NAME_ATTR_ID.equals(attrIds[i]))
            attributes.add(
               Session.DOMAIN_NAME_ATTR_ID,
               getDomainName());
         if (Session.SESSION_STATE_ATTR_ID.equals(attrIds[i]))
            attributes.add(
               Session.SESSION_STATE_ATTR_ID,
               this.getClass().getName());
         if (Session.IS_REAL_ATTR_ID.equals(attrIds[i]))
            attributes.add(
               Session.IS_REAL_ATTR_ID,
               new Boolean(true));

         // TEMPORARY...needs to go into states that contain only
         // User name as session attribute lets search stay simple
         try
         {
            if (Authenticatable.USER_NAME_ATTR_ID.equals(attrIds[i]))
               attributes.modify(
                  Authenticatable.USER_NAME_ATTR_ID,
                  getName());
         }
         catch (SessionException e)
         {
            if (I_DEBUG)
            {
               Debug.ignoreException(e);
            }
         }

      }
      return attributes;
   }

   /**
    * Inherited from Session
    *
    * @see Session#validateLinks
    */
   public void validateLinks
   (
   )
   throws SessionException
   {
      validateChildren();
      SessionEnumerator children = getChildren();
      while (children.hasMoreElements())
      {
         Session s = children.next();
         try
         {
            s.validateLinks();
         }
         catch (InvalidSessionException e)
         {
            if (I_DEBUG)
            {
               Debug.ignoreException("Already marked invalid", e);
            }
         }
      }
   }

   /**
    * Inherited from Session
    *
    * @see Session#isValid
    */
   public boolean isValid()
   {
      return true;
   }

   /**
    * Inherited from Session
    *
    * @see Session#getChildren
    */
   public SessionEnumerator getChildren
   (
   )
   throws SessionException
   {
      return attachedSession.getChildren();
   }

   /**
    * Inherited from Session
    *
    * @see Session#getParent
    */
   public Session getParent
   (
   )
   {
      return attachedSession.getParent();
   }

   /**
    * Inherited from Session
    *
    * @see Session#getSession
    */
   public Session getSession
   (
      String domainName
   )
   throws SessionException
   {
      Session s = getSession(
         domainName,
         new SessionEnv());
      if (DEBUG)
      {
         Debug.println("SS.getSession() = " + s);
      }
      return s;
   }

   /**
    * Inherited from Session
    *
    * @see Session#getSession
    */
   public Session getSessionTop
   (
      String domainName
   )
   throws SessionException
   {
      return getSessionTop(
         domainName,
         new SessionEnv());
   }

   /**
    * Inherited from Session
    *
    * @see Session#getSessionTop
    */
   public Session getSessionTop
   (
      String domainName,
      SessionEnv environment
   )
   throws SessionException
   {
      return getParent().
         getSessionTop(
            domainName,
            environment);
   }

   /**
    * Inherited from Session
    *
    * @see Session#close
    */
   public void close
   (
   )
   throws SessionException
   {
      // Need 'fixed' array of children so they can be removed from
      // original children vector...enum would be effected by removal.
      int size = attachedSession.children.size();
      Session[] children = new Session[size];
      attachedSession.children.copySessionsInto(children);
      for (int i = 0; i < size; i++)
      {
         try
         {
            // Close each child
            Session child = children[i];
            if (DEBUG || CLOSE_DEBUG)
            {
               Debug.printlnOnly("Closing: " + child.toString());
            }
            child.close();
         }
         catch (Exception e)
         {
            if (I_DEBUG)
            {
               Debug.ignoreException(e);
            }
         }
      }
      // Call specific state resource release method
      releaseResources();
      attachedSession.invalidateSelf(null);
   }

   /*
    * Method used by close to cause specific state to release any resources
    * associated with it.  Intended to be overridden, but not abstract so as
    * not to force everyone to write it.
    */
   protected void releaseResources()
   throws SessionException
   {
   }


   /**
    * Inherited from Session
    *
    * @see Session#search
    */
   public SessionEnumerator search
   (
      SessionAttrs matchingSessionAttrSet
   )
   throws SessionException
   {
      validateChildren();

      Vector searchResults = new Vector();
      Session childSession;

      // NOTE: Order is important here...returned enumeration should
      // include logical groupings of children...first parent, then
      // immediately follow with children

      // Only want to compare matching ids...so only get those
      String[] matchingIds = new String[matchingSessionAttrSet.count()];
      int count = 0;
      SessionAttrEnumerator attrEnum = matchingSessionAttrSet.getAttributes();
      while (attrEnum.hasMoreElements())
      {
         matchingIds[count++] = attrEnum.next().getSessionAttrId();
      }

      SessionEnumerator childEnum = getChildren();

      // Iterate through children
      while (childEnum.hasMoreElements())
      {
         childSession = childEnum.next();

         // Does child session belong in "found" sessions?
         SessionAttrs sessionAttrSet = childSession.getAttributes(matchingIds);
         if (sessionAttrSet.matchesSubset(matchingSessionAttrSet))
         {
            searchResults.addElement(childSession);
         }

         try
         {
            // Pass to children of child session
            if (childSession.hasChildren())
            {
               SessionEnumerator cs = childSession.search(matchingSessionAttrSet);
               while (cs.hasMoreElements())
               {
                  searchResults.addElement(
                     cs.next());
               }
            }
         }
         catch (Exception e)
         {
             // Do nothing...try next initial session
         }
      }

      SessionEnumerator sse = new SessionEnumerator(
         new com.novell.java.util.VectorEnumerator(searchResults));

      return sse;
   }

   protected void validateChildren
   (
   )
   throws SessionException
   {
   }

   /**
    * Inherited from Session
    *
    * @see Session#invalidate
    */
   public void invalidate
   (
      Throwable rootCause //non null if invalidating as the result of an exception (as opposed to someone calling close() )
   )
   throws SessionException
   {
      this.attachedSession.invalidate(rootCause);
   }

   /**
    * Inherited from Session
    *
    * @see Session#getService
    */
   public SessionService getService
   (
      String serviceKey
   )
   throws SessionException
   {
      if (DEBUG || SERVICE_DEBUG)
      {
         Debug.println("Unknown key: " + this);
      }
      throw new UnknownKeyException(serviceKey + ": " + attachedSession);
   }

   /**
    * Inherited from Session.
    *
    * @see Session#getUID
    */
   public UID getUID
   (
   )
   {
      return this.attachedSession.getUID();
   }

   /**
    * Inherited from Session.
    *
    * @exception SessionException Or a subclass thereof.
    */
   public boolean hasParent
   (
   )
   {
      return attachedSession.hasParent();
   }

   /**
    * Inherited from Session.
    *
    * @exception SessionException Or a subclass thereof.
    */
   public boolean hasChildren
   (
   )
   {
      return attachedSession.hasChildren();
   }

   public String toString()
   {
      StringBuffer out = new StringBuffer();
      out.append("SessionState$");
      if (JVMUID_DEBUG)
      {
         out.append(":U=" + uid);
      }
      out.append(
         ":" + this.getClass().
            toString());
      return out.toString();
   }

   /**
    * @deprecated Use com.novell.service.security and related packages.
    *
    * Inherited from Authenticatable
    *
    * @see Authenticatable#getName
    */
   public String getName
   (
   )
   throws SessionException
   {
      if (ERROR_ON_DEPRECATED)
      {
         System.out.println("This API has been deprecated..." +
            "see documentation for more details. Stack trace follows:");
         Thread.dumpStack();
      }

      throw new InvalidStateException("Not authenticatable");
   }

   /**
    * @deprecated Use com.novell.service.security and related packages.
    *
    * Inherited from Authenticatable
    *
    * @see Authenticatable#authenticate()
    */
   public void authenticate
   (
      String name
   )
   throws SessionException
   {
      if (ERROR_ON_DEPRECATED)
      {
         System.out.println("This API has been deprecated..." +
            "see documentation for more details. Stack trace follows:");
         Thread.dumpStack();
      }

      throw new InvalidStateException("Not authenticatable");
   }

   /**
    * @deprecated Use com.novell.service.security and related packages.
    *
    * Inherited from Authenticatable
    *
    * @see Authenticatable#authenticate()
    */
   public void authenticate
   (
   )
   throws SessionException
   {
      if (ERROR_ON_DEPRECATED)
      {
         System.out.println("This API has been deprecated..." +
            "see documentation for more details. Stack trace follows:");
         Thread.dumpStack();
      }

      throw new InvalidStateException("Not authenticatable");
   }

   /**
    * @deprecated Use com.novell.service.security and related packages.
    *
    * Inherited from Authenticatable
    *
    * @see Authenticatable#unauthenticate
    */
   public void unauthenticate
   (
   )
   throws SessionException
   {
      if (ERROR_ON_DEPRECATED)
      {
         System.out.println("This API has been deprecated..." +
            "see documentation for more details. Stack trace follows:");
         Thread.dumpStack();
      }

      throw new InvalidStateException("Not authenticatable");
   }

   /**
    * Inherited from Authenticatable
    *
    * @see Authenticatable#isAuthenticated
    */
   public boolean isAuthenticated
   (
   )
   throws SessionException
   {
      return false;
   }

   /**
    * Inherited from Authenticatable.
    *
    * @see Authenticatable#getAttributes(String[])
    */
   public Identity createIdentity
   (
      String userName
   )
   throws SessionException
   {
      throw new InvalidStateException("Not authenticatable");
   }

   /**
    *
    */
   protected Session getChild(String domainName)
   throws SessionException
   {
      return attachedSession.getChild(domainName);
   }

   /**
    * Inherited from Session
    */
   public Session findSession(String domainName)
   throws SessionException
   {
      if (DEBUG || FIND_DEBUG)
         Debug.println("Finding: " + domainName);

      Session session = null;
      session = attachedSession.getChild(domainName);

      if (null == session)
      {
         SessionEnumerator enum = getChildren();
         while (enum.hasMoreElements() && session == null)
         {
            Session next = enum.next();
            if (DEBUG || FIND_DEBUG)
               Debug.println("Child of " + attachedSession.domainName +
                             ": " + next.getDomainName());
            session = next.findSession(domainName);
         }
      }
      if (DEBUG || FIND_DEBUG)
         Debug.println("Found: " + session);
      return session;
   }

   /**
    * Inherited from Session
    */
   public Session findSessionTop(String domainName)
   throws SessionException
   {
      return getParent().
         findSessionTop(domainName);
   }

   /*
    * Throws the most common execption thrown from this class.
    */
   public SessionException getInvalidException()
   throws SessionException
   {
      return InvalidSessionState.makeInvalid(
         attachedSession,
         new SessionException(
            "Unexpected exception: getInvalidException() called on VALID state"));
   }
}

