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

  $Archive: /njcl_v2/src/com/novell/service/qms/naming/QueueDirContext.java $
  $Revision: 21 $
  $Modtime: 5/04/00 2:23p $
 
  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.qms.naming;


import java.io.*;
import java.util.*;

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

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

import com.novell.java.io.*;
import com.novell.java.io.spi.DataAccessor;
import com.novell.java.lang.*;
import com.novell.utility.naming.Environment;
import com.novell.utility.naming.directory.*;
import com.novell.service.bindery.BinderyUtil;
import com.novell.service.jncp.*;
import com.novell.service.qms.*;
import com.novell.service.rfc1960.*;
import com.novell.service.session.*;
import com.novell.service.session.xplat.*;


/**
 * Implements both the JNDI DirContext interface (via the AtomicDirContext)
 * as well as the QMSQueue interface. The QueueDirContext class is an
 * implementation class that represents a queue.
 *
 * <p>This mixed relationship allows the QueueDirContext class to
 * perform both naming and directory functions through JNDI, as well as
 * handling queue related information such as servers attaching and
 * servicing jobs.</p>
 *
 * @see QMSQueue
 */
public class QueueDirContext
   extends AtomicDirContext
   implements QMSQueue, DataAccessable
{
   // JNDI naming variables
   private static NameParser nameParser = new QMSNameParser();

  /**
   * @internal
   */
   protected QMSEnvironment environment;
   
   // NetWare queue variables
   private int queueID;
   private String queueName;
//   private Session session;       // Use environment.getSession () instead
   
   // Constructors ===========================================================
   
   /**
    * @internal
    *
    * Creates a queue object based on a bindery name and type.
    *
    * @param environment The environment to use for this queue.
    * @param session     The session where this queue is located.
    * @param name        The queue's bindery name.
    * @param type        The queue's bindery type.
    *
    * @exception NamingException If a naming error was encountered.
    */
   protected QueueDirContext(
         Hashtable   environment,
         Session     session,
         String      name,
         int         type )
      throws NamingException
   {
      // NOTE: This needs to be replaced by an int[] in the natives
      IntegerBuffer objID = new IntegerBuffer (0);
      
      this.environment = new QMSEnvironment (environment);
      this.environment.addToEnvironment (Environment.SESSION_OBJECT, session);
      queueName = name;

      try
      {  // We have the name and type, we need the ID
         this.environment.getCallsService ().getObjectID (
                                             queueName,
                                             BinderyUtil.byteswapID (type),
                                             objID);

         queueID = objID.intValue ();
      }
      catch (SessionException e)
      {
         NamingException ne = new NameNotFoundException ();
         ne.setRootCause (e);
         throw ne;
      }
      catch (Exception e)
      {
         NamingException ne = new NamingException ();
         ne.setRootCause (e);
         throw ne;
      }
   } /* QueueDirContext() */

   /**
    * @internal
    *
    * Creates a queue object based on a bindery ID.
    *
    * @param environment The environment to use for this queue.
    * @param session     The session where this queue is located.
    * @param ID          The queue's bindery ID.
    *
    * @exception NamingException If a naming error was encountered.
    */
   protected QueueDirContext(
         Hashtable   environment,
         Session     session,
         int         ID)
      throws NamingException
   {
      // NOTE: This needs to be replaced by an int[] in the natives
      IntegerBuffer type = new IntegerBuffer (0);
      
      this.environment = new QMSEnvironment (environment);
      this.environment.addToEnvironment (Environment.SESSION_OBJECT, session);
      queueID = ID;

      try
      {  // We have the ID, we need the name and type
         queueName = this.environment.getCallsService ().getObjectName (
                                                         queueID,
                                                         type);
         // NOTE: Why do we byte swap on getObjectID and not on getObjectName?
      }
      catch (SessionException e)
      {
         NamingException ne = new NameNotFoundException ();
         ne.setRootCause (e);
         throw ne;
      }
      catch (Exception e)
      {
         NamingException ne = new NamingException ();
         ne.setRootCause (e);
         throw ne;
      }
   } /* QueueDirContext() */


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

   /** @internal
    *
    */
   public String getNameInNamespace ()
      throws NamingException
   {
      return (queueName);
   }

   /**
    * @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
    *
   * Closes the queue job.
   *
   * @exception NamingException If a naming error was encountered.
   */
   public void close ()
      throws NamingException
   {
      // no close 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)
      {
         throw cont.fillInException (e);
      }
   } /* c_parseComponent() */

   /**
    * @internal
    */
   protected Object a_lookup (
         String name,
         Continuation cont)
      throws NamingException
   {
      QMSContextFactory factory = new QMSContextFactory ();
      Object object;

      if (isEmpty (name))
      {  // The user wants an instance of this object
         cont.setSuccess ();
         ReferenceFactoryImpl rf = new ReferenceFactoryImpl ();
         Reference ref = rf.createReference (getEnvironment ());
         try
         {  // Return an instance of this object through the factory
            object = factory.getObjectInstance (
                                 ref,
                                 null,
                                 null,
                                 getEnvironment ());
         }
         catch (Exception e)
         {
            cont.setError (this, name);
            throw cont.fillInException (new NameNotFoundException ());
         }
      }
      else
      {
         try
         {  // Use the factory to get the requested object (should be a job)
            object = factory.getObjectInstance (
                                 name,
                                 null,
                                 null,
                                 getEnvironment ());
         }
         catch (Exception e)
         {
            cont.setError (this, name);
            throw cont.fillInException (new NameNotFoundException ());
         }
      }

      if (object == null)
      {
         cont.setError (this, name);
         throw cont.fillInException (new NameNotFoundException ());
      }

      cont.setSuccess ();
      return (object);
   } /* a_lookup() */

   /**
    * @internal
    */
   protected Object a_lookupLink (
         String name,
         Continuation cont)
      throws NamingException
   {
      // There are no links so just reuse the generic lookup.
      return a_lookup (name, cont);
   } /* a_lookupLink() */

   /**
    * @internal
    */
   protected NamingEnumeration a_list (
         Continuation cont)
      throws NamingException
   {
      // We may or may not have queue jobs, so this enumeration may be empty
      cont.setSuccess();
      return new QueueNameClassEnumerator (this);
   } /* a_list() */

   /**
    * @internal
    */
   protected NamingEnumeration a_listBindings (
         Continuation cont)
      throws NamingException
   {
      // We may or may not have queue jobs, so this enumeration may be empty
      cont.setSuccess();
      return new QueueBindingEnumerator (this);
   } /* a_listBindings() */

   /**
    * @internal
    */
   protected void a_bind (
         String name,
         Object obj,
         Continuation cont)
      throws NamingException
   {
      // Queue jobs are not added by binding them, they are added by submit
      NotSupported (cont);
   } /* a_bind() */

   /**
    * @internal
    */
   protected void a_rebind (
         String name,
         Object obj,
         Continuation cont)
      throws NamingException
   {
      // Queue jobs are not added by binding them, they are added by submit
      NotSupported (cont);
   } /* a_rebind() */

   /**
    * @internal
    */
   protected void a_unbind (
         String name,
         Continuation cont)
      throws NamingException
   {
      // Queue jobs are not deleted by unbinding them, they are deleted or
      // serviced
      NotSupported (cont);
   } /* a_unbind() */

   /**
    * @internal
    */
   protected void a_rename (
         String oldName,
         Name newName,
         Continuation cont)
      throws NamingException
   {
      // Queue jobs names are assigned when they are submited
      NotSupported (cont);
   } /* a_rename() */

   /**
    * @internal
    */
   protected void a_destroySubcontext (
         String name,
         Continuation cont)
      throws NamingException
   {
      // Queue jobs are not deleted by destroying them as a subcontext, they
      //  are deleted or serviced
      NotSupported (cont);
   } /* a_destroySubcontext() */

   /**
    * @internal
    */
   protected Context a_createSubcontext (
         String name,
         Continuation cont)
      throws NamingException
   {
      // Queue jobs are not added by creating them as a subcontext, they are
      //  added by submit
      NotSupported (cont);
      return (null);             // Required by compiler
   } /* a_createSubcontext() */

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

   /**
    * @internal
    */
   protected DirContext a_getSchema (
         Continuation cont)
      throws NamingException
   {
      // We have no schema for queues
      NotSupported (cont);
      return (null);             // Required by compiler
   } /* a_getSchema() */

   /**
    * @internal
    */
   protected DirContext a_getSchemaClassDefinition (
         Continuation cont)
      throws NamingException
   {
      // We have no schema for queues
      NotSupported (cont);
      return (null);             // Required by compiler
   } /* a_getSchemaClassDefinition() */

   /**
    * @internal
    */
   protected Attributes a_getAttributes (
         String name,
         String[] attrIDs,
         Continuation cont)
      throws NamingException
   {
      if (! isEmpty (name))
      {
         QueueJobDirContext qjdsc = (QueueJobDirContext) a_lookup (name, cont);
         return (qjdsc.a_getAttributes ("", attrIDs, cont));
      }
      else
      {
         cont.setSuccess ();
         return (new QMSQueueAttributes ((QMSQueue) this, attrIDs));
      }
   } /* a_getAttributes() */

   /**
    * @internal
    */
   protected void a_modifyAttributes (
         String name,
         int mod_op,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      if (isEmpty (name))
      {
         ModificationItem[] failedMods;
         Vector failedModsVec = new Vector ();
         NamingEnumeration attrEnum = attrs.getAll ();
         Attribute attr;
         String attrID;
         boolean errors = false;
         Integer state;

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

            if (! attrID.equals (QMSStrings.QUEUE_ATTRID_STATE))
            {
               ModificationItem fmod = new ModificationItem (mod_op, attr);
               failedModsVec.addElement (fmod);
               errors = true;
               continue;
            }

            if (REPLACE_ATTRIBUTE != mod_op)
            {
               ModificationItem fmod = new ModificationItem (mod_op, attr);
               failedModsVec.addElement (fmod);
               errors = true;
               continue;
            }

            for (Enumeration e = attr.getAll (); e.hasMoreElements (); )
            {
               try
               {
                  state = (Integer) e.nextElement ();
                  setState (state.intValue ());
               }
               catch (Exception ex)
               {
                  ModificationItem fmod = new ModificationItem (mod_op, attr);
                  failedModsVec.addElement (fmod);
                  errors = true;
               }
            }
         } // while (attrEnum...)

         if (errors)
         {
            AttributeModificationException amx =
               new AttributeModificationException ();
            int failedModCount = failedModsVec.size ();

            failedMods = new ModificationItem[failedModCount];
            for (int x = 0; x < failedModCount; x++)
               failedMods[x] = (ModificationItem) failedModsVec.elementAt (x);

            amx.setUnexecutedModifications (failedMods);
            cont.setError (this, name);
            throw cont.fillInException (amx);
         }
      }
      else
      {
         QueueJobDirContext qjdsc = (QueueJobDirContext) a_lookup (name, cont);
         qjdsc.a_modifyAttributes (name, mod_op, attrs, cont);
      }
   } /* a_modifyAttributes() */

   /**
    * @internal
    */
   protected void a_modifyAttributes (
         String name,
         ModificationItem[] mods,
         Continuation cont)
      throws NamingException
   {
      if (isEmpty (name))
      {
         Attribute         attr;
         Attributes        attrSet = new BasicAttributes ();
         ModificationItem  modItem;

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

            attrSet.put (attr);

            try
            {
               a_modifyAttributes(
                  name,
                  mods[counter].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 a modEnum
               //  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);

               if ((mods.length - counter) > 1)
               {
                  ModificationItem[] failedMods =
                        new ModificationItem[mods.length - counter];

                  for (int counter2 = 0; counter < mods.length; counter++, counter2++)
                     failedMods[counter2] = mods[counter];

                  amx.setUnexecutedModifications (failedMods);
               }
               cont.setError (this, name);
               throw cont.fillInException (amx);
            }
            attrSet.remove (attr.getID ());
         }  // end main for loop
      } // if (isEmpty...)
      else
      {
         QueueJobDirContext qjdsc = (QueueJobDirContext) a_lookup (name, cont);
         qjdsc.a_modifyAttributes (name, mods, cont);
      }
   } /* a_modifyAttributes() */

   /**
    * @internal
    */
   protected void a_bind (
         String name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      // Queue jobs are not added by binding them, they are added by submit
      NotSupported (cont);
   } /* a_bind() */

   /**
    * @internal
    */
   protected void a_rebind (
         String name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      // Queue jobs are not added by binding them, they are added by submit
      NotSupported (cont);
   } /* a_rebind() */

   /**
    * @internal
    */
   protected DirContext a_createSubcontext (
         String name,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      // Queue jobs are not added by creating them as a subcontext, they are
      //  added by submit
      NotSupported (cont);
      return (null);             // Required by compiler
   } /* a_createSubcontext() */

   /** 
    * @internal
    *
    * Generic search.  If there is a set of matchingAttrs, this converts
    * the attribute set in matchingAttrs into a
    * com.novell.service.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
    *
    * Generic search. This search will first examine this object, then
    * search all children.  This is a depth first search.
    */
   protected NamingEnumeration a_search (
         String name,
         String filterExpr,
         Object [] filterArgs,
         SearchControls cons,
         Continuation cont)
      throws NamingException
   {
      if (!isEmpty ( name ))
      {
         QueueJobDirContext qjdsc = (QueueJobDirContext) a_lookup( name, cont );
         return( qjdsc.a_search( name, filterExpr, filterArgs, cons, cont ) );
      }

      if (null == cons)
         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
      {
         Rfc1960Parser ssp = new Rfc1960Parser (filterExpr);
         Vector searchResults = new Vector ();

         if(scope != SearchControls.ONELEVEL_SCOPE)
         {  // Search this context
            SearchResult sr = searchObject(ssp, cons, filterArgs);
            if (sr != null)
            {
               searchResults.addElement (sr);
            }
         }
         if (scope == SearchControls.OBJECT_SCOPE)
         {
            cont.setSuccess ();
            return new SearchEnumerator(searchResults);
         }
         
         // 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 = listBindings ("");  // Search bindings.
         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();
               sr.setName(bindName + sr.getName());
               searchResults.addElement(sr);

               if(sized)
                  countLim--;
            }
         }

         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);
      }
   } /* a_search() */

   /**
    * @internal
    */
   protected NamingEnumeration a_search (
         String name,
         String filter,
         SearchControls cons,
         Continuation cont)
      throws NamingException
   {
      // Reuse the basic search method
      return a_search (name, filter, null, cons, cont);
   } /* a_search() */

   // QMSQueue methods =======================================================

   /**
    * @internal
    *
    * Returns the state of the queue job.
    *
    * @return The state of the queue job as an int.
    *
    * @exception NSIException If a naming error was encountered.
    * 
    * @see #setState
    */
   public int getState ()
      throws NSIException
   {
      IntegerBuffer queueStatus     = new IntegerBuffer(0);
      IntegerBuffer numberOfJobs    = new IntegerBuffer(0);
      IntegerBuffer numberOfServers = new IntegerBuffer(0);
      int[] serverIDList    = new int[25];
      int[] serverConnList  = new int[25];

      try
      {
         environment.getCallsService ().readQueueCurrentStatus2 (
                                             queueID,
                                             queueStatus,
                                             numberOfJobs,
                                             numberOfServers,
                                             serverIDList,
                                             serverConnList);
      }
      catch (Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }

      return queueStatus.intValue ();
   } /* getState() */

   /**
    * @internal
    *
    * Sets the state of the queue job.
    *
    * @param newState The new state to which to set the queue job.
    *
    * @exception NSIException If a naming error was encountered.
    * 
    * @see #getState
    */
   public void setState (int newState)
      throws NSIException
   {
      try
      {  environment.getCallsService ().setQueueCurrentStatus2 (
                                             queueID,
                                             newState);
      }
      catch (Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }
   } /* setState() */

   /**
    * @internal
    *
    * Returns the ID of the queue job.
    *
    * @return The ID of the queue job.
    */
   public int getID ()
   {
      return (queueID);
   } /* getID() */

   // QMSQueue job related methods ===========================================

   /**
    * @internal
    *
    * Returns an enumeration of the queue jobs.
    *
    * @return An enumeration of the queue jobs.
    */
   public Enumeration listJobs ()
   {
      return (new QMSEnumerator (this));
   } /* listJobs() */

   /**
    * @internal
    *
    * Creates a queue job.
    *
    * @return A queue job object.
    */
   public QMSJob createJob ()
   {
      QMSJob job = null;

      try
      {
         job = new QueueJobDirContext (this);
      }
      catch (NamingException e)
      {
      }
      return (job);
   } /* creatJob() */

   /**
    * @internal
    *
    * Creates a queue job specified by a queue ID.
    *
    * @param ID The ID of the queue job to create.
    *
    * @return A queue job object.
    */
   public QMSJob createJob (int ID)
   {
      QMSJob job = null;

      try
      {
         job = new QueueJobDirContext (this, ID);
      }
      catch (NamingException e)
      {
      }
      return (job);
   } /* createJob() */

   // QMSQueue public accessor methods =======================================

   /**
    * @internal
    *
    * Opens a stream for a queue job.
    *
    * @param type The type of the queue job.
    *
    * @return A DataAccessor object.
    *
    * @exception IOException When there is an I/O error.
    */
   public DataAccessor openStream (int type)
      throws IOException
   {
      return openStream (type, null);
   } /* openStream() */

   /**
    * @internal
    *
    * Opens a stream for a queue job of the specified type and custom.
    *
    * @param type   The type of the queue job.
    * @param custom An object containing job information.
    *
    * @return A DataAccessor object.
    *
    * @exception IOException When there is an I/O error.
    */
   public DataAccessor openStream (int type, Object custom)
      throws IOException
   {
      IntegerBuffer fileHandle = new IntegerBuffer (0);
      NWQueueJob jobInfo;
      int ccode;
      QMSAccessor qa;

      jobInfo = (custom!=null) ? (NWQueueJob)custom : new NWQueueJob();

      try
      {
         environment.getCallsService ().createQueueFile2 (
                                             queueID,
                                             jobInfo,
                                             fileHandle);
         qa = new QMSAccessor (
                     fileHandle.intValue (),
                     environment.getSession (),
                     queueID,
                     jobInfo.getJobNumber ());
      }
      catch (Exception e)
      {
         throw new IOException (e.getMessage());
      }

      return (qa);
   } /* openStream() */

   /**
    * @internal
    *
    * Opens a stream for a queue job of the specified type and name.
    *
    * @param name The name of the queue job.
    * @param type The type of the queue job.
    *
    * @return A DataAccessor object.
    *
    * @exception IOException When there is an I/O error.
    */
   public DataAccessor openStream (
         String name,
         int type)
      throws IOException
   {
      throw new NoSubordinateAccessException ();
   } /* openStream() */

   /**
    * @internal
    *
    * Opens a stream for a queue job of the specified type, name
    * and custom.
    *
    * @param name   The name of the queue job.
    * @param type   The type of the queue job.
    * @param custom An object containing job information.
    *
    * @return A DataAccessor object.
    *
    * @exception IOException When there is an I/O error.
    */
   public DataAccessor openStream (
         String name,
         int type,
         Object custom)
      throws IOException
   {
      throw new NoSubordinateAccessException ();
   } /* openStream() */

   /**
    * @internal
    *
    * Determines whether the queue job supports random
    * access.
    *
    * @return A boolean set to TRUE if random access is supported,
    *         otherwise set to FALSE. The default is FALSE.
    */
   public boolean supportsRandomAccess ()
   {
      return false;
   } /* supportsRandomAccess() */

   /**
    * @internal
    *
    * Determines whether the queue job supports subordinate
    * random access.
    *
    * @return A boolean set to TRUE if subordinate random access is
    * supported; otherwise, set to FALSE. The default is FALSE.
    */
   public boolean supportsSubordinateRandomAccess ()
   {
      return false;
   } /* supportsSubordinateRandomAccess() */

   /**
    * @internal
    *
    * Determines whether the queue job supports input
    * streams.
    *
    * @return A boolean set to TRUE if input streams are
    * supported; otherwise, set to FALSE. The default is FALSE.
    */
   public boolean supportsInputStream ()
   {
      return false;
   } /* supportsInputStream() */

   /**
    * @internal
    *
    * Determines whether the queue job supports output
    * streams.
    *
    * @return A boolean set to TRUE if output streams are
    * supported; otherwise, set to FALSE. The default is TRUE.
    */
   public boolean supportsOutputStream ()
   {
      return true;
   } /* supportsOutputStream() */

   /**
    * @internal
    *
    * Determines whether the queue job supports subordinate
    * input streams.
    *
    * @return A boolean set to TRUE if subordinate input streams are
    * supported; otherwise, set to FALSE. The default is FALSE.
    */
   public boolean supportsSubordinateInputStream ()
   {
      return false;
   } /* supportsSubordinateInputStream() */

   /**
    * @internal
    *
    * Determines whether the queue job supports subordinate
    * output streams.
    *
    * @return A boolean set to TRUE if subordinate ourput streams are
    * supported; otherwise, set to FALSE. The default is FALSE.
    */
   public boolean supportsSubordinateOutputStream ()
   {
      return false;
   } /* supportsSubordinateOutputStream() */
   
   // Internal helper methods ================================================

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

   /*
    * Helper method for a_search
    *
    * <p>This method examines the current object
    * </p>
    *
    * @param ssp                 The search string parser to use for
    *                            obtaining the needed attribute id's, compare
    *                            operations and operands.
    *
    * @param filterArgs          Array that contains the
    *                            attribute values to compare on replacement.
    * @return                    SearchResult on match, null otherwise.
    * @exception                 NamingException
    *
    */
   private SearchResult searchObject (
         Rfc1960Parser ssp,
         SearchControls cons,
         Object [] filterArgs)
      throws NamingException
   {
      Attributes attrs;
      SearchStringComponent comp;
      String[] returnAttrs = cons.getReturningAttributes ();

      try
      {
         // Get all the attributes for this context
         attrs = getAttributes ("");

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

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

            // Compare componenet to each value in attribute
            attrID = comp.getAttributeId();
            attr = attrs.get ( attrID );
            if ( null == attr )
            {
               ssp.setCompareResult( comp, false );
               continue;
            }
            else if ( SearchStringComponent.PRESENT == comp.getOperationType() )
            {
               ssp.setCompareResult( comp, true );
               continue;
            }

            compared = compareInteger( attr, comp, filterArgs );

            // Store result of call to compareValue in parser.
            // This will allow parser to perform early out logic.
            ssp.setCompareResult (comp, compared);
         }

         // Check parser to see if we can bail out yet.
         if (false == ssp.compared ())
         {
            return null;
         }
      }
      catch (InvalidSearchFilterException e)
      {
         // Problem with the search filter and replacement params
         throw e;
      }
      catch (NamingException e)
      {
         // Do nothing. Assume search not found and return
         return null;
      }

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

      return new SearchResult(
                     "",
                     getClass().getName(),
                     cons.getReturningObjFlag() ? lookup("") : null,
                     attrs);
   } /* searchObject() */

   /*
    * Helper method for searchObject.  This method compares the value
    * specified in comp and filter to that in value.
    *
    * @param attr          The attributes value to compare against.
    *
    * @param ssComp        The component to be compared
    *
    * @param filterArgs    An array that contains the
    *                      attribute values to compare.
    *
    * @return              true if the attribute values match.
    *
    */
   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);
      }

      // 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);
   } /* compareInteger() */

} /* QueueDirContext */

