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

  $Archive: /njcl_v2/src/com/novell/service/bindery/BinderyObjectDirContext.java $
  $Revision: 21 $
  $Modtime: 1/28/00 1:51p $
 
  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 com.sun.jndi.toolkit.ctx.*;

import com.novell.java.lang.*;

import com.novell.service.jncp.*;

import com.novell.service.rfc1960.*;

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

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


/**
 * Represents an individual object found in the NetWare bindery.
 * Extensions of BinderyObjectDirContext include QueueBinderyObjectDirContext,
 * TreeBinderyObjectDirContext and ServerBinderyObjectDirContext,
 * which represent dynamic queue, tree and server entries, respectively,
 * in the bindery.
 *
 * <p>All bindery objects have the following mandatory attributes, which
 * are included in the variables defined in this class:
 * <ul>
 * <li>binderyObjectID
 * <li>binderyObjectType
 * <li>objFlags
 * <li>objSecurity
 * <li>hasProperties
 * </ul>
 *
 * <p>The bindery object name is used for the binding name. Because the
 * NetWare bindery does not require that names be unique, the type is
 * also used to uniquely identify a bindery object. Thus, the name of
 * a bindery object is composed of its name and type (for example
 * SUPERVISOR+1).
 *
 * @see BinderyDirContext
 * @see BinderyPropertyAttrVal
 * @see BinderyPropertyDataSegment
 */
