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

  $Archive: /njcl_v2rmi/src/com/novell/service/session/nds/NDSTreeSessionState.java $
  $Revision: 44 $
  $Modtime: 1/09/02 5:28p $

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

import java.rmi.RemoteException;
import com.novell.service.session.xplat.*;
import com.novell.service.jncpv2r.net.NetService;
import com.novell.service.jncpv2r.net.NetServiceWrapper;
import com.novell.service.jncpv2r.net.BasicNetService;
import com.novell.service.jncpv2r.net.AliasNetService;
import com.novell.service.session.*;
import com.novell.service.session.util.Debug;
import com.novell.service.jncp.NSIException;
import com.novell.service.session.spi.*;
import java.util.*;
import com.novell.java.security.Authenticator;

/** @internal
 */
public class NDSTreeSessionState extends NDSContextSessionState
{
   // 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
   final static private boolean FAIL_DEBUG = false; // when eval fails
   final static private boolean CACHE_DEBUG = false;

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

   public NDSTreeSessionState
   (
      SessionImpl attachedSession
   )
   throws SessionException, RemoteException
   {
      super(attachedSession, null);

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

      if (DEBUG)
      {
         Debug.println(
            "NTSS.<init>(): " + getContext());
      }
   }

   /*
    * Inherited from SessionState
    */
   protected void releaseResources()
   throws SessionException, RemoteException  //this method is not a remote method, but calls one, so must throw RemoteException
   {
      NDSContext context = getContext();
      try
      {
         context.logout();
      }
      catch (Throwable e)
      {
         if (I_DEBUG)
         {
            Debug.ignoreException(e);
         }
      }
      super.releaseResources();
   }

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