//============================================================================
// Class QMSEnumerator
//============================================================================

/*
 * This is a base class which the binding and name class enumerators use as
 * a delegate.
 */

class QMSEnumerator
   implements Enumeration
{
   private QueueDirContext queue;
   private int currentIndex = 0;
   private int numberOfJobs;
   private boolean moreItems = false;
   private int[] jobNumberList;

   protected QMSEnumerator (QueueDirContext queue)
   {
      int ccode;
      QueueJobListReply jobList = new QueueJobListReply();

      this.queue = queue;

      try
      {
         queue.environment.getCallsService ().getQueueJobList2 (
                                                queue.getID (),
                                                0,
                                                jobList);
      }
      catch (Exception e)
      {
         return;  // return an empty enumeration
      }

      numberOfJobs = jobList.getReplyQueueJobNumbers();
      if ( numberOfJobs > 0)
      {
         moreItems = true;
         jobNumberList = jobList.getJobNumberList();
      }
   } /* QMSEnumerator() */

   public boolean hasMoreElements ()
   {
      return (moreItems);
   }

   public Object nextElement ()
   {
      return ((Object) next());
   }

   public QMSJob next()
   {
      if (!moreItems)
         throw new NoSuchElementException ();

      QMSJob job = null;

      try
      {
         job = new QueueJobDirContext (
                        queue,
                        jobNumberList[currentIndex]);
      }
      catch (NamingException e)
      {
      }

      currentIndex++;

      if (currentIndex >= numberOfJobs)
      {
         moreItems = false;
         currentIndex = 0;
      }

      return (job);

   } /* next() */

   /**
    *
    */
   public void close ()
      throws NamingException
   {
   }

} /* QMSEnumerator */


