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

  $Archive: /njcl_v2/src/com/novell/service/bindery/BinderyDirContext.java $
  $Revision: 18 $
  $Modtime: 1/28/00 12:46p $
 
  Copyright (c) 1998 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.bindery;


import java.util.*;

import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.spi.*;

import com.sun.jndi.toolkit.ctx.*;

import com.novell.java.lang.*;

import com.novell.service.jncp.ServerErrors;
import com.novell.service.jncp.NSIException;

import com.novell.service.session.*;
import com.novell.service.session.xplat.*;

import com.novell.utility.naming.*;
import com.novell.utility.naming.directory.*;


/**
 * Implements the JNDI DirContext interface (via the AtomicDirContext).
 * BinderyDirContext represents the NetWare bindery for a given server,
 * and is effectively the bindery root object or container. All objects
 * found in the server's bindery (dynamic and static) are effectively
 * bound to this context object. The bindery objects are returned as
 * instances or subclasses of the BinderyObjectDirContext class.
 *
 * <p>Because the NetWare bindery is a flat name space, bindings to the
 * root of bindery (BinderyDirContext) are supported, but not to its
 * subordinates.
 *
 * @see BinderyObjectDirContext
 * @see QueueBinderyObjectDirContext
 * @see ServerBinderyObjectDirContext
 * @see TreeBinderyObjectDirContext
 */
