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

  $Archive: /njcl_v2/src/com/novell/service/session/xplat/NDSContext.java $
  $Revision: 50 $
  $Modtime: 3/20/03 5:54p $

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

import com.novell.service.jncpv2.clx.*;
import com.novell.java.lang.IntegerBuffer;
import com.novell.service.session.*;
import com.novell.service.security.NdsIdentity;
import com.novell.service.jncp.NSIException;
import com.novell.service.jncp.ClientException;
import com.novell.service.jncp.ClientErrors;
import com.novell.service.session.util.Debug;
import com.novell.service.session.util.JVMUID;
import java.io.Serializable;
import java.rmi.server.UID;

/** @internal
 * The link to an NDS tree, much like a connection, is a link to a server.
 */
public class NDSContext implements 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  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  PASSWORD_DEBUG = false;

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

   // Need to cause actual connection to authenticate
   // canDSAuthenticate() doesn't work multi-tree
   private boolean NLM_KLUDGE_CAN_AUTH = true;

   public NativeContext nativeContext;
   protected boolean hasHandle = false;
   protected UID uid;
   private Throwable reason;
   private XplatUtil xplatUtil;
   private SessionEnv environment;
   private String treeName;

//   protected synchronized void finalize
   protected void finalize
   (
   )
   throws Throwable
   {
      if (DEBUG || FINALIZER_DEBUG)
      {
         Debug.println("->NDSContext.finalize()");
      }
      // Java language spec: super.finalize() NOT automatically called
      super.finalize();
      close();

      if (JVMUID_DEBUG)
      {
         JVMUID.getInstance().remove(uid);
      }
      if (DEBUG)
      {
         Debug.println("<-NDSContext.finalize()");
      }
   }

   public NDSContext(
      NativeContext nativeContext,
      SessionEnv environment,
      XplatUtil xplatUtil)
   throws SessionException
   {
      this(nativeContext, environment, null, xplatUtil);
   }

   public NDSContext(
      NativeContext nativeContext,
      SessionEnv environment,
      String treeName,
      XplatUtil xplatUtil)
   throws SessionException
   {
      this.xplatUtil = xplatUtil;
      setNativeHandle(nativeContext);
      this.environment = environment;
      this.treeName = treeName;
      if (JVMUID_DEBUG)
      {
         // Assign unique id to this context
         JVMUID jvmUid = JVMUID.getInstance();
         uid = jvmUid.createUID();
//         jvmUid.write(uid, this);
      }
      setReason(
         new SessionException("Context not fully initialized"));
   }

//   synchronized public SessionEnv getEnvironment()
   public SessionEnv getEnvironment()
   {
      return this.environment;
   }

   //Only called for logging purposes and want no side effects.
   public String getTreeNameIfSet()
   {
      return this.treeName;
   }

   public String getTreeName()
   throws SessionException
   {
      if (null == this.treeName)
      {
         String treeName = (String)getInfo(Natives.DCK_TREE_NAME);
         this.treeName = DomainName.strip(treeName);
      }
      return this.treeName;
   }

//   synchronized public String toString()
   public String toString()
   {
      StringBuffer out = new StringBuffer();
      out.append("Context$");
      if (JVMUID_DEBUG)
      {
         out.append(":U=" + uid);
      }
      if (isValid())
         out.append(this.nativeContext);
      else
         out.append(":C=invalid");

      if (this.nativeContext instanceof NativeContextDuplicate)
      {
         try
         {

            Connection connection = getConnectionIfAvail();
            if (null != connection)
               out.append(":" + connection.toString());
         }
         catch (SessionException e)
         {
            if (I_DEBUG)
            {
               Debug.ignoreException(e);
            }
         }
      }

      if (S_DEBUG)
      {
         try
         {
            if (isValid())
            {
               out.append(":tree=" + getTreeName());
               out.append(getAttributes().toString());
            }
         }
         catch (Exception e)
         {
            if (I_DEBUG)
            {
               Debug.ignoreException("unexpected", e);
            }
         }
      }

      return out.toString();
   }

//   synchronized public NativeContext getNativeHandle
   public NativeContext getNativeHandle
   (
   )
   throws SessionException
   {
      if (isValid())
         return this.nativeContext;
      else
         throw new InvalidStateException(
            "Context invalid",
            getReason());
   }