//============================================================================
// Class QueueBindingEnumerator
//============================================================================

class QueueBindingEnumerator
   implements NamingEnumeration
{
   private Enumeration jobs;
   private QueueDirContext ctx;

   public QueueBindingEnumerator (QueueDirContext ctx)
   {
      this.ctx = ctx;
      this.jobs = ctx.listJobs();
   }

   public boolean hasMoreElements ()
   {
      return (jobs.hasMoreElements());
   }

   public boolean hasMore ()
   {
      return hasMoreElements();
   }

   public Object nextElement ()
   {
      try
      {
         return next();
      }
      catch (NamingException e)
      {
         throw new RuntimeException (e.getMessage ());
      }
   }

   public Object next ()
      throws NamingException
   {
      QMSJob job = (QMSJob) jobs.nextElement ();
      String name = String.valueOf (job.getID ());
      return (new Binding (name, ctx.lookup (name)));
   }

   /**
    *
    */
   public void close ()
      throws NamingException
   {
   }

} /* QueueBindingEnumerator */


//============================================================================
// Class QueueNameClassEnumerator
//============================================================================

class QueueNameClassEnumerator
   implements NamingEnumeration
{
   private Enumeration jobs;

   public QueueNameClassEnumerator (QueueDirContext ctx)
   {
      this.jobs = ctx.listJobs ();
   }

   public boolean hasMoreElements ()
   {
      return (jobs.hasMoreElements ());
   }

   public boolean hasMore ()
   {
      return hasMoreElements ();
   }

   public Object nextElement ()
   {
      return next ();
   }

   public Object next ()
   {
      QMSJob job = (QMSJob) jobs.nextElement ();
      String name = String.valueOf (job.getID ());
      return (new NameClassPair (
                     name,
                     "com.novell.service.qms.naming.QueueJobDirContext"));
   }

   /**
    *
    */
   public void close ()
      throws NamingException
   {
   }

} /* QueueNameClassEnumerator */