public class BinderyDirContext
   extends AtomicDirContext
   implements Referenceable
{
   // JNDI naming variables
   private static NameParser nameParser = new FlatNameParser ();

  /**
   *@internal
   */
   protected BinderyEnvironment environment;

   /**@internal
    * Constructs an object representing a NetWare bindery.
    *
    * @param  session            The session to the NetWare server holding
    *                            the bindery which this object represents.
    */
   public BinderyDirContext (
      BinderyEnvironment env)
   {
      this.environment = env;
   }

   /**@internal
    * Construct a java object of the appropriate type (a factory method).
    *
    * <p>All the parameters, except the connection, are assumed to have been
    * obtained from a call to read the bindery.
    * </p>
    *
    * @param binderyObjectNameOnly  The object name, without a type on it.
    * @param binderyObjectType      The object type, in human-readable formt.
    * @param binderyObjectID        The object ID.
    * @param hasProperties          Whether object has bindery props, 0=no.
    * @param objSecurity            The type of r/w access for this property.
    * @param objFlags               0=Static object, 1=dynamic.
    */
   public BinderyObjectDirContext createObject (
         String   binderyObjectNameOnly,
         int      binderyObjectType,
         int      binderyObjectID,
         int      hasProperties,
         int      objSecurity,
         int      objFlags)
      throws NamingException
   {
      BinderyObjectDirContext obj;

      switch (binderyObjectType)
      {
         case BinderyUtil.NW_PRINT_QUEUE_TYPE_HR:
         case BinderyUtil.NW_ARCHIVE_QUEUE_TYPE_HR:
         case BinderyUtil.NW_JOB_QUEUE_TYPE_HR:
            obj = new QueueBinderyObjectDirContext (
                           environment,
                           binderyObjectNameOnly,
                           binderyObjectType,
                           binderyObjectID,
                           hasProperties,
                           objSecurity,
                           objFlags);
            break;
         case BinderyUtil.NW_SERVER_TYPE_HR:
            obj = new ServerBinderyObjectDirContext (
                           environment,
                           binderyObjectNameOnly,
                           binderyObjectType,
                           binderyObjectID,
                           hasProperties,
                           objSecurity,
                           objFlags);

            break;
         case BinderyUtil.NDS_TREE_TYPE_HR:
            obj = new TreeBinderyObjectDirContext (
                           environment,
                           binderyObjectNameOnly,
                           binderyObjectType,
                           binderyObjectID,
                           hasProperties,
                           objSecurity,
                           objFlags);
            break;
         default:
            obj = new BinderyObjectDirContext (
                           environment,
                           binderyObjectNameOnly,
                           binderyObjectType,
                           binderyObjectID,
                           hasProperties,
                           objSecurity,
                           objFlags);
      } // switch ()

      // Pass on a copy of our environment to our new child
      obj.environment = new BinderyEnvironment (environment);
      return (obj);

   } // createObject ()


   // ******************** Context Interface ********************

   /** 
    *
    */
   public String getNameInNamespace ()
      throws NamingException
   {
      return ("[Root]");
   }

   /**@internal
    * Adds a new environment property to the environment of this
    * context.
    *
    * @param propName The non-NULL name of the environment property to
    *                 add. If it already exists in the environment,
    *                 overwrite and return the old value.
    * @param propVal  The non-NULL property value.
    *
    * @return 	       The value that propName used to have in the
    *			          environment; NULL if not there before.
    *
    * @exception NamingException If a naming errpr was encountered.
    *
    * @see #getEnvironment
    * @see #removeFromEnvironment
    */
   public Object addToEnvironment (
         String propName,
         Object propVal)
      throws NamingException
   {
      return (environment.addToEnvironment (propName, propVal));
   } // addToEnvironment ()

   /**@internal
    * Removes an environment property from the environment of this
    * context.
    *
    * @param propName The non-NULL name of the environment
    *                 property to remove.
    *
    * @return 	       The value associated with propName; NULL
    *                 if propName was not in the environment.
    *
    * @exception NamingException If a naming error was encountered.
    *
    * @see #getEnvironment
    * @see #addToEnvironment
    */
   public Object removeFromEnvironment (
         String propName)
      throws NamingException
   {
      return (environment.removeFromEnvironment (propName));
   } /* removeFromEnvironment() */

   /**@internal
    * Returns the environment in effect for this context.
    *
    * @return The non-NULL (but possibly empty) environment for
    *         this context.
    *
    * @exception NamingException If a naming error was encountered.
    *
    * @see #addToEnvironment
    * @see #removeFromEnvironment
    */
   public Hashtable getEnvironment ()
      throws NamingException
   {
      // Ask the environment to clone itself (the 'true' parameter)
      return environment.getEnvironment (true);
   } /* getEnvironment() */

  /**
   * @internal
   */
   public void close() throws NamingException
   {
      //no action necessary
   }
   
   // AtomicDirContext methods ===============================================

   /**@internal
    * Parses the 'inputName' into two parts: Head and Tail.
    *
    * <p>Head = the first component in the inputName; 
    * Tail = the rest of the unused inputName.
    *
    * <p>Subclasses should provide an implementation for this method,
    * which parses inputName using its own name syntax.</p>
    *
    * @param inputName The name to be parsed.
    * @param cont      ?
    *
    * @return A StringHeadTail object. 
    *
    * @exception NamingException If a naming error was encountered.
    */
   protected StringHeadTail c_parseComponent (
         String inputName,
         Continuation cont)
      throws NamingException
   {
      try
      {
         CompoundName n = (CompoundName) nameParser.parse (inputName);

         if (n.isEmpty () || 1 == n.size ())
         {
            return new StringHeadTail (inputName, null);
         }
         else
         {
            return new StringHeadTail (
                        n.getPrefix (1).toString (),
                        n.getSuffix (1).toString ());
         }
      }
      catch (NamingException e)
      {
         cont.setError (this, inputName);
         throw cont.fillInException (e);
      }
   }  // c_parseComponent()

   /**@internal
    * Looks up a bindery object name.  Expects an atomic name of a bindery
    * object with the "+nnnn" added to it so we can know the object type.
    *
    * @param binderyObjectName   The name, with the +nnnn object type.
    *
    * @return              The object of the name that was looked up.
    */
   protected Object a_lookup (
         String binderyObjectName,
         Continuation cont)
      throws NamingException
   {
      if (binderyObjectName.length () == 0)
      {
         cont.setSuccess ();
         return this;
      }

      int plusIndex = binderyObjectName.lastIndexOf ("+");
      if (plusIndex < 0)
      {
         cont.setError (this, binderyObjectName);
         throw cont.fillInException (new javax.naming.InvalidNameException ());
      }

      int  byte1, byte2;
      int  hrInt, wireInt;

      IntegerBuffer binderyObjectType = new IntegerBuffer (0);
      IntegerBuffer hasProperties = new IntegerBuffer (0);
      IntegerBuffer objFlags      = new IntegerBuffer (0);
      IntegerBuffer objSecurity   = new IntegerBuffer (0);

      BinderyObjectDirContext obj = null;

      try
      {  // Scan to see if the object exists, then create a java object.
         StringBuffer nextName = new StringBuffer ();
         IntegerBuffer binderyObjectID =
            new IntegerBuffer (BinderyUtil.NW_FIRST_SCAN_ID);

         String binderyObjectNameOnly = new String (
            binderyObjectName.substring (0,plusIndex));

         // The object type on the name is in human-readable form,
         //   but on the wire, it needs to have its bytes swapped.
         Integer hrType = Integer.valueOf (
                           binderyObjectName.substring (plusIndex + 1), 16);

         wireInt = BinderyUtil.byteswapID (hrType.intValue ());

         try
         {
            environment.getCallsService ().scanObject (
                              binderyObjectNameOnly,
                              wireInt,                   // wire formated type
                              binderyObjectID,
                              nextName,                  // next object name
                              binderyObjectType,
                              hasProperties,
                              objFlags,
                              objSecurity);
         }
         catch (NSIException e)
         {
            NamingException ne = new NameNotFoundException (binderyObjectName);
            ne.setRootCause (e);
            cont.setError (this, binderyObjectName);
            throw cont.fillInException (ne);
         }

         // boType needs to be in human form, which we have in hrType
         obj = this.createObject (
                        binderyObjectNameOnly,
                        hrType.intValue (),
                        binderyObjectID.intValue (),
                        hasProperties.intValue (),
                        objSecurity.intValue (),
                        objFlags.intValue ());
      }
      catch (NamingException e)
      {
         cont.setError (this, binderyObjectName);
         throw cont.fillInException (e);
      }
      catch (Exception nsix)
      {
         cont.setError (this, binderyObjectName);
         NamingException e = new NamingException ();
         e.setRootCause (nsix);
         throw cont.fillInException (e);
      }

      if (obj == null)
      {
         cont.setError (this, binderyObjectName);
         NameNotFoundException e = new NameNotFoundException ();
         throw cont.fillInException (e);
      }
      else
      {
         cont.setSuccess ();
      }
      return obj;

   }  // a_lookup ()

   /**@internal
    */
   protected Object a_lookupLink (
         String name,
         Continuation cont)
      throws NamingException
   {
      return a_lookup(name, cont);
   } // a_lookupLink ()

   /**@internal
    */
   protected NamingEnumeration a_list (
         Continuation cont)
      throws NamingException
   {
      cont.setSuccess();
      return (new BinderyObjectNCEnumerator (environment));
   } // a_list ()

   /**@internal
    */
   protected NamingEnumeration a_listBindings (
         Continuation cont)
      throws NamingException
   {
      cont.setSuccess ();
      return (new BinderyObjectBEnumerator (this));
   } // a_listBindings ()

   /**@internal
    */
   protected void a_bind (
         String name,
         Object obj,
         Continuation cont)
      throws NamingException
   {
      // Name space is flat so bindings are not supported
      notSupported (cont);
   } // a_bind ()

   /**@internal
    */
   protected void a_rebind (
         String name,
         Object obj,
         Continuation cont)
      throws NamingException
   {
      // Name space is flat so bindings are not supported
      notSupported (cont);
   } // a_rebind ()

   /**@internal
    */
   protected void a_unbind (
         String name,
         Continuation cont)
      throws NamingException
   {
      // Name space is flat so bindings are not supported
      notSupported (cont);
   } // a_unbind ()

   /**@internal
    * Since NWRenameObject requires an objectType, BDSC.rename must be
    * called with the oldname containing the "+nnnn".  On the other hand,
    * the real name in the bindery does not contain the type, so the new
    * name should not have one (we will strip it if it does).
    */
   protected void a_rename (
         String oldName,
         Name newName,
         Continuation cont)
      throws NamingException
   {
      int    ccode, connHandle, plusIndex;
      String oldNameOnly, newNameOnly;

      // must have a name to rename
      if (true == isEmpty (oldName))
      {
         cont.setError (this, oldName);
         throw cont.fillInException (new InvalidNameException ());
      }

      // test for +
      plusIndex = oldName.lastIndexOf ("+");
      if (plusIndex == -1)
      {
         cont.setError (this, oldName);
         throw cont.fillInException (new InvalidNameException ());
      }

      oldNameOnly = new String (oldName.substring (0, plusIndex));

      // The object type on the name is in human-readable form,
      //   but on the wire, it needs to have its bytes swapped.

      Integer hrType = Integer.valueOf(oldName.substring(plusIndex + 1),16);
      int wireInt    = BinderyUtil.byteswapID (hrType.intValue ());

      // Similarly, remove the type if on newname

      String newNameString = newName.toString();
      plusIndex = newNameString.lastIndexOf("+");
      if( plusIndex == -1 )
      {
         newNameOnly = newNameString;
      }
      else
      {
         newNameOnly = new String(newNameString.substring(0,plusIndex));
      }

      try
      {
         environment.getCallsService ().renameObject (
                        oldNameOnly,
                        newNameOnly,
                        wireInt);
      }
      catch (Exception e)
      {
         cont.setError( this, newName );
         NamingException ne = new NamingException();
         ne.setRootCause (e);
         throw cont.fillInException (ne);
      }
   }  // end a_rename ()

   /**@internal
    * Since NWDeleteObject requires an objectType, destroySubcontext must
    * be called with the oldname containing the "+nnnn".
    */
   protected void a_destroySubcontext (
         String name,
         Continuation cont)
      throws NamingException
   {
      int    plusIndex;
      String nameOnly ;

      // test for +
      plusIndex = name.lastIndexOf ("+");
      if (plusIndex == -1)
      {
         cont.setError (this, name);
         throw cont.fillInException (new InvalidNameException ());
      }

      nameOnly = new String (name.substring (0,plusIndex));

      // The object type on the name is in human-readable form,
      //   but on the wire, it needs to have its bytes swapped.
      Integer hrType = Integer.valueOf(name.substring(plusIndex + 1),16);
      int wireInt    = BinderyUtil.byteswapID (hrType.intValue ());

      try
      {
         environment.getCallsService ().deleteObject (nameOnly, wireInt);
      }
      catch (Exception e)
      {
         if (e instanceof NSIException)
         {
            int ccode = ((NSIException) e).getCCode ();

            if (ccode == ServerErrors.NWE_BIND_NO_SUCH_OBJ)
               e = null;
         }

         if (e != null)
         {
            cont.setError (this, name);
            NamingException ne = new NamingException ();
            ne.setRootCause (e);
            throw cont.fillInException (ne);
         }
      }
   }  // a_destroySubcontext ()

   /**@internal
    */
   protected Context a_createSubcontext (
         String name,
         Continuation cont)
      throws NamingException
   {
      // We require that there be at least some set of attributes
      notSupported (cont);
      return (null);
   } // a_createSubcontext ()

   /**@internal
    */
   protected NameParser a_getNameParser (
         Continuation cont)
      throws NamingException
   {
      cont.setSuccess();
      return (nameParser);
   }

   // DirContext methods =====================================================

   /**@internal
    */
   protected Attributes a_getAttributes (
         String name,
         String[] attrIds,
         Continuation cont)
      throws NamingException
   {
      // No attributes for this object, return an empty set.

      if( name.equals("") )
      {
         cont.setSuccess();
         return( new BasicAttributes( true ) );
      }

      BinderyObjectDirContext bodsc =
         (BinderyObjectDirContext)a_lookup( name, cont );

      return( bodsc.a_getAttributes( "", attrIds, cont ) );

   } // end a_getAttributes ()

   /**@internal
    */
   protected void a_modifyAttributes (
         String name,
         int mod_op,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      if (name.equals (""))
      {
         cont.setError (this, name);
         throw cont.fillInException (new OperationNotSupportedException ());
      }

      BinderyObjectDirContext bodsc =
         (BinderyObjectDirContext) a_lookup (name, cont);

      bodsc.a_modifyAttributes ("", mod_op, attrs, cont);

      return;
   } // a_modifyAttributes ()

   /**@internal
    */
   protected void a_modifyAttributes (
         String name,
         ModificationItem[] mods,
         Continuation cont)
      throws NamingException
   {
      if (name.equals (""))
      {
         cont.setError (this, name);
         throw cont.fillInException( new OperationNotSupportedException() );
      }

      BinderyObjectDirContext bodsc =
         (BinderyObjectDirContext) a_lookup (name, cont);

      bodsc.a_modifyAttributes ("", mods, cont);

      return;
   } // a_modifyAttributes ()

   /**@internal
    */
   protected void a_bind (
         String name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      // Bindery objects are created by createsubcontext, not bind
      notSupported (cont);
   } // a_bind ()

   /**@internal
    */
   protected void a_rebind (
         String name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      // Bindery objects are created by createsubcontext, not bind
      notSupported (cont);
   } // a_rebind ()

   /**@internal
    * CreateSubcontext is used to create java objects that correspond to
    * Bindery Objects.  The attributes TYPE, SECURITY, and DYNAMIC must
    * be supplied in the attrSet in order to create the object.
    */
   protected DirContext a_createSubcontext (
         String givenName,
         Attributes attrSet,
         Continuation cont)
      throws NamingException
   {

      if (givenName.length () == 0)
      {
         cont.setError (this, givenName);
         throw cont.fillInException (new InvalidNameException ());
      }

      /*  Since we need to check that the three attrs have been provided,
       *  we create a flag for each one.  We use the flags after all the
       *  tests in order to create an exception with a detailed message
       *  about which required attrs were not passed in.
       */

      boolean noType=false, noSec=false, noDyn=false;

      int   ccode = 0, connHandle;
      int   objType=0,  objFlag=0,   objSecurity=0 ;
      int   wireType=0, plusIndex=0;

      Attribute    attr;
      Integer      obj;
      String       newName  = new String();

      BinderyObjectClassDefContext classDef =
         new BinderyObjectClassDefContext();

      // First, check for required attrs and extract their values

      // Just in case the name has the type attached ...

      plusIndex = givenName.lastIndexOf("+");

      if( plusIndex > 0 )
      {
         newName = new String( givenName.substring(0,plusIndex) );
         objType = ( Integer.valueOf(
                     givenName.substring(plusIndex + 1),16) ).intValue();
      }
      else
         newName = new String( givenName );

      attr = attrSet.get( classDef.attrNames[classDef.TYPE_NAME] );
      if(attr == null  &&  objType == 0)
      {
         noType = true;
      }
      else
      {
         /* If object type hasn't been derived from the given name, we need
          * to pull it from the attribute, but it's possible that the attr
          * has no value.  ***/

         if( objType == 0)
         {
            try
            {
               obj = (Integer)attr.getAll().nextElement();
               objType = obj.intValue();
            }
            catch( NoSuchElementException e )
            {
               noType = true;
            }
         }
      }  // end looking for ObjectType

      attr = attrSet.get( classDef.attrNames[classDef.SECURITY_NAME] );
      if(attr == null)
      {
         noSec = true;
      }
      else
      {
         try
         {
            obj = (Integer)attr.getAll().nextElement();
            objSecurity = obj.intValue();
         }
         catch( NoSuchElementException e )   // attr has no value
         {
            noSec = true;
         }
      }

      attr = attrSet.get( classDef.attrNames[classDef.DYNAMIC_NAME] );
      if(attr == null)
      {
         noDyn = true;
      }
      else
      {
         try
         {
            Object o = attr.getAll().nextElement();
            if (o instanceof Integer)
               objFlag = ((Integer) o).intValue ();
            else if (o instanceof Boolean)
               objFlag = (((Boolean) o).booleanValue () ?
                              BinderyUtil.BF_DYNAMIC : BinderyUtil.BF_STATIC);
         }
         catch( NoSuchElementException e )   // attr has no value
         {
            noDyn = true;
         }
      }  // end else


      if( noType || noSec || noDyn )
      {
         StringBuffer missings = new StringBuffer();

         if( noType )
            missings.append( classDef.attrNames[classDef.TYPE_NAME] );
         if( noSec )
            missings.append(" " +classDef.attrNames[classDef.SECURITY_NAME]);
         if( noDyn )
            missings.append(" " + classDef.attrNames[classDef.DYNAMIC_NAME]);

         InvalidAttributesException iasx =
            new InvalidAttributesException( missings.toString() );

         cont.setError( this, givenName );
         throw cont.fillInException( iasx );
      }

      // Go ahead and create the object.

      // The object type should be in human-readable form,
      //   but on the wire, it needs to have its bytes swapped.

      wireType = BinderyUtil.byteswapID( objType );

      try
      {
         environment.getCallsService ().createObject (
                        newName,
                        wireType,
                        objFlag,
                        objSecurity);
      }
      catch (NSIException e)
      {
         if (e.getCCode () == ServerErrors.NWE_BIND_OBJ_ALREADY_EXISTS )
         {
            cont.setError( this, newName );
            throw cont.fillInException( new NameAlreadyBoundException() );
         }
         NamingException ne = new NamingException();
         cont.setError( this, givenName );
         ne.setRootCause (e);
         throw cont.fillInException (ne);
      }
      catch (Exception e)
      {
         NamingException ne = new NamingException();
         cont.setError( this, givenName );
         ne.setRootCause (e);
         throw cont.fillInException (ne);
      }

      // Once created, do the lookup needed to create the obj to be returned.

      String namePlus = new String(
         newName + "+" + Integer.toHexString(objType) );
      DirContext dsObj = (DirContext)a_lookup( namePlus, cont );

      /*  If there are additional attrs, add them to the new object.
       *  Create a new attrSet to pass to modify, but don't use the
       *   required attrs, which we've already used in creating the object.
       */

      Attribute          modAttr;
      Attributes         modAttrSet = new BasicAttributes( true );
      NamingEnumeration  attrEnum;
      String             attrID;

      attrEnum = attrSet.getAll();

      while( attrEnum.hasMoreElements() )
      {
         modAttr = (Attribute)attrEnum.next();
         attrID = modAttr.getID().toUpperCase();

         if( attrID.equals(
               classDef.attrNames[classDef.TYPE_NAME].toUpperCase() )     ||
             attrID.equals(
               classDef.attrNames[classDef.SECURITY_NAME].toUpperCase() ) ||
             attrID.equals(
               classDef.attrNames[classDef.DYNAMIC_NAME].toUpperCase() )   )
         {
             continue;
         }

         modAttrSet.put( modAttr );

      }  // end while getting attributes

      if( modAttrSet.size() > 0 )
      {
         dsObj.modifyAttributes( "", ADD_ATTRIBUTE, modAttrSet );
      }

      return(dsObj);

   } // createSubcontext ()

   /**@internal
    * Generic search.  If there is a set of matchingAttrs, this converts
    * the attribute set in matchingAttrs into a
    * com.novell.utility.naming.directory.SearchFilterFactory which is then
    * accessed in order to call the overloaded version of search that takes
    * a search filter string and array of arg's.
    * If matchingAttrs is empty, it returns itself and all subcontexts.
    */
   protected NamingEnumeration a_search (
         Attributes matchingAttrs,
         String[] returnAttrs,
         Continuation cont)
      throws NamingException
   {
      if (matchingAttrs == null || matchingAttrs.size () == 0)
      {  //use default SearchControls which excludes yourself from return
         Vector searchResults = new Vector ();
         NamingEnumeration be = listBindings (""); //Add all bindings
         while (be.hasMoreElements ())
         {
            Binding binding = (Binding)be.next ();
            Attributes attrs = ((DirContext)binding.getObject())
               .getAttributes("", returnAttrs);

            // Add this binding using default SearchControls
            searchResults.addElement(new SearchResult(
               binding.getName(), binding.getClassName(), null, attrs));
         }
         
         cont.setSuccess ();
         return new SearchEnumerator (searchResults);
      }
      else
      {
         // Valid attribute set.  Convert to filter and use filter to search
         SearchFilterFactory sff = new SearchFilterFactory (matchingAttrs);
         SearchControls constraints = new SearchControls ();
         constraints.setReturningAttributes (returnAttrs);
         return a_search (
            "",
            sff.getExpression (),
            sff.getArgs (),
            constraints,
            cont);
      }
   } // a_search ()

   /**@internal
    */
   protected NamingEnumeration a_search (
         String name,
         String filterExpr,
         Object [] filterArgs,
         SearchControls cons,
         Continuation cont)
      throws NamingException
   {
      if (!isEmpty (name))
      {
         BinderyObjectDirContext bodsc = 
            (BinderyObjectDirContext)a_lookup( name, cont );
         return(bodsc.a_search("", filterExpr, filterArgs, cons, cont) );
      }

      if (cons==null)
         cons = new SearchControls ();

      int timeLim = cons.getTimeLimit ();
      long timeEnd = 0, timeLeft = 0;
      long countLim = cons.getCountLimit ();
      boolean timed = false;
      boolean sized = countLim == 0 ? false : true;
      int scope = cons.getSearchScope ();

      if (timeLim != 0)
      {
         timeEnd = System.currentTimeMillis () + timeLim;
         timed = true;
      }

      try
      {
         Vector searchResults = new Vector ();

         // This class has no attributes, therefore will never match any valid
         // filter. So we never even need to look at the filter at this layer
         if(scope == SearchControls.OBJECT_SCOPE)
         {
            cont.setSuccess ();
            return new SearchEnumerator();
         }

         // Construct new constraints based on those passed in.
         // Change scope to OBJECT_SCOPE if ONELEVEL_SCOPE is passed in.
         SearchControls constraints = new SearchControls(
            scope == SearchControls.ONELEVEL_SCOPE ?
               SearchControls.OBJECT_SCOPE : scope,
            countLim,
            timeLim,
            cons.getReturningAttributes (),
            cons.getReturningObjFlag (),
            cons.getDerefLinkFlag ());

         NamingEnumeration be = a_listBindings(cont);
         while (be.hasMoreElements ())
         {
            // Check and update constraints.
            if(timed)
            {
               if((timeLeft = (timeEnd - System.currentTimeMillis ())) <= 0)
               {
                  // Time limit has been reached.  Bail out.
                  break;
               }
               constraints.setTimeLimit ((int)timeLeft);
            }
            if(sized)
            {
               if (countLim <= 0)
               {
                  // Count limit has been reached.  Bail out.
                  break;
               }
               constraints.setCountLimit (countLim);
            }

            Binding bind = (Binding) be.next();
            String bindName = bind.getName();
            DirContext child = (DirContext) bind.getObject();
            NamingEnumeration se = 
               child.search ("", filterExpr, filterArgs, constraints);
            
            while(se.hasMoreElements ())
            {
               SearchResult sr = (SearchResult)se.next();
               String newName = composeName(sr.getName(), bindName);
               sr.setName(newName);
               searchResults.addElement(sr);

               if(sized)
                  countLim--;
            }
         }

         cont.setSuccess ();
         return new SearchEnumerator (searchResults);
      }
      catch (NamingException ne)
      {
         cont.setError (this, name);
         throw cont.fillInException (ne);
      }
   } // a_search ()

   /**@internal
    */
   protected NamingEnumeration a_search (
         String name,
         String filterExpr,
         SearchControls cons,
         Continuation cont)
      throws NamingException
   {
      return a_search (name, filterExpr, null, cons, cont);
   } // a_search ()

   /**@internal
    */
   protected DirContext a_getSchema (
         Continuation cont)
      throws NamingException
   {
      DirContext dsc = new BinderySchemaContext ();
      cont.setSuccess ();
      return (dsc);
   } // a_getSchema ()

   /**@internal
    */
   protected DirContext a_getSchemaClassDefinition (
         Continuation cont)
      throws NamingException
   {
      cont.setError (this, "");
      throw cont.fillInException (new OperationNotSupportedException ());
   } // a_getSchemaClassDefinition ()

   // Referenceable methods ==================================================

   /**@internal
    */
   public Reference getReference ()
      throws NamingException
   {
      Reference ref = null;
      try
      {
         // Since this is the top of the bindery world and since a reference
         //   to this class is created by the public createRef, just use it.

         String serverName = environment.getSession ().getDomainName ();

         Properties p = new Properties ();
         p.put (PROVIDER_URL, serverName);

         com.novell.service.bindery.ReferenceFactoryImpl referenceFactory =
            new com.novell.service.bindery.ReferenceFactoryImpl ();

         ref = referenceFactory.createReference (p);
      }
      catch (Exception e)
      {
         NamingException ne = new NamingException ();
         ne.setRootCause (e);
         throw (ne);
      }
      return ref;
   } // getReference ()

   // Helper methods =========================================================

   /*
    * Common method for unsupported functions
    */
   private void notSupported (
         Continuation cont)
      throws NamingException
   {
      cont.setError (this, "");
      throw cont.fillInException (new OperationNotSupportedException ());
   } // notSupported ()

} // class BinderyDirContext