      if (DEBUG)
      {
         Debug.println("NTSS.authenticate(): " +
            getDomainName() +
            " : " + name +
            " : " + this);
      }
      if (isAuthenticated())
         throw new AlreadyAuthenticatedException();
      else
      {
         Authenticator.login(
            createIdentity(name));
      }
   }

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

      if (DEBUG)
      {
         Debug.println("NTSS.unauthenticate(): " +
            " : " + this);
      }
      if (!isAuthenticated())
         throw new NotAuthenticatedException();
      else
      {
         Authenticator.logout(
            createIdentity(getName()));
      }
   }

   /**
    * Inherited from Session
    *
    * @see Session#getAttributes()
    */
   public SessionAttrs getAttributes
   (
   )
   throws SessionException, RemoteException
   {
      try
      {
         // Get inherited attributes
         SessionAttrs attributes = super.getAttributes();

         // Override with this session's
         String attrIds[] =
         {
            Session.SESSION_TYPE_ATTR_ID
         };
         attributes.modify(
            getStateAttrs(attrIds));

         return attributes;
      }
      catch (NSIException e)
      {
         throw InvalidSessionState.makeInvalid(this.attachedSession, e);
      }
   }

   /**
    * Inherited from Session
    *
    * @see Session#getAttributes(String[])
    */
   public SessionAttrs getAttributes
   (
      String attrIds[]
   )
   throws SessionException, RemoteException
   {
      try
      {
         // Get this state's attributes
         SessionAttrs attributes = getStateAttrs(attrIds);

         if (attributes.count() != attrIds.length)
         {
            // Get inherited attributes
            attributes.merge(
               super.getAttributes(attrIds));
         }

         return attributes;
      }
      catch (NSIException e)
      {
         throw InvalidSessionState.makeInvalid(this.attachedSession, e);
      }
   }

   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.SESSION_TYPE_ATTR_ID.equals(attrIds[i]))
         {
            attributes.modify(
               Session.SESSION_TYPE_ATTR_ID,
               Xplat.TREE_SESSION_TYPE);
         }
      }
      return attributes;
   }

   private boolean specialCompareByteArrays(byte[] b1, byte[] b2)
   {
      if(b1 == null || b2 == null || (b1.length != b2.length))
         return false;
      for(int x=0; x < b1.length; x++)
      {
         if(b1[x] != b2[x])
            return false;
      }
      return true;
   }
   private boolean specialCompareForClusterHack(String domainName, Session sess)
   {
      //cluster server dn is returned incorrectly so need to compare against something else.
      boolean match = false;
      try
      {
         int handle = Natives.openConnection(context.getNativeHandle(), domainName);
         Address newAddr = Natives.getConnInfoAddress(handle, 0, 0);
         SessionAttrs attrs = sess.getAttributes(new String[] {Xplat.CONN_SERVER_ADDRESS_ATTR_ID});
         Address oldAddr = (Address)attrs.getValue(Xplat.CONN_SERVER_ADDRESS_ATTR_ID);
         Natives.closeConnection(handle);
         byte[] newIP = newAddr.getAddress();
         byte[] oldIP = oldAddr.getAddress();
         if(specialCompareByteArrays(oldIP, newIP))
            match = true;
      }
      catch(Exception e)
      {}
      return match;
   }
   
   /**
    * Inherited from Session
    *
    * @see Session#getSession
    */
   public Session getSession
   (
      String domainName,
      SessionEnv environment
   )
   throws SessionException, RemoteException
   {
      String uDomainName = domainName.toUpperCase();

      validateChildren();
      Session session = findSession(uDomainName);

      // Must match dn if provided
      if (null != session)
      {
         if (domainName.indexOf('.') != -1)  // if has '.' then must be dn
         {
            // Compare typeless
            String sDomainName = DomainName.stripTypes(domainName);
            NDSServerSessionState state =
               (NDSServerSessionState)((SessionImpl)session).getState();
            String cDomainName = DomainName.stripTypes(
               state.context.getServerDN());
            if(!sDomainName.equalsIgnoreCase(cDomainName))
               if(!specialCompareForClusterHack(uDomainName, session))
                 session = null;
         }
      }
      if (null == session)
      {
         if (FAIL_DEBUG)
         {
            Debug.printlnOnly("Failed as existing session under this tree (" +
               attachedSession.getDomainName() + "): '" +
               uDomainName + "'");
         }
         // Doesn't exist...return new
         Vector exceptions = new Vector();
         try
         {
            session = this.addNewSessionByContext(
               uDomainName,
               environment);
         }
         catch (Exception e)
         {
            if (I_DEBUG || FAIL_DEBUG)
            {
               Debug.printlnOnly("Failed under this tree via context(" +
                  attachedSession.getDomainName() + "): '" +
                  uDomainName + "'");
            }
            if (I_DEBUG)
            {
               Debug.ignoreException(e);
            }
            exceptions.addElement(e);

            try
            {
               session = this.addNewSession(
                  uDomainName,
                  environment);
            }
            catch (Exception e1)
            {
               if (I_DEBUG || FAIL_DEBUG)
               {
                  Debug.printlnOnly("Failed under this tree via bindery name(" +
                     attachedSession.getDomainName() + "): '" +
                     uDomainName + "'");
               }
               if (I_DEBUG)
               {
                  Debug.ignoreException(e);
               }
               exceptions.addElement(e1);
               InvalidDomainNameException t =
                  new InvalidDomainNameException(domainName);
               t.setRootCauses(exceptions);
               if (DEBUG)
               {
                  Debug.dumpException(t);
               }
               throw t;
            }
         }

         if (DEBUG)
         {
            Debug.println(
               "added server: " + domainName + " to: " + getDomainName());
         }
      }

      if (DEBUG)
      {
         Debug.println("NTSS.getSession(): session = " + session);
      }
      return session;
   }

   /*
    * Validate just children, not children's children.
    */
   protected void validateChildren
   (
   )
   throws SessionException, RemoteException  //this method is not a remote method, but calls one, so must throw RemoteException
   {
      if (DEBUG || CACHE_DEBUG)
      {
         Debug.println("Validating children...");
      }

      Connections conns = xplatUtil.
         getTreeConnections(
            getDomainName());

      if (DEBUG || CACHE_DEBUG)
      {
         Debug.println("Connection list: " + conns);
      }

      SessionEnumerator sessionEnum = getChildren();

      if (DEBUG || CACHE_DEBUG)
      {
         Debug.println("Children enum has more elements: " +
            new Boolean(sessionEnum.hasMoreElements()).toString());
      }

      ConnectionEnumerator connEnum;
//      String attrIds[] = {Xplat.CONN_CONNECTION_REFERENCE_ATTR_ID};

       // Validate existing sessions: if ref exists in session, but not
       // in real world, remove session.
      while (sessionEnum.hasMoreElements())
      {
         Session session = null;
         try
         {
            session = sessionEnum.next();
            if (DEBUG || CACHE_DEBUG)
            {
               Debug.println("Validating: " + session);
            }

            int sessionRef = 0;

             // Split assignments for easy debug
/*
            SessionAttrs attributes = session.getAttributes(attrIds);
            Integer value = (Integer)attributes.getValue(attrIds[0]);
            sessionRef = value.intValue();
*/
            NDSServerSessionState state = (NDSServerSessionState)
               ((SessionImpl)session).getState();
            Connection sessionConnection = state.getContext().
               getConnection();

            boolean found = false;

            connEnum = conns.elements();
            while (connEnum.hasMoreElements())
            {
               Connection connection = connEnum.next();
               if (DEBUG || CACHE_DEBUG)
               {
                  Debug.println("Comparing: " + connection +
                     " with 0x" + Integer.toHexString(sessionRef));
               }

               if (xplatUtil.equals(
                  connection,
                  sessionConnection))
               {
                  found = true;
                   // Don't need to check next time
                  conns.remove(connection);
                  break;
               }
            }
            if (!found)
            {
               // Close session if it doesn't exist in real world.
               if (DEBUG)
               {
                  Debug.println(
                     "NTSS.validateChildren(): Session not found in requester: " + session);
               }
               InvalidSessionState.makeInvalid(
                  (SessionImpl)session,
                  new SessionException("Session not found in requester" + session));
            }
            else
               // Make sure state is correct
               session.isAuthenticated();
         }
         catch (Exception e)
         {
            if (I_DEBUG)
            {
               Debug.ignoreException("Assume invalid", e);
            }
            attachedSession.removeChild((SessionImpl)session);
         }
      }

       // Add new sessions: any remaining conns must be new sessions.
      connEnum = conns.elements();
      while (connEnum.hasMoreElements())
      {
         Connection connection = connEnum.next();

         try
         {
            // NOTE: Remove domain name...just pass connection?
            String name = xplatUtil.getServer(connection);
            this.addNewSession(
               connection,
               name.toString(),
               new SessionEnv());
         }
         catch (Exception e)
         {
            if (I_DEBUG)
            {
               Debug.ignoreException(e);
            }
         }
      }
      if (DEBUG)
      {
         Debug.println("<-NTSS.validateChildren()");
      }
   }

   /**
    * Inherited from Session
    *
    * @see Session#getService
    */
   public SessionService getService
   (
      String serviceKey
   )
   throws SessionException, RemoteException
   {
      if (DEBUG)
      {
         Debug.println("NTSS.getService(): " +
            getDomainName() + ":" + this);
      }
      isAuthenticated();
      try
      {
         if (serviceKey.equals(BasicNetService.KEY))
         {
            if(sessionManager.useCustomSockets())
               return new NetServiceWrapper(new BasicNetService(
                  getContext().duplicate(), sessionManager.getCustomSocketFactories()));
            else
               return new NetServiceWrapper(new BasicNetService(
                  getContext().duplicate()));
         }
         if (serviceKey.equals(AliasNetService.KEY))
         {
            if(sessionManager.useCustomSockets())
               return new NetServiceWrapper(new AliasNetService(
                  getContext().duplicate(), sessionManager.getCustomSocketFactories()));
            else
               return new NetServiceWrapper(new AliasNetService(
                  getContext().duplicate()));
         }
         if (serviceKey.equals(NDSShibboleth.KEY))
         {
            if(sessionManager.useCustomSockets())
               return new ShibbolethWrapper(new NDSShibboleth(
                  (SessionImpl)attachedSession, sessionManager.getCustomSocketFactories()));
            else
               return new ShibbolethWrapper(new NDSShibboleth(
                  (SessionImpl)attachedSession));
         }
         if (serviceKey.equals(PersistenceService.KEY))
         {
            if(sessionManager.useCustomSockets())
               return new NDSTreePersistenceService(
                  (SessionImpl)this.attachedSession, sessionManager.getCustomSocketFactories());
            else
               return new NDSTreePersistenceService(
                  (SessionImpl)this.attachedSession);
         }
         if (serviceKey.equals(NetService.KEY))
         {
            if(sessionManager.useCustomSockets())
               return new NetServiceWrapper(new BasicNetService(
                  getContext().duplicate(), sessionManager.getCustomSocketFactories()));
            else
               return new NetServiceWrapper(new BasicNetService(
                  getContext().duplicate()));
         }

         // Add registered services here
      }
      catch (Exception e)
      {
         throw InvalidSessionState.makeInvalid(this.attachedSession, e);
      }
      return super.getService(serviceKey);
   }

   /**
    * Inherited from Authenticatable
    *
    * @see Authenticatable#isAuthenticated
    */
   public boolean isAuthenticated
   (
   )
   throws SessionException, RemoteException
   {
      if (DEBUG)
      {
         Debug.println("NTSS.isAuthenticated(): " +
            getDomainName() +
            " : " + getContext() +
            " : " + getContext().
               getConnection());
      }
      try
      {
         if(getContext().isAuthenticated()) //check if context has credentials.
         {
            if (DEBUG)
            {
               Debug.println("NTSS.isAuthenticated(): !TRUE!!!");
            }
            getContext().getConnection(); //Make sure still have a valid connection
            return true;
         }
         if (DEBUG)
         {
            Debug.println("NTSS.isAuthenticated(): FALSE");
         }
         return false;
      }
      catch (NSIException e)
      {
         throw InvalidSessionState.makeInvalid(this.attachedSession, e);
      }
   }

   /**
    * Add Session to existing children, no questions asked.
    */
   private Session addNewSession
   (
      String domainName,
      SessionEnv  environment
   )
   throws SessionException, RemoteException  //this method is not a remote method, but calls one, so must throw RemoteException
   {
      Connection connection = xplatUtil.getConnectionByName(
         environment,
         getDomainName(), // tree name
         domainName,
         Natives.OWNER_NDS);

      try
      {
         return addNewSession(
            connection,
            domainName,
            environment);
      }
      catch (SessionException e)
      {
         // Clean up hanging connection
         xplatUtil.close(connection);
         throw e;
      }
      catch (RuntimeException e)
      {
         // Clean up hanging connection
         xplatUtil.close(connection);
         throw e;
      }
   }

   /**
    * Add Session to existing children, no questions asked.
    */
   private Session addNewSessionByContext
   (
      String domainName,
      SessionEnv  environment
   )
   throws SessionException, RemoteException  //this method is not a remote method, but calls one, so must throw RemoteException
   {
      Connection connection = xplatUtil.getConnectionByName(
         getDomainName(), // tree name
         context,
         domainName,
         Natives.OWNER_NDS);

      try
      {
         return addNewSession(
            connection,
            DomainName.stripQualifiers(domainName),
            environment);
      }
      catch (SessionException e)
      {
         // Clean up hanging connection
         xplatUtil.close(connection);
         throw (SessionException)e.fillInStackTrace();
      }
      catch (RuntimeException e)
      {
         // Clean up hanging connection
         xplatUtil.close(connection);
         throw (RuntimeException)e.fillInStackTrace();
      }
   }

   /**
    * Add Session to existing children, no questions asked.
    */
   private Session addNewSession
   (
      Connection  connection,
      String      domainName,
      SessionEnv  environment
   )
   throws SessionException, RemoteException  //this method is not a remote method, but calls one, so must throw RemoteException
   {
      if (DEBUG || CACHE_DEBUG)
      {
         Debug.println("Adding new session: " +
            connection + ":" + domainName);
      }
      SessionImpl newSession;

//      synchronized (this.attachedSession)
      {
         if(sessionManager.useCustomSockets())
            newSession = new SessionImpl(this.attachedSession, domainName,
               (SessionEnv)environment.clone(), sessionManager.getCustomSocketFactories());
         else
            newSession = new SessionImpl(this.attachedSession, domainName,
               (SessionEnv)environment.clone());

         NDSContext myContext = getContext();
         NDSContext newContext = null;
         try
         {
            newContext = myContext.
                  duplicate(
                     (SessionEnv)environment.clone(),
                     connection);
         }
         catch (NSIException e)
         {
            int ccode = e.getCCode();
            // Range: server requester failures && other requester failures
            if ((ccode >= 0x8900 && ccode < 0x8923) ||
                (ccode >= 0x8800 && ccode <= 0x88FF))
            {
               throw InvalidSessionState.makeInvalid(this.attachedSession, e);
            }
         }
         new NDSServerSessionState(
            newSession,
            newContext);

         attachedSession.addChild(newSession);
      }

      if (DEBUG || CACHE_DEBUG)
      {
         Debug.println("Added session: " + newSession);
      }

      return newSession;
   }

   /**
    * Inherited from Session
    */
   public Session findSession(String domainName)
   throws SessionException, RemoteException
   {
      Session session = super.findSession(
         DomainName.stripQualifiers(domainName));
      return session;
   }
}