//   synchronized public void setNativeHandle
   public void setNativeHandle
   (
      NativeContext nativeContext
   )
   throws SessionException
   {
      if (isValid())
         throw new InvalidStateException("Has handle");
      this.nativeContext = nativeContext;
      this.hasHandle = true;
   }

//   synchronized public int getHandle
   public int getHandle
   (
   )
   throws SessionException
   {
      if (isValid())
         return this.nativeContext.contextHandle;
      else
         throw new InvalidStateException(
            "Context invalid",
            getReason());
   }

/*
   synchronized public void setHandle
   (
      int handle
   )
   throws SessionException
   {
      if (isValid())
         throw new InvalidStateException("Has handle");
      this.nativeContext = new NativeContext(0, handle);
      this.hasHandle = true;
   }

   synchronized public void setHandle
   (
      IntegerBuffer handle
   )
   throws SessionException
   {
      setHandle(
         handle.intValue());
   }
*/

   public Connection getConnection() throws SessionException
   {
      if (!isValid())
         throw new InvalidStateException("Context invalid", getReason());

      boolean isTree = false;
      Connection connection = null;
      if (DEBUG)
      {
         Debug.println("->NDSContext.getConnection(" + this + ")");
      }

      // Get flags on the context handle.
      int flags = ((Integer)xplatUtil.getContextInfo(
         this,
         Natives.DCK_FLAGS)).intValue();

      if ((flags & Natives.DCV_DISALLOW_REFERRALS) == 0)
         isTree = true;


      if (isTree)
      {
         // Only find new connection if context is tree-level with
         // "floating" connection.
         Exception savedException = null;
         try
         {
            connection = getConnectionIfAvail();
         }
         catch (Exception e)
         {
            // Safe to ignore...conn must already be closed
            savedException = e;
            if (I_DEBUG)
            {
               Debug.ignoreException(e);
            }
         }
         if (null == connection)
         {
            try
            {
               connection = xplatUtil.getConnectionByTreeName(
                  environment,
                  getTreeName(),
                  Natives.OWNER_NDS);

               int reference = xplatUtil.getInfoValue(
                  connection,
                  Natives.CONN_INFO_CONN_REF);
               try
               {
                  int handle = Natives.openConnection(reference);

                  xplatUtil.setContextInfo(
                     this,
                     Natives.DCK_LAST_CONNECTION,
                     new Integer(handle));
               }
               catch (NSIException e1)
               {
                  xplatUtil.close(connection);
                  xplatUtil.setReason(connection, e1);
               }
            }
            catch (Exception e)
            {
            }
         }
         if (null == connection)
            throw xplatUtil.generateContextFailure(
               this,
               new SessionException("Lost all connections to tree"));
      }
      else // not tree
      {
         try
         {
            connection = getConnectionIfAvail();
         }
         catch (Exception e)
         {
            throw xplatUtil.generateContextFailure(this, e);
         }
      }
      return connection;
   }

//   synchronized public Connection getConnectionIfAvail
   public Connection getConnectionIfAvail
   (
   )
   throws SessionException
   {
      if (isValid())
      {
         return xplatUtil.getConnectionIfAvail(this);
      }
      throw new InvalidStateException(
         "Context invalid",
         getReason());
   }

//   synchronized public boolean isValid
   public boolean isValid
   (
   )
   {
      return this.hasHandle;
   }

//   synchronized public Object getInfo
   public Object getInfo
   (
      int key
   )
   throws SessionException
   {
      return xplatUtil.getContextInfo(
         this,
         key);
   }

//   synchronized public void setInfo
   public void setInfo
   (
      int key,
      Object value
   )
   throws SessionException
   {
      xplatUtil.setContextInfo(
         this,
         key,
         value);
   }

//   synchronized public void close
   public void close
   (
   )
   throws SessionException
   {
      try
      {
         // Make sure set to not valid if checks for true
         synchronized (this)
         {
            if (isValid())
            {
               hasHandle = false;
               nativeContext.close();
   //            xplatUtil.freeContext(
   //               this);
            }
         }

      }
      finally
      {
         setReason(
            new SessionException("Context closed"));
      }
   }

   /**
    * @deprecated Use com.novell.service.security and related packages.
    */