/** @internal
 *  BinderyObjectBEnumerator returns the name-object pairs for each
 *  object scanned for.
 */
class BinderyObjectBEnumerator
   implements NamingEnumeration
{
   private BinderyDirContext bContext;

   private boolean moreElements;   // Remembers if there are more objects
   private Binding nextBinding;
   private IntegerBuffer objID;

   public BinderyObjectBEnumerator (
         BinderyDirContext bCtx)
   {
      this.bContext = bCtx;

      moreElements = true;
      objID = new IntegerBuffer (BinderyUtil.NW_FIRST_SCAN_ID);
      nextElement ();
   } // BinderyObjectBEnumerator ()

   public boolean hasMoreElements ()
   {
      return (moreElements);
   } //hasMoreElements ()

   public boolean hasMore ()
   {
      return (moreElements);
   } // hasMore ()

   public Object next ()
   {
      return (this.nextElement ());
   } // next ()

   private Binding getNextBinding ()
      throws NSIException, NamingException
   {
      int ccode;
      StringBuffer objectName =
         new StringBuffer( BinderyUtil.NW_MAX_OBJECT_NAME_LEN );
      BinderyObjectDirContext obj;

      IntegerBuffer objType       = new IntegerBuffer(0);
      IntegerBuffer hasProperties = new IntegerBuffer(0);
      IntegerBuffer objFlags      = new IntegerBuffer(0);
      IntegerBuffer objSecurity   = new IntegerBuffer(0);

      try
      {
         bContext.environment.getCallsService ().scanObject (
                        "*",
                        BinderyUtil.NW_ANY_TYPE,
                        objID,
                        objectName,
                        objType,
                        hasProperties,
                        objFlags,
                        objSecurity);
      }
      catch (NSIException e)
      {
         if (e.getCCode () == ServerErrors.NWE_BIND_NO_SUCH_OBJ)
         {
            moreElements =  false;
            return (null);
         }
         else
         {
            moreElements = false;
            NamingException ne = new NameNotFoundException();
            ne.setRootCause (e);
            throw ne;
         }
      }
      catch (Exception e)
      {
         moreElements = false;
         NamingException ne = new NamingException();
         ne.setRootCause (e);
         throw ne;
      }

      // CreateObject expects the type in human-readable format.

      int hrType = BinderyUtil.byteswapID( objType.intValue() );

      obj = this.bContext.createObject(
         objectName.toString(),
         hrType,
         objID.intValue(),
         hasProperties.intValue(),
         objSecurity.intValue(),
         objFlags.intValue()     );

      return new Binding(
         objectName.toString() + "+" + Integer.toHexString(hrType),
         obj);
   } // getNextBinding ()

   public Object nextElement ()
   {
      Binding currentBinding;

      if (moreElements == false)
      {
         throw new NoSuchElementException();
      }

      currentBinding = nextBinding;

      try
      {
         nextBinding = getNextBinding();
      }
      catch (NSIException e)
      {
         throw new NoSuchElementException(e.toString());
      }
      catch (NamingException e)
      {
         throw new NoSuchElementException(e.toString());
      }

      return currentBinding;

   } // nextElement ()

   /**
    *
    */
   public void close() throws NamingException
   {
      //no action necessary
   }

} /* class BinderyObjectBEnumerator */

