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

  $Archive: /njcl_v2rmi/src/com/novell/service/session/xplat/XplatUtil.java $
  $Revision: 74 $
  $Modtime: 5/15/03 4:59p $

  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 java.rmi.RemoteException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.Vector;
import java.io.Serializable;
import com.novell.service.session.util.Debug;
import com.novell.service.jncp.*;
import com.novell.service.jncpv2r.cal.CalJNI;
import com.novell.service.jncpv2r.net.NetJNI;
import com.novell.service.security.NdsIdentity;
import com.novell.service.session.InvalidStateException;
import com.novell.service.session.SessionAttr;
import com.novell.service.session.SessionAttrEnumerator;
import com.novell.service.session.SessionAttrs;
import com.novell.service.session.SessionEnv;
import com.novell.service.session.SessionException;
import com.novell.service.session.spi.SessionManagerImpl;
import com.novell.service.session.SessionManagerFactory;
import com.novell.service.session.SessionRuntimeException;
import com.novell.service.session.InvalidDomainNameException;
import com.novell.service.session.InvalidUserNameException;
import com.novell.service.session.util.TwoWayHashtable;
import com.novell.service.toolkit.jcl.NWLong;
import com.novell.service.toolkit.jcl.NWVersion;
import com.novell.java.lang.IntegerBuffer;

/** @internal
 * Utility class shared by NDS and Bindery Session providers.
 */
public class XplatUtil
{
   // 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  ENTRY_DEBUG = false;
   final static private boolean  EXIT_DEBUG = false;
   final static private boolean  OPEN_CLOSE_CONN_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  CACHE_DEBUG = false;
   final static private boolean  JVMUID_DEBUG = false;
   final static private boolean  OTHER_DEBUG = false;

   // Print message if deprecated API is used
   final static private boolean ERROR_ON_DEPRECATED = false;
   final private static String INVALID_CONNECTION =
      "Connection no longer valid";
   final private static String INVALID_CONTEXT =
      "Context no longer valid";

   // Can't get valid tree name from reference
   static private boolean NLM_KLUDGE_NO_TREE_FROM_REF = true;
   // Can't get valid user id from reference
   static private boolean NLM_KLUDGE_NO_USER_FROM_REF = true;
   // NT requires us to force licenses...they only keep the license per
   // handle
   static private boolean NT_KLUDGE = true;
   // May open successfully w/bad tree name
   static private boolean NLM_KLUDGE_BADTREE = true;
   // May open successfully w/bad server name
   static private boolean NLM_KLUDGE_BADSERVER = true;
   // This seems to be a client32 problem...even if we close the last handle
   // that we opened...the ref still shows up when we scan
   static private boolean C32_KLUDGE_FORCE_CLOSED = true;

   // Validate tree using conn?
   static boolean CREATE_CONTEXT_CHECK_TREE = true;
   // Give conn to context to use?
   static boolean CREATE_CONTEXT_GIVE_CONN = true;
   // Track given conn in cache?
   static boolean CREATE_CONTEXT_PUT_CACHE = false;

   private static XplatUtil singletonInstance = null;

   private static Hashtable privateInstances = new Hashtable();

   // Used for translating attribute ids to native equivalents
   private static TwoWayHashtable connInfo = null;
   private static TwoWayHashtable contextInfo = null;

   private Connections connectionCache = new Connections();
   public NativeContexts nativeContexts = null;
   public int scope;

   protected void finalize()
   throws Throwable
   {
      if (DEBUG || FINALIZER_DEBUG)
      {
         Debug.println(this.toString());
      }
      if (ENTRY_DEBUG)
      {
         Debug.println("Entry: " + this);
         Debug.printCaller(-1);
      }
      /*
      singletonInstance = null;
      privateInstances = null;
      connInfo = null;
      contextInfo = null;
      connectionCache = null;
      nativeContexts = null;
      */
      shutDown();

      // Java language spec: super.finalize() NOT automatically called
      super.finalize();

      if (DEBUG || EXIT_DEBUG)
      {
         Debug.println("Exit");
      }
   }

   protected void classFinalize()
   throws Throwable
   {
      if (DEBUG || ENTRY_DEBUG)
      {
         Debug.println("Entry: " + this);
         Debug.printCaller(-1);
      }
   }

   static
   {
      System.runFinalizersOnExit(true);
      if (Boolean.getBoolean("com.novell.service.session.util.Debug"))
         Debug.getDebug();  // side effect...loads the class
   }

   private XplatUtil(int scope)
   throws SessionException
   {
      this.scope = scope;
      if (DEBUG || ENTRY_DEBUG)
      {
         Debug.println("Entry");
         Debug.printCaller(-1);
      }
      this.nativeContexts = new NativeContexts();
      this.connectionCache = new Connections();
      if (DEBUG || EXIT_DEBUG)
      {
         Debug.println("Exit");
      }
   }

   /**
    * Return singleton instance of this class.
    */
   public static XplatUtil getInstance(SessionManagerImpl sm)
   throws SessionException, RemoteException
   {
      int scope = sm.getScope();
      if (0 == (scope & SessionManagerFactory.PRIVATE_SCOPE))
         return getPublicInstance(sm);
      else
         return getPrivateInstance(sm);
   }


   /**
    * Return singleton instance of this class.
    */
   public static XplatUtil getPublicInstance(SessionManagerImpl sm)
   {
      if (null == singletonInstance)
      {
         try
         {
            singletonInstance = new XplatUtil(sm.getScope());
            // Open default connection
            Connection connection =
               singletonInstance.getConnectionDefault();
         }
         catch (SessionException e)
         {
            throw new SessionRuntimeException("Caught exception", e);
         }
      }
      return (singletonInstance);
   }

   /**
    * Return singleton instance of this class.
    */
   public static XplatUtil getPrivateInstance(SessionManagerImpl sm)
   throws SessionException, RemoteException
   {
      XplatUtil privateInstance;

      synchronized (privateInstances)
      {
         privateInstance = (XplatUtil)privateInstances.get(sm.getUID());

         if (null == privateInstance)
         {
            try
            {
               privateInstance = new XplatUtil(sm.getScope());
               privateInstances.put(sm.getUID(), privateInstance);
               // Open default connection
               Connection connection =
                  privateInstance.getConnectionDefault();
            }
            catch (SessionException e)
            {
               throw new SessionRuntimeException("Caught exception", e);
            }
         }
      }
      return (privateInstance);
   }

   public void shutDown()
   {
      try
      {
         int size = nativeContexts.size();
         NativeContext[] children = new NativeContext[size];
         Enumeration enum = nativeContexts.nativeContexts();
         int i=0;
         while (enum.hasMoreElements())
         {
            children[i++] = (NativeContext)enum.nextElement();
         }
         for (i = 0; i < size; i++)
         {
            NativeContext ctx = children[i];
            try
            {
               ctx.close();
            }
            catch (Exception e)
            {
               if (I_DEBUG)
               {
                  Debug.ignoreException(e);
               }
            }
         }
      }
      catch (Exception e)
      {
         if (I_DEBUG)
         {
            Debug.ignoreException(e);
         }
      }

      try
      {
         int size = connectionCache.size();
         Connection[] children = new Connection[size];
         Enumeration enum = connectionCache.elements();
         int i=0;
         while (enum.hasMoreElements())
         {
            children[i++] = (Connection)enum.nextElement();
         }
         for (i = 0; i < size; i++)
         {
            Connection conn = children[i];
            try
            {
               close(conn);
            }
            catch (Exception e)
            {
               if (I_DEBUG)
               {
                  Debug.ignoreException(e);
               }
            }
         }
      }
      catch (Exception e)
      {
         if (I_DEBUG)
         {
            Debug.ignoreException(e);
         }
      }
   }