//   synchronized public void login
   public void login
   (
      String userName,
      String password,
      boolean extendedCharPasswordFlag
   )
   throws SessionException
   {
      if (ERROR_ON_DEPRECATED)
      {
         System.out.println("This API has been deprecated..." +
            "see documentation for more details. Stack trace follows:");
         Thread.dumpStack();
      }

      if (DEBUG || PASSWORD_DEBUG)
      {
         Debug.println("Login to context: " + toString());
      }
      xplatUtil.loginContext(this, manipulateNDSName(userName),
         password, extendedCharPasswordFlag);

      if (NLM_KLUDGE_CAN_AUTH)
      {
         if (DEBUG)
         {
            Debug.println("NDSContext.login().authenticate()");
         }
//         authenticate();
      }
   }

   public void loginNAS
   (
      NdsIdentity ident,
      String userName,
      String password,
      boolean extendedCharPasswordFlag
   )
   throws SessionException
   {
      if (DEBUG || PASSWORD_DEBUG)
         Debug.println("Login to context NAS: " + toString());

      xplatUtil.loginContextNAS(
         this,
         ident,
         manipulateNDSName(userName),
         password,
         extendedCharPasswordFlag);
   }


   /**
    *
    */
//   synchronized public void loginAsService
   public void loginAsService
   (
      String serviceName
   )
   throws SessionException
   {
      xplatUtil.loginAsServiceContext(
         this,
         manipulateNDSName(serviceName));
   }

   /**
    *
    */
//   synchronized public void logout
   public void logout
   (
   )
   throws SessionException
   {
      xplatUtil.logoutContext(
         this);
   }

   public void logoutNAS(NdsIdentity ident) throws SessionException
   {
      xplatUtil.logoutContextNAS(this, ident);
   }

//   synchronized public boolean isAuthenticated
   public boolean isAuthenticated
   (
   )
   throws SessionException
   {
      boolean authState = false;
      if (NLM_KLUDGE_CAN_AUTH)
      {
         if (DEBUG)
         {
            Debug.println("NDSContext.isAuthenticated() KLUDGE");
         }
         int authType;
         if (DEBUG)
         {
            Debug.println("NDSContext.isAuthenticated() TREE = " +
               getTreeName());
         }
         authState = xplatUtil.specialIsDSAuthenticated(
            this);
      }
      else
         authState = xplatUtil.canDSAuthenticate(
            this);
      if (DEBUG)
      {
         Debug.println("NDSContext.isAuthenticated() " +
            (authState ? "TRUE" : "FALSE"));
      }

      return authState;
   }

   /**
    * Retrieves the attributes specific to NDSContext
    */
//   synchronized public SessionAttrs getAttributes
   public SessionAttrs getAttributes
   (
   )
   throws SessionException
   {
      String[] ids = new String[6];
      int i = 0;

       // Add attributes specific to NDSContext
      ids[i++] = Xplat.DCK_CONFIDENCE_ATTR_ID;
      ids[i++] = Xplat.DCK_NAME_CONTEXT_ATTR_ID;
      ids[i++] = Xplat.DCK_TRANSPORT_TYPE_ATTR_ID;
      ids[i++] = Xplat.DCK_REFERRAL_SCOPE_ATTR_ID;
      ids[i++] = Xplat.DCK_LAST_CONNECTION_ATTR_ID;
      ids[i++] = Xplat.DCK_TREE_NAME_ATTR_ID;

      SessionAttrs attributes = getAttributes(ids);

      return attributes;
   }

   /**
    * Retrieves the attributes specific to NDSContext
    */
//   synchronized public SessionAttrs getAttributes
   public SessionAttrs getAttributes
   (
      String attrIds[]
   )
   throws SessionException
   {
      SessionAttrs attributes = new SessionAttrs();

      int sVal;
      Object o;

      for (int i = 0; i < attrIds.length; i++)
      {
         sVal = Natives.contextIntForAttr(attrIds[i]);
         try
         {
            switch (sVal)
            {
               case Natives.DCK_CONFIDENCE:
               case Natives.DCK_NAME_CONTEXT:
               case Natives.DCK_TRANSPORT_TYPE:
               case Natives.DCK_REFERRAL_SCOPE:
               case Natives.DCK_LAST_CONNECTION:
               case Natives.DCK_TREE_NAME:
                  o = getInfo(sVal);
                  attributes.add(attrIds[i], o);
                  break;

               default:
                  o = null;
                   // Do nothing
            }
         }
         catch (NSIException e)
         {
            if (I_DEBUG)
            {
               Debug.ignoreException(e);
            }
         }
      }

      return attributes;
   }

   /**
    *
    */