/**@internal
 * BinderyObjectNCEnumerator returns the name class pairs for each
 * object scanned for.
 */
class BinderyObjectNCEnumerator implements NamingEnumeration
{
   private BinderyEnvironment environment;
   private boolean moreElements;   // Remembers if there are more objects
   private NameClassPair nextPair;
   private IntegerBuffer objID;

   public BinderyObjectNCEnumerator (
         BinderyEnvironment environment)
   {
      this.environment = environment;
      moreElements = true;
      objID = new IntegerBuffer (BinderyUtil.NW_FIRST_SCAN_ID);
      nextElement ();
   } // BinderyObjectNCEnumerator ()

   public boolean hasMoreElements ()
   {
      return (moreElements);
   } // hasMoreElements ()

   public boolean hasMore ()
   {
      return (moreElements);
   } // hasMore ()

   public Object next ()
   {
      return (this.nextElement ());
   } // next ()

   private NameClassPair nextNameClassPair ()
      throws NSIException, NamingException
   {
      int  byte1, byte2;
      int  ccode, oldInt, newInt;
      StringBuffer objectName =
         new StringBuffer( BinderyUtil.NW_MAX_OBJECT_NAME_LEN );
      IntegerBuffer objType = new IntegerBuffer();

      try
      {
         environment.getCallsService ().scanObject (
            "*",
            BinderyUtil.NW_ANY_TYPE,  //type for wild card objects
            objID,
            objectName,
            objType, // objType
            null,    // hasPropertiesFlag
            null,    // objFlags
            null);   // objSecurity
      }
      catch (NSIException e)
      {
         if (e.getCCode () == ServerErrors.NWE_BIND_NO_SUCH_OBJ)
         {
            moreElements =  false;
            return (null);
         }
         else
         {
            moreElements = false;
            NamingException ne = new NameNotFoundException();
            ne.setRootCause (e);
            throw ne;
         }
      }
      catch (Exception e)
      {
         moreElements = false;
         NamingException ne = new NamingException();
         ne.setRootCause (e);
         throw ne;
      }

      String className;

      switch (objType.intValue ())
      {
         case BinderyUtil.NW_PRINT_QUEUE_TYPE:
         case BinderyUtil.NW_ARCHIVE_QUEUE_TYPE:
         case BinderyUtil.NW_JOB_QUEUE_TYPE:
            className = new String (environment.QUEUE_BINDERY_OBJECT);
            break;

         case BinderyUtil.NW_SERVER_TYPE:
            className = new String (environment.SERVER_BINDERY_OBJECT);
            break;

         case BinderyUtil.NDS_TREE_TYPE:
            className = new String (environment.TREE_BINDERY_OBJECT);
            break;

         default:
            className = new String (environment.BINDERY_OBJECT);
      }

      // Display the type to users in human-readable format.

      newInt = BinderyUtil.byteswapID( objType.intValue() );

      return new NameClassPair (objectName.toString()+
                                 "+" +
                                 Integer.toHexString(newInt),
                                 className );


   } // nextNameClassPair ()

   public Object nextElement ()
   {
      NameClassPair currentPair;

      if (moreElements == false)
      {
         throw new NoSuchElementException();
      }

      currentPair = nextPair;

      try
      {
         nextPair = nextNameClassPair();
      }
      catch (NSIException e)
      {
         throw new NoSuchElementException(e.toString());
      }
      catch (NamingException e)
      {
         throw new NoSuchElementException(e.toString());
      }
      return currentPair;
   } // nextElement ()

   /**
    *
    */
   public void close() throws NamingException
   {
      //no action necessary
   }
} /* BinderyObjectNCEnumerator */