public class BinderyObjectDirContext
   extends AtomicDirContext
   implements Referenceable
{
   private static NameParser BODSCNameParser = new FlatNameParser();
   
  /**
   * @internal
   */
   protected BinderyEnvironment environment;

   // These protected variables were dup'd in ServerBODSC and TreeBODSC, so
   //   I figure I ought to do them just once.    We may get rid of them
   //   altogether, but they seem very useful to have.  For now, they can be
   //   turned into attribs in getAttrib so that we don't have to hit the
   //   wire again for them.

  /**
   * The name of a bindery object used for the binding name and
   * consisting of its name and type.
   */
   protected String     binderyObjectNameOnly;
   
  /**
   * The type of the bindery object assigned upon creation.
   */
   protected int        binderyObjectType;
   
  /**
   * The ID of the bindery object assigned by the server.
   */
   protected int        binderyObjectID;
   
  /**
   * Determines if additional attributes exist for this object.
   * Set to TRUE if additional attributes exist, otherwise set
   * to FALSE.
   */
   protected int        hasProperties;
   
  /**
   * Defines the access control for the object.
   */
   protected int        objSecurity;
   
  /**
   * Defines whether the object is static or dynamic.
   */
   protected int        objFlags;

   /**
    * Constructs a BinderyObjectDirContext with only a
    * BinderyEnvironment parameter. This is a NULL constructor,
    * which is required for extension classes that don't explicitly
    * call the parameterized constructor.
    *
    * @param env Contains the environment variables used to control
    *            the behavior of the bindery name provider. 
    */
   public BinderyObjectDirContext (
         BinderyEnvironment env)
      throws NamingException
   {
      this.environment = env;
   }

   /**
    * Constructs a BinderyObjectDirContext object with the passed
    * in parameters.
    *
    * <p>This constructor is used by ServerBinderyObjectDirContext,
    * QueueBinderyObjectDirContext and TreeBinderyObjectDirContext.
    * Its numerous parameters are all obtained from a call to the
    * native NWScanObject.
    * </p>
    *
    * @param env                Contains the environment variables used
    *                           to control the behavior of the bindery
    *                           name provider.
    * @param binderyObjectName  The object name, without a type on it.
    * @param binderyObjectType  The object type, in human-readable format.
    * @param binderyObjectID    The object ID.
    * @param hasProperties      Whether the object has bindery properties;
    *                           0 = no.
    * @param objSecurity        The type of read/write access control for
    *                           this property.
    * @param objFlags           Defines whether the object is static or
    *                           dynamic (0=Static, 1=dynamic).
    */
   public BinderyObjectDirContext (
         Environment    env,
         String         binderyObjectName,
         int            binderyObjectType,
         int            binderyObjectID,
         int            hasProperties,
         int            objSecurity,
         int            objFlags )
      throws NamingException
   {
      this.environment = new BinderyEnvironment (env);

      this.binderyObjectNameOnly = new String (binderyObjectName);

      // binderyObjectType is assumed to be in human format, not wire fmt.
      this.binderyObjectType = binderyObjectType;
      this.binderyObjectID   = binderyObjectID;
      this.hasProperties     = hasProperties;
      this.objSecurity       = objSecurity;
      this.objFlags          = objFlags;
   }


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

   /** @internal
    *
    */
   public String getNameInNamespace ()
      throws NamingException
   {
      return (binderyObjectNameOnly + "+" + binderyObjectType);
   }

   /**
    * @internal
    *
    *    This is a leaf node, so there's nothing to look up.
    */
   protected Object a_lookup (
         String binderyObjectName,
         Continuation cont)
      throws NamingException
   {
      if (isEmpty(binderyObjectName))
      {
         cont.setSuccess();
         return this;
      }
      cont.setError(this, binderyObjectName);
      NameNotFoundException e = new NameNotFoundException();
      throw cont.fillInException(e);

   }  // a_lookup ()

   /**
    * @internal
    *
    *    This is a leaf node, so there's nothing to look up.
    */
   protected NamingEnumeration a_list (
         Continuation cont)
      throws NamingException
   {
      cont.setSuccess();
      return new NamingEnumerator();
   }

   /**
    * @internal
    *
    */
   protected Attributes a_getAttributes (
         String name,
         String[] attrIds,
         Continuation cont)
      throws NamingException
   {
      if ( !isEmpty(name) )
      {
         cont.setError(this, name);
         NameNotFoundException e = new NameNotFoundException();
         throw cont.fillInException(e);
      }

      // If attrIds equals null, all attributes should be returned
      boolean doAll = (attrIds == null) ? true : false;

      // Use the official list of names to name the attribs.
      BinderyObjectClassDefContext classDef =
         new BinderyObjectClassDefContext();

      Attributes aSet    = new NAttributes( true );
      String       attrName;     // to make code easier to read

      /* Extract the protected data and make them AttrVals, doing only the
       * attrs that are asked for. */

      attrName = classDef.attrNames[classDef.TYPE_NAME];

      if( doAll  ||  isInList(attrName, attrIds) )
      {
         /* Note: even tho the type should be on the name, it exists
          *       as an attr so that CreateSubCtxWAttr doesn't require
          *       a mangled name, and thus we ought to return it here.  ***/

         Integer av1     = new Integer( binderyObjectType );
         Attribute attr1 = new BasicAttribute(attrName, av1 );
         aSet.put( attr1 );
      }

      attrName = classDef.attrNames[classDef.ID_NAME];

      if( doAll  ||  isInList(attrName, attrIds) )
      {
         Integer av1     = new Integer( binderyObjectID );
         Attribute attr1 = new BasicAttribute(attrName, av1 );
         aSet.put( attr1 );
      }

      attrName = classDef.attrNames[classDef.SECURITY_NAME];

      if( doAll  ||  isInList(attrName, attrIds) )
      {
         Integer av1     = new Integer( objSecurity );
         Attribute attr1 = new BasicAttribute(attrName, av1 );
         aSet.put( attr1 );
      }

      attrName = classDef.attrNames[classDef.DYNAMIC_NAME];

      if( doAll  ||  isInList(attrName, attrIds) )
      {
         Boolean av1     = new Boolean( 0 != objFlags );
         Attribute attr1 = new BasicAttribute(attrName, av1 );
         aSet.put( attr1 );
      }

      attrName = classDef.attrNames[classDef.HAS_PROPERTIES_NAME];

      if( doAll  ||  isInList(attrName, attrIds) )
      {
         Boolean av1     = new Boolean( 0 != hasProperties );
         Attribute attr1 = new BasicAttribute(attrName, av1 );
         aSet.put( attr1 );
      }

      // Now, scan for the properties, if there are any and user wants them

      attrName = classDef.attrNames[classDef.PROPERTIES_NAME];

      if( hasProperties!=0  &&  (doAll || isInList(attrName, attrIds)) )
      {
         Attribute attrB = new BasicAttribute( attrName );

         int moreProps=1;

         IntegerBuffer iterHandle       = new IntegerBuffer(-1);
         IntegerBuffer propertyFlags    = new IntegerBuffer(0);
         IntegerBuffer propertySecurity = new IntegerBuffer(0);
         IntegerBuffer valueAvailable   = new IntegerBuffer(0);
         IntegerBuffer moreFlag         = new IntegerBuffer(1);

         StringBuffer propertyName  = new StringBuffer();

         // variables for property values:

         int    moreSegs, segNum, wireType;
         byte[] segData = new byte[BinderyPropertyDataSegment.SEGMENT_LENGTH];
         IntegerBuffer propFlags    = new IntegerBuffer(0);
         IntegerBuffer moreSegments = new IntegerBuffer(0);

         // We store the ID type in human fmt, but the libs need it wired.

         wireType = BinderyUtil.byteswapID( binderyObjectType );
         for( ; moreProps != 0; propertyName.setLength(0) )
         {
            try
            {
               environment.getCallsService ().scanProperty (
                                                   binderyObjectNameOnly,
                                                   wireType,
                                                   "*",
                                                   iterHandle,
                                                   propertyName,
                                                   propertyFlags,
                                                   propertySecurity,
                                                   valueAvailable,
                                                   moreFlag);
            }
            catch (NSIException nsie)
            {
               // The moreFlag isn't set to 0 til the scan AFTER the last
               // prop, at which time you also get a non-0 ccode.  STUPID.
               // But to account for this, get the value and skip the rest
               // if we have read past the last property.

               if (nsie.getCCode() == ServerErrors.NWE_BIND_NO_SUCH_PROP &&
                   (moreProps = moreFlag.intValue()) == 0)
               {
                  continue;
               }
               else
               {
                  NamingException ne = new NamingException();
                  cont.setError( this, name );
                  ne.setRootCause ( nsie );
                  throw cont.fillInException (ne);
               }
            }
            catch (Exception e)
            {
               NamingException ne = new NamingException();
               cont.setError( this, name );
               ne.setRootCause ( e );
               throw cont.fillInException (ne);
            }

            // Now create the BPAttrVal, then read PropValues and add them
            //    before adding the AttrVal to the BP Attribute

            BinderyPropertyAttrVal bpav = new BinderyPropertyAttrVal(
               propertyName.toString(),
               propertyFlags.intValue(),
               propertySecurity.intValue() );

            // Now, scan for the property's values

            if( valueAvailable.intValue() != 0 )
            {
               for( segNum=1, moreSegs=1; moreSegs != 0; segNum++ )
               {
                  try
                  {
                     // Don't want leftovers in another value ... memset()?
                     segData[0] = segData[1] = segData[2] = segData[3] = 0;

                     try
                     {
                        environment.getCallsService ().readPropertyValue (
                                                   binderyObjectNameOnly,
                                                   wireType,
                                                   propertyName.toString(),
                                                   segNum,
                                                   segData,
                                                   moreSegments,
                                                   propFlags );
                     }
                     catch (NSIException e)
                     {
                        // Doc says to go til more==0 or ccode==No_such.
                        // Don't know why a db doesn't know how many exist,
                        //    and this makes an ugly, extra test, but ...
                        if (e.getCCode () == ServerErrors.NWE_BIND_NO_SUCH_SEGMENT)
                        {
                           moreSegs = 0;
                           continue;
                        }
                        else
                        {
                           NameNotFoundException ne =
                              new NameNotFoundException();
                           cont.setError( this, name );
                           ne.setRootCause(e);
                           throw cont.fillInException (ne);
                        }
                     }

                     moreSegs = moreSegments.intValue();


                     /* Now create the data seg object and add it to BPAV.
                      * Since the flags from ReadValue can be diff from the
                      * ones in ScanProp (probly a bug), use the Scan's.  */

                     BinderyPropertyDataSegment bpds =
                        new BinderyPropertyDataSegment(
                           segData,
                           segNum,
                           propertyFlags.intValue() );

                     bpav.addDataSegment( bpds );
                  }
                  catch (Exception e)
                  {
                     NamingException ne = new NamingException();
                     cont.setError( this, name );
                     ne.setRootCause ( e );
                     throw cont.fillInException (ne);
                  }

               }  // end for(segs)
            }     // end if values for current property

            attrB.add( bpav ); //new value

         }  // end for(scanProps)

         aSet.put( attrB );

      }  // end if(doing properties)

      return( aSet );

   }  // end a_getAttributes

   /**
    * @internal
    *
    *  <p>This is the version of modAttrs that does the real work.
    *
    *  <p>This first pass is relatively simple, in contrast with the
    *  richness of the underlying Bindery API.  In other words, detailed
    *  mods are not possible.  We've implemented the 3 JNDI mods ops
    *  directly:  ADD -> CreateProperty,  DEL -> DeleteProperty,  and
    *  REPLACE -> Delete then Create.  Thus, users are responsible for
    *  doing their own mods on a Bindery Property (by creating a new one
    *  using methods in BinderyPropertyAttrVal and
    *  BinderyPropertyDataSegment).
    *
    *  <p>To do an old NWAddObjectToSet, you may put the ID of the object to
    *  be added into a BinderyPropertyDataSegment which is then added to a
    *  BinderyPropertyAttrVal having the name of the set and belonging to a
    *  java object corresponding to a Bindery Object containing that
    *  property.  Then call modifyAttrs with the Add operator.
    *
    *  <p>Individual properties may be modified singly by creating attrSets
    *  that contain only that one property.   If more than one property
    *  exists in an attrSet, all are attempted to be modified.  Any that do
    *  not succeed are returned in the AttrModException, whose explanation
    *  includes info about each failed modification. </p>
    */
   protected void a_modifyAttributes (
         String name,
         int mod_op,
         Attributes attrSet,
         Continuation cont)
      throws NamingException
   {
      int ccode=0, connHandle;

      // Since we are a leaf ctx, can only act on ourselves.  So ...
      if ( !isEmpty(name) )
      {
         cont.setError(this, name);
         NameNotFoundException e = new NameNotFoundException();
         throw cont.fillInException(e);
      }

      // Since we're modifying an attrSet, need a loop.

      boolean   errors = false;
      boolean   isSecurityAttr;
      String    attrID;
      Attribute attr;
      NamingEnumeration attrEnum = attrSet.getAll();

      BinderyObjectClassDefContext classDef =
         new BinderyObjectClassDefContext();

      Vector failedMods = new Vector();

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

         // First, make sure that the name of the attr to mod is valid.

         if( false == isBinderyAttrName( attrID ) )
         {
            failedMods.addElement( new ModificationItem(mod_op, attr) );
            errors = true;
            continue;
         }

         // Second, not all attrs are modifiable, so make sure this one is.
         //    would be nice to use a syntax or attrDefs, ie a ReadOnly defs,
         //    but this should work for now.

         isSecurityAttr =
            attrID.equals(classDef.attrNames[classDef.SECURITY_NAME]);

         if( !isSecurityAttr   &&
             !attrID.equals(classDef.attrNames[classDef.PROPERTIES_NAME])   )
         {
            failedMods.addElement( new ModificationItem(mod_op, attr) );
            errors = true;
            continue;
         }

         // Third, the security attr can only be Replaced, not Added or Del'd

         if( isSecurityAttr  &&  mod_op != REPLACE_ATTRIBUTE )
         {
            failedMods.addElement( new ModificationItem(mod_op, attr) );
            errors = true;
            continue;
         }

         // Now we know that we're really going to do it.
         // Since attrs can have multiple values, do another loop

         int wireType = BinderyUtil.byteswapID( this.binderyObjectType );

         BinderyPropertyAttrVal bpav     = null;
         Integer                security = null;

         for( Enumeration e = attr.getAll();  e.hasMoreElements(); )
         {
            if(isSecurityAttr)
               security = (Integer)e.nextElement();
            else
               bpav = (BinderyPropertyAttrVal)e.nextElement();

            switch( mod_op )
            {
            case ADD_ATTRIBUTE:
               ccode = addAttrVal(bpav);
               break;

            case REMOVE_ATTRIBUTE:
               ccode = delAttrVal(bpav);
               break;

            case REPLACE_ATTRIBUTE:
               if( isSecurityAttr )
               {
                  int propSecurity = security.intValue();

                  try
                  {
                     environment.getCallsService ().changeObjectSecurity (
                                                   binderyObjectNameOnly,
                                                   wireType,
                                                   propSecurity );
                  }
                  catch (Exception ex)
                  {
                     ccode = -1;
                  }
               }     // end if Security attr
               else
               {
                  ccode = delAttrVal(bpav);
                  if( ccode == 0 )
                     ccode = addAttrVal(bpav );

               }     // end else BinderyProperty
               break;

            default:
               failedMods.addElement( new ModificationItem(mod_op, attr) );
               errors = true;
               break;

            }  // end switch

            // Deal with any errors from the modifications

            if( ccode != 0 )
            {
               failedMods.addElement( new ModificationItem(mod_op, attr) );
               errors = true;
            }
         }  // end for-values
      }     // end while loop

      if( errors )
      {
         AttributeModificationException amx =
            new AttributeModificationException();
         ModificationItem failures[] =
            new ModificationItem[ failedMods.size() ];

         for( int i=0; i < failedMods.size(); i++ )
         {
            failures[i] = (ModificationItem)failedMods.elementAt(i);
         }
         amx.setUnexecutedModifications( failures );
         cont.setError( this, name );
         throw cont.fillInException (amx);
      }

   }  // end first modAttrs

   /**
    * @internal
    *
    *  This version of modAttrs is mostly for the convenience of users, so
    *  we put all the real work in the other version. Here, we sequentially
    *  pull out the individual mods and call the other version, so that they
    *  are executed in the order that they exist in the modEnumeration.  If
    *  any of them fail, no more mods are attempted.
    */

   protected void a_modifyAttributes (
         String name,
         ModificationItem[] mods,
         Continuation cont)
      throws NamingException
   {
      Attribute        attr;
      Attributes       attrSet = new BasicAttributes( true );
      ModificationItem modItem;

      // Since we are a leaf ctx, can only act on ourselves.  So,

      if ( !isEmpty(name) )
      {
         cont.setError(this, name);
         NameNotFoundException e = new NameNotFoundException();
         throw cont.fillInException(e);
      }

      for( int i=0;  i < mods.length;  i++ )
      {
         modItem = mods[i];
         attr    = modItem.getAttribute();

         attrSet.put( attr );

         try
         {
            a_modifyAttributes(
               name,
               modItem.getModificationOp(),
               attrSet,
               cont    );
         }
         catch( NamingException ne )
         {
            /* If the real modify doesn't work, set it as the root cause of
             * the exception to be thrown from here.  Then create an array
             * of all the mods that weren't executed, including the one that
             * caused the exception we just caught.                     ***/

            AttributeModificationException amx =
               new AttributeModificationException();
            amx.setRootCause( ne );

            int len = mods.length - i + 1;
            ModificationItem[] modArray = new ModificationItem[len];

            for( int j=0;  i< mods.length;  i++, j++ )
            {
               modArray[j] = mods[i];
            }
            amx.setUnexecutedModifications( modArray );
            cont.setError( this, name );
            throw cont.fillInException (amx);
         }
         attrSet.remove( attr.getID() );

      }  // end main while loop

   }  // end second modAttrs

   /**
    * @internal
    *
    *    This is a leaf node, so there's nothing to bind.
    */
   protected void a_bind(
         String name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      cont.setError( this, name );
      throw cont.fillInException( new OperationNotSupportedException() );
   }

   /**
    * @internal
    *
    *    This is a leaf node, so there's nothing to rebind.
    */
   protected void a_rebind(
         String name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      cont.setError( this, name );
      throw cont.fillInException( new OperationNotSupportedException() );
   }

   /**
    * @internal
    *
    *    This is a leaf node, so there are no subcontexts.
    */
   protected DirContext a_createSubcontext(
         String name,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      cont.setError( this, name );
      throw cont.fillInException( new OperationNotSupportedException() );
   }


   /**
    * @internal
    *
    *  If there is a set of matchingAttrs, this converts the attribute set
    *  in matchingAttrs into a com.novell.jndi.naming.directory.SearchFilter
    *  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.
    */
   protected NamingEnumeration a_search (
         Attributes matchingAttrs,
         String[] attrsToReturn,
         Continuation cont)
      throws NamingException
   {
      if (matchingAttrs == null || matchingAttrs.size () == 0)
      {  //use default SearchControls which excludes yourself from return
         cont.setSuccess();
         return new SearchEnumerator();
      }
      else
      {
         // Valid attribute set.  Convert to filter and use filter to search
         SearchFilterFactory sff = new SearchFilterFactory (matchingAttrs);
         SearchControls constraints = new SearchControls ();
         constraints.setReturningAttributes(attrsToReturn);
         return a_search (
            "",
            sff.getExpression (),
            sff.getArgs (),
            constraints,
            cont);
      }
   }  // end first search

   /**
    * @internal
    *
    *    Version of a_search that the others funnel into.
    *
    *    <p>This version is still a setup for another method that does the
    *    real work, searchObject.
    */
   protected NamingEnumeration a_search (
         String            name,
         String            filterExpr,
         Object[]          filterArgs,
         SearchControls cons,
         Continuation      cont)
      throws NamingException
   {
      if (!isEmpty( name ))   // nothing below us
      {
         cont.setError(this, name);
         throw cont.fillInException( new NameNotFoundException() );
      }

      try
      {
         if(cons==null || cons.getSearchScope()==SearchControls.ONELEVEL_SCOPE)
         {
            cont.setSuccess ();
            return new SearchEnumerator();
         }

         Rfc1960Parser ssp = new Rfc1960Parser (filterExpr);
         Vector searchResults = new Vector ();
         searchObject(ssp, cons.getReturningAttributes(), filterArgs,
            cons.getReturningObjFlag(), searchResults);
         cont.setSuccess ();
         return new SearchEnumerator (searchResults);
      }
      catch (IllegalArgumentException e)
      {
         cont.setError (this, name);
         InvalidSearchFilterException ne =
               new InvalidSearchFilterException ();
         ne.setRootCause (e);
         throw cont.fillInException (ne);
      }
      catch (NamingException ne)
      {
         cont.setError (this, name);
         throw cont.fillInException (ne);
      }
   }  // end the a_search that does the work

   /**
    * @internal
    *
    */
   public NamingEnumeration a_search (
         String name,
         String filterExpr,
         SearchControls cons,
         Continuation cont)
      throws NamingException
   {
      if (isEmpty (name))
      {
         return a_search (name, filterExpr, null, cons, cont);
      }
      else     // we're a leaf node, so no bindings to search
      {
         cont.setError(this, name);
         NameNotFoundException e = new NameNotFoundException();
         throw cont.fillInException(e);
      }
   }  // end third a_search

   /**
    * @internal
    *
    * Get Bindery system's root schema
    */
   protected DirContext a_getSchema (
         Continuation cont)
      throws NamingException
   {
      DirContext dsc = new BinderySchemaContext();
      cont.setSuccess();
      return( dsc );
   }

   /**
    * @internal
    *
    */
   protected DirContext a_getSchemaClassDefinition (
         Continuation cont)
      throws NamingException
   {
      Binding[] bindings = { 
         new Binding (
                        "Bindery Object",
                        BinderyObjectClassDefContext.class.getName (),
                        new BinderyObjectClassDefContext ()) };

      DirContext root = new StaticSchemaContext ("[Root]", bindings);

      cont.setSuccess();
      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));
   }  // end 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
   }

   /**
    * @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
   {
      /* When trying to Federate, we gag on c_pC since it hasn't yet been
       * implemented in our world.  Do it here in case any BODSC needs it,
       * since it seems the federating derivatives both need it.
       * Note: this is all stolen from the TestAtomicContext class. */
      try
      {
         CompoundName n = (CompoundName)BODSCNameParser.parse(inputName);

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

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

   /**
    * @internal
    *
    *  from the Referenceable interface:
    */
   public Reference getReference ()
      throws NamingException
   {
      Reference ref = null;
      try
      {
         String serverName = environment.getSession ().getDomainName ();

         String objectName = new String( binderyObjectNameOnly.toString()+
                                 "+" +
                                 Integer.toHexString(binderyObjectType) );

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

         ref = referenceFactory.createReference(
                  serverName,
                  ClassNames.BINDERY_OBJECT,
                  objectName );
      }
      catch (Exception e)
      {
         NamingException ne = new NamingException();
         ne.setRootCause ( e );
         throw (ne);
      }
      return ref;

   }  // end getReference

   /*********************     Private Methods     *************************/

   /**
    *  This method returns true if a string is in a string array.
    *  It is used primarily in getAttrs to know whether an attribute
    *  should be returned.
    */

   private boolean isInList( String name, String list[] )
   {
      boolean b = false;

      for( int i=0;  i < list.length;  i++ )
      {
         if( name.toUpperCase().equals( list[i].toUpperCase() ) )
         {
            b = true;
            break;
         }
      }
      return( b );

   }  // end isInList

   /**
    *   This method checks a string against the official list of names
    *   in the class definitions.
    */

   private boolean isBinderyAttrName( String name )
   {
      boolean b = false;

      BinderyObjectClassDefContext classDef =
         new BinderyObjectClassDefContext();

      for( int i=0;  i < classDef.NUMBER_OF_NAMES;  i++ )
      {
         if( name.toUpperCase().equals(classDef.attrNames[i].toUpperCase()) )
         {
            b = true;
            break;
         }
      }
      return( b );

   }  // end isBinderyAttrName

   /**  This method makes modifyAttributes cleaner in two ways.
    *   First, the Replace op is really just a Delete/Add, so "add" must be
    *   used in two places (add & replace).  Second, a JNDI attribute can be
    *   many bindery properties, so having this method allows us to do the
    *   Enumeration of the attr up in modAttrs, then the low-level here.
    *   Note that we return on the first error, even if there are additional
    *   IDs or segments to be added.
    */

   private int addAttrVal(
      BinderyPropertyAttrVal property )
   {
      int ccode = 0;
      int wireType = BinderyUtil.byteswapID( this.binderyObjectType );

      String propName  = property.getName();
      boolean isSet    = property.getSetFlag();
      int propSecurity = property.getSecurity();
      int propFlags    = property.getPropertyFlags();

      // The property may already exist, ie may be adding data segs or just
      //   IDs.  But call create anyway, as its the simplest test.

      try
      {
         environment.getCallsService ().createProperty (
                                    binderyObjectNameOnly,
                                    wireType,
                                    propName,
                                    propFlags,
                                    propSecurity );
      }
      catch (NSIException e)
      {
         ccode = e.getCCode ();
      }
      catch (Exception e)
      {
         return 0;
      }

      if (ccode == ServerErrors.NWE_BIND_PROP_ALREADY_EXISTS)
      {
         ccode = 0;
      }

      // Now, write the data out, too.  If this is set data, we can't just
      // write segments, but need to AddObjectToSet.

      boolean moreSegs = true;
      byte[] seg       = new byte[BinderyPropertyDataSegment.SEGMENT_LENGTH];
      int numSegs      = property.getNumOfSegs();

      firstLoop: for( int i=0; i < numSegs; i++ )
      {
         if( ccode != 0 )
            break;

         if( isSet )
         {
            Vector ids = property.getDataSegment(i).getSetData();
            for( int j=0;  j < ids.size(); j++ )
            {
               Integer id = (Integer)ids.elementAt(j);

               String objName;
               IntegerBuffer objType = new IntegerBuffer(0);

               try
               {
                  objName = environment.getCallsService ().getObjectName (
                                    id.intValue(),
                                    objType );

                  environment.getCallsService ().addObjectToSet (
                                    binderyObjectNameOnly,
                                    wireType,
                                    propName,
                                    objName,
                                    objType.intValue()  );
               }
               catch (NSIException e)
               {
                  ccode = e.getCCode ();
                  break firstLoop;
               }
               catch (Exception e)
               {
                  return 0;
               }
            }  // end looping thru IDs
         }
         else     // is not set data
         {
            if( i == numSegs-1 )
               moreSegs = false;

            seg = property.getDataSegment(i).getByteData();

            try
            {
               environment.getCallsService ().writePropertyValue (
                                 binderyObjectNameOnly,
                                 wireType,
                                 propName,
                                 i+1,           // 1-based in Bindery
                                 seg,
                                 moreSegs );
            }
            catch (NSIException e)
            {
               ccode = e.getCCode ();
               break firstLoop;
            }
            catch (Exception e)
            {
               return 0;
            }
         }

      }  // end for loop

      return( ccode );

   } // end addAttrVal

   /** Modularize the native call, as it's made in two places.
       This call deletes the Property unless it is a Set property and the
       user has provided specific values in the set.  In this case, this
       method calls NWDeleteObjectFromSet.
    */

   private int delAttrVal(BinderyPropertyAttrVal property )
   {
      int ccode    = 0;
      int wireType = BinderyUtil.byteswapID( binderyObjectType );

      String propName  = property.getName();
      boolean isSet    = property.getSetFlag();
      int numSegs      = property.getNumOfSegs();

      if( isSet  &&  numSegs > 0)
      {
         firstLoop: for( int i=0; i < numSegs; i++ )
         {
            Vector ids = property.getDataSegment(i).getSetData();
            for( int j=0;  j < ids.size(); j++ )
            {
               Integer id = (Integer)ids.elementAt(j);

               String objName;
               IntegerBuffer objType = new IntegerBuffer(0);

               try
               {
                  objName = environment.getCallsService ().getObjectName (
                                 id.intValue(),
                                 objType );

                  environment.getCallsService ().deleteObjectFromSet (
                                 binderyObjectNameOnly,
                                 wireType,
                                 propName,
                                 objName,
                                 objType.intValue()  );
               }
               catch (NSIException e)
               {
                  ccode = e.getCCode ();
                  break firstLoop;
               }
               catch (Exception e)
               {
                  return 0;
               }

            }  // end looping thru IDs
         }  // end for loop
      }
      else     // is not set data
      {
         try
         {
            environment.getCallsService ().deleteProperty (
                        binderyObjectNameOnly,
                        wireType,
                        propName   );
         }
         catch (NSIException e)
         {
            ccode = e.getCCode ();
         }
         catch (Exception e)
         {
            return 0;
         }
      }

      return( ccode );

    } // end delAttrVal

   /**
    * Helper method for a_search
    *
    * <p>This method simply breaks up a long, complex piece of code into two
    * pieces, for easier maintenance.  The a_search method will deal with
    * recursion, and when to stop.  This method will deal with obtaining
    * the attribute values and doing the compares that are needed.
    * </p>
    *
    * @param ssp              The search string parser to use for
    *                            obtaining the needed attribute id's, compare
    *                            operations and operands.
    *
    * @param returnAttrs      The attributes that should be returned
    *                            with any contexts that match.
    * @param filterArgs       The object array that contains the
    *                            attribute values to compare on replacement.
    * @param retObject        True means include the obj in the search reslt.
    *
    * @param vResult          (out) The Vector to store matched results in.
    *
    * @return                 True means a result was added to the vResult
    *                            False otherwise.
    * @exception              NamingException
    *
    */

   private boolean searchObject (
         Rfc1960Parser ssp,
         String[]      returnAttrs,
         Object[]      filterArgs,
         boolean       retObject,
         Vector        vResult  )
      throws NamingException
   {
      // Get all the attributes for this context

      Attributes attrSet = getAttributes ("");

      try
      {
         BinderyObjectClassDefContext classDef =
            new BinderyObjectClassDefContext();

         // Compare each search string
         while( ssp.hasMoreElements() )
         {
            boolean   result = false;
            Attribute attr;
            String    attrID;
            SearchStringComponent  ssComp;

            // Get next SearchStringComponent from parser
            ssComp = ssp.next ();

            // First, ensure that this component's ID is an attr of this ctx
            // Then if that satisfies the component's operation, done with it

            attrID = ssComp.getAttributeId().toUpperCase() ;
            attr   = attrSet.get( attrID );
            if (null == attr)
            {
               ssp.setCompareResult (ssComp, false);
               continue;
            }
            else if(
               SearchStringComponent.PRESENT == ssComp.getOperationType() )
            {
               ssp.setCompareResult (ssComp, true);
               continue;
            }

            if(attrID.equals(
                 classDef.attrNames[classDef.PROPERTIES_NAME].toUpperCase()))
               result = compareProperty( attr, ssComp, filterArgs );

            else if(attrID.equals( classDef.
                     attrNames[classDef.DYNAMIC_NAME].toUpperCase())    ||
                    attrID.equals( classDef.
                     attrNames[classDef.HAS_PROPERTIES_NAME].toUpperCase()) )

                  result = compareBoolean( attr, ssComp, filterArgs );

            else
               result = compareInteger( attr, ssComp, filterArgs );

            // Store result of call to compare___ in parser so that it can
            //    perform early-out logic in hasMoreElements().
            ssp.setCompareResult( ssComp, result );

         }  // end while

         // If overall result was false, do no more.

         if( false == ssp.compared() )
         {
            return false;
         }
      }
      catch (InvalidSearchFilterException e)
      {
            throw e;
      }
      catch (NamingException ne)
      {
         return false;   // Do nothing. Assume search not found and return
      }

      if (null != returnAttrs)
      {
         // Replace full set of attrs with the subset asked for
         attrSet = getAttributes ("", returnAttrs);
      }

      if( retObject )
         vResult.addElement(new SearchResult("", getClass().getName(), this, attrSet));
      else
         vResult.addElement(new SearchResult("", getClass().getName(), null, attrSet));
      return true;
   }  // end searchObject

   /**
    * Helper method for searchObject.  Determines if the attribute param
    *    matches the component and filter.  Is used for the two Bindery
    *    attrs that are Boolean objects.
    *
    * @param attr          The attribute to compare against.
    *
    * @param ssComp        The component to be compared.
    *
    * @param filterArgs    The object array that contains any
    *                         objects to be used in the comparison.
    *
    * @return              true if the attribute values match.
    * @exception           NamingException
    *
    */
   private boolean compareBoolean (
         Attribute             attr,
         SearchStringComponent ssComp,
         Object[]              filterArgs )
      throws NamingException
   {
      // Booleans can only compare on Equals or Present, and the latter
      //    is handled in the calling routine.

      if ( SearchStringComponent.EQUALS != ssComp.getOperationType() )
      {
         return( false );
      }

      boolean result = false;
      Boolean bValue;

      // Even tho' the Boolean attrs can only have one value, attrValues
      //    must be accessed via an Enum.  Getting it may throw an
      //    Exception which will just let get thrown thru.

      Enumeration values = attr.getAll();
      bValue = (Boolean)values.nextElement();

      // Since the Boolean class has an 'equals' method, simply use it.
      // But it requires another Boolean, so create one if there is none
      //    stored in the filter, ie, just a 'true' or 'false' operand.

      if (ssComp.operandReplacement ())
      {
         try
         {
            result = bValue.equals(filterArgs[ssComp.getReplacementIndex()]);
         }
         catch (ArrayIndexOutOfBoundsException e)
         {
            NamingException ne = new InvalidSearchFilterException ();
            ne.setRootCause (e);
            throw ne;
         }
      }
      else
      {
         result = bValue.equals( Boolean.valueOf(ssComp.getOperand()) );
      }

      return(result);

   }  // end compareBoolean

   /**
    * Helper method for searchObject.  Determines if the attribute param
    *    matches the component and filter.  Is used for the two Bindery
    *    attrs that are Integer objects.
    *
    * @param attr             The attribute to compare against.
    *
    * @param ssComp           The component to be compared
    *
    * @param filterArgs       The object array that contains the
    *                            attribute values to compare on replacement.
    *
    * @return                 true if the attribute values match.
    * @exception              NamingException
    *
    */
   private boolean compareInteger (
         Attribute             attr,
         SearchStringComponent ssComp,
         Object[]              filterArgs )
      throws NamingException
   {
      int opType = ssComp.getOperationType();

      // The following two compare types are not valid for Integer attr Vals.
      // "PRESENT" has already been tested in searchObject()

      if( SearchStringComponent.APPROXIMATE == opType ||
          SearchStringComponent.SUBSTRING   == opType   )
      {
         return( false );
      }

      // Also, the 'equals' op is the only one valid for Bindery IDs.

      BinderyObjectClassDefContext classDef =
         new BinderyObjectClassDefContext();

      if( SearchStringComponent.EQUALS != opType  &&
          ssComp.getAttributeId().equals(
             classDef.attrNames[classDef.ID_NAME])  )
      {
         return( false );
      }

      // Setup to start testing the data.

      boolean result = false;

      // Even tho' the Integer attrs can only have one value, attrValues
      //    must be accessed via an Enum.

      Enumeration values;
      try
      {
         values = attr.getAll();
      }
      catch( Exception e )
      {
         return( false );
      }
      Integer iValue = (Integer)values.nextElement();
      int iNumber  = iValue.intValue();

      // The value we're testing against may already be an Integer object,
      //    so extract it.  If it's not an Integer object, convert it
      //    directly to an int.  We assume that a string operand
      //    is a decimal-based number, not hex.

      int opNumber;

      if (ssComp.operandReplacement ())
      {
         Integer opValue ;
         try
         {
            opValue = (Integer)filterArgs[ ssComp.getReplacementIndex() ];
         }
         catch (ArrayIndexOutOfBoundsException e)
         {
            NamingException ne = new InvalidSearchFilterException ();
            ne.setRootCause (e);
            throw ne;
         }
         catch (ClassCastException e)       // Wrong type in filterArgs
         {
            return false;
         }
         opNumber = opValue.intValue();
      }
      else
      {
         try
         {
            opNumber = Integer.parseInt( ssComp.getOperand() );
         }
         catch( NumberFormatException nfe )
         {
            InvalidSearchFilterException isfe =
               new InvalidSearchFilterException ();
            isfe.setRootCause( nfe );
            throw isfe;
         }
      }

      // Start the actual tests.

      if( SearchStringComponent.EQUALS == opType )
      {
         result = (iNumber == opNumber);
      }
      else if( SearchStringComponent.LESS_OR_EQUAL == opType )
      {
         result = (iNumber <= opNumber);
      }
      else
      {
         result = (iNumber >= opNumber);
      }

      return(result);

   } // end compareInteger

   /**
    * Helper method for searchObject.  Determines if the attribute param
    *    matches the component and filter.  Is used for the Bindery
    *    Properties attribute values.
    *
    * <p>Only the EQUALS and SUBSTRING operation are valid, and the latter
    *    is valid only for a name passed as the operand in the component
    *    to be compared.  This name is compared to the name of the bindery
    *    property.   The EQUALS operation may take the name as a string,
    *    or a full BinderyPropertyAttrVal object.
    * </p>
    *
    * @param attr             The attribute to compare against.
    *
    * @param ssComp           The component to be compared
    *
    * @param filterArgs       The object array that contains the
    *                            attribute values to compare on replacement.
    *
    * @return                 true if the attribute values match.
    * @exception              NamingException
    *
    */

   private boolean compareProperty (
         Attribute             attr,
         SearchStringComponent ssComp,
         Object[]              filterArgs )
      throws NamingException
   {
      int opType = ssComp.getOperationType();

      // The following three compare types are not valid.

      if( SearchStringComponent.APPROXIMATE == opType   ||
          SearchStringComponent.LESS_OR_EQUAL == opType  ||
          SearchStringComponent.GREATER_OR_EQUAL == opType )
      {
         return( false );
      }

      // Also, the 'substring' op is only valid if a name is passed as the
      //    operand part of the searchStringComponent.  Since a name is the
      //    only valid string operand (all other comparisons must pass an
      //    object), this test is simple.

      if( ssComp.operandReplacement()     &&
          SearchStringComponent.SUBSTRING == opType )
      {
         return( false );
      }

      // Setup to start testing the data.  While there may be multiple values
      //   to this attr, there is only one operand, so grab it first.

      boolean result = false;
      String  opStr  = null;
      BinderyPropertyAttrVal opValue = null;

      if( ssComp.operandReplacement() )
      {
         try
         {
            opValue = (BinderyPropertyAttrVal)
               filterArgs[ ssComp.getReplacementIndex() ];
         }
         catch (ArrayIndexOutOfBoundsException e)
         {
            NamingException ne = new InvalidSearchFilterException ();
            ne.setRootCause (e);
            throw ne;
         }
         catch (ClassCastException e)       // Wrong type in filterArgs
         {
            return false;
         }
      }
      else
         opStr = ssComp.getOperand();

      // Attribute values must be accessed via an Enum.
      // Compare component to each value in attribute.

      Enumeration values;
      try
      {
         values = attr.getAll();
      }
      catch( Exception e )
      {
         return( false );
      }
      while( values.hasMoreElements()  &&  result==false)
      {
         BinderyPropertyAttrVal bpav =
            (BinderyPropertyAttrVal)values.nextElement();

         if( ssComp.operandReplacement() )
         {
            result = bpav.equals( opValue );
         }
         else
         {
            if( SearchStringComponent.SUBSTRING == opType )
            {
               result = SearchStringComponent.compareSubString(
                           opStr, bpav.getName(), true );
            }
            else
               result = opStr.equals( bpav.getName() );
         }

      }  // end while loop

      return(result);

   } // end compareProperty

} // class BinderyObjectDirContext