//   synchronized public void authenticate
   public void authenticate
   (
   )
   throws SessionException
   {
      authenticate(getConnection());
   }

   /**
    *
    */
//   synchronized public void authenticate
   public void authenticate
   (
      Connection connection
   )
   throws SessionException
   {
      xplatUtil.authenticateConn(
         this,
         connection);
   }

//   synchronized public NDSContext duplicate
   public NDSContext duplicate
   (
   )
   throws SessionException
   {
      return xplatUtil.duplicateContext(this);
   }

//   synchronized public NDSContext duplicate
   public NDSContext duplicate
   (
      SessionEnv environment,
      Connection connection
   )
   throws SessionException
   {
      // Make sure conn is still valid or dup will fail
//      getConnection();

      return xplatUtil.duplicateContext(this, environment, connection);
   }

//   synchronized public String getName
   public String getName
   (
   )
   throws SessionException
   {
      return xplatUtil.getContextName(
         this);
   }

   /**
    * @deprecated Use com.novell.service.security and related packages.
    */
//   synchronized public void changePassword
   public void changePassword
   (
      String userName,
      String oldPassword,
      String newPassword,
      boolean extendedCharPasswordFlag
   )
   throws SessionException
   {
      if (ERROR_ON_DEPRECATED)
      {
         System.out.println("This API has been deprecated..." +
            "see documentation for more details. Stack trace follows:");
         Thread.dumpStack();
      }

      xplatUtil.changePasswordContext(
         this,
         manipulateNDSName(userName),
         oldPassword,
         newPassword,
         extendedCharPasswordFlag);
   }

   /**
    * @deprecated Use com.novell.service.security and related packages.
    */
//   synchronized public void setPassword
   public void setPassword
   (
      String objName,
      String password,
      boolean extendedCharPasswordFlag
   )
   throws SessionException
   {
      if (ERROR_ON_DEPRECATED)
      {
         System.out.println("This API has been deprecated..." +
            "see documentation for more details. Stack trace follows:");
         Thread.dumpStack();
      }

      xplatUtil.setPasswordContext(
         this,
         objName,
         password,
         extendedCharPasswordFlag);
   }

   /**
    * @deprecated Use com.novell.service.security and related packages.
    */
//   synchronized public void verifyPassword
   public void verifyPassword
   (
      String objName,
      String password,
      boolean extendedCharPasswordFlag
   )
   throws SessionException
   {
      if (ERROR_ON_DEPRECATED)
      {
         System.out.println("This API has been deprecated..." +
            "see documentation for more details. Stack trace follows:");
         Thread.dumpStack();
      }

      xplatUtil.verifyPasswordContext(
         this,
         objName,
         password,
         extendedCharPasswordFlag);
   }

//   synchronized public void ping
   public void ping
   (
   )
   throws SessionException
   {
      xplatUtil.contextPing(
         this);
   }

//   private synchronized String manipulateNDSName(String name)
   private String manipulateNDSName(String name)
   {
      if (0 == name.length())
         name = new String("[root]");
      else if ('.' == name.charAt(0))
         name = name.substring(1);
      return name;
   }

//   synchronized protected void setReason
   protected void setReason
   (
      Throwable reason
   )
   {
      this.reason = reason;
   }

//   synchronized protected Throwable getReason
   protected Throwable getReason
   (
   )
   {
      return this.reason;
   }

//   synchronized public Integer getDSNLMBuild()
   public Integer getDSNLMBuild()
   throws SessionException
   {
      return xplatUtil.getDSNLMBuild(this);
   }

//   synchronized public Integer getDSServerRootDepth()
   public Integer getDSServerRootDepth()
   throws SessionException
   {
      return xplatUtil.getDSServerRootDepth(this);
   }

//   synchronized public Boolean isMasterReplica()
   public Boolean isMasterReplica()
   throws SessionException
   {
      return xplatUtil.isMasterReplica(this);
   }

//   synchronized public String getServerDN()
   public String getServerDN()
   throws SessionException
   {
      return xplatUtil.getServerDN(this);
   }
}

