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

  $Archive: /njcl_v2/src/com/novell/service/qms/naming/QueueJobDirContext.java $
  $Revision: 14 $
  $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.service.jncp.*;
import com.novell.service.qms.*;
import com.novell.service.rfc1960.*;
import com.novell.service.session.SessionException;

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


/**
 * Implements both the JNDI DirContext interface (via AtomicDirContext)
 * as well as the QMSJob interface. The QueueJobDirContext is an
 * implementation class that represents queue jobs.
 *
 * <p>This mixed relationship allows the QueueJobDirContext class
 * to perform both naming and directory functions (through JNDI), as
 * well as handling QMS job related information such as target server,
 * execution time, and so forth.</p>
 *
 * @see QMSJob
 */
public class QueueJobDirContext
   extends AtomicDirContext
   implements QMSJob
{
   // JNDI naming variables
   private static NameParser nameParser = new QMSNameParser ();
   private QMSEnvironment environment;
   private static Hashtable modAttrList;
   private static Hashtable attrTypes;
   
   // NetWare queue variables
   private QueueDirContext queue;
//   private int queueID = 0;      // Use queue.getID() instead
   private int jobID = 0;
   private QMSOutputStream os = null;
   private boolean jobExists = false;
   private NWQueueJob jobInfo;
//   private Session session;       // Use environment.getSession () instead

   // These are used to work with the attributes
   private static final int TARGETSERVERID      = 1;
   private static final int TARGETEXECUTIONTIME = 2;
   private static final int TYPE                = 3;
   private static final int POSITION            = 4;
   private static final int CONTROLFLAGS        = 5;
   private static final int DESCRIPTION         = 6;

   // Constructors ===========================================================
   static 
   {
      // NOTE: This should be changed into a static schema
      attrTypes = new Hashtable ();
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_CLIENTSTATION, "Integer");
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_CLIENTTASK, "Integer");
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_CLIENTID, "Integer");
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_TARGETSERVERID, "Integer");
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_TARGETEXECUTIONTIME, "Date");
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_ENTRYTIME, "Date");
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_ID, "Integer");
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_TYPE, "Integer");
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_POSITION, "Integer");
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_CONTROLFLAGS, "Integer");
      attrTypes.put (QMSStrings.QUEUE_JOB_ATTRID_DESCRIPTION, "String");

      modAttrList = new Hashtable ();
      modAttrList.put (QMSStrings.QUEUE_JOB_ATTRID_TARGETSERVERID,
                       new Integer (TARGETSERVERID));
      modAttrList.put (QMSStrings.QUEUE_JOB_ATTRID_TARGETEXECUTIONTIME,
                       new Integer (TARGETEXECUTIONTIME));
      modAttrList.put (QMSStrings.QUEUE_JOB_ATTRID_TYPE,
                       new Integer (TYPE));
      modAttrList.put (QMSStrings.QUEUE_JOB_ATTRID_POSITION,
                       new Integer (POSITION));
      modAttrList.put (QMSStrings.QUEUE_JOB_ATTRID_CONTROLFLAGS,
                       new Integer (CONTROLFLAGS));
      modAttrList.put (QMSStrings.QUEUE_JOB_ATTRID_DESCRIPTION,
                       new Integer (DESCRIPTION));
   }

   /**
    * @internal
    *
    * Creates a new queue job (QueueJobDirContext object) in memory
    * for the specified queue.
    *
    * <p>The new queue job is created and left in memory only for
    * the specified queue, but the job is not submitted to the queue
    * until the submit() method is called.</p>
    *
    * @param queue The queue for which the job is created.
    *
    * @exception NamingException If a naming error was encountered.
    */
   protected QueueJobDirContext (
         QueueDirContext queue)
      throws NamingException
   {
      // Save the queue we're creating the queue job in
      this.queue = queue;
      
      // Get our 'parent' JNDI environment
      environment = new QMSEnvironment (queue.getEnvironment ());

      // Create a new, empty queue job info
      this.jobInfo = new NWQueueJob();
   } /* QueueJobDirContext() */

   /**
    * @internal
    *
    * Creates a new queue job (QueueJobDirContext object) in memory
    * for the specified queue and existing job.
    *
    * <p>The new queue job is created in memory based on an existing
    * queue job.</p>
    *
    * @param queue  The queue for which the job is created.
    * @param jobID  The ID of the job for which this object is created.
    *
    * @exception NamingException If a naming error was encountered.
    */
   protected QueueJobDirContext (
         QueueDirContext queue,
         int             jobID)
      throws NamingException
   {
      // Reuse the basic constructor
      this (queue);                 // Sets queue, environment and jobInfo

      // Save the jobID we were given since this is a live job
      this.jobID = jobID;

      try
      {
         environment.getCallsService ().readQueueJobEntry2 (
                                             queue.getID (),
                                             this.jobID,
                                             jobInfo);
      }
      catch (SessionException e)
      {
         NamingException ne = new NameNotFoundException ();
         ne.setRootCause (e);
         throw ne;
      }
      catch (Exception e)
      {
         NamingException ne = new NamingException ();
         ne.setRootCause (e);
         throw ne;
      }

      this.jobExists = true;
   } /* QueueJobDirContext() */


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

   /** @internal
    *
    */
   public String getNameInNamespace ()
      throws NamingException
   {
      return (queue.getNameInNamespace () + "/" + String.valueOf (jobID));
   }

   /**
    * @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 environment, overwrite
    *                  and return the old value.
    * @param propVal   The non-NULL environment property value.
    *
    * @return 	        The value that propName previously had in the
    *			           environment; NULL if not there before.
    *
    * @exception NamingException If a naming error 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() */

   // AtomicDirContext methods ===============================================

   /**
    * @internal
    *
    * Parses the inputName into two parts: Head and Tail.
    *
    * <p>Head = the first component in the inputName 
    * <p>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
   {
      Object object = null;

      if (isEmpty (name))
      {
         try
         {  // Return an instance of this object through the factory
            QMSContextFactory factory = new QMSContextFactory ();
            object = factory.getObjectInstance (
                                       String.valueOf (jobID),
                                       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
   {
      // There's no reason to fail this so return success.
      cont.setSuccess();
      
      // We don't have any subordinates so just return an empty enumerator.
      return (new NamingEnumerator ());
   } /* a_list() */

  /**
   * @internal
   */
   protected void a_bind (
         String name,
         Object obj,
         Continuation cont)
      throws NamingException
   {
      // We don't allow anything to be bound to a queue job
      NotSupported (cont);
   } /* a_bind() */

  /**
   * @internal
   */
   protected void a_rebind (
         String name,
         Object obj,
         Continuation cont)
      throws NamingException
   {
      // We don't allow anything to be bound to a queue job
      NotSupported (cont);
   } /* a_rebind() */

  /**
   * @internal
   */
   protected void a_unbind (
         String name,
         Continuation cont)
      throws NamingException
   {
      // A queue job doesn't get deleted by unbind
      NotSupported (cont);
   } /* a_unbind() */

  /**
   * @internal
   */
   protected void a_rename (
         String oldName,
         Name newName,
         Continuation cont)
      throws NamingException
   {
      // A queue job's name (its ID) is assigned by the queue upon submission
      NotSupported (cont);
   } /* a_rename() */

  /**
   * @internal
   */
   protected void a_destroySubcontext (
         String name,
         Continuation cont)
      throws NamingException
   {
      // We don't have subordinates of a queue job
      NotSupported (cont);
   } /* a_destroySubcontext() */

  /**
   * @internal
   */
   protected Context a_createSubcontext (
         String name,
         Continuation cont)
      throws NamingException
   {
      // We don't have subordinates of a queue job
      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
    *
   * Closes the queue job.
   *
   * @exception NamingException If a naming error was encountered.
   */
   public void close ()
      throws NamingException
   {
      // ... no close necessary
   } /* close() */

  /**
   * @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))
      {  // We have no subordinates
         cont.setError (this, name);
         throw cont.fillInException (new NameNotFoundException ());
      }
      else
         return (new QMSQueueJobAttributes ((QMSJob) this, attrIDs));
   } /* a_getAttributes() */

  /**
   * @internal
   */
   protected void a_modifyAttributes (
         String name,
         int mod_op,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      if (! isEmpty (name))
      {  // We have no subordinates
         cont.setError (this, name);
         throw cont.fillInException (new NameNotFoundException ());
      }

      ModificationItem[] failedMods;
      Vector failedModsVec = new Vector ();
      NamingEnumeration attrEnum = attrs.getAll ();
      Attribute attr;
      String attrID;
      boolean errors = false;
      Integer setMethod;

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

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

         setMethod = (Integer) modAttrList.get (attrID);
         if (null == setMethod)  // not a valid attributeID
         {
            ModificationItem fmod = new ModificationItem (mod_op, attr);
            failedModsVec.addElement (fmod);
            errors = true;
            continue;
         }

         // This is ugly but will work for now
         for (Enumeration e = attr.getAll (); e.hasMoreElements (); )
         {
            try
            {
               switch (setMethod.intValue ())
               {
                  case TARGETSERVERID:
                     Integer tsid = (Integer) e.nextElement ();
                     setTargetServerID (tsid.intValue ());
                     break;

                  case TARGETEXECUTIONTIME:
                     Date tet = (Date) e.nextElement ();
                     setTargetExecutionTime (tet);
                     break;

                  case TYPE:
                     Integer t = (Integer) e.nextElement ();
                     setType (t.intValue ());
                     break;

                  case POSITION:
                     Integer pos = (Integer) e.nextElement ();
                     setPosition (pos.intValue ());
                     break;

                  case CONTROLFLAGS:
                     Integer cf = (Integer) e.nextElement ();
                     setControlFlags (cf.intValue ());
                     break;

                  case DESCRIPTION:
                     String description = (String) e.nextElement ();
                     setDescription (description);
                     break;

                  default:
                     ModificationItem fmod = new ModificationItem (mod_op, attr);
                     failedModsVec.addElement (fmod);
                     errors = true;
                     break;
               }
            }
            catch (Exception ex)
            {
               ModificationItem fmod = new ModificationItem (mod_op, attr);
               failedModsVec.addElement (fmod);
               errors = true;
            }
         }
      }

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

  /**
   * @internal
   */
   protected void a_modifyAttributes (
         String name,
         ModificationItem[] mods,
         Continuation cont)
      throws NamingException
   {
      if (!isEmpty( name ))   // nothing below us
      {
         cont.setError(this, name);
         throw cont.fillInException( new NameNotFoundException() );
      }

      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 while loop
   } /* a_modifyAttributes() */

  /**
   * @internal
   */
   protected void a_bind (
         String name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      // We don't allow anything to be bound to a queue job
      NotSupported (cont);
   } /* a_bind() */

  /**
   * @internal
   */
   protected void a_rebind (
         String name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      // We don't allow anything to be bound to a queue job
      NotSupported (cont);
   } /* a_rebind() */

  /**
   * @internal
   */
   protected DirContext a_createSubcontext (
         String name,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      // We don't have subordinates of a queue job
      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
         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 (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))   // 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 ();
         SearchResult sr = searchObject (ssp, cons, filterArgs);
         if (sr != null)
         {
            searchResults.addElement (sr);
         }
         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() */

   // QMSJob methods =========================================================
   
   /**
    * @internal
    *
    * Submits the job into the queue and returns a QMSOutputStream
    * object.
    *
    * @return An output stream on which to write job data.
    *
    * @exception IOException  When there is an I/O error.
    * @exception NSIException If a naming error was encountered.
    */
   public QMSOutputStream submit ()
      throws IOException, NSIException
   {
      if (jobExists)
      {  // The user is attempting to resubmit a job - this isn't legal
         throw NSIExceptionBuilder.build (JOB_ALREADY_OPEN);
      }
      os = new QMSOutputStream ((DataAccessable) queue, jobInfo);
      jobExists = true;
      jobID = jobInfo.getJobNumber ();
      return (os);
   } /* submit() */

   // QMSJob public accessor methods =========================================

  /**
    * @internal
    *
   * Returns the client station from jobInfo.
   *
   * @return The client station as an int.
   */
   public int getClientStation()
   {
      return (jobInfo.getClientStation ());
   }

  /**
    * @internal
    *
   * Returns the client task from jobInfo.
   *
   * @return The client task as an int.
   */
   public int getClientTask()
   {
      return( jobInfo.getClientTask() );
   }

  /**
    * @internal
    *
   * Returns the client ID from jobInfo.
   *
   * @return The client ID as an int.
   */
   public int getClientID()
   {
      return( jobInfo.getClientID() );
   }

  /**
    * @internal
    *
   * Returns the target server ID from jobInfo.
   *
   * @return The target server ID as an int.
   */
   public int getTargetServerID()
   {
      return( jobInfo.getTargetServerID() );
   }

  /**
    * @internal
    *
   * Returns the target execution time from jobInfo.
   *
   * @return The target execution time as a date object.
   */
   public Date getTargetExecutionTime ()
   {
      return (jobInfo.getTargetExecutionTime ());
   }

  /**
    * @internal
    *
   * Returns the job entry time from jobInfo.
   *
   * @return The job entry time as a date object.
   */
   public Date getEntryTime()
   {
      return( jobInfo.getJobEntryTime() );
   }

  /**
    * @internal
    *
   * Returns the job ID.
   *
   * @return The job ID as an int.
   */
   public int getID()
   {
      return(jobID);

   }

  /**
    * @internal
    *
   * Returns the job type from jobInfo.
   *
   * @return The job type as an int.
   */
   public int getType()
   {
      return( jobInfo.getJobType() );
   }

  /**
    * @internal
    *
   * Returns the job position in the queue.
   *
   * @return The job position in the queue as an int.
   *
   * @exception NSIException If a naming error was encountered.
   */
   public int getPosition()
      throws NSIException
   {
      // Update our cached copy to always return current position
      if (jobExists)
      {
         try
         {
            environment.getCallsService ().readQueueJobEntry2 (
                                                queue.getID (),
                                                jobID,
                                                jobInfo);
         }
         catch (Exception e)
         {
            throw NSIExceptionBuilder.build (e);
         }
      }
      return (jobInfo.getJobPosition ());
   }

  /**
    * @internal
    *
   * Returns the job control flags from jobInfo.
   *
   * @return The job control flags as an int.
   */
   public int getControlFlags()
   {
      return( jobInfo.getJobControlFlags() );
   }

  /**
    * @internal
    *
   * Returns the job file name from jobInfo.
   *
   * @return The job file name as an array of bites.
   */
   public byte[] getFileName()
   {
      return( jobInfo.getJobFileName() );
   }

  /**
    * @internal
    *
   * Returns the job file handle from jobInfo.
   *
   * @return The job file handle as an int.
   */
   public int getFileHandle()
   {
      return( jobInfo.getJobFileHandle() );
   }

  /**
    * @internal
    *
   * Returns the servicing server station from jobInfo.
   *
   * @return The servicing server station as an int.
   */
   public int getServicingServerStation()
   {
      return( jobInfo.getServicingServerStation() );
   }

  /**
    * @internal
    *
   * Returns the servicing server task from jobInfo.
   *
   * @return The servicing server task as an int.
   */
   public int getServicingServerTask()
   {
      return( jobInfo.getServicingServerTask() );
   }

  /**
    * @internal
    *
   * Returns the servicing server ID from jobInfo.
   *
   * @return The servicing server ID as an int.
   */
   public int getServicingServerID()
   {
      return( jobInfo.getServicingServerID() );
   }

  /**
    * @internal
    *
   * Returns the job description from jobInfo.
   *
   * @return The job description as a String.
   */
   public String getDescription()
   {
      return( jobInfo.getJobDescription() );
   }

  /**
    * @internal
    *
   * Returns the client record area from jobInfo.
   *
   * @return The client record area as an array of bites.
   */
   public byte[] getClientRecordArea()
   {
      return( jobInfo.getClientRecordArea() );
   }

   /* Set methods */

  /**
    * @internal
    *
   * Sets the target server ID in jobInfo.
   *
   * @param targetServerID The target server ID as an int.
   *
   * @exception NSIException If a naming error was encountered.
   */
   public void setTargetServerID(int targetServerID )
      throws NSIException
   {
      jobInfo.setTargetServerID( targetServerID );
      if (  !jobExists )
         return;

      try
      {
         environment.getCallsService ().changeQueueJobEntry2 (
                                             queue.getID (),
                                             jobInfo);
      }
      catch (Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }
   }

  /**
    * @internal
    *
   * Sets the target execution time in jobInfo.
   *
   * @param targetExecutionTime The target server execution time
   * as a Date object.
   *
   * @exception NSIException If a naming error was encountered.
   */
   public void setTargetExecutionTime( Date targetExecutionTime )
      throws NSIException
   {
      jobInfo.setTargetExecutionTime( targetExecutionTime );
      if (  !jobExists )
         return;

      try
      {
         environment.getCallsService ().changeQueueJobEntry2 (
                                             queue.getID (),
                                             jobInfo);
      }
      catch (Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }
   }

  /**
    * @internal
    *
   * Sets the job type in jobInfo.
   *
   * @param type The job type as an int.
   *
   * @exception NSIException If a naming error was encountered.
   */
   public void setType( int type )
      throws NSIException
   {
      jobInfo.setJobType( type );
      if (  !jobExists )
         return;

      try
      {
         environment.getCallsService ().changeQueueJobEntry2 (
                                             queue.getID (),
                                             jobInfo);
      }
      catch (Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }
   }

  /**
    * @internal
    *
   * Sets the job position in jobInfo.
   *
   * @param newPosition The job position as an int.
   *
   * @exception NSIException If a naming error was encountered.
   */
   public void setPosition( int newPosition )
      throws NSIException
   {
      if (  !jobExists )
         return;

      try
      {
         environment.getCallsService ().changeQueueJobPosition2 (
                                             queue.getID (),
                                             jobID,
                                             newPosition);

         // Reload the cached information
         environment.getCallsService ().readQueueJobEntry2 (
                                             queue.getID (),
                                             jobID,
                                             jobInfo);
      }
      catch (Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }
   }

   /**
    * @internal
    *
    * Sets or changes the queue job control flags.
    *
    * <p>This includes setting a job to operator hold, or user hold, etc.
    * The valid flags that can be set are the following:
    * <ul>
    * <li> QF_AUTO_START
    * <li> QF_ENTRY_RESTART
    * <li> QF_ENTRY_OPEN
    * <li> QF_USER_HOLD
    * <li> QF_OPERATOR_HOLD
    * </ul></p>
    *
    * @param controlFlags The control flags to be set.
    *
    * @exception NSIException If a naming error was encountered.
    */
   public void setControlFlags (
         int controlFlags)
      throws NSIException
   {
      // Update the in-memory data
      jobInfo.setJobControlFlags (controlFlags);
      
      // This is a queue job in memory only so do nothing else
      if (!jobExists)
         return;

      try
      {  // The job exists so lets tell the queue about the change
         environment.getCallsService ().changeQueueJobEntry2 (
                                             queue.getID (),
                                             jobInfo);
      }
      catch (Exception e)
      {
         throw NSIExceptionBuilder.build (e);
      }
   } /* setControlFlags() */

   /**
    * @internal
    *
    * Sets or changes the queue job description string.
    *
    * @param description  The new job description string.
    *
    * @exception NSIException If a naming error was encountered.
    */
   public void setDescription (
         String description)
      throws NSIException
   {
      // Update the in-memory data
      jobInfo.setJobDescription (description);
      
      // This is a queue job in memory only so do nothing else
      if (!jobExists)
         return;

      try
      {  // The job exists so lets tell the queue about the change
         environment.getCallsService ().changeQueueJobEntry2 (
                                             queue.getID (),
                                             jobInfo);
      }
      catch (Exception e)
      {
         throw NSIExceptionBuilder.build (e);
      }
   } /* setDescription() */

   /**
    * @internal
    *
    * Sets or changes the parameters in the queue job client record area.
    *
    * @param clientRecordArea The new client record area data as an
    *                         array of bytes.
    *
    * @exception NSIException If a naming error was encountered.
    */
   public void setClientRecordArea (
         byte[] clientRecordArea)
      throws NSIException
   {
      // Update the in-memory data
      jobInfo.setClientRecordArea (clientRecordArea);

      // If the job doesn't exist then we're done
      if (!jobExists)
         return;

      try
      {  // The job exists so lets tell the queue about the change
         environment.getCallsService ().changeQueueJobEntry2 (
                                             queue.getID (),
                                             jobInfo);

      }
      catch (Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }
   } /* setClientRecordArea() */

   /**
    * @internal
    *
    * Cancels this job and removes it from the queue.
    *
    * @exception IOException  An I/O error has occurred.
    * @exception NSIException If a naming error was encountered.
    */
   public void cancel ()
      throws IOException, NSIException
   {
      // This is a queue job in memory only so do nothing else
      if (!jobExists)
         return;

      if (null == os)
      {
         try
         {  // This job is real but has no output stream so just remove it
            environment.getCallsService ().removeJobFromQueue2 (
                                                queue.getID (),
                                                jobID);
         }
         catch (Exception e)
         {
            throw NSIExceptionBuilder.build (e);
         }
      }
      else
      {  // Stream still open so just abort the job
         os.abort ();
      }
   } /* cancel() */

   // Internal helper methods ================================================

   /*
    * 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.
    *
    * @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
    *                            if this context matches.
    * @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;
            String valueType;

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

            // Compare component 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;
            }

            valueType = (String) attrTypes.get( attrID );

            if ( valueType.equals( "Integer" ) )
            {
               compared = compareInteger( attr, comp, filterArgs );
            }
            else if ( valueType.equals( "Date" ) )
            {
               compared = compareDate( attr, comp, filterArgs );
            }
            else if ( valueType.equals( "String " ) )
            {
               compared = compareString( 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() */

   /*
    * 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 compareDate (
         Attribute               attr,
         SearchStringComponent   ssComp,
         Object[]                filterArgs)
      throws NamingException
   {
      int opType = ssComp.getOperationType ();

      // The following two compare types are not valid for Date 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 Date attrs can only have one value, attrValues
      //    must be accessed via an Enum.

      Enumeration values;
      try
      {
         values = attr.getAll ();
      }
      catch (Exception e)
      {
         return (false);
      }
      Date dValue = (Date) values.nextElement ();

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

      if (ssComp.operandReplacement ())
      {
         try
         {
            opValue = (Date) 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
      {
         try
         {
            opValue = new Date( Long.parseLong( ssComp.getOperand() ) );
         }
         catch (NumberFormatException nfe)
         {
            InvalidSearchFilterException isfe;
            isfe = new InvalidSearchFilterException ();
            isfe.setRootCause (nfe);
            throw isfe;
         }
      }

      // Start the actual tests.
      if (SearchStringComponent.EQUALS == opType)
      {
         result = dValue.equals (opValue);
      }
      else if (SearchStringComponent.LESS_OR_EQUAL == opType)
      {
         result = dValue.equals (opValue) || dValue.before (opValue);
      }
      else
      {
         result = dValue.equals (opValue) || dValue.after (opValue);
      }

      return (result);
   }  /* compareDate() */

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

      // Setup to start testing the data.
      boolean result = false;

      // Although the String attrs can only have one value, attrValues
      //    must be accessed via an Enum.
      Enumeration values;
      try
      {
         values = attr.getAll();
      }
      catch( Exception e )
      {
         return( false );
      }
      String sValue = (String) values.nextElement();

      String opStr;

      if (ssComp.operandReplacement ())
      {
         try
         {
            opStr = (String) 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();

      // Start the actual tests
      if (SearchStringComponent.SUBSTRING == opType)
      {
         result = SearchStringComponent.compareSubString (
                                             opStr,
                                             sValue,
                                             true);
      }
      else
      {
         result = opStr.equals (sValue);
      }

      return (result);
   } /* compareString() */

}  /* QueueJobDirContext */