   public boolean equals(
      Connection c1,
      Connection c2)
   {
//      this.connectionCache.lockRead();
      try
      {
         return (c1.reference == c2.reference);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public void setServer(
      Connection c1,
      String server)
   {
//      this.connectionCache.lockWrite();
      try
      {
         c1.server = server;
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public void setTree(
      Connection c1,
      String tree)
   {
//      this.connectionCache.lockWrite();
      try
      {
         c1.tree = DomainName.strip(tree);
//         if (c1.tree.length() == 32)
//            Thread.dumpStack();
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public void checkOwner(
      Connection c1,
      int owner)
   throws SessionException
   {
//      this.connectionCache.lockWrite();
      try
      {
         if (DEBUG)
         {
            Debug.println("Check owner: " + c1.toString() +
               " to " + Natives.getOwnerString(owner));
         }
         int state = Natives.getConnInfoLong(
            c1.handle,
            c1.reference,
            Natives.CONN_INFO_AUTHENT_STATE);

         // Authenticated state always overrides ownership
         if (Natives.AUTH_TYPE_NDS == state)
            setOwner(c1, Natives.OWNER_NDS);
         else if (Natives.AUTH_TYPE_BIND == state)
            setOwner(c1, Natives.OWNER_BINDERY);
         // Override existing owner if not authed and not already owned
         else if (Natives.OWNER_NONE == c1.owner)
         {
            // Can't set to NDS if not NDS capable
            if (Natives.OWNER_NDS == owner)
            {
               int NDSCapable = Natives.getConnInfoLong(
                  c1.handle,
                  c1.reference,
                  Natives.CONN_INFO_NDS_STATE);
               if (Natives.NDS_CAPABLE == NDSCapable)
               {
                  setOwner(c1, owner);
               }
            }
            else
               setOwner(c1, owner);
         }
      }
      catch (SessionException e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public void setOwner(
      Connection c1,
      int owner)
   {
//      this.connectionCache.lockWrite();
      try
      {
         if (DEBUG)
         {
            Debug.printlnOnly("SetOwner from: " + toString(c1) +
               " to " + Natives.getOwnerString(owner));
         }
         c1.owner = owner;
         c1.ownerTrace =
               new SessionException("Connection ownership trace");
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public int getOwner(
      Connection c1)
   {
      return c1.owner;
   }

   public String getServer(
      Connection c1)
   throws SessionException
   {
      if (null == c1.server)
      {
//         this.connectionCache.lockWrite();
         try
         {
            if (null == c1.server)
            {
               c1.server = Natives.getConnInfoString(
                  c1.handle,
                  c1.reference,
                  Natives.CONN_INFO_SERVER_NAME);
               if (null == c1.server)
               {
                  throw new SessionException(
                     "Unable to obtain server name from reference: " + c1.reference);
               }
            }
         }
         catch (SessionException e)
         {
            throw invalidate(c1, e);
         }
         finally
         {
//            this.connectionCache.unlock();
         }
      }
      return c1.server;
   }

   public String getTree(
      Connection c1)
   throws SessionException
   {
      if (null == c1.tree)
      {
//         this.connectionCache.lockWrite();
         try
         {
            if (null == c1.tree)
            {
               setTree(
                  c1,
                  Natives.getConnInfoString(
                     c1.handle,
                     c1.reference,
                     Natives.CONN_INFO_TREE_NAME));
               if (null == c1.tree)
               {
                  if (Natives.NDS_CAPABLE ==
                      getInfoValue(c1, Natives.CONN_INFO_NDS_STATE))
                  {
                     throw new SessionException(
                        "Unable to obtain tree name from reference: " + c1.reference);
                  }
                  else
                     c1.tree = "";
               }
            }
         }
         catch (SessionException e)
         {
            throw invalidate(c1, e);
         }
         finally
         {
//            this.connectionCache.unlock();
         }
      }
      return c1.tree;
   }

   public String toString(
      Connection c1)
   {
//      this.connectionCache.lockRead();
      try
      {
         StringBuffer out = new StringBuffer();
         out.append("Connection$");
         if (DEBUG || JVMUID_DEBUG)
         {
            out.append(":U=" + c1.uid);
         }
         if (c1.isValid)
         {
            out.append(
               ":H=0x" + Integer.toHexString(c1.handle));
            out.append(
               ":R=0x" + Integer.toHexString(c1.reference));
            out.append(
               ":O=" + Natives.getOwnerString(c1.owner));
            if (null != c1.server)
               out.append(
                  ":server=" + c1.server);
            if (null != c1.tree)
               out.append(
                  ":tree=" + c1.tree);
         }
         else
            out.append(":R=invalid");

         return out.toString();
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public int getReference(
      Connection c1)
   throws SessionException
   {
//      this.connectionCache.lockRead();
      try
      {
         if (!c1.isValid)
            throw new InvalidStateException(
               INVALID_CONNECTION,
               getReason(c1));

         return c1.reference;
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public int getHandle(
      Connection c1)
   throws SessionException
   {
      if (DEBUG || ENTRY_DEBUG)
         Debug.println("Entry");
//      this.connectionCache.lockRead();
      try
      {
         if (!c1.isValid)
            throw new InvalidStateException(
               INVALID_CONNECTION,
               getReason(c1));

         if (DEBUG || EXIT_DEBUG)
            Debug.println("Exit: " + toString(c1));

         return c1.handle;
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public void setReason(
      Connection c1,
      Throwable reason)
   {
//      this.connectionCache.lockWrite();
      try
      {
         c1.reason = reason;
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public Throwable getReason(
      Connection c1)
   {
      return c1.reason;
   }

   public SessionException invalidate(
      Connection c1,
      Throwable reason)
   throws SessionException
   {
      if (DEBUG || ENTRY_DEBUG)
         Debug.println("Entry: " + toString(c1));
      if (c1.isValid)
      {
         try
         {
            close(c1);
         }
         catch(Exception e)
         {} //Ignore failure
         SessionException e = new SessionException(
            "Connection (Tree:" + c1.tree + " Server:" + c1.server + ") invalidated - (" + reason + ")", reason);
         setReason(c1, e);
      }
      return new InvalidStateException(INVALID_CONNECTION, getReason(c1));
   }

   public void keep(
      Connection c1)
   throws SessionException
   {
//      this.connectionCache.lockRead();
      try
      {
         Natives.keep(c1.handle);
      }
      catch (SessionException e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public boolean isValid(
      Connection c1)
   {
      return c1.isValid;
   }

   public Object getInfo(
      Connection c1,
      int infoLevel)
   throws SessionException
   {
      switch (infoLevel)
      {
         case Natives.CONN_INFO_AUTHENT_STATE:
         case Natives.CONN_INFO_BCAST_STATE:
         case Natives.CONN_INFO_CONN_REF:
         case Natives.CONN_INFO_CONN_NUMBER:
         case Natives.CONN_INFO_USER_ID:
         case Natives.CONN_INFO_NDS_STATE:
         case Natives.CONN_INFO_MAX_PACKET_SIZE:
         case Natives.CONN_INFO_LICENSE_STATE:
         case Natives.CONN_INFO_DISTANCE:
            return new Integer(
               getInfoValue(c1, infoLevel));

         case Natives.CONN_INFO_TREE_NAME:
         case Natives.CONN_INFO_SERVER_NAME:
            return getInfoString(c1, infoLevel);

         case Natives.CONN_INFO_SERVER_VERSION:
            return getInfoVersion(c1, infoLevel);

         case Natives.CONN_INFO_SERVER_ADDRESS:
            return getInfoAddress(c1, infoLevel);

         default:
            throw new SessionException("infoLevel invalid: 0x" +
               Integer.toHexString(infoLevel));
      }
   }

   public int getInfoValue(
      Connection c1,
      int infoLevel)
   throws SessionException
   {
      int info;
//      this.connectionCache.lockRead();
      try
      {
         info = Natives.getConnInfoLong(
            c1.handle,
            c1.reference,
            infoLevel);
      }
      catch (Exception e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
      return info;
   }

   public String getInfoString(
      Connection c1,
      int infoLevel)
   throws SessionException
   {
      String info = "";
      switch (infoLevel)
      {
         case Natives.CONN_INFO_SERVER_NAME:
            info = getServer(c1);  //cached
            break;
         case Natives.CONN_INFO_TREE_NAME:
            info = getTree(c1);  //cached
            break;
         default:
//            this.connectionCache.lockRead();
            try
            {
               info = Natives.getConnInfoString(
                  c1.handle,
                  c1.reference,
                  infoLevel);
            }
            catch (SessionException e)
            {
               throw invalidate(c1, e);
            }
            finally
            {
//               this.connectionCache.unlock();
            }
      }
      return info;
   }

   public Address getInfoAddress(
      Connection c1)
   throws SessionException
   {
      return getInfoAddress(c1, Address.TYPE_NONE);
   }

   public Address getInfoAddress(
      Connection c1,
      int type)
   throws SessionException
   {
//      this.connectionCache.lockRead();
      try
      {
         return Natives.getConnInfoAddress(
            c1.handle,
            c1.reference,
            type);
      }
      catch (SessionException e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public Version getInfoVersion(
      Connection c1,
      int infoLevel)
   throws SessionException
   {
//      this.connectionCache.lockRead();
      try
      {
         return Natives.getConnInfoVersion(
            c1.handle,
            c1.reference,
            infoLevel);
      }
      catch (SessionException e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   /**
    * Close connection handle.
    */
   public void close(
      Connection c1)
   throws SessionException
   {
      if (DEBUG || ENTRY_DEBUG)
         Debug.println("Entry: " + toString(c1));
      synchronized (c1)
      {
         try
         {
            if (c1.isValid)
            {
               c1.reason = new SessionException("Connection closed");
               removeConnection(c1);
               Natives.closeConnection(c1.handle);
            }
         }
         finally
         {
            c1.isValid = false;
         }
      }
   }

   /**
    * Close connection reference (system-level close).
    */
   public void closeRef(
      Connection c1)
   throws SessionException
   {
      if (DEBUG || ENTRY_DEBUG)
         Debug.println("Entry: " + toString(c1));
      try
      {
         if (c1.isValid)
         {
            c1.reason = new SessionException("Connection reference closed");
            removeConnection(c1);
            Natives.closeRef(c1.reference);
         }
      }
      finally
      {
         c1.isValid = false;
      }
   }

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

         if (null == userName)
            throw new NullPointerException(userName);

         Natives.loginConnection(
            c1.handle,
            userName,
            password);
      }
      catch (InvalidUserNameException e)
      {
         // rethrow
         throw e;
      }
      catch (SessionException e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   /**
    *
    */
   public void logout(
      Connection c1)
   throws SessionException
   {
//      this.connectionCache.lockRead();
      try
      {
         Natives.logoutConnection(
            c1.handle);
      }
      catch (SessionException e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public String getUserName(
      Connection c1)
   throws SessionException
   {
//      this.connectionCache.lockRead();
      try
      {
         // Try to get userName
         Integer userId = new Integer(
            getInfoValue(c1, Natives.CONN_INFO_USER_ID));
         try
         {
            if (null != userId)
            {
               if (0 != userId.intValue())
                  return Natives.getUserName(
                     c1.handle,
                     userId.intValue());
            }
         }
         catch (Exception e)
         {
            if (DEBUG || I_DEBUG)
            {
               Debug.ignoreException("unable to obtain", e);
            }
         }
         return ""; // Default to empty string
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   /**
    * Retrieves the attributes specific to the connection
    */
   public SessionAttrs getAttributes(
      Connection c1)
   throws SessionException
   {
      String[] ids =
      {
         Xplat.CONN_AUTHENTICATION_STATE_ATTR_ID,
         Xplat.CONN_BROADCAST_STATE_ATTR_ID,
         Xplat.CONN_CONNECTION_REFERENCE_ATTR_ID,
         Xplat.CONN_TREE_NAME_ATTR_ID,
         Xplat.CONN_CONNECTION_NUMBER_ATTR_ID,
         Xplat.CONN_USER_ID_ATTR_ID,
         Xplat.CONN_SERVER_NAME_ATTR_ID,
         Xplat.CONN_NDS_STATE_ATTR_ID,
         Xplat.CONN_MAX_PACKET_SIZE_ATTR_ID,
         Xplat.CONN_LICENSE_STATE_ATTR_ID,
         Xplat.CONN_DISTANCE_ATTR_ID,
         Xplat.CONN_SERVER_VERSION_ATTR_ID,
         Xplat.CONN_SERVER_ADDRESS_ATTR_ID
      };

      SessionAttrs attributes = getAttributes(c1, ids);

      return attributes;
   }

   /**
    * Retrieves the attributes specific to the connection
    */
   public SessionAttrs getAttributes(
      Connection c1,
      String attrIds[])
   throws SessionException
   {
//      this.connectionCache.lockWrite();
      try
      {
         SessionAttrs attributes = new SessionAttrs();

         int sVal;
         Object o;

         for (int i = 0; i < attrIds.length; i++)
         {
            sVal = Natives.connIntForAttr(attrIds[i]);
            try
            {
               switch (sVal)
               {
                  case Natives.CONN_INFO_AUTHENT_STATE:
                  case Natives.CONN_INFO_BCAST_STATE:
                  case Natives.CONN_INFO_CONN_REF:
                  case Natives.CONN_INFO_CONN_NUMBER:
                  case Natives.CONN_INFO_NDS_STATE:
                  case Natives.CONN_INFO_MAX_PACKET_SIZE:
                  case Natives.CONN_INFO_LICENSE_STATE:
                  case Natives.CONN_INFO_DISTANCE:
                  case Natives.CONN_INFO_USER_ID:
                     o = new Integer(getInfoValue(c1, sVal));
                     break;

                  case Natives.CONN_INFO_TREE_NAME:
                  case Natives.CONN_INFO_SERVER_NAME:
                     o = getInfoString(c1, sVal);
                     break;

                  case Natives.CONN_INFO_SERVER_ADDRESS:
                     o = getInfoAddress(c1);
                     break;

                  case Natives.CONN_INFO_SERVER_VERSION:
                     o = getInfoVersion(c1, sVal);
                     break;

                  default:
                     o = null;
                      // Do nothing
               }

               if (null != o)
                  attributes.add(attrIds[i], o);
            }
            catch (NSIException e)
            {
               if (DEBUG || I_DEBUG)
               {
                  Debug.ignoreException(e);
               }
            }
         }
         return attributes;
      }
      catch (SessionException e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public void changePassword(
      Connection c1,
      String userName,
      String oldPassword,
      String newPassword)
   throws SessionException
   {
//      this.connectionCache.lockRead();
      try
      {
         Natives.changePasswordConnection(
            c1.handle,
            userName,
            oldPassword,
            newPassword);
      }
      catch (SessionException e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public void setPassword(
      Connection c1,
      String objName,
      int objType,
      String password)
   throws SessionException
   {
//      this.connectionCache.lockRead();
      try
      {
         Natives.setPasswordConnection(
            c1.handle,
            objName,
            objType,
            password);
      }
      catch (SessionException e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public void verifyPassword(
      Connection c1,
      String objName,
      int objType,
      String password)
   throws SessionException
   {
//      this.connectionCache.lockRead();
      try
      {
         Natives.verifyPasswordConnection(
            c1.handle,
            objName,
            objType,
            password);
      }
      catch (SessionException e)
      {
         throw invalidate(c1, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   /**
    * Returns Enumeration of all cached connections.
    *
   * NOTE: Caller is responsible for releasing the read lock.
    */
   private ConnectionEnumerator enumerateCachedConnectionsWithLock()
   throws SessionException
   {
      // NOTE: See below...read lock will be acquired.
      return (new ConnectionEnumerator(
         getCachedConnectionsWithLock()));
   }

   /**
    * Returns Vector of all cached connections, no matter who owns them.
    *
    * NOTE: Caller is responsible for releasing the read lock.
    */
   private Connections getCachedConnectionsWithLock()
   throws SessionException
   {
      if (DEBUG)
      {
         Debug.println("Giving out raw cache");
      }
      try
      {
//         this.connectionCache.lockRead();
      }
      catch (Exception e)
      {
//         this.connectionCache.unlock();
      }
      // NOTE: When things work correctly here, this method returns with
      // a read lock acquired on the cache.  It is the responsibility of the
      // caller to unlock the read lock when done with the cache.  This was
      // done for performance reasons...only alternative considered is to
      // copy the cache.
      return ((Connections)this.connectionCache);
   }

   /**
    * Updates the connection cache and returns Vector of all connections,
    * no matter who owns them.
    */
   private void updateConnectionCache()
   throws SessionException
   {
      // Only update cache if SCAN_SCOPE is on
      if (0 == (scope & SessionManagerFactory.NO_SCAN_SCOPE))
      {
//         this.connectionCache.lockWrite();
         try
         {
            Hashtable reqRefs = new Hashtable();

            RequesterConnectionSearchEnumerator enum = enumerateRequesterInfo(
               Natives.CONN_INFO_NONE,
               0);
            while (enum.hasMoreElements())
            {
               try
               {
                  Integer reference = enum.next();

                  if (!this.connectionCache.containsKey(reference))
                  {
                     // Add new refs to cache
                     if (DEBUG)
                        Debug.println("Update connection cache: " + reference);
                     int ref = reference.intValue();
                     int handle = Natives.openConnection(ref);
                     addConnection(ref, handle, Natives.OWNER_NONE);
                  }
                  // Build potiential list of refs to remove from cache
                  reqRefs.put(reference, reference);
               }
               catch (Exception e)
               {
                  // Ignore special cases where we scan for (and get) a successful
                  // reference, but we fail to get further information on the
                  // connection
                  if (I_DEBUG)
                  {
                     Debug.ignoreException("Transient connection ignored", e);
                  }
               }
            }

            ConnectionEnumerator cacheEnum = this.connectionCache.elements();

            // Remove missing conns from cache
            while (cacheEnum.hasMoreElements())
            {
               Connection cacheConn = cacheEnum.next();

               // someone else could have already removed us
//               if (null != cacheConn)
               {
                  int ref = 0;
//                  try
                  {
                     ref = getReference(cacheConn);
                  }
/*
                  catch (Exception e)
                  {
                     // someone else could have already removed us
                     // fall through and remove cacheConn object from cache.
                     // NOTE: May need to synchronize all cache access to avoid
                     // checks like c1
                     if (I_DEBUG)
                     {
                        Debug.ignoreException("Connection already invalid", e);
                     }
                  }
*/
                  Integer reference = new Integer(ref);
                  if (reqRefs.containsKey(reference))
                  {
                     if (CACHE_DEBUG)
                     {
                        Debug.println("Entry matches cache: 0x" +
                           Integer.toHexString(
                              reference.intValue()));
                     }
                     // Found, no need to check again...speed up next
                     // containsKey call above by removing c1 one from search
                     reqRefs.remove(reference);
                  }
                  else
                  {
                     if (CACHE_DEBUG)
                     {
                        Debug.println("Removing entry from cache: " + cacheConn);
                     }
                     // Not found, remove from cache
                     try
                     {
                        removeConnection(cacheConn);
                     }
                     catch (Exception e)
                     {
                        if (I_DEBUG)
                        {
                           Debug.ignoreException("Connection already removed", e);
                        }
                     }
                  }
               }
            }
         }
         finally
         {
//            this.connectionCache.unlock();
         }
      }
   }

   /**
    * Removes entry from native cache.
    */
   private void removeConnection(
      Connection c1)
   throws SessionException
   {
      if (DEBUG || CACHE_DEBUG)
      {
         Debug.println("Removing connection from cache (Connection): " +
            "0x" + Integer.toHexString(
            c1.hashCode()));
      }
      try
      {
//         this.connectionCache.lockWrite();
         // Must synchronize on c1 as well, since we don't want someone else
         // modifying c1 while we remove it.
         synchronized (c1)
         {
            this.connectionCache.remove(c1);
         }
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   /**
    * Addes native connection to cache.
    *
    * Must be private...someone else must worry about synchronization.
    */
   private Connection addConnection(
      int reference,
      int handle,
      int owner)
   throws SessionException
   {
      if (DEBUG || CACHE_DEBUG)
      {
         Debug.println("Adding connection to cache: " +
            "0x" + Integer.toHexString(reference) +
            ": OWNER=" + getOwnerString(owner));
      }

      try
      {
         // Not necessary if this method remains private.
         //this.connectionCache.lockWrite();
         // Make sure it still isn't in cache after acquiring lock
         // Connection connection = this.connectionCache.get(reference);
         Connection connection = null;
         //if (null != connection)
         {
            // Add missing element
            connection =
               new Connection(reference, handle, owner, this);
            this.connectionCache.put(connection);

            if (S_DEBUG)
            {
               String ndsCapable = "NDS_UNKNOWN";
               String authState = "AUTH_UNKNOWN";

               try
               {
                  int NDS = Natives.getConnInfoLong(
                     0,
                     reference,
                     Natives.CONN_INFO_NDS_STATE);
                  if (NDS == Natives.NDS_CAPABLE)
                     ndsCapable = "NDS_CAPABLE";
                  else
                     ndsCapable = "!NO NDS!";
                  int state = Natives.getConnInfoLong(
                     0,
                     reference,
                     Natives.CONN_INFO_AUTHENT_STATE);
                  switch (state)
                  {
                     case Natives.AUTH_TYPE_NONE:
                        authState = "NOT_AUTH";
                        break;
                     case Natives.AUTH_TYPE_BIND:
                        authState = "BIND_AUTH";
                        break;
                     case Natives.AUTH_TYPE_NDS:
                        authState = "NDS_AUTH";
                        break;
                     default:
                        authState = "STRANGE_AUTH(" + Integer.toString(state) + ")";
                  }
               }
               catch (Exception e)
               {
                  if (I_DEBUG)
                     Debug.ignoreException("EXCEPTION GETTING NATIVE INFO FOR DEBUG:", e);
               }
               String server = getServer(connection);
               String tree = getTree(connection);
               Debug.println("Exit: " +
                  ":NATIVE=0x" + Integer.toHexString(reference) +
                  ":OWNER=" + getOwnerString(owner) +
                  ":Server=" + server + ":Tree=" + tree +
                  ":" + ndsCapable +
                  ":" + authState);
            } // if (S_DEBUG)
         }
         //else
         //   checkOwner(connection, owner);
         return (connection);
      }
      finally
      {
         //this.connectionCache.unlock();
      }
   }

   /**
    * Returns Enumeration of Bindery connections.
    */
   public ConnectionEnumerator enumerateBinderyConnections()
   throws SessionException
   {
      return (getBinderyConnections().elements());
   }

   /**
    * Returns Vector of Bindery connections.
    */
   public Connections getBinderyConnections()
   throws SessionException
   {
      Connections returnedConns = new Connections();
      updateConnectionCache();
      try
      {
         ConnectionEnumerator enum = enumerateCachedConnectionsWithLock();
         while (enum.hasMoreElements())
         {
            Connection conn = enum.next();
            try
            {
               checkOwner(conn, Natives.OWNER_BINDERY);
               if (Natives.OWNER_BINDERY == getOwner(conn))
                  returnedConns.put(conn);
            }
            catch (Exception e)
            {
               // Requester state can change out from under us at any time
               if (I_DEBUG)
               {
                  Debug.ignoreException(e);
               }
            }
         }
      }
      finally
      {
//         this.connectionCache.unlock();
      }
      return (returnedConns);
   }

   /**
    * Returns Enumeration of connections under domain name.
    */
   public ConnectionEnumerator enumerateTreeConnections(
      String domainName)
   throws SessionException
   {
      return (getTreeConnections(domainName).elements());
   }

   /**
    * Returns Vector of connections under domain name.
    */
   public Connections getTreeConnections(
      String domainName)
   throws SessionException
   {
      Connections returnedConns = new Connections();
      updateConnectionCache();
      try
      {
         ConnectionEnumerator enum = enumerateCachedConnectionsWithLock();
         while (enum.hasMoreElements())
         {
            Connection conn = enum.next();

            try
            {
               checkOwner(conn, Natives.OWNER_NDS);
               if (Natives.OWNER_NDS == getOwner(conn))
               {
                  String treeName = getTree(conn);
                  if (treeName.equals(domainName))
                     returnedConns.put(conn);
               }
            }
            catch (Exception e)
            {
               // Requester state can change out from under us at any time
               if (I_DEBUG)
               {
                  Debug.ignoreException(e);
               }
            }
         }
      }
      finally
      {
//         this.connectionCache.unlock();
      }
      return (returnedConns);
   }

   /**
    * Returns Enumeration of tree names currently attached to.
    */
   public Enumeration enumerateTrees()
   throws SessionException
   {
      return (getTrees().
         elements());
   }

   /**
    * Returns Hashtable of tree names currently attached to.
    */
   public Hashtable getTrees()
   throws SessionException
   {
      Hashtable trees = new Hashtable();
      updateConnectionCache();

      // Search for NDS-tree CONNECTIONS
      SessionAttrs attrs = new SessionAttrs();
      attrs.add(
         Xplat.CONN_NDS_STATE_ATTR_ID,
         new Integer(Natives.NDS_CAPABLE));

      try
      {
//         this.connectionCache.lockRead();
         ConnectionSearchEnumerator connEnum =
            new ConnectionSearchEnumerator(attrs, this.connectionCache);

         while (connEnum.hasMoreElements())
         {
            try
            {
               Connection conn = connEnum.next();
               String tree = getTree(conn);
               if (!trees.contains(tree))
                  trees.put(tree, tree);
            }
            catch (SessionException e)
            {
               // Requester state can change out from under us at any time
               if (I_DEBUG)
               {
                  Debug.ignoreException(e);
               }
            }
         }

         // get trees by context
         Enumeration treeEnum = nativeContexts.trees();
         while (treeEnum.hasMoreElements())
         {
            String tree = (String)treeEnum.nextElement();
            if (!trees.contains(tree))
               trees.put(tree, tree);
         }
      }
      finally
      {
//         this.connectionCache.unlock();
      }

      return (trees);
   }

   /**
    * Get handle to use for lookup
    */
   private int getLookupHandle(
      SessionEnv environment)
   throws SessionException
   {
      String lookupName = null;
      Object o = environment.get(Xplat.LOOKUP_DOMAIN);
      if (null != o)
         lookupName = (String)o;
      return getHandleFromName(lookupName);
   }


   /**
    * Get connection handle if exists in cache
    */
   private int getHandleFromName(
      String domainName)
   throws SessionException
   {
      int connHandle = -1;
      if (null != domainName)
      {
         updateConnectionCache();

         SessionAttrs attrs = new SessionAttrs();
         attrs.add(
            Xplat.CONN_SERVER_NAME_ATTR_ID,
            domainName);

         try
         {
//            this.connectionCache.lockRead();
            ConnectionSearchEnumerator enum1 =
               new ConnectionSearchEnumerator(attrs, this.connectionCache);

            // Important...must already be connected to start name
            if (enum1.hasMoreElements())
            {
               Connection c1 = null;
               try
               {
                  c1 = enum1.next();
                  connHandle = Natives.getConnInfoLong(
                     0,
                     getReference(c1),
                     Natives.CONN_INFO_SERVER_NAME);
               }
               catch (SessionException e)
               {
                  throw invalidate(c1, e);
               }
            }
         }
         finally
         {
//            this.connectionCache.unlock();
         }
      }
      return connHandle;
   }

   /**
    */
   public Connection getConnectionFromHandle(
      int handle,
      int owner)
   throws SessionException
   {
      int reference;
      reference = Natives.getConnInfoLong(
         handle,
         0,
         Natives.CONN_INFO_CONN_REF);

      try
      {
//         this.connectionCache.lockRead();
         Connection conn = this.connectionCache.get(reference);
         if (null == conn)
         {
            if (DEBUG)
            {
               Debug.println("Adding context's new connection to cache");
            }
            int cacheHandle = Natives.openConnection(reference);
            conn = addConnection(reference, cacheHandle, owner);
         }
         return conn;
      }
      finally
      {
//         this.connectionCache.unlock();
      }
   }

   public RequesterConnectionSearchEnumerator enumerateRequesterInfo(
      int scanLevel,
      String scanString)
   throws SessionException
   {
      return (new RequesterConnectionSearchEnumerator(scanLevel, scanString));
   }

   public RequesterConnectionSearchEnumerator enumerateRequesterInfo(
      int scanLevel,
      int scanValue)
   throws SessionException
   {
      return (new RequesterConnectionSearchEnumerator(scanLevel, scanValue));
   }

   public RequesterConnectionSearchEnumerator enumerateRequesterInfo(
      int scanLevel,
      Version scanVersion)
   throws SessionException
   {
      NWVersion v = new NWVersion(
         scanVersion.majorVersion,
         scanVersion.minorVersion,
         scanVersion.revision);
      return (new RequesterConnectionSearchEnumerator(scanLevel, scanVersion));
   }

   private String getString(
      Object object)
   {
      if (object instanceof Integer)
      {
         int i = ((Integer)object).intValue();
         return ("0x" + Integer.toHexString(i) + "(" + i + ")");
      }
      if (object instanceof Version)
      {
         Version v = (Version)object;
         return
            (Long.toString (v.majorVersion) + "." +
            Long.toString (v.minorVersion) + "." +
            Long.toString (v.revision));
      }
      return (object.toString());
   }

   protected String getOwnerString(
      int owner)
   {
      switch (owner)
      {
         case Natives.OWNER_NONE:
            return (Natives.OWNER_NONE_STRING);
         case Natives.OWNER_BINDERY:
            return (Natives.OWNER_BINDERY_STRING);
         case Natives.OWNER_NDS:
            return (Natives.OWNER_NDS_STRING);
         default:
            return ("UNKNOWN");
      }
   }

   /*
    * Open a default connection.
    *
    * @exception SessionException Or a subclass thereof.
    */
   public Connection getConnectionDefault()
   throws SessionException
   {
      Connection connection = null;

      // We can always lockRead() when we're in a lockWrite(), and we want
      // to bracket this whole group for write...so lock first.
//      this.connectionCache.lockWrite();
      try
      {
         updateConnectionCache();
         // Find in cache
         connection = findConnectionDefault();
         if (null == connection)
         {
            // Not in cache, try direct open
            connection = openConnectionDefault();
         }
      }
      finally
      {
//         this.connectionCache.unlock();
      }
      return connection;
   }

   /*
    * Open a connection by server name.
    *
    * @exception SessionException Or a subclass thereof.
    *
    * Must be private...someone else must worry about synchronization.
    */
   private Connection findConnectionDefault()
   throws SessionException
   {
      Connection connection = null;

      try
      {
         // Find in cache
         ConnectionEnumerator enum = enumerateCachedConnectionsWithLock();

         if (enum.hasMoreElements())
         {
            connection = enum.next();
         }
      }
      catch (Exception e)
      {
         throw new SessionRuntimeException(
            "Unable to obtain default connection", e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }

      return (connection);
   }

   /*
    * Open a connection by server name.
    *
    * @exception SessionException Or a subclass thereof.
    *
    * Must be private...someone else must worry about synchronization.
    */
   private Connection openConnectionDefault()
   throws SessionException
   {
      Connection connection = null;

      // Not necessary if this method remains private.
      //this.connectionCache.lockWrite();
      try
      {
         int handle = Natives.openConnection();
         int ref = Natives.getConnectionReference(handle);
         connection = addConnection(ref, handle, Natives.OWNER_NONE);
      }
      catch (Exception e)
      {
         throw new SessionRuntimeException(
            "Unable to obtain default connection", e);
      }
      finally
      {
         //this.connectionCache.unlock();
      }

      return (connection);
   }

   /*
    * Open a connection by server name.
    *
    * @exception SessionException Or a subclass thereof.
    */
   public Connection getConnectionByName(
      SessionEnv environment,
      String domainName,
      int owner)
   throws SessionException
   {
      Connection connection = null;

      // We can always lockRead() when we're in a lockWrite(), and we want
      // to bracket this whole group for write...so lock first.
//      this.connectionCache.lockWrite();
      try
      {
         updateConnectionCache();
         // Find in cache
         connection = findConnectionByName(
            environment,
            domainName,
            owner);
         if (null == connection)
         {
            // Not in cache, try direct open
            connection = openConnectionByName(
               environment,
               domainName,
               owner);
         }
      }
      finally
      {
//         this.connectionCache.unlock();
      }

      return (connection);
   }

   /*
    * Open a connection by server name.
    *
    * @exception SessionException Or a subclass thereof.
    *
    * Must be private...someone else must worry about synchronization.
    */
   private Connection findConnectionByName(
      SessionEnv environment,
      String domainName,
      int owner)
   throws SessionException
   {
      Connection connection = null;
      int nameFormat = Natives.NAME_FORMAT_BIND;
      int mode = Natives.OPEN_LICENSED;

//      this.connectionCache.lockRead();
      try
      {
         // Find in cache
         SessionAttrs attrs = new SessionAttrs();
         attrs.add(
            Xplat.CONN_SERVER_NAME_ATTR_ID,
            domainName);
         ConnectionSearchEnumerator enum =
            new ConnectionSearchEnumerator(attrs, this.connectionCache);

         if (enum.hasMoreElements())
         {
            connection = enum.next();
            if (getOwner(connection) == Natives.OWNER_NONE)
               setOwner(connection, owner);
            if (getOwner(connection) != owner)
               throw new NotConnectionOwnerException(
                  toString(connection), connection.ownerTrace);
         }
      }
      catch (Exception e)
      {
         // I'm assuming here that everything is messed up...shouldn't
         // get to this.
         throw new InvalidDomainNameException(domainName, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }

      return (connection);
   }

   /*
    * Open a connection by server name.
    *
    * @exception SessionException Or a subclass thereof.
    *
    * Must be private...someone else must worry about synchronization.
    */
   private Connection openConnectionByName(
      SessionEnv environment,
      String domainName,
      int owner)
   throws SessionException
   {
      Connection connection = null;
      int nameFormat = Natives.NAME_FORMAT_BIND;
      int mode = Natives.OPEN_LICENSED;

      // Not necessary if this method remains private.
      //this.connectionCache.lockWrite();
      try
      {
         int handle = Natives.openConnection(
            (Address)environment.get(Xplat.DOMAIN_ADDRESS),
            getLookupHandle(environment),
            domainName,
            nameFormat,
            mode);

         int ref = Natives.getConnectionReference(handle);
         connection = addConnection(ref, handle, owner);
      }
      catch (Exception e)
      {
         throw new InvalidDomainNameException(domainName, e);
      }
      finally
      {
         //this.connectionCache.unlock();
      }

      return (connection);
   }

   /*
    * Open a connection by server name iff the tree name matches.
    *
    * @exception SessionException Or a subclass thereof.
    */
   public Connection getConnectionByName(
      SessionEnv environment,
      String treeName,
      String domainName,
      int owner)
   throws SessionException
   {
      Connection connection = null;

      // We can always lockRead() when we're in a lockWrite(), and we want
      // to bracket this whole group for write...so lock first.
//      this.connectionCache.lockWrite();
      try
      {
         updateConnectionCache();
         // Find in cache
         connection = findConnectionByName(
            environment,
            treeName,
            domainName,
            owner);
         if (null == connection)
         {
            // Not in cache, try direct open
            connection = openConnectionByName(
               environment,
               treeName,
               domainName,
               owner);
         }
      }
      finally
      {
//         this.connectionCache.unlock();
      }

      return (connection);
   }

   /*
    * Open a connection by server name iff the tree name matches.
    *
    * @exception SessionException Or a subclass thereof.
    *
    * Must be private...someone else must worry about synchronization.
    */
   public Connection findConnectionByName(
      SessionEnv environment,
      String treeName,
      String domainName,
      int owner)
   throws SessionException
   {
      Connection connection = null;
      int nameFormat = Natives.NAME_FORMAT_BIND;
      int mode = Natives.OPEN_LICENSED;

      treeName = DomainName.strip(treeName);

//      this.connectionCache.lockRead();
      try
      {
         // Find in cache
         SessionAttrs attrs = new SessionAttrs();
         attrs.add(
            Xplat.CONN_SERVER_NAME_ATTR_ID,
            domainName);
         ConnectionSearchEnumerator enum =
            new ConnectionSearchEnumerator(attrs, this.connectionCache);

         if (enum.hasMoreElements())
         {
            connection = enum.next();
            if (getOwner(connection) == Natives.OWNER_NONE)
               setOwner(connection, owner);
            if (getOwner(connection) != owner)
               throw new NotConnectionOwnerException(
                  toString(connection), connection.ownerTrace);
            String connectionTree = getTree(connection);
            if (!connectionTree.equals(treeName))
            {
               throw new ServerTreeMismatchException(
                  domainName,
                  treeName,
                  connectionTree);
            }
         }
      }
      catch (NoSuchElementException e)
      {
         throw new InvalidDomainNameException(domainName, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }

      return (connection);
   }

   /*
    * Open a connection by server name iff the tree name matches.
    *
    * @exception SessionException Or a subclass thereof.
    *
    * Must be private...someone else must worry about synchronization.
    */
   private Connection openConnectionByName(
      SessionEnv environment,
      String treeName,
      String domainName,
      int owner)
   throws SessionException
   {
      Connection connection = null;
      int nameFormat = Natives.NAME_FORMAT_BIND;
      int mode = Natives.OPEN_LICENSED;

      treeName = DomainName.strip(treeName);

      // Not necessary if this method remains private.
      //this.connectionCache.lockWrite();
      try
      {
         int handle = Natives.openConnection(
            (Address)environment.get(Xplat.DOMAIN_ADDRESS),
            getLookupHandle(environment),
            domainName,
            nameFormat,
            mode);

         String connectionTree = Natives.getConnInfoString(
            handle,
            0,
            Natives.CONN_INFO_TREE_NAME);

         if (!connectionTree.equals(treeName))
         {
            // bad handle...trees need to match
            if (C32_KLUDGE_FORCE_CLOSED)
               Natives.closeRefByHandle(handle);
            else
               Natives.closeConnection(handle);
            throw new ServerTreeMismatchException(
               domainName,
               treeName,
               connectionTree);
         }
         int ref = Natives.getConnectionReference(handle);
         connection = addConnection(ref, handle, owner);
      }
      catch (NoSuchElementException e)
      {
         throw new InvalidDomainNameException(domainName, e);
      }
      finally
      {
         //this.connectionCache.unlock();
      }

      if (DEBUG || EXIT_DEBUG)
      {
         Debug.println(
            "Exit: " + toString(connection)
            );
      }
      return (connection);
   }

   /*
    * Get a connection by server name using the context.
    *
    * @exception SessionException Or a subclass thereof.
    */
   public Connection getConnectionByName(
      String treeName,
      NDSContext context,
      String domainName,
      int owner)
   throws SessionException
   {
      Connection connection = null;

      // We can always lockRead() when we're in a lockWrite(), and we want
      // to bracket this whole group for write...so lock first.
//      this.connectionCache.lockWrite();
      try
      {
         updateConnectionCache();
         // Find in cache
         connection = findConnectionByName(
            treeName,
            context,
            domainName,
            owner);
         if (null == connection)
         {
            // Not in cache, try direct open
            connection = openConnectionByName(
               treeName,
               context,
               domainName,
               owner);
         }
      }
      finally
      {
//         this.connectionCache.unlock();
      }

      return (connection);
   }

   /*
    * Find a connection by server name using the context.
    *
    * @exception SessionException Or a subclass thereof.
    *
    * Must be private...someone else must worry about synchronization.
    */
   public Connection findConnectionByName(
      String treeName,
      NDSContext context,
      String domainName,
      int owner)
   throws SessionException
   {
      Connection connection = null;
      int nameFormat = Natives.NAME_FORMAT_BIND;
      int mode = Natives.OPEN_LICENSED;

      String sDomainName = DomainName.stripQualifiers(domainName);

//      this.connectionCache.lockRead();
      try
      {
         // Find in cache
         SessionAttrs attrs = new SessionAttrs();
         attrs.add(
            Xplat.CONN_SERVER_NAME_ATTR_ID,
            sDomainName);
         ConnectionSearchEnumerator enum =
            new ConnectionSearchEnumerator(attrs, this.connectionCache);

         if (enum.hasMoreElements())
         {
            connection = enum.next();
            if (getOwner(connection) == Natives.OWNER_NONE)
               setOwner(connection, owner);
            if (getOwner(connection) != owner)
               throw new NotConnectionOwnerException(
                  toString(connection), connection.ownerTrace);
         }
      }
      catch (NoSuchElementException e)
      {
         // I'm assuming here that everything is messed up...shouldn't
         // get to this.
         throw new InvalidDomainNameException(domainName, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }
      if (null != connection)
      {
         String dn = Natives.getServerDN(
            context.nativeContext,
            connection.handle);
         String aDomainName = DomainName.stripTypes(domainName);
         String bDomainName = DomainName.stripTypes(dn);
         if (!aDomainName.equalsIgnoreCase(bDomainName))
            throw new InvalidDomainNameException(domainName + " (" + aDomainName + " != " + bDomainName + ")");
      }

      return (connection);
   }

   /*
    * Open a connection by server name using the context.
    *
    * @exception SessionException Or a subclass thereof.
    *
    * Must be private...someone else must worry about synchronization.
    */
   private Connection openConnectionByName(
      String treeName,
      NDSContext context,
      String domainName,
      int owner)
   throws SessionException
   {
      Connection connection = null;

      // Not necessary if this method remains private.
      //this.connectionCache.lockWrite();
      try
      {
         int handle = Natives.openConnection(
            context.getNativeHandle(),
            domainName);

         String connectionTree = Natives.getConnInfoString(
            handle,
            0,
            Natives.CONN_INFO_TREE_NAME);

         if (OPEN_CLOSE_CONN_DEBUG)
         {
            Debug.println("Comparing conn tree: " + connectionTree +
                          " to creating tree: " + treeName);
         }
         if (!connectionTree.equals(treeName))
         {
            // bad handle...trees need to match
            if (C32_KLUDGE_FORCE_CLOSED)
               Natives.closeRefByHandle(handle);
            else
               Natives.closeConnection(handle);
            throw new ServerTreeMismatchException(
               domainName,
               treeName,
               connectionTree);
         }

         int ref = Natives.getConnectionReference(handle);
         connection = addConnection(ref, handle, owner);
      }
      catch (NoSuchElementException e)
      {
         throw new InvalidDomainNameException(domainName, e);
      }
      finally
      {
         //this.connectionCache.unlock();
      }

      if (DEBUG || EXIT_DEBUG)
      {
         Debug.println(
            "Exit: " + toString(connection)
            );
      }
      return (connection);
   }

   /*
    * Open a connection by tree name.
    *
    * @exception SessionException Or a subclass thereof.
    */
   public Connection getConnectionByTreeName(
      SessionEnv environment,
      String treeName,
      int owner)
   throws SessionException
   {
      Connection connection = null;

      // We can always lockRead() when we're in a lockWrite(), and we want
      // to bracket this whole group for write...so lock first.
//      this.connectionCache.lockWrite();
      try
      {
         updateConnectionCache();
         // Find in cache
         connection = findConnectionByTreeName(
            environment,
            treeName,
            owner);
         if (null == connection)
         {
            // Not in cache, try direct open
            connection = openConnectionByTreeName(
               environment,
               treeName,
               owner);
         }
      }
      finally
      {
//         this.connectionCache.unlock();
      }

      return (connection);
   }

   /*
    * Open a connection by tree name.
    *
    * @exception SessionException Or a subclass thereof.
    *
    * Must be private...someone else must worry about synchronization.
    */
   public Connection findConnectionByTreeName(
      SessionEnv environment,
      String treeName,
      int owner)
   throws SessionException
   {
      Connection connection = null;
      int nameFormat = Natives.NAME_FORMAT_NDS_TREE;
      int mode = Natives.OPEN_LICENSED;

      treeName = DomainName.strip(treeName);

//      this.connectionCache.lockRead();
      try
      {
         // Find in cache
         SessionAttrs attrs = new SessionAttrs();
         attrs.add(
            Xplat.CONN_TREE_NAME_ATTR_ID,
            treeName);
         ConnectionSearchEnumerator enum =
            new ConnectionSearchEnumerator(attrs, this.connectionCache);

         // loop through all connections with matching tree name
         // until one with matching owner is found
         while (enum.hasMoreElements())
         {
            connection = enum.next();
            if (getOwner(connection) == Natives.OWNER_NONE)
            {

               setOwner(connection, owner);
            }
            if (getOwner(connection) == owner)
               break;
         }
         if (null != connection && getOwner(connection) != owner)
            throw new NotConnectionOwnerException(
               toString(connection), connection.ownerTrace);
      }
      catch (Exception e)
      {
         // I'm assuming here that everything is messed up...shouldn't
         // get to this.
         throw new InvalidDomainNameException(treeName, e);
      }
      finally
      {
//         this.connectionCache.unlock();
      }

      return (connection);
   }

   /*
    * Open a connection by tree name.
    *
    * @exception SessionException Or a subclass thereof.
    *
    * Must be private...someone else must worry about synchronization.
    */
   private Connection openConnectionByTreeName(
      SessionEnv environment,
      String treeName,
      int owner)
   throws SessionException
   {
      Connection connection = null;
      int nameFormat = Natives.NAME_FORMAT_NDS_TREE;
      int mode = Natives.OPEN_LICENSED;

      treeName = DomainName.strip(treeName);

      // Not necessary if this method remains private.
      //this.connectionCache.lockWrite();
      try
      {
         int handle = Natives.openConnection(
            (Address)environment.get(Xplat.DOMAIN_ADDRESS),
            getLookupHandle(environment),
            treeName,
            nameFormat,
            mode);

         int ref = Natives.getConnectionReference(handle);
         connection = addConnection(ref, handle, owner);
      }
      catch (NoSuchElementException e)
      {
         throw new InvalidDomainNameException(treeName, e);
      }
      finally
      {
         //this.connectionCache.unlock();
      }

      if (DEBUG || EXIT_DEBUG)
      {
         Debug.println(
            "Exit: " + toString(connection)
            );
      }
      return (connection);
   }

   public NDSContext createContext(
      SessionEnv environment,
      String treeName)
   throws SessionException
   {
      if (DEBUG || ENTRY_DEBUG)
         Debug.println("Entry: " + treeName);
      NativeContextMaster nativeContext = null;
      boolean handleIsCached = true;
      int handle = -1;

      treeName = DomainName.strip(treeName);

      if (CREATE_CONTEXT_CHECK_TREE)
      {
         updateConnectionCache();
      }
//      this.connectionCache.lockRead();
      try
      {
         if (CREATE_CONTEXT_CHECK_TREE)
         {
            // Validate tree name by first looking for existing connection to it
            Connection connection = findConnectionByTreeName(
               environment,
               treeName,
               Natives.OWNER_NDS);

            if (null != connection)
            {
               try
               {
                  // Get "private" handle via existing connection
                  handle = Natives.openConnection(
                     getReference(connection));
               }
               catch (Exception e)
               {
                  // Outside influence could potentially invalidate the reference,
                  // so handle this edge case by ignoring the exception and
                  // opening the connection directly.
                  if (I_DEBUG)
                  {
                     Debug.ignoreException(
                        "Transient connection went invalid",
                        e);
                  }
               }
            }
            if (-1 == handle)
            {
               // Get "private" handle
               handle = Natives.openConnection(
                  (Address)environment.get(Xplat.DOMAIN_ADDRESS),
                  getLookupHandle(environment),
                  treeName,
                  Natives.NAME_FORMAT_NDS_TREE,
                  Natives.OPEN_LICENSED);
               handleIsCached = false;
            }
            // What if this fails because connection not owner?
            // Can we look up different connection to tree?
         }

         // Create new nativeContext handle
         nativeContext = Natives.createNativeContext(treeName);
         this.nativeContexts.put(nativeContext);

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

         // - turn off string translation
         flags = flags & ~Natives.DCV_XLATE_STRINGS;
         // - turn on typeless names
         flags = flags | Natives.DCV_TYPELESS_NAMES;
         // - turn off name canonicalization
         flags = flags & ~Natives.DCV_CANONICALIZE_NAMES;

         // Set flags on the nativeContext handle.
         Natives.setContextInfo(
            nativeContext,
            Natives.DCK_FLAGS,
            new Integer(flags));

         // - disable the name cache (see bug 215114)
         if (Natives.NLM_KLUDGE_INVALID_CACHE)
         {
            Natives.setContextInfo(
               nativeContext,
               Natives.DCK_NAME_CACHE_DEPTH,
               new Integer(0));
         }

         // - set tree name
         Natives.setContextInfo(
            nativeContext,
            Natives.DCK_TREE_NAME, treeName);

         // - set nameContext to [root]
         Natives.setContextInfo(
            nativeContext,
            Natives.DCK_NAME_CONTEXT,
            "[Root]");

         {
            // Force DS to set last connection so it opens the connection
            // in it's own thread group
            try
            {
               if (CREATE_CONTEXT_GIVE_CONN)
               {
                  // - set last connection
                  Natives.setContextInfo(
                     nativeContext,
                     Natives.DCK_LAST_CONNECTION,
                     new Integer(handle)
                     );
                  if (CREATE_CONTEXT_PUT_CACHE)
                  {
                     int reference = Natives.getConnectionReference(handle);
                     addConnection(
                        reference,
                        Natives.openConnection(reference),
                        Natives.OWNER_NDS);
                  }
               }
               else
                  Natives.contextPing(nativeContext);
            }
            catch (NSIException e)
            {
               nativeContext.close();
               if (CREATE_CONTEXT_CHECK_TREE)
               {
                  if (-1 != handle)
                  {
                     if (handleIsCached)
                        // Keep handle in cache if retrieved from cache
                        Natives.closeConnection(handle);
                     else
                     {
                        // Handle not from cache, "forget about it"
                        Natives.closeRef(
                           Natives.getConnectionReference(handle));
                     }
                  }
               }
               throw e;
            }
         }
      }
      catch (RuntimeException e)
      {
         throw e;
      }
      catch (SessionException e)
      {
         throw e;
      }
      finally
      {
//         this.connectionCache.unlock();
      }
      NDSContext context = new NDSContext(
         nativeContext,
         environment,
         treeName,
         this);
      // Side effect...add connection to cache
//      getConnectionIfAvail(context);
      return context;
   }

   public Connection getConnectionIfAvail
   (
      NDSContext context
   )
   throws SessionException
   {
      // - get last connection
      Integer connHandle = (Integer)getContextInfo(
         context,
         Natives.DCK_LAST_CONNECTION);

      Connection connection = null;
      // Lookup handle in cache
      try
      {
         connection = getConnectionFromHandle(
            connHandle.intValue(),
            Natives.OWNER_NDS);
      }
      catch (NSIException e)
      {
         int ccode = e.getCCode();
         // Range: server requester failures && other requester failures
         if ((ccode > 0x8900 && ccode < 0x8918) ||
             (ccode >= 0x8800 && ccode <= 0x88FF))
         {
            throw e;
         }
         else
         {
            generateContextFailure(context, e);
         }
      }

      // Yes, return if null or not
      return connection;
   }


   public NDSContext duplicateContext(
      NDSContext context)
   throws SessionException
   {
      if (DEBUG || ENTRY_DEBUG)
         Debug.println("Entry: " + context);

      // Create new context handle
      NativeContext newNativeContext = Natives.duplicateNativeContext(
         context.getNativeHandle());

      return (new NDSContext(
         newNativeContext,
         context.getEnvironment(),
         this));
   }

   synchronized public NDSContext duplicateContext(
      NDSContext context,
      SessionEnv environment,
      Connection connection)
   throws SessionException
   {
      if (DEBUG || ENTRY_DEBUG)
         Debug.println("Entry: " +
            context + ", " +
            toString(connection)+ ")"
         );

      // Create new context handle
      NativeContext newNativeContext = null;
      try
      {
         newNativeContext = Natives.duplicateNativeContext(
            context.getNativeHandle());
      }
      catch (NSIException e)
      {
         int ccode = e.getCCode();
         // Range: server requester failures && other requester failures
         if ((ccode > 0x8900 && ccode < 0x8918) ||
             (ccode >= 0x8800 && ccode <= 0x88FF))
         {
            // We may have closed conn out from under context
            if (I_DEBUG)
            {
               Debug.ignoreException("Context lost handle", e);
            }
            // dup handle...
            int handle = Natives.openConnection(getReference(connection));
            // - set last connection
            Natives.setContextInfo (
               context.getNativeHandle(),
               Natives.DCK_LAST_CONNECTION,
               new Integer(handle)
            );
            newNativeContext = Natives.duplicateNativeContext(
               context.getNativeHandle());
         }
         else
         {
            throw e;
         }
      }

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

      // - turn on disallow referrals
      flags = flags | Natives.DCV_DISALLOW_REFERRALS;

      // Set flags on the context handle.
      Natives.setContextInfo (
         newNativeContext,
         Natives.DCK_FLAGS,
         new Integer(flags));

      // Removed:  as it turns out...setContextInfo is kind enough to close
      // the other connection it may have had when we replace it
      if (false)
      {
         try
         {
            // - get last connection from dupped context and close handle
            // since we're going to replace it
            Integer oldHandle = (Integer)Natives.getContextInfo (
               newNativeContext,
               Natives.DCK_LAST_CONNECTION
            );
            Natives.closeConnection(oldHandle.intValue());
         }
         catch (Exception e)
         {
            if (I_DEBUG)
               com.novell.service.session.util.Debug.ignoreException(e);
         }

      }

      // dup handle...
      int handle = Natives.openConnection(getReference(connection));

      // - set last connection
      Natives.setContextInfo (
         newNativeContext,
         Natives.DCK_LAST_CONNECTION,
         new Integer(handle)
      );

      if (DEBUG || OPEN_CLOSE_CONN_DEBUG)
      {
         Debug.println("giving up connection: " +
            "0x" + Integer.toHexString(handle)
         );
      }

      NDSContext newContext = new NDSContext(
         newNativeContext,
         environment,
         null,
         this);

      if (DEBUG || EXIT_DEBUG)
      {
         Debug.println(
            "Exit: " + newContext.toString());
      }

      return (newContext);
   }

   /**
    * Frees an NDSContext.
    */
   public void freeContext(
      NDSContext context)
   throws SessionException
   {
      try
      {
         Natives.freeNativeContext(
            context.nativeContext);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   /**
    * Gets information from an NDSContext.
    */
   public Object getContextInfo(
      NDSContext context,
      int key)
   throws SessionException
   {
      try
      {
         return Natives.getContextInfo(
            context.nativeContext,
            key);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   /**
   * Sets information in an NDSContext.
    */
   public void setContextInfo(
      NDSContext context,
      int key,
      Object value)
   throws SessionException
   {
      try
      {
         Natives.setContextInfo(
            context.nativeContext,
            key,
            value);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   /**
    * Login on an NDSContext.
    */
   public void loginContext(
      NDSContext context,
      String userName,
      String password,
      boolean extendedCharPasswordFlag)
   throws SessionException
   {
      try
      {
         Natives.loginContext(
            context.nativeContext, userName, password, extendedCharPasswordFlag);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   public void loginContextNAS(
      NDSContext context,
      NdsIdentity ident,
      String userName,
      String password,
      boolean extendedCharPasswordFlag)
   throws SessionException
   {
      try
      {
         Natives.loginContextNAS(
            context.nativeContext,
            ident,
            userName,
            password,
            extendedCharPasswordFlag);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   /**
    * Login on an NDSContext.
    */
   public void loginAsServiceContext(
      NDSContext context,
      String serviceName)
   throws SessionException
   {
      try
      {
         Natives.loginAsServiceContext(
            context.nativeContext,
            serviceName);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }


   /**
    * Logout on an NDSContext.
    */
   public void logoutContext(
      NDSContext context)
   throws SessionException
   {
      try
      {
         Natives.logoutContext(
            context.nativeContext);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   public void logoutContextNAS(NDSContext context, NdsIdentity ident)
   throws SessionException
   {
      try
      {
         Natives.logoutContextNAS(context.nativeContext, ident);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   /**
    * Authenticate a connection using an NDSContext.
    */
   public void authenticateConn(
      NDSContext context,
      Connection connection)
   throws SessionException
   {
      try
      {
         Natives.authenticateConn(
            context.nativeContext,
            connection.handle);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateConnectionFailure(connection, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateConnectionFailure(connection, e);
            default:
               throw e;
         }
      }
   }

   /**
    * Unauthenticate a connection using an NDSContext.
    */
   public void unauthenticateConn(
      NDSContext context,
      Connection connection)
   throws SessionException
   {
      close(connection);
   }

   /**
    * Returns true if the NDSContext can be used to authenticate a
    * connection.
    */
   public boolean canDSAuthenticate(
      NDSContext context)
   throws SessionException
   {
      try
      {
         return Natives.canDSAuthenticate(
            context.nativeContext);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   /**
    * Returns true if the NDSContext can be used to authenticate a
    * connection.
    */
   public boolean specialIsDSAuthenticated(
      NDSContext context)
   throws SessionException
   {
      try
      {
         return Natives.specialIsDSAuthenticated(
            context.nativeContext);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   public String getContextName(
      NDSContext context)
   throws SessionException
   {
      try
      {
         return Natives.getContextName(
            context.nativeContext);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   /**
    */
   public void changePasswordContext(
      NDSContext context,
      String userName,
      String oldPassword,
      String newPassword,
      boolean extendedCharPasswordFlag)
   throws SessionException
   {
      try
      {
         Natives.changePasswordContext(
            context.nativeContext,
            userName,
            oldPassword,
            newPassword,
            extendedCharPasswordFlag);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   /**
    */
   public void setPasswordContext(
      NDSContext context,
      String objName,
      String password,
      boolean extendedCharPasswordFlag)
   throws SessionException
   {
      try
      {
         Natives.setPasswordContext(
            context.nativeContext,
            objName,
            password,
            extendedCharPasswordFlag);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   /**
    */
   public void verifyPasswordContext(
      NDSContext context,
      String objName,
      String password,
      boolean extendedCharPasswordFlag)
   throws SessionException
   {
      try
      {
         Natives.verifyPasswordContext(
            context.nativeContext,
            objName,
            password,
            extendedCharPasswordFlag);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }


   public void contextPing(
      NDSContext context)
   throws SessionException
   {
      try
      {
         Natives.contextPing(
            context.nativeContext);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   public Integer getDSNLMBuild(
      NDSContext context)
   throws SessionException
   {
      try
      {
         return Natives.getDSNLMBuild(
            context.getConnection().handle);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

//   synchronized public Integer getDSServerRootDepth()
   public Integer getDSServerRootDepth(
      NDSContext context)
   throws SessionException
   {
      try
      {
         return Natives.getDSServerRootDepth(
            context.getConnection().handle);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

//   synchronized public Boolean isMasterReplica()
   public Boolean isMasterReplica(
      NDSContext context)
   throws SessionException
   {
      try
      {
         return Natives.isMasterReplica(
            context.getConnection().handle);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

//   synchronized public String getServerDN()
   public String getServerDN(
      NDSContext context)
   throws SessionException
   {
      try
      {
         return Natives.getServerDN(
            context.nativeContext,
            context.getConnection().handle);
      }
      catch (ServerRequesterException e)
      {
         // We don't know how to "fix" a context with a bad connection
         throw generateContextFailure(context, e);
      }
      catch (ClientException e)
      {
         switch (e.getCCode())
         {
            // We don't know how to "fix" a context with a bad connection
            case ClientErrors.INVALID_CONN_HANDLE:
               throw generateContextFailure(context, e);
            default:
               throw e;
         }
      }
   }

   protected InvalidStateException generateContextFailure(
      NDSContext context,
      Exception e)
   {
      try
      {
         context.close();
      }
      catch (SessionException e1)
      {
         // ignore
         if (I_DEBUG)
         {
            Debug.ignoreException(e1);
         }
      }
      context.setReason(e);
      return new InvalidStateException(
         INVALID_CONTEXT + "(Tree:" + context.getTreeNameIfSet() + ")" + " - (" + e + ")",
         e);
   }

   private InvalidStateException generateConnectionFailure(
      Connection connection,
      Exception e)
   {
      try
      {
         close(connection);
      }
      catch (SessionException e1)
      {
         // ignore
         if (I_DEBUG)
         {
            Debug.ignoreException(e1);
         }
      }
      setReason(connection, e);
      return new InvalidStateException(
         INVALID_CONNECTION + "- (" + e + ")",
         e);
   }

   // INNER CLASS
   /*
    * Search connections in the requester.
    */
   public class RequesterConnectionSearchEnumerator implements Enumeration
   {
      private int scanType;
      private String scanString;
      private int scanMode;
      private int lookAhead = -1;
      private boolean firstScan = true;
      private int scanValue;
      private NWVersion scanVersion;
      private int scanLevel;
      private NWLong scanIterator = null;

      protected RequesterConnectionSearchEnumerator(
         int scanLevel,
         String scanString)
      throws SessionException
      {
         this.scanString = scanString;
         this.scanType = Natives.TYPE_STRING;
         this.scanLevel = scanLevel;
         this.scanMode = Natives.MATCH_EQUALS;
         this.scanIterator = new NWLong(0);
         if (DEBUG)
         {
            Debug.println("RequesterConnectionSearchEnumerator.<init>(): Start scan...");
         }
         nextElement();
         this.firstScan = false;
      }

      protected RequesterConnectionSearchEnumerator(
         int scanLevel,
         Version scanVersion)
      throws SessionException
      {
         this.scanVersion = new NWVersion(
            scanVersion.majorVersion,
            scanVersion.minorVersion,
            scanVersion.revision);
         this.scanType = Natives.TYPE_VERSION;
         this.scanLevel = scanLevel;
         this.scanMode = Natives.MATCH_EQUALS;
         this.scanIterator = new NWLong(0);
         nextElement();
         this.firstScan = false;
      }

      protected RequesterConnectionSearchEnumerator(
         int scanLevel,
         int scanValue)
      throws SessionException
      {
         this.scanValue = scanValue;
         this.scanType = Natives.TYPE_VALUE;
         this.scanLevel = scanLevel;
         this.scanMode = Natives.MATCH_EQUALS;
         this.scanIterator = new NWLong(0);
         nextElement();
         this.firstScan = false;
      }

      /**
       * Returns the next matching Connection.
       */
      private int doScanWork()
      throws SessionException
      {
         int refToReturn;
         if ((-1 == this.lookAhead) && (!this.firstScan))
            throw new java.util.NoSuchElementException();

         refToReturn = this.lookAhead;
         this.lookAhead = -1;

         int connRef = -1;

         switch (this.scanType)
         {
            case Natives.TYPE_STRING:
               connRef = Natives.scanConnInfoString(
                  this.scanIterator,
                  this.scanLevel,
                  this.scanString,
                  this.scanMode);
               break;
            case Natives.TYPE_VALUE:
               connRef = Natives.scanConnInfoLong(
                  this.scanIterator,
                  this.scanLevel,
                  this.scanValue,
                  this.scanMode);
               break;
            case Natives.TYPE_VERSION:
               connRef = Natives.scanConnInfoVersion(
                  this.scanIterator,
                  this.scanLevel,
                  this.scanVersion,
                  this.scanMode);
               break;
            default:
               throw new SessionException(
                  "Invalid scan type: " + this.scanType);
         }
         this.lookAhead = connRef;

         return (refToReturn);
      }

      /**
       * Inherited from java.util.Enumeration.
       *
       * @see java.util.Enumeration#hasMoreElements
       */
      public boolean hasMoreElements()
      {
         return(-1 != this.lookAhead);
      }

      /**
       * Inherited from java.util.Enumeration.
       *
       * @see java.util.Enumeration#nextElement
       */
      public Object nextElement()
      throws NoSuchElementException
      {
         int ref = -1;
         try
         {
            ref = doScanWork();
         }
         catch (SessionException e)
         {
            throw new NoSuchElementException(
               e.toString());
         }
         if (DEBUG)
         {
            Debug.println("RequesterConnectionSearchEnumerator.nextElement(): Scan found: " +
               "0x" + Integer.toHexString(ref));
         }
         return (new Integer(ref));
      }

      public Integer next()
      throws NoSuchElementException
      {
         return((Integer) nextElement());
      }

      /**
       * Returns an array of Connections for the next object(s) in the enumeration.
       */
      public Integer[] next(
         int batchSize)
      {
         Integer[] batch = new Integer[(int) batchSize];

         for (int i = 0; i < batchSize; i++)
         {
            batch[i] = next();
         }
         return (batch);
      }
   } // class RequesterConnectionSearchEnumerator
}

