Package org.apache.manifoldcf.crawler.system

Source Code of org.apache.manifoldcf.crawler.system.ManifoldCF

/* $Id: ManifoldCF.java 996524 2010-09-13 13:38:01Z kwright $ */

/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.manifoldcf.crawler.system;

import org.apache.manifoldcf.core.interfaces.*;
import org.apache.manifoldcf.agents.interfaces.*;
import org.apache.manifoldcf.crawler.interfaces.*;
import org.apache.manifoldcf.authorities.interfaces.*;

import java.io.*;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.*;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class ManifoldCF extends org.apache.manifoldcf.agents.system.ManifoldCF
{
  public static final String _rcsid = "@(#)$Id: ManifoldCF.java 996524 2010-09-13 13:38:01Z kwright $";

  // Initialization flag.
  protected static boolean crawlerInitialized = false;
 
  // Thread objects.
  // These get filled in as threads are created.
  protected static InitializationThread initializationThread = null;
  protected static JobStartThread jobStartThread = null;
  protected static StufferThread stufferThread = null;
  protected static FinisherThread finisherThread = null;
  protected static JobNotificationThread notificationThread = null;
  protected static StartupThread startupThread = null;
  protected static StartDeleteThread startDeleteThread = null;
  protected static JobDeleteThread jobDeleteThread = null;
  protected static WorkerThread[] workerThreads = null;
  protected static ExpireStufferThread expireStufferThread = null;
  protected static ExpireThread[] expireThreads = null;
  protected static DocumentDeleteStufferThread deleteStufferThread = null;
  protected static DocumentDeleteThread[] deleteThreads = null;
  protected static DocumentCleanupStufferThread cleanupStufferThread = null;
  protected static DocumentCleanupThread[] cleanupThreads = null;
  protected static JobResetThread jobResetThread = null;
  protected static SeedingThread seedingThread = null;
  protected static IdleCleanupThread idleCleanupThread = null;
  protected static SetPriorityThread setPriorityThread = null;

  // Reset managers
  /** Worker thread pool reset manager */
  protected static WorkerResetManager workerResetManager = null;
  /** Delete thread pool reset manager */
  protected static DocDeleteResetManager docDeleteResetManager = null;
  /** Cleanup thread pool reset manager */
  protected static DocCleanupResetManager docCleanupResetManager = null;

  // Number of worker threads
  protected static int numWorkerThreads = 0;
  // Number of delete threads
  protected static int numDeleteThreads = 0;
  // Number of cleanup threads
  protected static int numCleanupThreads = 0;
  // Number of expiration threads
  protected static int numExpireThreads = 0;
  // Factor for low water level in queueing
  protected static float lowWaterFactor = 5.0f;
  // Factor in amount to stuff
  protected static float stuffAmtFactor = 0.5f;

  // Properties
  protected static final String workerThreadCountProperty = "org.apache.manifoldcf.crawler.threads";
  protected static final String deleteThreadCountProperty = "org.apache.manifoldcf.crawler.deletethreads";
  protected static final String cleanupThreadCountProperty = "org.apache.manifoldcf.crawler.cleanupthreads";
  protected static final String expireThreadCountProperty = "org.apache.manifoldcf.crawler.expirethreads";
  protected static final String lowWaterFactorProperty = "org.apache.manifoldcf.crawler.lowwaterfactor";
  protected static final String stuffAmtFactorProperty = "org.apache.manifoldcf.crawler.stuffamountfactor";
  protected static final String connectorsConfigurationFileProperty = "org.apache.manifoldcf.connectorsconfigurationfile";
  protected static final String databaseSuperuserNameProperty = "org.apache.manifoldcf.dbsuperusername";
  protected static final String databaseSuperuserPasswordProperty = "org.apache.manifoldcf.dbsuperuserpassword";
  protected static final String salt = "org.apache.manifoldcf.salt";

  /** This object is used to make sure the initialization sequence is atomic.  Shutdown cannot occur until the system is in a known state. */
  protected static Integer startupLock = new Integer(0);
 
  /** Initialize environment.
  */
  public static void initializeEnvironment()
    throws ManifoldCFException
  {
    synchronized (initializeFlagLock)
    {
      org.apache.manifoldcf.agents.system.ManifoldCF.initializeEnvironment();
      org.apache.manifoldcf.authorities.system.ManifoldCF.localInitialize();
      org.apache.manifoldcf.crawler.system.ManifoldCF.localInitialize();
    }
  }

  public static void cleanUpEnvironment()
  {
    synchronized (initializeFlagLock)
    {
      org.apache.manifoldcf.authorities.system.ManifoldCF.localCleanup();
      org.apache.manifoldcf.crawler.system.ManifoldCF.localCleanup();
      org.apache.manifoldcf.agents.system.ManifoldCF.cleanUpEnvironment();
    }
  }
 
  public static void localInitialize()
    throws ManifoldCFException
  {
    synchronized (initializeFlagLock)
    {
     
      if (crawlerInitialized)
        return;
     
      Logging.initializeLoggers();
      Logging.setLogLevels();
      crawlerInitialized = true;
    }
  }
 
  public static void localCleanup()
  {
  }
 
  /** Create system database using superuser properties from properties.xml.
  */
  public static void createSystemDatabase(IThreadContext threadContext)
    throws ManifoldCFException
  {
    // Get the specified superuser name and password, in case this isn't Derby we're using
    String superuserName = getProperty(databaseSuperuserNameProperty);
    if (superuserName == null)
      superuserName = "";
    String superuserPassword = getProperty(databaseSuperuserPasswordProperty);
    if (superuserPassword == null)
      superuserPassword = "";
    createSystemDatabase(threadContext,superuserName,superuserPassword);
  }
 
  /** Register this agent */
  public static void registerThisAgent(IThreadContext tc)
    throws ManifoldCFException
  {
    // Register
    IAgentManager agentMgr = AgentManagerFactory.make(tc);
    agentMgr.registerAgent("org.apache.manifoldcf.crawler.system.CrawlerAgent");
  }

  /** Register or re-register all connectors, based on a connectors.xml file.
  */
  public static void reregisterAllConnectors(IThreadContext tc)
    throws ManifoldCFException
  {
    // Read connectors configuration file (to figure out what we need to register)
    File connectorConfigFile = getFileProperty(connectorsConfigurationFileProperty);
    Connectors c = readConnectorDeclarations(connectorConfigFile);
   
    // Unregister all the connectors we don't want.
    unregisterAllConnectors(tc,c);

    // Register (or update) all connectors specified by connectors.xml
    registerConnectors(tc,c);
  }
 
  /** Read connectors configuration file.
  */
  public static Connectors readConnectorDeclarations(File connectorConfigFile)
    throws ManifoldCFException
  {
    Connectors c = null;
    if (connectorConfigFile != null)
    {
      try
      {
        // Open the file, read it, and attempt to do the connector registrations
        InputStream is = new FileInputStream(connectorConfigFile);
        try
        {
          c = new Connectors(is);
        }
        finally
        {
          is.close();
        }
      }
      catch (FileNotFoundException e)
      {
        throw new ManifoldCFException("Couldn't find connector configuration file: "+e.getMessage(),e);
      }
      catch (IOException e)
      {
        throw new ManifoldCFException("Error reading connector configuration file: "+e.getMessage(),e);
      }
    }
    return c;
  }

  /** Unregister all connectors.
  */
  public static void unregisterAllConnectors(IThreadContext tc)
    throws ManifoldCFException
  {
    unregisterAllConnectors(tc,null);
  }

  // Connectors configuration file
  protected static final String NODE_OUTPUTCONNECTOR = "outputconnector";
  protected static final String NODE_MAPPINGCONNECTOR = "mappingconnector";
  protected static final String NODE_AUTHORITYCONNECTOR = "authorityconnector";
  protected static final String NODE_REPOSITORYCONNECTOR = "repositoryconnector";
  protected static final String ATTRIBUTE_NAME = "name";
  protected static final String ATTRIBUTE_CLASS = "class";
 
  /** Unregister all connectors which don't match a specified connector list.
  */
  public static void unregisterAllConnectors(IThreadContext tc, Connectors c)
    throws ManifoldCFException
  {
    // Create a map of class name and description, so we can compare what we can find
    // against what we want.
    Map<String,String> desiredOutputConnectors = new HashMap<String,String>();
    Map<String,String> desiredMappingConnectors = new HashMap<String,String>();
    Map<String,String> desiredAuthorityConnectors = new HashMap<String,String>();
    Map<String,String> desiredRepositoryConnectors = new HashMap<String,String>();

    if (c != null)
    {
      for (int i = 0; i < c.getChildCount(); i++)
      {
        ConfigurationNode cn = c.findChild(i);
        if (cn.getType().equals(NODE_OUTPUTCONNECTOR))
        {
          String name = cn.getAttributeValue(ATTRIBUTE_NAME);
          String className = cn.getAttributeValue(ATTRIBUTE_CLASS);
          desiredOutputConnectors.put(className,name);
        }
        else if (cn.getType().equals(NODE_MAPPINGCONNECTOR))
        {
          String name = cn.getAttributeValue(ATTRIBUTE_NAME);
          String className = cn.getAttributeValue(ATTRIBUTE_CLASS);
          desiredMappingConnectors.put(className,name);
        }
        else if (cn.getType().equals(NODE_AUTHORITYCONNECTOR))
        {
          String name = cn.getAttributeValue(ATTRIBUTE_NAME);
          String className = cn.getAttributeValue(ATTRIBUTE_CLASS);
          desiredAuthorityConnectors.put(className,name);
        }
        else if (cn.getType().equals(NODE_REPOSITORYCONNECTOR))
        {
          String name = cn.getAttributeValue(ATTRIBUTE_NAME);
          String className = cn.getAttributeValue(ATTRIBUTE_CLASS);
          desiredRepositoryConnectors.put(className,name);
        }
      }
    }

    // Grab a database handle, so we can use transactions later.
    IDBInterface database = DBInterfaceFactory.make(tc,
      ManifoldCF.getMasterDatabaseName(),
      ManifoldCF.getMasterDatabaseUsername(),
      ManifoldCF.getMasterDatabasePassword());

    // Output connectors...
    {
      IOutputConnectorManager mgr = OutputConnectorManagerFactory.make(tc);
      IOutputConnectionManager connManager = OutputConnectionManagerFactory.make(tc);
      IResultSet classNames = mgr.getConnectors();
      int i = 0;
      while (i < classNames.getRowCount())
      {
        IResultRow row = classNames.getRow(i++);
        String className = (String)row.getValue("classname");
        String description = (String)row.getValue("description");
        if (desiredOutputConnectors.get(className) == null || !desiredOutputConnectors.get(className).equals(description))
        {
          // Deregistration should be done in a transaction
          database.beginTransaction();
          try
          {
            // Find the connection names that come with this class
            String[] connectionNames = connManager.findConnectionsForConnector(className);
            // For all connection names, notify all agents of the deregistration
            AgentManagerFactory.noteOutputConnectorDeregistration(tc,connectionNames);
            // Now that all jobs have been placed into an appropriate state, actually do the deregistration itself.
            mgr.unregisterConnector(className);
          }
          catch (ManifoldCFException e)
          {
            database.signalRollback();
            throw e;
          }
          catch (Error e)
          {
            database.signalRollback();
            throw e;
          }
          finally
          {
            database.endTransaction();
          }
        }
      }
      System.err.println("Successfully unregistered all output connectors");
    }

    // Mapping connectors...
    {
      IMappingConnectorManager mgr = MappingConnectorManagerFactory.make(tc);
      IResultSet classNames = mgr.getConnectors();
      int i = 0;
      while (i < classNames.getRowCount())
      {
        IResultRow row = classNames.getRow(i++);
        String className = (String)row.getValue("classname");
        String description = (String)row.getValue("description");
        if (desiredMappingConnectors.get(className) == null || !desiredMappingConnectors.get(className).equals(description))
        {
          mgr.unregisterConnector(className);
        }
      }
      System.err.println("Successfully unregistered all mapping connectors");
    }

    // Authority connectors...
    {
      IAuthorityConnectorManager mgr = AuthorityConnectorManagerFactory.make(tc);
      IResultSet classNames = mgr.getConnectors();
      int i = 0;
      while (i < classNames.getRowCount())
      {
        IResultRow row = classNames.getRow(i++);
        String className = (String)row.getValue("classname");
        String description = (String)row.getValue("description");
        if (desiredAuthorityConnectors.get(className) == null || !desiredAuthorityConnectors.get(className).equals(description))
        {
          mgr.unregisterConnector(className);
        }
      }
      System.err.println("Successfully unregistered all authority connectors");
    }
     
    // Repository connectors...
    {
      IConnectorManager mgr = ConnectorManagerFactory.make(tc);
      IJobManager jobManager = JobManagerFactory.make(tc);
      IRepositoryConnectionManager connManager = RepositoryConnectionManagerFactory.make(tc);
      IResultSet classNames = mgr.getConnectors();
      int i = 0;
      while (i < classNames.getRowCount())
      {
        IResultRow row = classNames.getRow(i++);
        String className = (String)row.getValue("classname");
        String description = (String)row.getValue("description");
        if (desiredRepositoryConnectors.get(className) == null || !desiredRepositoryConnectors.get(className).equals(description))
        {
          // Deregistration should be done in a transaction
          database.beginTransaction();
          try
          {
            // Find the connection names that come with this class
            String[] connectionNames = connManager.findConnectionsForConnector(className);
            // For each connection name, modify the jobs to note that the connector is no longer installed
            jobManager.noteConnectorDeregistration(connectionNames);
            // Now that all jobs have been placed into an appropriate state, actually do the deregistration itself.
            mgr.unregisterConnector(className);
          }
          catch (ManifoldCFException e)
          {
            database.signalRollback();
            throw e;
          }
          catch (Error e)
          {
            database.signalRollback();
            throw e;
          }
          finally
          {
            database.endTransaction();
          }
        }
      }
      System.err.println("Successfully unregistered all repository connectors");
    }
  }


  /** Register all connectors as specified by a Connectors structure, usually read from the connectors.xml file.
  */
  public static void registerConnectors(IThreadContext tc, Connectors c)
    throws ManifoldCFException
  {
    if (c != null)
    {
      // Grab a database handle, so we can use transactions later.
      IDBInterface database = DBInterfaceFactory.make(tc,
        ManifoldCF.getMasterDatabaseName(),
        ManifoldCF.getMasterDatabaseUsername(),
        ManifoldCF.getMasterDatabasePassword());
       
      // Other code will go here to discover and register various connectors that exist in the classpath
      int i = 0;
      while (i < c.getChildCount())
      {
        ConfigurationNode cn = c.findChild(i++);
        if (cn.getType().equals(NODE_OUTPUTCONNECTOR))
        {
          String name = cn.getAttributeValue(ATTRIBUTE_NAME);
          String className = cn.getAttributeValue(ATTRIBUTE_CLASS);
          IOutputConnectorManager mgr = OutputConnectorManagerFactory.make(tc);
          IOutputConnectionManager connManager = OutputConnectionManagerFactory.make(tc);
          // Registration should be done in a transaction
          database.beginTransaction();
          try
          {
            // First, register connector
            mgr.registerConnector(name,className);
            // Then, signal to all jobs that might depend on this connector that they can switch state
            // Find the connection names that come with this class
            String[] connectionNames = connManager.findConnectionsForConnector(className);
            // For all connection names, notify all agents of the registration
            AgentManagerFactory.noteOutputConnectorRegistration(tc,connectionNames);
          }
          catch (ManifoldCFException e)
          {
            database.signalRollback();
            throw e;
          }
          catch (Error e)
          {
            database.signalRollback();
            throw e;
          }
          finally
          {
            database.endTransaction();
          }
          System.err.println("Successfully registered output connector '"+className+"'");
        }
        else if (cn.getType().equals(NODE_AUTHORITYCONNECTOR))
        {
          String name = cn.getAttributeValue(ATTRIBUTE_NAME);
          String className = cn.getAttributeValue(ATTRIBUTE_CLASS);
          IAuthorityConnectorManager mgr = AuthorityConnectorManagerFactory.make(tc);
          mgr.registerConnector(name,className);
          System.err.println("Successfully registered authority connector '"+className+"'");
        }
        else if (cn.getType().equals(NODE_MAPPINGCONNECTOR))
        {
          String name = cn.getAttributeValue(ATTRIBUTE_NAME);
          String className = cn.getAttributeValue(ATTRIBUTE_CLASS);
          IMappingConnectorManager mgr = MappingConnectorManagerFactory.make(tc);
          mgr.registerConnector(name,className);
          System.err.println("Successfully registered mapping connector '"+className+"'");
        }
        else if (cn.getType().equals(NODE_REPOSITORYCONNECTOR))
        {
          String name = cn.getAttributeValue(ATTRIBUTE_NAME);
          String className = cn.getAttributeValue(ATTRIBUTE_CLASS);
          IConnectorManager mgr = ConnectorManagerFactory.make(tc);
          IJobManager jobManager = JobManagerFactory.make(tc);
          IRepositoryConnectionManager connManager = RepositoryConnectionManagerFactory.make(tc);
          // Deregistration should be done in a transaction
          database.beginTransaction();
          try
          {
            // First, register connector
            mgr.registerConnector(name,className);
            // Then, signal to all jobs that might depend on this connector that they can switch state
            // Find the connection names that come with this class
            String[] connectionNames = connManager.findConnectionsForConnector(className);
            // For each connection name, modify the jobs to note that the connector is now installed
            jobManager.noteConnectorRegistration(connectionNames);
          }
          catch (ManifoldCFException e)
          {
            database.signalRollback();
            throw e;
          }
          catch (Error e)
          {
            database.signalRollback();
            throw e;
          }
          finally
          {
            database.endTransaction();
          }
          System.err.println("Successfully registered repository connector '"+className+"'");
        }
        else
          throw new ManifoldCFException("Unrecognized connectors node type '"+cn.getType()+"'");
      }
    }
  }

  /** Install all the crawler system tables.
  *@param threadcontext is the thread context.
  */
  public static void installSystemTables(IThreadContext threadcontext)
    throws ManifoldCFException
  {
    IConnectorManager repConnMgr = ConnectorManagerFactory.make(threadcontext);
    IRepositoryConnectionManager repCon = RepositoryConnectionManagerFactory.make(threadcontext);
    IJobManager jobManager = JobManagerFactory.make(threadcontext);
    org.apache.manifoldcf.authorities.system.ManifoldCF.installSystemTables(threadcontext);
    repConnMgr.install();
    repCon.install();
    jobManager.install();
  }

  /** Uninstall all the crawler system tables.
  *@param threadcontext is the thread context.
  */
  public static void deinstallSystemTables(IThreadContext threadcontext)
    throws ManifoldCFException
  {
    ManifoldCFException se = null;

    IConnectorManager repConnMgr = ConnectorManagerFactory.make(threadcontext);
    IRepositoryConnectionManager repCon = RepositoryConnectionManagerFactory.make(threadcontext);
    IJobManager jobManager = JobManagerFactory.make(threadcontext);
    jobManager.deinstall();
    repCon.deinstall();
    repConnMgr.deinstall();
    org.apache.manifoldcf.authorities.system.ManifoldCF.deinstallSystemTables(threadcontext);
    if (se != null)
      throw se;
  }


  /** Start everything.
  */
  public static void startSystem(IThreadContext threadContext)
    throws ManifoldCFException
  {
    Logging.root.info("Starting up pull-agent...");
    synchronized (startupLock)
    {
      // Now, start all the threads
      String maxThreads = getProperty(workerThreadCountProperty);
      if (maxThreads == null)
        maxThreads = "100";
      numWorkerThreads = new Integer(maxThreads).intValue();
      if (numWorkerThreads < 1 || numWorkerThreads > 300)
        throw new ManifoldCFException("Illegal value for the number of worker threads");
      String maxDeleteThreads = getProperty(deleteThreadCountProperty);
      if (maxDeleteThreads == null)
        maxDeleteThreads = "10";
      String maxCleanupThreads = getProperty(cleanupThreadCountProperty);
      if (maxCleanupThreads == null)
        maxCleanupThreads = "10";
      String maxExpireThreads = getProperty(expireThreadCountProperty);
      if (maxExpireThreads == null)
        maxExpireThreads = "10";
      numDeleteThreads = new Integer(maxDeleteThreads).intValue();
      if (numDeleteThreads < 1 || numDeleteThreads > 300)
        throw new ManifoldCFException("Illegal value for the number of delete threads");
      numCleanupThreads = new Integer(maxCleanupThreads).intValue();
      if (numCleanupThreads < 1 || numCleanupThreads > 300)
        throw new ManifoldCFException("Illegal value for the number of cleanup threads");
      numExpireThreads = new Integer(maxExpireThreads).intValue();
      if (numExpireThreads < 1 || numExpireThreads > 300)
        throw new ManifoldCFException("Illegal value for the number of expire threads");
      String lowWaterFactorString = getProperty(lowWaterFactorProperty);
      if (lowWaterFactorString == null)
        lowWaterFactorString = "5";
      lowWaterFactor = new Float(lowWaterFactorString).floatValue();
      if (lowWaterFactor < 1.0 || lowWaterFactor > 1000.0)
        throw new ManifoldCFException("Illegal value for the low water factor");
      String stuffAmtFactorString = getProperty(stuffAmtFactorProperty);
      if (stuffAmtFactorString == null)
        stuffAmtFactorString = "2";
      stuffAmtFactor = new Float(stuffAmtFactorString).floatValue();
      if (stuffAmtFactor < 0.1 || stuffAmtFactor > 1000.0)
        throw new ManifoldCFException("Illegal value for the stuffing amount factor");


      // Create the threads and objects.  This MUST be completed before there is any chance of "shutdownSystem" getting called.

      QueueTracker queueTracker = new QueueTracker();


      DocumentQueue documentQueue = new DocumentQueue();
      DocumentDeleteQueue documentDeleteQueue = new DocumentDeleteQueue();
      DocumentCleanupQueue documentCleanupQueue = new DocumentCleanupQueue();
      DocumentCleanupQueue expireQueue = new DocumentCleanupQueue();

      BlockingDocuments blockingDocuments = new BlockingDocuments();

      workerResetManager = new WorkerResetManager(documentQueue,expireQueue);
      docDeleteResetManager = new DocDeleteResetManager(documentDeleteQueue);
      docCleanupResetManager = new DocCleanupResetManager(documentCleanupQueue);

      jobStartThread = new JobStartThread();
      startupThread = new StartupThread(queueTracker);
      startDeleteThread = new StartDeleteThread();
      finisherThread = new FinisherThread();
      notificationThread = new JobNotificationThread();
      jobDeleteThread = new JobDeleteThread();
      stufferThread = new StufferThread(documentQueue,numWorkerThreads,workerResetManager,queueTracker,blockingDocuments,lowWaterFactor,stuffAmtFactor);
      expireStufferThread = new ExpireStufferThread(expireQueue,numExpireThreads,workerResetManager);
      setPriorityThread = new SetPriorityThread(queueTracker,numWorkerThreads,blockingDocuments);

      workerThreads = new WorkerThread[numWorkerThreads];
      int i = 0;
      while (i < numWorkerThreads)
      {
        workerThreads[i] = new WorkerThread(Integer.toString(i),documentQueue,workerResetManager,queueTracker);
        i++;
      }

      expireThreads = new ExpireThread[numExpireThreads];
      i = 0;
      while (i < numExpireThreads)
      {
        expireThreads[i] = new ExpireThread(Integer.toString(i),expireQueue,queueTracker,workerResetManager);
        i++;
      }

      deleteStufferThread = new DocumentDeleteStufferThread(documentDeleteQueue,numDeleteThreads,docDeleteResetManager);
      deleteThreads = new DocumentDeleteThread[numDeleteThreads];
      i = 0;
      while (i < numDeleteThreads)
      {
        deleteThreads[i] = new DocumentDeleteThread(Integer.toString(i),documentDeleteQueue,docDeleteResetManager);
        i++;
      }
     
      cleanupStufferThread = new DocumentCleanupStufferThread(documentCleanupQueue,numCleanupThreads,docCleanupResetManager);
      cleanupThreads = new DocumentCleanupThread[numCleanupThreads];
      i = 0;
      while (i < numCleanupThreads)
      {
        cleanupThreads[i] = new DocumentCleanupThread(Integer.toString(i),documentCleanupQueue,queueTracker,docCleanupResetManager);
        i++;
      }

      jobResetThread = new JobResetThread(queueTracker);
      seedingThread = new SeedingThread(queueTracker);
      idleCleanupThread = new IdleCleanupThread();

      initializationThread = new InitializationThread(queueTracker);
      // Start the initialization thread.  This does the initialization work and starts all the other threads when that's done.  It then exits.
      initializationThread.start();
    }
    Logging.root.info("Pull-agent started");
  }

  protected static class InitializationThread extends Thread
  {

    protected final QueueTracker queueTracker;

    public InitializationThread(QueueTracker queueTracker)
    {
      super();
      this.queueTracker = queueTracker;
      setName("Initialization thread");
      setDaemon(true);
    }

    public void run()
    {
      int i;

      // Initialize the database
      try
      {
        IThreadContext threadContext = ThreadContextFactory.make();

        // First, get a job manager
        IJobManager jobManager = JobManagerFactory.make(threadContext);
        IRepositoryConnectionManager mgr = RepositoryConnectionManagerFactory.make(threadContext);

        Logging.threads.debug("Agents process starting initialization...");

        // Call the database to get it ready
        jobManager.prepareForStart();

        Logging.threads.debug("Agents process reprioritizing documents...");

        HashMap connectionMap = new HashMap();
        HashMap jobDescriptionMap = new HashMap();
        // Reprioritize all documents in the jobqueue, 1000 at a time
        long currentTime = System.currentTimeMillis();

        // Do the 'not yet processed' documents only.  Documents that are queued for reprocessing will be assigned
        // new priorities.  Already processed documents won't.  This guarantees that our bins are appropriate for current thread
        // activity.
        // In order for this to be the correct functionality, ALL reseeding and requeuing operations MUST reset the associated document
        // priorities.
        while (true)
        {
          long startTime = System.currentTimeMillis();

          DocumentDescription[] docs = jobManager.getNextNotYetProcessedReprioritizationDocuments(currentTime, 10000);
          if (docs.length == 0)
            break;

          // Calculate new priorities for all these documents
          writeDocumentPriorities(threadContext,mgr,jobManager,docs,connectionMap,jobDescriptionMap,queueTracker,currentTime);

          Logging.threads.debug("Reprioritized "+Integer.toString(docs.length)+" not-yet-processed documents in "+new Long(System.currentTimeMillis()-startTime)+" ms");
        }

        Logging.threads.debug("Agents process initialization complete!");

        // Start all the threads
        jobStartThread.start();
        startupThread.start();
        startDeleteThread.start();
        finisherThread.start();
        notificationThread.start();
        jobDeleteThread.start();
        stufferThread.start();
        expireStufferThread.start();
        setPriorityThread.start();

        i = 0;
        while (i < numWorkerThreads)
        {
          workerThreads[i].start();
          i++;
        }

        i = 0;
        while (i < numExpireThreads)
        {
          expireThreads[i].start();
          i++;
        }

        cleanupStufferThread.start();
        i = 0;
        while (i < numCleanupThreads)
        {
          cleanupThreads[i].start();
          i++;
        }

        deleteStufferThread.start();
        i = 0;
        while (i < numDeleteThreads)
        {
          deleteThreads[i].start();
          i++;
        }

        jobResetThread.start();
        seedingThread.start();
        idleCleanupThread.start();
        // exit!
      }
      catch (Throwable e)
      {
        // Severe error on initialization
        if (e instanceof ManifoldCFException)
        {
          // Deal with interrupted exception gracefully, because it means somebody is trying to shut us down before we got started.
          if (((ManifoldCFException)e).getErrorCode() == ManifoldCFException.INTERRUPTED)
            return;
        }
        System.err.println("agents process could not start - shutting down");
        Logging.threads.fatal("Startup initialization error tossed: "+e.getMessage(),e);
        System.exit(-300);
      }
    }
  }

  /** Stop the system.
  */
  public static void stopSystem(IThreadContext threadContext)
    throws ManifoldCFException
  {
    Logging.root.info("Shutting down pull-agent...");
    synchronized (startupLock)
    {
      while (initializationThread != null || jobDeleteThread != null || startupThread != null || startDeleteThread != null ||
        jobStartThread != null || stufferThread != null ||
        finisherThread != null || notificationThread != null || workerThreads != null || expireStufferThread != null || expireThreads != null ||
        deleteStufferThread != null || deleteThreads != null ||
        cleanupStufferThread != null || cleanupThreads != null ||
        jobResetThread != null || seedingThread != null || idleCleanupThread != null || setPriorityThread != null)
      {
        // Send an interrupt to all threads that are still there.
        // In theory, this only needs to be done once.  In practice, I have seen cases where the thread loses track of the fact that it has been
        // interrupted (which may be a JVM bug - who knows?), but in any case there's no harm in doing it again.
        if (initializationThread != null)
        {
          initializationThread.interrupt();
        }
        if (setPriorityThread != null)
        {
          setPriorityThread.interrupt();
        }
        if (jobStartThread != null)
        {
          jobStartThread.interrupt();
        }
        if (jobDeleteThread != null)
        {
          jobDeleteThread.interrupt();
        }
        if (startupThread != null)
        {
          startupThread.interrupt();
        }
        if (startDeleteThread != null)
        {
          startDeleteThread.interrupt();
        }
        if (stufferThread != null)
        {
          stufferThread.interrupt();
        }
        if (expireStufferThread != null)
        {
          expireStufferThread.interrupt();
        }
        if (finisherThread != null)
        {
          finisherThread.interrupt();
        }
        if (notificationThread != null)
        {
          notificationThread.interrupt();
        }
        if (workerThreads != null)
        {
          int i = 0;
          while (i < workerThreads.length)
          {
            Thread workerThread = workerThreads[i++];
            if (workerThread != null)
              workerThread.interrupt();
          }
        }
        if (expireThreads != null)
        {
          int i = 0;
          while (i < expireThreads.length)
          {
            Thread expireThread = expireThreads[i++];
            if (expireThread != null)
              expireThread.interrupt();
          }
        }
        if (cleanupStufferThread != null)
        {
          cleanupStufferThread.interrupt();
        }
        if (cleanupThreads != null)
        {
          int i = 0;
          while (i < cleanupThreads.length)
          {
            Thread cleanupThread = cleanupThreads[i++];
            if (cleanupThread != null)
              cleanupThread.interrupt();
          }
        }
        if (deleteStufferThread != null)
        {
          deleteStufferThread.interrupt();
        }
        if (deleteThreads != null)
        {
          int i = 0;
          while (i < deleteThreads.length)
          {
            Thread deleteThread = deleteThreads[i++];
            if (deleteThread != null)
              deleteThread.interrupt();
          }
        }
        if (jobResetThread != null)
        {
          jobResetThread.interrupt();
        }
        if (seedingThread != null)
        {
          seedingThread.interrupt();
        }
        if (idleCleanupThread != null)
        {
          idleCleanupThread.interrupt();
        }

        // Now, wait for all threads to die.
        try
        {
          ManifoldCF.sleep(1000L);
        }
        catch (InterruptedException e)
        {
        }

        // Check to see which died.
        if (initializationThread != null)
        {
          if (!initializationThread.isAlive())
            initializationThread = null;
        }
        if (setPriorityThread != null)
        {
          if (!setPriorityThread.isAlive())
            setPriorityThread = null;
        }
        if (jobDeleteThread != null)
        {
          if (!jobDeleteThread.isAlive())
            jobDeleteThread = null;
        }
        if (startupThread != null)
        {
          if (!startupThread.isAlive())
            startupThread = null;
        }
        if (startDeleteThread != null)
        {
          if (!startDeleteThread.isAlive())
            startDeleteThread = null;
        }
        if (jobStartThread != null)
        {
          if (!jobStartThread.isAlive())
            jobStartThread = null;
        }
        if (stufferThread != null)
        {
          if (!stufferThread.isAlive())
            stufferThread = null;
        }
        if (expireStufferThread != null)
        {
          if (!expireStufferThread.isAlive())
            expireStufferThread = null;
        }
        if (finisherThread != null)
        {
          if (!finisherThread.isAlive())
            finisherThread = null;
        }
        if (notificationThread != null)
        {
          if (!notificationThread.isAlive())
            notificationThread = null;
        }
        if (workerThreads != null)
        {
          int i = 0;
          boolean isAlive = false;
          while (i < workerThreads.length)
          {
            Thread workerThread = workerThreads[i];
            if (workerThread != null)
            {
              if (!workerThread.isAlive())
                workerThreads[i] = null;
              else
                isAlive = true;
            }
            i++;
          }
          if (!isAlive)
            workerThreads = null;
        }

        if (expireThreads != null)
        {
          int i = 0;
          boolean isAlive = false;
          while (i < expireThreads.length)
          {
            Thread expireThread = expireThreads[i];
            if (expireThread != null)
            {
              if (!expireThread.isAlive())
                expireThreads[i] = null;
              else
                isAlive = true;
            }
            i++;
          }
          if (!isAlive)
            expireThreads = null;
        }

        if (cleanupStufferThread != null)
        {
          if (!cleanupStufferThread.isAlive())
            cleanupStufferThread = null;
        }
        if (cleanupThreads != null)
        {
          int i = 0;
          boolean isAlive = false;
          while (i < cleanupThreads.length)
          {
            Thread cleanupThread = cleanupThreads[i];
            if (cleanupThread != null)
            {
              if (!cleanupThread.isAlive())
                cleanupThreads[i] = null;
              else
                isAlive = true;
            }
            i++;
          }
          if (!isAlive)
            cleanupThreads = null;
        }

        if (deleteStufferThread != null)
        {
          if (!deleteStufferThread.isAlive())
            deleteStufferThread = null;
        }
        if (deleteThreads != null)
        {
          int i = 0;
          boolean isAlive = false;
          while (i < deleteThreads.length)
          {
            Thread deleteThread = deleteThreads[i];
            if (deleteThread != null)
            {
              if (!deleteThread.isAlive())
                deleteThreads[i] = null;
              else
                isAlive = true;
            }
            i++;
          }
          if (!isAlive)
            deleteThreads = null;
        }
        if (jobResetThread != null)
        {
          if (!jobResetThread.isAlive())
            jobResetThread = null;
        }
        if (seedingThread != null)
        {
          if (!seedingThread.isAlive())
            seedingThread = null;
        }
        if (idleCleanupThread != null)
        {
          if (!idleCleanupThread.isAlive())
            idleCleanupThread = null;
        }
      }

      // Threads are down; release connectors
      RepositoryConnectorFactory.closeAllConnectors(threadContext);
      numWorkerThreads = 0;
      numDeleteThreads = 0;
      numExpireThreads = 0;
    }
    Logging.root.info("Pull-agent successfully shut down");
  }
 

  /** Atomically export the crawler configuration */
  public static void exportConfiguration(IThreadContext threadContext, String exportFilename, String passCode)
    throws ManifoldCFException
  {
    // The basic idea here is that we open a zip stream, into which we dump all the pertinent information in a transactionally-consistent manner.
    // First, we need a database handle...
    IDBInterface database = DBInterfaceFactory.make(threadContext,
      ManifoldCF.getMasterDatabaseName(),
      ManifoldCF.getMasterDatabaseUsername(),
      ManifoldCF.getMasterDatabasePassword());
    // Also create the following managers, which will handle the actual details of writing configuration data
    IOutputConnectionManager outputManager = OutputConnectionManagerFactory.make(threadContext);
    IRepositoryConnectionManager connManager = RepositoryConnectionManagerFactory.make(threadContext);
    IMappingConnectionManager mappingManager = MappingConnectionManagerFactory.make(threadContext);
    IAuthorityConnectionManager authManager = AuthorityConnectionManagerFactory.make(threadContext);
    IJobManager jobManager = JobManagerFactory.make(threadContext);

    File outputFile = new File(exportFilename);

    // Create a zip output stream, which is what we will use as a mechanism for handling the output data
    try
    {
     
      OutputStream os = new FileOutputStream(outputFile);
     
      try
      {
       
        java.util.zip.ZipOutputStream zos = null;
        CipherOutputStream cos = null;
       
        // Check whether we need to encrypt the file content:
        if (passCode != null && passCode.length() > 0)
        {
         
          // Write IV as a prefix:
          SecureRandom random = new SecureRandom();
          byte[] iv = new byte[IV_LENGTH];
          random.nextBytes(iv);
          os.write(iv);
          os.flush();
         
          Cipher cipher = null;
          try
          {
            cipher = getCipher(Cipher.ENCRYPT_MODE, passCode, iv);
          }
          catch (GeneralSecurityException gse)
          {
            throw new ManifoldCFException("Could not encrypt configuratiom file: " + gse.getMessage());
          }
         
          cos = new CipherOutputStream(os, cipher);
          zos = new java.util.zip.ZipOutputStream(cos);
        }
        else
          zos = new java.util.zip.ZipOutputStream(os);
        try
        {
          // Now, work within a transaction.
          database.beginTransaction();
          try
          {
            // At the outermost level, I've decided that the best structure is to have a zipentry for each
            // manager.  Each manager must manage its own data as a binary blob, including any format versioning information,
            // This guarantees flexibility for the future.

            // The zipentries must be written in an order that permits their proper restoration.  The "lowest level" is thus
            // written first, which yields the order: authority connections, repository connections, jobs
            java.util.zip.ZipEntry outputEntry = new java.util.zip.ZipEntry("outputs");
            zos.putNextEntry(outputEntry);
            outputManager.exportConfiguration(zos);
            zos.closeEntry();

            java.util.zip.ZipEntry mappingEntry = new java.util.zip.ZipEntry("mappings");
            zos.putNextEntry(mappingEntry);
            mappingManager.exportConfiguration(zos);
            zos.closeEntry();

            java.util.zip.ZipEntry authEntry = new java.util.zip.ZipEntry("authorities");
            zos.putNextEntry(authEntry);
            authManager.exportConfiguration(zos);
            zos.closeEntry();

            java.util.zip.ZipEntry connEntry = new java.util.zip.ZipEntry("connections");
            zos.putNextEntry(connEntry);
            connManager.exportConfiguration(zos);
            zos.closeEntry();

            java.util.zip.ZipEntry jobsEntry = new java.util.zip.ZipEntry("jobs");
            zos.putNextEntry(jobsEntry);
            jobManager.exportConfiguration(zos);
            zos.closeEntry();

            // All done
          }
          catch (ManifoldCFException e)
          {
            database.signalRollback();
            throw e;
          }
          catch (Error e)
          {
            database.signalRollback();
            throw e;
          }
          finally
          {
            database.endTransaction();
          }
        }
        finally
        {
          zos.close();
          if (cos != null) {
            cos.close();
          }
        }
      }
      finally
      {
        os.close();
      }
    }
    catch (java.io.IOException e)
    {
      // On error, delete any file we created
      outputFile.delete();
      // Convert I/O error into lcf exception
      throw new ManifoldCFException("Error creating configuration file: "+e.getMessage(),e);
    }
  }
 

  /** Atomically import a crawler configuration */
  public static void importConfiguration(IThreadContext threadContext, String importFilename, String passCode)
    throws ManifoldCFException
  {
    // First, we need a database handle...
    IDBInterface database = DBInterfaceFactory.make(threadContext,
      ManifoldCF.getMasterDatabaseName(),
      ManifoldCF.getMasterDatabaseUsername(),
      ManifoldCF.getMasterDatabasePassword());
    // Also create the following managers, which will handle the actual details of reading configuration data
    IOutputConnectionManager outputManager = OutputConnectionManagerFactory.make(threadContext);
    IRepositoryConnectionManager connManager = RepositoryConnectionManagerFactory.make(threadContext);
    IMappingConnectionManager mappingManager = MappingConnectionManagerFactory.make(threadContext);
    IAuthorityConnectionManager authManager = AuthorityConnectionManagerFactory.make(threadContext);
    IJobManager jobManager = JobManagerFactory.make(threadContext);

    File inputFile = new File(importFilename);

    // Create a zip input stream, which is what we will use as a mechanism for handling the input data
    try
    {
      InputStream is = new FileInputStream(inputFile);
      try
      {
        java.util.zip.ZipInputStream zis = null;
        CipherInputStream cis = null;
       
        // Check whether we need to decrypt the file content:
        if (passCode != null && passCode.length() > 0)
        {
         
          byte[] iv = new byte[IV_LENGTH];
          is.read(iv);

          Cipher cipher = null;
          try
          {
            cipher = getCipher(Cipher.DECRYPT_MODE, passCode, iv);
          }
          catch (GeneralSecurityException gse)
          {
            throw new ManifoldCFException("Could not decrypt configuratiom file: " + gse.getMessage());
          }
          cis = new CipherInputStream(is, cipher);
          zis = new java.util.zip.ZipInputStream(cis);
        }
        else
          zis = new java.util.zip.ZipInputStream(is);

        try
        {
          // Now, work within a transaction.
          database.beginTransaction();
          try
          {
            // Process the entries in the order in which they were recorded.
            int entries = 0;
            while (true)
            {
              java.util.zip.ZipEntry z = zis.getNextEntry();
              // Stop if there are no more entries
              if (z == null)
                break;
              entries++;
              // Get the name of the entry
              String name = z.getName();
              if (name.equals("outputs"))
                outputManager.importConfiguration(zis);
              else if (name.equals("mappings"))
                mappingManager.importConfiguration(zis);
              else if (name.equals("authorities"))
                authManager.importConfiguration(zis);
              else if (name.equals("connections"))
                connManager.importConfiguration(zis);
              else if (name.equals("jobs"))
                jobManager.importConfiguration(zis);
              else
                throw new ManifoldCFException("Configuration file has an entry named '"+name+"' that I do not recognize");
              zis.closeEntry();

            }
            if (entries == 0 && passCode != null && passCode.length() > 0)
              throw new ManifoldCFException("Cannot read configuration file. Please check your passcode and/or SALT value.");
            // All done!!
          }
          catch (ManifoldCFException e)
          {
            database.signalRollback();
            throw e;
          }
          catch (Error e)
          {
            database.signalRollback();
            throw e;
          }
          finally
          {
            database.endTransaction();
          }
        }
        finally
        {
          zis.close();
          if (cis != null) {
            cis.close();
          }
        }
      }
      finally
      {
        is.close();
      }
    }
    catch (java.io.IOException e)
    {
      // Convert I/O error into lcf exception
      throw new ManifoldCFException("Error reading configuration file: "+e.getMessage(),e);
    }
  }


  /** Get the maximum number of worker threads.
  */
  public static int getMaxWorkerThreads()
  {
    return numWorkerThreads;
  }

  /** Get the maximum number of delete threads.
  */
  public static int getMaxDeleteThreads()
  {
    return numDeleteThreads;
  }

  /** Get the maximum number of expire threads.
  */
  public static int getMaxExpireThreads()
  {
    return numExpireThreads;
  }

  /** Requeue documents due to carrydown.
  */
  public static void requeueDocumentsDueToCarrydown(IJobManager jobManager, DocumentDescription[] requeueCandidates,
    IRepositoryConnector connector, IRepositoryConnection connection, QueueTracker queueTracker, long currentTime)
    throws ManifoldCFException
  {
    // A list of document descriptions from finishDocuments() above represents those documents that may need to be requeued, for the
    // reason that carrydown information for those documents has changed.  In order to requeue, we need to calculate document priorities, however.
    double[] docPriorities = new double[requeueCandidates.length];
    String[][] binNames = new String[requeueCandidates.length][];
    int q = 0;
    while (q < requeueCandidates.length)
    {
      DocumentDescription dd = requeueCandidates[q];
      String[] bins = calculateBins(connector,dd.getDocumentIdentifier());
      binNames[q] = bins;
      docPriorities[q] = queueTracker.calculatePriority(bins,connection);
      if (Logging.scheduling.isDebugEnabled())
        Logging.scheduling.debug("Document '"+dd.getDocumentIdentifier()+" given priority "+new Double(docPriorities[q]).toString());
      q++;
    }

    // Now, requeue the documents with the new priorities
    boolean[] trackerNote = jobManager.carrydownChangeDocumentMultiple(requeueCandidates,currentTime,docPriorities);

    // Free the unused priorities.
    // Inform queuetracker about what we used and what we didn't
    q = 0;
    while (q < trackerNote.length)
    {
      if (trackerNote[q] == false)
      {
        String[] bins = binNames[q];
        queueTracker.notePriorityNotUsed(bins,connection,docPriorities[q]);
      }
      q++;
    }
  }

  /** Stuff colons so we can't have conflicts. */
  public static String colonStuff(String input)
  {
    StringBuilder sb = new StringBuilder();
    int i = 0;
    while (i < input.length())
    {
      char x  = input.charAt(i++);
      if (x == ':' || x == '\\')
        sb.append('\\');
      sb.append(x);
    }
    return sb.toString();
  }

  /** Create a global string */
  public static String createGlobalString(String simpleString)
  {
    return ":" + simpleString;
  }

  /** Create a connection-specific string */
  public static String createConnectionSpecificString(String connectionName, String simpleString)
  {
    return "C "+colonStuff(connectionName) + ":" + simpleString;
  }

  /** Create a job-specific string */
  public static String createJobSpecificString(Long jobID, String simpleString)
  {
    return "J "+jobID.toString() + ":" + simpleString;
  }

  /** Given a connector object and a document identifier, calculate its bins.
  */
  public static String[] calculateBins(IRepositoryConnector connector, String documentIdentifier)
  {
    // Get the bins for the document identifier
    return connector.getBinNames(documentIdentifier);
  }

  /** Reset all (active) document priorities.  This operation may occur due to various externally-triggered
  * events, such a job abort, pause, resume, wait, or unwait.
  */
  public static void resetAllDocumentPriorities(IThreadContext threadContext, QueueTracker queueTracker, long currentTime)
    throws ManifoldCFException
  {
    IJobManager jobManager = JobManagerFactory.make(threadContext);
    IRepositoryConnectionManager connectionManager = RepositoryConnectionManagerFactory.make(threadContext);
   
    // Reset the queue tracker
    queueTracker.beginReset();
    // Perform the reprioritization, for all active documents in active jobs.  During this time,
    // it is safe to have other threads assign new priorities to documents, but it is NOT safe
    // for other threads to attempt to change the minimum priority level.  The queuetracker object
    // will therefore block that from occurring, until the reset is complete.
    try
    {
      // Reprioritize all documents in the jobqueue, 1000 at a time

      HashMap connectionMap = new HashMap();
      HashMap jobDescriptionMap = new HashMap();

      // Do the 'not yet processed' documents only.  Documents that are queued for reprocessing will be assigned
      // new priorities.  Already processed documents won't.  This guarantees that our bins are appropriate for current thread
      // activity.
      // In order for this to be the correct functionality, ALL reseeding and requeuing operations MUST reset the associated document
      // priorities.
      while (true)
      {
        long startTime = System.currentTimeMillis();

        DocumentDescription[] docs = jobManager.getNextNotYetProcessedReprioritizationDocuments(currentTime, 10000);
        if (docs.length == 0)
          break;

        // Calculate new priorities for all these documents
        writeDocumentPriorities(threadContext,connectionManager,jobManager,docs,connectionMap,jobDescriptionMap,queueTracker,currentTime);

        Logging.threads.debug("Reprioritized "+Integer.toString(docs.length)+" not-yet-processed documents in "+new Long(System.currentTimeMillis()-startTime)+" ms");
      }
    }
    finally
    {
      queueTracker.endReset();
    }
  }
 
  /** Write a set of document priorities, based on the current queue tracker.
  */
  public static void writeDocumentPriorities(IThreadContext threadContext, IRepositoryConnectionManager mgr, IJobManager jobManager, DocumentDescription[] descs, HashMap connectionMap, HashMap jobDescriptionMap, QueueTracker queueTracker, long currentTime)
    throws ManifoldCFException
  {
    if (Logging.scheduling.isDebugEnabled())
      Logging.scheduling.debug("Reprioritizing "+Integer.toString(descs.length)+" documents");


    double[] priorities = new double[descs.length];

    // Go through the documents and calculate the priorities
    int i = 0;
    while (i < descs.length)
    {
      DocumentDescription dd = descs[i];
      IJobDescription job = (IJobDescription)jobDescriptionMap.get(dd.getJobID());
      if (job == null)
      {
        job = jobManager.load(dd.getJobID(),true);
        jobDescriptionMap.put(dd.getJobID(),job);
      }
      String connectionName = job.getConnectionName();
      IRepositoryConnection connection = (IRepositoryConnection)connectionMap.get(connectionName);
      if (connection == null)
      {
        connection = mgr.load(connectionName);
        connectionMap.put(connectionName,connection);
      }

      String[] binNames;
      // Grab a connector handle
      IRepositoryConnector connector = RepositoryConnectorFactory.grab(threadContext,
        connection.getClassName(),
        connection.getConfigParams(),
        connection.getMaxConnections());
      try
      {
        if (connector == null)
          binNames = new String[]{""};
        else
          // Get the bins for the document identifier
          binNames = connector.getBinNames(descs[i].getDocumentIdentifier());
      }
      finally
      {
        RepositoryConnectorFactory.release(connector);
      }

      priorities[i] = queueTracker.calculatePriority(binNames,connection);
      if (Logging.scheduling.isDebugEnabled())
        Logging.scheduling.debug("Document '"+dd.getDocumentIdentifier()+"' given priority "+new Double(priorities[i]).toString());

      i++;
    }

    // Now, write all the priorities we can.
    jobManager.writeDocumentPriorities(currentTime,descs,priorities);


  }

  /** Request permission from agent to delete an output connection.
  *@param threadContext is the thread context.
  *@param connName is the name of the output connection.
  *@return true if the connection is in use, false otherwise.
  */
  public static boolean isOutputConnectionInUse(IThreadContext threadContext, String connName)
    throws ManifoldCFException
  {
    // Check with job manager.
    IJobManager jobManager = JobManagerFactory.make(threadContext);
    return jobManager.checkIfOutputReference(connName);
  }

  /** Note the deregistration of a set of output connections.
  *@param threadContext is the thread context.
  *@param connectionNames are the names of the connections being deregistered.
  */
  public static void noteOutputConnectorDeregistration(IThreadContext threadContext, String[] connectionNames)
    throws ManifoldCFException
  {
    // Notify job manager
    IJobManager jobManager = JobManagerFactory.make(threadContext);
    jobManager.noteOutputConnectorDeregistration(connectionNames);
  }

  /** Note the registration of a set of output connections.
  *@param threadContext is the thread context.
  *@param connectionNames are the names of the connections being registered.
  */
  public static void noteOutputConnectorRegistration(IThreadContext threadContext, String[] connectionNames)
    throws ManifoldCFException
  {
    // Notify job manager
    IJobManager jobManager = JobManagerFactory.make(threadContext);
    jobManager.noteOutputConnectorRegistration(connectionNames);
  }

  /** Note the change in configuration of an output connection.
  *@param threadContext is the thread context.
  *@param connectionName is the output connection name.
  */
  public static void noteOutputConnectionChange(IThreadContext threadContext, String connectionName)
    throws ManifoldCFException
  {
    // Notify job manager
    IJobManager jobManager = JobManagerFactory.make(threadContext);
    jobManager.noteOutputConnectionChange(connectionName);
  }
 
  /** Qualify output activity name.
  *@param outputActivityName is the name of the output activity.
  *@param outputConnectionName is the corresponding name of the output connection.
  *@return the qualified (global) activity name.
  */
  public static String qualifyOutputActivityName(String outputActivityName, String outputConnectionName)
  {
    return outputActivityName+" ("+outputConnectionName+")";
  }

  /** Get the activities list for a given repository connection.
  */
  public static String[] getActivitiesList(IThreadContext threadContext, String connectionName)
    throws ManifoldCFException
  {
    IRepositoryConnectionManager connectionManager = RepositoryConnectionManagerFactory.make(threadContext);
    IRepositoryConnection thisConnection = connectionManager.load(connectionName);
    if (thisConnection == null)
      return null;
    String[] outputActivityList = OutputConnectionManagerFactory.getAllOutputActivities(threadContext);
    String[] connectorActivityList = RepositoryConnectorFactory.getActivitiesList(threadContext,thisConnection.getClassName());
    String[] globalActivityList = IRepositoryConnectionManager.activitySet;
    String[] activityList = new String[outputActivityList.length + ((connectorActivityList==null)?0:connectorActivityList.length) + globalActivityList.length];
    int k2 = 0;
    int j;
    if (outputActivityList != null)
    {
      j = 0;
      while (j < outputActivityList.length)
      {
        activityList[k2++] = outputActivityList[j++];
      }
    }
    if (connectorActivityList != null)
    {
      j = 0;
      while (j < connectorActivityList.length)
      {
        activityList[k2++] = connectorActivityList[j++];
      }
    }
    j = 0;
    while (j < globalActivityList.length)
    {
      activityList[k2++] = globalActivityList[j++];
    }
    java.util.Arrays.sort(activityList);
    return activityList;
  }
 
  private static final int IV_LENGTH = 16;
 
  private static Cipher getCipher(final int mode, final String passCode, final byte[] iv) throws GeneralSecurityException,
    ManifoldCFException
  {
    final String saltValue = getProperty(salt);

    if (saltValue == null || saltValue.length() == 0)
      throw new ManifoldCFException("Missing required SALT value");
   
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    KeySpec keySpec = new PBEKeySpec(passCode.toCharArray(), saltValue.getBytes(), 1024, 128);
    SecretKey secretKey = factory.generateSecret(keySpec);

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    SecretKeySpec key = new SecretKeySpec(secretKey.getEncoded(), "AES");
    IvParameterSpec parameterSpec = new IvParameterSpec(iv);
    cipher.init(mode, key, parameterSpec);
    return cipher;
  }
 
 
  // ========================== API support ===========================
 
  protected static final String API_JOBNODE = "job";
  protected static final String API_JOBSTATUSNODE = "jobstatus";
  protected static final String API_REPOSITORYCONNECTORNODE = "repositoryconnector";
  protected static final String API_OUTPUTCONNECTORNODE = "outputconnector";
  protected static final String API_AUTHORITYCONNECTORNODE = "authorityconnector";
  protected static final String API_MAPPINGCONNECTORNODE = "mappingconnector";
  protected static final String API_REPOSITORYCONNECTIONNODE = "repositoryconnection";
  protected static final String API_OUTPUTCONNECTIONNODE = "outputconnection";
  protected static final String API_AUTHORITYCONNECTIONNODE = "authorityconnection";
  protected static final String API_MAPPINGCONNECTIONNODE = "mappingconnection";
  protected static final String API_CHECKRESULTNODE = "check_result";
  protected static final String API_JOBIDNODE = "job_id";
  protected static final String API_CONNECTIONNAMENODE = "connection_name";
  protected final static String API_ROWNODE = "row";
  protected final static String API_COLUMNNODE = "column";
  protected final static String API_NAMENODE = "name";
  protected final static String API_VALUENODE = "value";
  protected final static String API_ACTIVITYNODE = "activity";
 
  // Connector nodes
  protected static final String CONNECTORNODE_DESCRIPTION = "description";
  protected static final String CONNECTORNODE_CLASSNAME = "class_name";
 
  /** Decode path element.
  * Path elements in the API world cannot have "/" characters, or they become impossible to parse.  This method undoes
  * escaping that prevents "/" from appearing.
  */
  public static String decodeAPIPathElement(String startingPathElement)
    throws ManifoldCFException
  {
    StringBuilder sb = new StringBuilder();
    int i = 0;
    while (i < startingPathElement.length())
    {
      char x = startingPathElement.charAt(i++);
      if (x == '.')
      {
        if (i == startingPathElement.length())
          throw new ManifoldCFException("Element decoding failed; illegal '.' character in '"+startingPathElement+"'");
       
        x = startingPathElement.charAt(i++);
        if (x == '.')
          sb.append(x);
        else if (x == '+')
          sb.append('/');
        else
          throw new ManifoldCFException("Element decoding failed; illegal post-'.' character in '"+startingPathElement+"'");
      }
      else
        sb.append(x);
    }
    return sb.toString();
  }

  // Read (GET) functions
 
  // Read result codes
  public static final int READRESULT_NOTFOUND = 0;
  public static final int READRESULT_FOUND = 1;

  /** Read jobs */
  protected static int apiReadJobs(IThreadContext tc, Configuration output)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      IJobDescription[] jobs = jobManager.getAllJobs();
      int i = 0;
      while (i < jobs.length)
      {
        ConfigurationNode jobNode = new ConfigurationNode(API_JOBNODE);
        formatJobDescription(jobNode,jobs[i++]);
        output.addChild(output.getChildCount(),jobNode);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** Read a job */
  protected static int apiReadJob(IThreadContext tc, Configuration output, Long jobID)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      IJobDescription job = jobManager.load(jobID);
      if (job != null)
      {
        // Fill the return object with job information
        ConfigurationNode jobNode = new ConfigurationNode(API_JOBNODE);
        formatJobDescription(jobNode,job);
        output.addChild(output.getChildCount(),jobNode);
      }
      else
      {
        createErrorNode(output,"Job does not exist.");
        return READRESULT_NOTFOUND;
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** Read an output connection status */
  protected static int apiReadOutputConnectionStatus(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IOutputConnectionManager connectionManager = OutputConnectionManagerFactory.make(tc);
      IOutputConnection connection = connectionManager.load(connectionName);
      if (connection == null)
      {
        createErrorNode(output,"Connection '"+connectionName+"' does not exist");
        return READRESULT_NOTFOUND;
      }
         
      String results;
      // Grab a connection handle, and call the test method
      IOutputConnector connector = OutputConnectorFactory.grab(tc,connection.getClassName(),connection.getConfigParams(),connection.getMaxConnections());
      try
      {
        results = connector.check();
      }
      catch (ManifoldCFException e)
      {
        results = e.getMessage();
      }
      finally
      {
        OutputConnectorFactory.release(connector);
      }
         
      ConfigurationNode response = new ConfigurationNode(API_CHECKRESULTNODE);
      response.setValue(results);
      output.addChild(output.getChildCount(),response);
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** Read an authority connection status */
  protected static int apiReadAuthorityConnectionStatus(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IAuthorityConnectionManager connectionManager = AuthorityConnectionManagerFactory.make(tc);
      IAuthorityConnection connection = connectionManager.load(connectionName);
      if (connection == null)
      {
        createErrorNode(output,"Connection '"+connectionName+"' does not exist");
        return READRESULT_NOTFOUND;
      }
         
      String results;
      // Grab a connection handle, and call the test method
      IAuthorityConnector connector = AuthorityConnectorFactory.grab(tc,connection.getClassName(),connection.getConfigParams(),connection.getMaxConnections());
      try
      {
        results = connector.check();
      }
      catch (ManifoldCFException e)
      {
        results = e.getMessage();
      }
      finally
      {
        AuthorityConnectorFactory.release(connector);
      }
         
      ConfigurationNode response = new ConfigurationNode(API_CHECKRESULTNODE);
      response.setValue(results);
      output.addChild(output.getChildCount(),response);
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** Read a mapping connection status */
  protected static int apiReadMappingConnectionStatus(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IMappingConnectionManager connectionManager = MappingConnectionManagerFactory.make(tc);
      IMappingConnection connection = connectionManager.load(connectionName);
      if (connection == null)
      {
        createErrorNode(output,"Connection '"+connectionName+"' does not exist");
        return READRESULT_NOTFOUND;
      }
         
      String results;
      // Grab a connection handle, and call the test method
      IMappingConnector connector = MappingConnectorFactory.grab(tc,connection.getClassName(),connection.getConfigParams(),connection.getMaxConnections());
      try
      {
        results = connector.check();
      }
      catch (ManifoldCFException e)
      {
        results = e.getMessage();
      }
      finally
      {
        MappingConnectorFactory.release(connector);
      }
         
      ConfigurationNode response = new ConfigurationNode(API_CHECKRESULTNODE);
      response.setValue(results);
      output.addChild(output.getChildCount(),response);
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** Read a repository connection status */
  protected static int apiReadRepositoryConnectionStatus(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IRepositoryConnectionManager connectionManager = RepositoryConnectionManagerFactory.make(tc);
      IRepositoryConnection connection = connectionManager.load(connectionName);
      if (connection == null)
      {
        createErrorNode(output,"Connection '"+connectionName+"' does not exist");
        return READRESULT_NOTFOUND;
      }
         
      String results;
      // Grab a connection handle, and call the test method
      IRepositoryConnector connector = RepositoryConnectorFactory.grab(tc,connection.getClassName(),connection.getConfigParams(),connection.getMaxConnections());
      try
      {
        results = connector.check();
      }
      catch (ManifoldCFException e)
      {
        results = e.getMessage();
      }
      finally
      {
        RepositoryConnectorFactory.release(connector);
      }
         
      ConfigurationNode response = new ConfigurationNode(API_CHECKRESULTNODE);
      response.setValue(results);
      output.addChild(output.getChildCount(),response);
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** Read an output connection's info */
  protected static int apiReadOutputConnectionInfo(IThreadContext tc, Configuration output, String connectionName, String command)
    throws ManifoldCFException
  {
    try
    {
      IOutputConnectionManager connectionManager = OutputConnectionManagerFactory.make(tc);
      IOutputConnection connection = connectionManager.load(connectionName);
      if (connection == null)
      {
        createErrorNode(output,"Connection '"+connectionName+"' does not exist");
        return READRESULT_NOTFOUND;
      }

      // Grab a connection handle, and call the test method
      IOutputConnector connector = OutputConnectorFactory.grab(tc,connection.getClassName(),connection.getConfigParams(),connection.getMaxConnections());
      try
      {
        return connector.requestInfo(output,command)?READRESULT_FOUND:READRESULT_NOTFOUND;
      }
      finally
      {
        OutputConnectorFactory.release(connector);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** Read a repository connection's info */
  protected static int apiReadRepositoryConnectionInfo(IThreadContext tc, Configuration output, String connectionName, String command)
    throws ManifoldCFException
  {
    try
    {
      IRepositoryConnectionManager connectionManager = RepositoryConnectionManagerFactory.make(tc);
      IRepositoryConnection connection = connectionManager.load(connectionName);
      if (connection == null)
      {
        createErrorNode(output,"Connection '"+connectionName+"' does not exist");
        return READRESULT_NOTFOUND;
      }

      // Grab a connection handle, and call the test method
      IRepositoryConnector connector = RepositoryConnectorFactory.grab(tc,connection.getClassName(),connection.getConfigParams(),connection.getMaxConnections());
      try
      {
        return connector.requestInfo(output,command)?READRESULT_FOUND:READRESULT_NOTFOUND;
      }
      finally
      {
        RepositoryConnectorFactory.release(connector);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** Get api job statuses */
  protected static int apiReadJobStatuses(IThreadContext tc, Configuration output)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      JobStatus[] jobStatuses = jobManager.getAllStatus();
      int i = 0;
      while (i < jobStatuses.length)
      {
        ConfigurationNode jobStatusNode = new ConfigurationNode(API_JOBSTATUSNODE);
        formatJobStatus(jobStatusNode,jobStatuses[i++]);
        output.addChild(output.getChildCount(),jobStatusNode);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** Get api job status */
  protected static int apiReadJobStatus(IThreadContext tc, Configuration output, Long jobID)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      JobStatus status = jobManager.getStatus(jobID);
      if (status != null)
      {
        ConfigurationNode jobStatusNode = new ConfigurationNode(API_JOBSTATUSNODE);
        formatJobStatus(jobStatusNode,status);
        output.addChild(output.getChildCount(),jobStatusNode);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** Get api job status with no counts */
  protected static int apiReadJobStatusNoCounts(IThreadContext tc, Configuration output, Long jobID)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      JobStatus status = jobManager.getStatus(jobID,false);
      if (status != null)
      {
        ConfigurationNode jobStatusNode = new ConfigurationNode(API_JOBSTATUSNODE);
        formatJobStatus(jobStatusNode,status);
        output.addChild(output.getChildCount(),jobStatusNode);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** Get output connections */
  protected static int apiReadOutputConnections(IThreadContext tc, Configuration output)
    throws ManifoldCFException
  {
    try
    {
      IOutputConnectionManager connManager = OutputConnectionManagerFactory.make(tc);
      IOutputConnection[] connections = connManager.getAllConnections();
      int i = 0;
      while (i < connections.length)
      {
        ConfigurationNode connectionNode = new ConfigurationNode(API_OUTPUTCONNECTIONNODE);
        formatOutputConnection(connectionNode,connections[i++]);
        output.addChild(output.getChildCount(),connectionNode);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** Read output connection */
  protected static int apiReadOutputConnection(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IOutputConnectionManager connectionManager = OutputConnectionManagerFactory.make(tc);
      IOutputConnection connection = connectionManager.load(connectionName);
      if (connection != null)
      {
        // Fill the return object with job information
        ConfigurationNode connectionNode = new ConfigurationNode(API_OUTPUTCONNECTIONNODE);
        formatOutputConnection(connectionNode,connection);
        output.addChild(output.getChildCount(),connectionNode);
      }
      else
      {
        createErrorNode(output,"Connection '"+connectionName+"' does not exist.");
        return READRESULT_NOTFOUND;
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** Get authority connections */
  protected static int apiReadAuthorityConnections(IThreadContext tc, Configuration output)
    throws ManifoldCFException
  {
    try
    {
      IAuthorityConnectionManager connManager = AuthorityConnectionManagerFactory.make(tc);
      IAuthorityConnection[] connections = connManager.getAllConnections();
      int i = 0;
      while (i < connections.length)
      {
        ConfigurationNode connectionNode = new ConfigurationNode(API_AUTHORITYCONNECTIONNODE);
        formatAuthorityConnection(connectionNode,connections[i++]);
        output.addChild(output.getChildCount(),connectionNode);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** Get mapping connections */
  protected static int apiReadMappingConnections(IThreadContext tc, Configuration output)
    throws ManifoldCFException
  {
    try
    {
      IMappingConnectionManager connManager = MappingConnectionManagerFactory.make(tc);
      IMappingConnection[] connections = connManager.getAllConnections();
      int i = 0;
      while (i < connections.length)
      {
        ConfigurationNode connectionNode = new ConfigurationNode(API_MAPPINGCONNECTIONNODE);
        formatMappingConnection(connectionNode,connections[i++]);
        output.addChild(output.getChildCount(),connectionNode);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** Read authority connection */
  protected static int apiReadAuthorityConnection(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IAuthorityConnectionManager connectionManager = AuthorityConnectionManagerFactory.make(tc);
      IAuthorityConnection connection = connectionManager.load(connectionName);
      if (connection != null)
      {
        // Fill the return object with job information
        ConfigurationNode connectionNode = new ConfigurationNode(API_AUTHORITYCONNECTIONNODE);
        formatAuthorityConnection(connectionNode,connection);
        output.addChild(output.getChildCount(),connectionNode);
      }
      else
      {
        createErrorNode(output,"Authority connection '"+connectionName+"' does not exist.");
        return READRESULT_NOTFOUND;
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** Read mapping connection */
  protected static int apiReadMappingConnection(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IMappingConnectionManager connectionManager = MappingConnectionManagerFactory.make(tc);
      IMappingConnection connection = connectionManager.load(connectionName);
      if (connection != null)
      {
        // Fill the return object with job information
        ConfigurationNode connectionNode = new ConfigurationNode(API_MAPPINGCONNECTIONNODE);
        formatMappingConnection(connectionNode,connection);
        output.addChild(output.getChildCount(),connectionNode);
      }
      else
      {
        createErrorNode(output,"Mapping connection '"+connectionName+"' does not exist.");
        return READRESULT_NOTFOUND;
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** Get repository connections */
  protected static int apiReadRepositoryConnections(IThreadContext tc, Configuration output)
    throws ManifoldCFException
  {
    try
    {
      IRepositoryConnectionManager connManager = RepositoryConnectionManagerFactory.make(tc);
      IRepositoryConnection[] connections = connManager.getAllConnections();
      int i = 0;
      while (i < connections.length)
      {
        ConfigurationNode connectionNode = new ConfigurationNode(API_REPOSITORYCONNECTIONNODE);
        formatRepositoryConnection(connectionNode,connections[i++]);
        output.addChild(output.getChildCount(),connectionNode);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** Read repository connection */
  protected static int apiReadRepositoryConnection(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IRepositoryConnectionManager connectionManager = RepositoryConnectionManagerFactory.make(tc);
      IRepositoryConnection connection = connectionManager.load(connectionName);
      if (connection != null)
      {
        // Fill the return object with job information
        ConfigurationNode connectionNode = new ConfigurationNode(API_REPOSITORYCONNECTIONNODE);
        formatRepositoryConnection(connectionNode,connection);
        output.addChild(output.getChildCount(),connectionNode);
      }
      else
      {
        createErrorNode(output,"Repository connection '"+connectionName+"' does not exist");
        return READRESULT_NOTFOUND;
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** List output connectors */
  protected static int apiReadOutputConnectors(IThreadContext tc, Configuration output)
    throws ManifoldCFException
  {
    // List registered output connectors
    try
    {
      IOutputConnectorManager manager = OutputConnectorManagerFactory.make(tc);
      IResultSet resultSet = manager.getConnectors();
      int j = 0;
      while (j < resultSet.getRowCount())
      {
        IResultRow row = resultSet.getRow(j++);
        ConfigurationNode child = new ConfigurationNode(API_OUTPUTCONNECTORNODE);
        String description = (String)row.getValue("description");
        String className = (String)row.getValue("classname");
        ConfigurationNode node;
        if (description != null)
        {
          node = new ConfigurationNode(CONNECTORNODE_DESCRIPTION);
          node.setValue(description);
          child.addChild(child.getChildCount(),node);
        }
        node = new ConfigurationNode(CONNECTORNODE_CLASSNAME);
        node.setValue(className);
        child.addChild(child.getChildCount(),node);

        output.addChild(output.getChildCount(),child);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** List authority connectors */
  protected static int apiReadAuthorityConnectors(IThreadContext tc, Configuration output)
    throws ManifoldCFException
  {
    // List registered authority connectors
    try
    {
      IAuthorityConnectorManager manager = AuthorityConnectorManagerFactory.make(tc);
      IResultSet resultSet = manager.getConnectors();
      int j = 0;
      while (j < resultSet.getRowCount())
      {
        IResultRow row = resultSet.getRow(j++);
        ConfigurationNode child = new ConfigurationNode(API_AUTHORITYCONNECTORNODE);
        String description = (String)row.getValue("description");
        String className = (String)row.getValue("classname");
        ConfigurationNode node;
        if (description != null)
        {
          node = new ConfigurationNode(CONNECTORNODE_DESCRIPTION);
          node.setValue(description);
          child.addChild(child.getChildCount(),node);
        }
        node = new ConfigurationNode(CONNECTORNODE_CLASSNAME);
        node.setValue(className);
        child.addChild(child.getChildCount(),node);

        output.addChild(output.getChildCount(),child);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** List mapping connectors */
  protected static int apiReadMappingConnectors(IThreadContext tc, Configuration output)
    throws ManifoldCFException
  {
    // List registered authority connectors
    try
    {
      IMappingConnectorManager manager = MappingConnectorManagerFactory.make(tc);
      IResultSet resultSet = manager.getConnectors();
      int j = 0;
      while (j < resultSet.getRowCount())
      {
        IResultRow row = resultSet.getRow(j++);
        ConfigurationNode child = new ConfigurationNode(API_MAPPINGCONNECTORNODE);
        String description = (String)row.getValue("description");
        String className = (String)row.getValue("classname");
        ConfigurationNode node;
        if (description != null)
        {
          node = new ConfigurationNode(CONNECTORNODE_DESCRIPTION);
          node.setValue(description);
          child.addChild(child.getChildCount(),node);
        }
        node = new ConfigurationNode(CONNECTORNODE_CLASSNAME);
        node.setValue(className);
        child.addChild(child.getChildCount(),node);

        output.addChild(output.getChildCount(),child);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

  /** List repository connectors */
  protected static int apiReadRepositoryConnectors(IThreadContext tc, Configuration output)
    throws ManifoldCFException
  {
    // List registered repository connectors
    try
    {
      IConnectorManager manager = ConnectorManagerFactory.make(tc);
      IResultSet resultSet = manager.getConnectors();
      int j = 0;
      while (j < resultSet.getRowCount())
      {
        IResultRow row = resultSet.getRow(j++);
        ConfigurationNode child = new ConfigurationNode(API_REPOSITORYCONNECTORNODE);
        String description = (String)row.getValue("description");
        String className = (String)row.getValue("classname");
        ConfigurationNode node;
        if (description != null)
        {
          node = new ConfigurationNode(CONNECTORNODE_DESCRIPTION);
          node.setValue(description);
          child.addChild(child.getChildCount(),node);
        }
        node = new ConfigurationNode(CONNECTORNODE_CLASSNAME);
        node.setValue(className);
        child.addChild(child.getChildCount(),node);

        output.addChild(output.getChildCount(),child);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }

     
  protected final static Map<String,Integer> docState;
  static
  {
    docState = new HashMap<String,Integer>();
    docState.put("neverprocessed",new Integer(IJobManager.DOCSTATE_NEVERPROCESSED));
    docState.put("previouslyprocessed",new Integer(IJobManager.DOCSTATE_PREVIOUSLYPROCESSED));
    docState.put("outofscope",new Integer(IJobManager.DOCSTATE_OUTOFSCOPE));
  }

  protected final static Map<String,Integer> docStatus;
  static
  {
    docStatus = new HashMap<String,Integer>();
    docStatus.put("inactive",new Integer(IJobManager.DOCSTATUS_INACTIVE));
    docStatus.put("processing",new Integer(IJobManager.DOCSTATUS_PROCESSING));
    docStatus.put("expiring",new Integer(IJobManager.DOCSTATUS_EXPIRING));
    docStatus.put("deleting",new Integer(IJobManager.DOCSTATUS_DELETING));
    docStatus.put("readyforprocessing",new Integer(IJobManager.DOCSTATUS_READYFORPROCESSING));
    docStatus.put("readyforexpiration",new Integer(IJobManager.DOCSTATUS_READYFOREXPIRATION));
    docStatus.put("waitingforprocessing",new Integer(IJobManager.DOCSTATUS_WAITINGFORPROCESSING));
    docStatus.put("waitingforexpiration",new Integer(IJobManager.DOCSTATUS_WAITINGFOREXPIRATION));
    docStatus.put("waitingforever",new Integer(IJobManager.DOCSTATUS_WAITINGFOREVER));
    docStatus.put("hopcountexceeded",new Integer(IJobManager.DOCSTATUS_HOPCOUNTEXCEEDED));
  }

  /** Queue reports */
  protected static int apiReadRepositoryConnectionQueue(IThreadContext tc, Configuration output,
    String connectionName, Map<String,List<String>> queryParameters) throws ManifoldCFException
  {
    if (queryParameters == null)
      queryParameters = new HashMap<String,List<String>>();

    // Jobs (specified by id)
    Long[] jobs;
    List<String> jobList = queryParameters.get("job");
    if (jobList == null)
      jobs = new Long[0];
    else
    {
      jobs = new Long[jobList.size()];
      for (int i = 0; i < jobs.length; i++)
      {
        jobs[i] = new Long(jobList.get(i));
      }
    }

    // Now time
    long now;
    List<String> nowList = queryParameters.get("now");
    if (nowList == null || nowList.size() == 0)
      now = System.currentTimeMillis();
    else if (nowList.size() > 1)
      throw new ManifoldCFException("Multiple values for now parameter");
    else
      now = new Long(nowList.get(0)).longValue();
     
    // Identifier match
    RegExpCriteria idMatch;
    List<String> idMatchList = queryParameters.get("idmatch");
    List<String> idMatchInsensitiveList = queryParameters.get("idmatch_insensitive");
    if (idMatchList != null && idMatchInsensitiveList != null)
      throw new ManifoldCFException("Either use idmatch or idmatch_insensitive, not both.");
    boolean isInsensitiveIdMatch;
    if (idMatchInsensitiveList != null)
    {
      idMatchList = idMatchInsensitiveList;
      isInsensitiveIdMatch = true;
    }
    else
      isInsensitiveIdMatch = false;
     
    if (idMatchList == null || idMatchList.size() == 0)
      idMatch = null;
    else if (idMatchList.size() > 1)
      throw new ManifoldCFException("Multiple id match regexps specified.");
    else
      idMatch = new RegExpCriteria(idMatchList.get(0),isInsensitiveIdMatch);

    List<String> stateMatchList = queryParameters.get("statematch");
    int[] matchStates;
    if (stateMatchList == null)
      matchStates = new int[0];
    else
    {
      matchStates = new int[stateMatchList.size()];
      for (int i = 0; i < matchStates.length; i++)
      {
        Integer value = docState.get(stateMatchList.get(i));
        if (value == null)
          throw new ManifoldCFException("Unrecognized state value: '"+stateMatchList.get(i)+"'");
        matchStates[i] = value.intValue();
      }
    }
     
    List<String> statusMatchList = queryParameters.get("statusmatch");
    int[] matchStatuses;
    if (statusMatchList == null)
      matchStatuses = new int[0];
    else
    {
      matchStatuses = new int[statusMatchList.size()];
      for (int i = 0; i < matchStatuses.length; i++)
      {
        Integer value = docStatus.get(statusMatchList.get(i));
        if (value == null)
          throw new ManifoldCFException("Unrecognized status value: '"+statusMatchList.get(i)+"'");
        matchStatuses[i] = value.intValue();
      }
    }
     
    StatusFilterCriteria filterCriteria = new StatusFilterCriteria(jobs,now,idMatch,matchStates,matchStatuses);
     
    // Look for sort order parameters...
    SortOrder sortOrder = new SortOrder();
    List<String> sortColumnsList = queryParameters.get("sortcolumn");
    List<String> sortColumnsDirList = queryParameters.get("sortcolumn_direction");
    if (sortColumnsList != null || sortColumnsDirList != null)
    {
      if (sortColumnsList == null || sortColumnsDirList == null || sortColumnsList.size() != sortColumnsDirList.size())
        throw new ManifoldCFException("sortcolumn and sortcolumn_direction must have the same cardinality.");
      for (int i = 0; i < sortColumnsList.size(); i++)
      {
        String column = sortColumnsList.get(i);
        String dir = sortColumnsDirList.get(i);
        int dirInt;
        if (dir.equals("ascending"))
          dirInt = SortOrder.SORT_ASCENDING;
        else if (dir.equals("descending"))
          dirInt = SortOrder.SORT_DESCENDING;
        else
          throw new ManifoldCFException("sortcolumn_direction must be 'ascending' or 'descending'.");
        sortOrder.addCriteria(column,dirInt);
      }
    }
     
    // Start row and row count
    int startRow;
    List<String> startRowList = queryParameters.get("startrow");
    if (startRowList == null || startRowList.size() == 0)
      startRow = 0;
    else if (startRowList.size() > 1)
      throw new ManifoldCFException("Multiple start rows specified.");
    else
      startRow = new Integer(startRowList.get(0)).intValue();
     
    int rowCount;
    List<String> rowCountList = queryParameters.get("rowcount");
    if (rowCountList == null || rowCountList.size() == 0)
      rowCount = 20;
    else if (rowCountList.size() > 1)
      throw new ManifoldCFException("Multiple row counts specified.");
    else
      rowCount = new Integer(rowCountList.get(0)).intValue();

    List<String> reportTypeList = queryParameters.get("report");
    String reportType;
    if (reportTypeList == null || reportTypeList.size() == 0)
      reportType = "document";
    else if (reportTypeList.size() > 1)
      throw new ManifoldCFException("Multiple report types specified.");
    else
      reportType = reportTypeList.get(0);

    IJobManager jobManager = JobManagerFactory.make(tc);
     
    IResultSet result;
    String[] resultColumns;
     
    if (reportType.equals("document"))
    {
      try
      {
        result = jobManager.genDocumentStatus(connectionName,filterCriteria,sortOrder,startRow,rowCount);
      }
      catch (ManifoldCFException e)
      {
        createErrorNode(output,e);
        return READRESULT_FOUND;
      }
      resultColumns = new String[]{"identifier","job","state","status","scheduled","action","retrycount","retrylimit"};
    }
    else if (reportType.equals("status"))
    {
      BucketDescription idBucket;
      List<String> idBucketList = queryParameters.get("idbucket");
      List<String> idBucketInsensitiveList = queryParameters.get("idbucket_insensitive");
      if (idBucketList != null && idBucketInsensitiveList != null)
        throw new ManifoldCFException("Either use idbucket or idbucket_insensitive, not both.");
      boolean isInsensitiveIdBucket;
      if (idBucketInsensitiveList != null)
      {
        idBucketList = idBucketInsensitiveList;
        isInsensitiveIdBucket = true;
      }
      else
        isInsensitiveIdBucket = false;
      if (idBucketList == null || idBucketList.size() == 0)
        idBucket = new BucketDescription("()",false);
      else if (idBucketList.size() > 1)
        throw new ManifoldCFException("Multiple idbucket regexps specified.");
      else
        idBucket = new BucketDescription(idBucketList.get(0),isInsensitiveIdBucket);
       
      try
      {
        result = jobManager.genQueueStatus(connectionName,filterCriteria,sortOrder,idBucket,startRow,rowCount);
      }
      catch (ManifoldCFException e)
      {
        createErrorNode(output,e);
        return READRESULT_FOUND;
      }
      resultColumns = new String[]{"idbucket","inactive","processing","expiring","deleting",
        "processready","expireready","processwaiting","expirewaiting","waitingforever","hopcountexceeded"};
    }
    else
      throw new ManifoldCFException("Unknown report type '"+reportType+"'.");

    createResultsetNode(output,result,resultColumns);
    return READRESULT_FOUND;
  }
 
  /** Get jobs for connection */
  protected static int apiReadRepositoryConnectionJobs(IThreadContext tc, Configuration output,
    String connectionName) throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      IJobDescription[] jobs = jobManager.findJobsForConnection(connectionName);
      if (jobs == null)
      {
        createErrorNode(output,"Unknown connection '"+connectionName+"'");
        return READRESULT_NOTFOUND;
      }
      int i = 0;
      while (i < jobs.length)
      {
        ConfigurationNode jobNode = new ConfigurationNode(API_JOBNODE);
        formatJobDescription(jobNode,jobs[i++]);
        output.addChild(output.getChildCount(),jobNode);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** History reports */
  protected static int apiReadRepositoryConnectionHistory(IThreadContext tc, Configuration output,
    String connectionName, Map<String,List<String>> queryParameters) throws ManifoldCFException
  {
    if (queryParameters == null)
      queryParameters = new HashMap<String,List<String>>();
     
    // Look for filter criteria parameters...
     
    // Start time
    List<String> startTimeList = queryParameters.get("starttime");
    Long startTime;
    if (startTimeList == null || startTimeList.size() == 0)
      startTime = null;
    else if (startTimeList.size() > 1)
      throw new ManifoldCFException("Multiple start times specified.");
    else
      startTime = new Long(startTimeList.get(0));

    // End time
    List<String> endTimeList = queryParameters.get("endtime");
    Long endTime;
    if (endTimeList == null || endTimeList.size() == 0)
      endTime = null;
    else if (endTimeList.size() > 1)
      throw new ManifoldCFException("Multiple end times specified.");
    else
      endTime = new Long(endTimeList.get(0));
     
    // Activities
    List<String> activityList = queryParameters.get("activity");
    String[] activities;
    if (activityList == null)
      activities = new String[0];
    else
      activities = activityList.toArray(new String[0]);
     
    // Entity match
    RegExpCriteria entityMatch;
    List<String> entityMatchList = queryParameters.get("entitymatch");
    List<String> entityMatchInsensitiveList = queryParameters.get("entitymatch_insensitive");
    if (entityMatchList != null && entityMatchInsensitiveList != null)
      throw new ManifoldCFException("Either use entitymatch or entitymatch_insensitive, not both.");
    boolean isInsensitiveEntityMatch;
    if (entityMatchInsensitiveList != null)
    {
      entityMatchList = entityMatchInsensitiveList;
      isInsensitiveEntityMatch = true;
    }
    else
      isInsensitiveEntityMatch = false;
     
    if (entityMatchList == null || entityMatchList.size() == 0)
      entityMatch = null;
    else if (entityMatchList.size() > 1)
      throw new ManifoldCFException("Multiple entity match regexps specified.");
    else
      entityMatch = new RegExpCriteria(entityMatchList.get(0),isInsensitiveEntityMatch);
     
    // Result code match
    RegExpCriteria resultCodeMatch;
    List<String> resultCodeMatchList = queryParameters.get("resultcodematch");
    List<String> resultCodeMatchInsensitiveList = queryParameters.get("resultcodematch_insensitive");
    if (resultCodeMatchList != null && resultCodeMatchInsensitiveList != null)
      throw new ManifoldCFException("Either use resultcodematch or resultcodematch_insensitive, not both.");
    boolean isInsensitiveResultCodeMatch;
    if (entityMatchInsensitiveList != null)
    {
      resultCodeMatchList = resultCodeMatchInsensitiveList;
      isInsensitiveResultCodeMatch = true;
    }
    else
      isInsensitiveResultCodeMatch = false;
     
    if (resultCodeMatchList == null || resultCodeMatchList.size() == 0)
      resultCodeMatch = null;
    else if (resultCodeMatchList.size() > 1)
      throw new ManifoldCFException("Multiple resultcode match regexps specified.");
    else
      resultCodeMatch = new RegExpCriteria(resultCodeMatchList.get(0),isInsensitiveResultCodeMatch);
     
    // Filter criteria
    FilterCriteria filterCriteria = new FilterCriteria(activities,startTime,endTime,entityMatch,resultCodeMatch);
     
    // Look for sort order parameters...
    SortOrder sortOrder = new SortOrder();
    List<String> sortColumnsList = queryParameters.get("sortcolumn");
    List<String> sortColumnsDirList = queryParameters.get("sortcolumn_direction");
    if (sortColumnsList != null || sortColumnsDirList != null)
    {
      if (sortColumnsList == null || sortColumnsDirList == null || sortColumnsList.size() != sortColumnsDirList.size())
        throw new ManifoldCFException("sortcolumn and sortcolumn_direction must have the same cardinality.");
      for (int i = 0; i < sortColumnsList.size(); i++)
      {
        String column = sortColumnsList.get(i);
        String dir = sortColumnsDirList.get(i);
        int dirInt;
        if (dir.equals("ascending"))
          dirInt = SortOrder.SORT_ASCENDING;
        else if (dir.equals("descending"))
          dirInt = SortOrder.SORT_DESCENDING;
        else
          throw new ManifoldCFException("sortcolumn_direction must be 'ascending' or 'descending'.");
        sortOrder.addCriteria(column,dirInt);
      }
    }
     
    // Start row and row count
    int startRow;
    List<String> startRowList = queryParameters.get("startrow");
    if (startRowList == null || startRowList.size() == 0)
      startRow = 0;
    else if (startRowList.size() > 1)
      throw new ManifoldCFException("Multiple start rows specified.");
    else
      startRow = new Integer(startRowList.get(0)).intValue();
     
    int rowCount;
    List<String> rowCountList = queryParameters.get("rowcount");
    if (rowCountList == null || rowCountList.size() == 0)
      rowCount = 20;
    else if (rowCountList.size() > 1)
      throw new ManifoldCFException("Multiple row counts specified.");
    else
      rowCount = new Integer(rowCountList.get(0)).intValue();

    List<String> reportTypeList = queryParameters.get("report");
    String reportType;
    if (reportTypeList == null || reportTypeList.size() == 0)
      reportType = "simple";
    else if (reportTypeList.size() > 1)
      throw new ManifoldCFException("Multiple report types specified.");
    else
      reportType = reportTypeList.get(0);

    IRepositoryConnectionManager connectionManager = RepositoryConnectionManagerFactory.make(tc);
     
    IResultSet result;
    String[] resultColumns;
     
    if (reportType.equals("simple"))
    {
      try
      {
        result = connectionManager.genHistorySimple(connectionName,filterCriteria,sortOrder,startRow,rowCount);
      }
      catch (ManifoldCFException e)
      {
        createErrorNode(output,e);
        return READRESULT_FOUND;
      }
      resultColumns = new String[]{"starttime","resultcode","resultdesc","identifier","activity","bytes","elapsedtime"};
    }
    else if (reportType.equals("maxactivity"))
    {
      long maxInterval = connectionManager.getMaxRows();
      long actualRows = connectionManager.countHistoryRows(connectionName,filterCriteria);
      if (actualRows > maxInterval)
        throw new ManifoldCFException("Too many history rows specified for maxactivity report - actual is "+actualRows+", max is "+maxInterval+".");
       
      BucketDescription idBucket;
      List<String> idBucketList = queryParameters.get("idbucket");
      List<String> idBucketInsensitiveList = queryParameters.get("idbucket_insensitive");
      if (idBucketList != null && idBucketInsensitiveList != null)
        throw new ManifoldCFException("Either use idbucket or idbucket_insensitive, not both.");
      boolean isInsensitiveIdBucket;
      if (idBucketInsensitiveList != null)
      {
        idBucketList = idBucketInsensitiveList;
        isInsensitiveIdBucket = true;
      }
      else
        isInsensitiveIdBucket = false;
      if (idBucketList == null || idBucketList.size() == 0)
        idBucket = new BucketDescription("()",false);
      else if (idBucketList.size() > 1)
        throw new ManifoldCFException("Multiple idbucket regexps specified.");
      else
        idBucket = new BucketDescription(idBucketList.get(0),isInsensitiveIdBucket);

      long interval;
      List<String> intervalList = queryParameters.get("interval");
      if (intervalList == null || intervalList.size() == 0)
        interval = 300000L;
      else if (intervalList.size() > 1)
        throw new ManifoldCFException("Multiple intervals specified.");
      else
        interval = new Long(intervalList.get(0)).longValue();
       
      try
      {
        result = connectionManager.genHistoryActivityCount(connectionName,filterCriteria,sortOrder,idBucket,interval,startRow,rowCount);
      }
      catch (ManifoldCFException e)
      {
        createErrorNode(output,e);
        return READRESULT_FOUND;
      }
      resultColumns = new String[]{"starttime","endtime","activitycount","idbucket"};
    }
    else if (reportType.equals("maxbandwidth"))
    {
      long maxInterval = connectionManager.getMaxRows();
      long actualRows = connectionManager.countHistoryRows(connectionName,filterCriteria);
      if (actualRows > maxInterval)
        throw new ManifoldCFException("Too many history rows specified for maxbandwidth report - actual is "+actualRows+", max is "+maxInterval+".");
       
      BucketDescription idBucket;
      List<String> idBucketList = queryParameters.get("idbucket");
      List<String> idBucketInsensitiveList = queryParameters.get("idbucket_insensitive");
      if (idBucketList != null && idBucketInsensitiveList != null)
        throw new ManifoldCFException("Either use idbucket or idbucket_insensitive, not both.");
      boolean isInsensitiveIdBucket;
      if (idBucketInsensitiveList != null)
      {
        idBucketList = idBucketInsensitiveList;
        isInsensitiveIdBucket = true;
      }
      else
        isInsensitiveIdBucket = false;
      if (idBucketList == null || idBucketList.size() == 0)
        idBucket = new BucketDescription("()",false);
      else if (idBucketList.size() > 1)
        throw new ManifoldCFException("Multiple idbucket regexps specified.");
      else
        idBucket = new BucketDescription(idBucketList.get(0),isInsensitiveIdBucket);
       
      long interval;
      List<String> intervalList = queryParameters.get("interval");
      if (intervalList == null || intervalList.size() == 0)
        interval = 300000L;
      else if (intervalList.size() > 1)
        throw new ManifoldCFException("Multiple intervals specified.");
      else
        interval = new Long(intervalList.get(0)).longValue();

      try
      {
        result = connectionManager.genHistoryByteCount(connectionName,filterCriteria,sortOrder,idBucket,interval,startRow,rowCount);
      }
      catch (ManifoldCFException e)
      {
        createErrorNode(output,e);
        return READRESULT_FOUND;
      }
      resultColumns = new String[]{"starttime","endtime","bytecount","idbucket"};
    }
    else if (reportType.equals("result"))
    {
      BucketDescription idBucket;
      List<String> idBucketList = queryParameters.get("idbucket");
      List<String> idBucketInsensitiveList = queryParameters.get("idbucket_insensitive");
      if (idBucketList != null && idBucketInsensitiveList != null)
        throw new ManifoldCFException("Either use idbucket or idbucket_insensitive, not both.");
      boolean isInsensitiveIdBucket;
      if (idBucketInsensitiveList != null)
      {
        idBucketList = idBucketInsensitiveList;
        isInsensitiveIdBucket = true;
      }
      else
        isInsensitiveIdBucket = false;
      if (idBucketList == null || idBucketList.size() == 0)
        idBucket = new BucketDescription("()",false);
      else if (idBucketList.size() > 1)
        throw new ManifoldCFException("Multiple idbucket regexps specified.");
      else
        idBucket = new BucketDescription(idBucketList.get(0),isInsensitiveIdBucket);

      BucketDescription resultCodeBucket;
      List<String> resultCodeBucketList = queryParameters.get("resultcodebucket");
      List<String> resultCodeBucketInsensitiveList = queryParameters.get("resultcodebucket_insensitive");
      if (resultCodeBucketList != null && resultCodeBucketInsensitiveList != null)
        throw new ManifoldCFException("Either use resultcodebucket or resultcodebucket_insensitive, not both.");
      boolean isInsensitiveResultCodeBucket;
      if (resultCodeBucketInsensitiveList != null)
      {
        resultCodeBucketList = resultCodeBucketInsensitiveList;
        isInsensitiveResultCodeBucket = true;
      }
      else
        isInsensitiveResultCodeBucket = false;
      if (resultCodeBucketList == null || resultCodeBucketList.size() == 0)
        resultCodeBucket = new BucketDescription("(.*)",false);
      else if (resultCodeBucketList.size() > 1)
        throw new ManifoldCFException("Multiple resultcodebucket regexps specified.");
      else
        resultCodeBucket = new BucketDescription(resultCodeBucketList.get(0),isInsensitiveResultCodeBucket);

      try
      {
        result = connectionManager.genHistoryResultCodes(connectionName,filterCriteria,sortOrder,resultCodeBucket,idBucket,startRow,rowCount);
      }
      catch (ManifoldCFException e)
      {
        createErrorNode(output,e);
        return READRESULT_FOUND;
      }
      resultColumns = new String[]{"idbucket","resultcodebucket","eventcount"};
    }
    else
      throw new ManifoldCFException("Unknown report type '"+reportType+"'.");

    createResultsetNode(output,result,resultColumns);
    return READRESULT_FOUND;
  }
 
  /** Add a resultset node to the output. */
  protected static void createResultsetNode(Configuration output, IResultSet result, String[] resultColumns)
    throws ManifoldCFException
  {
    // Go through result set and add results to output
    for (int i = 0; i < result.getRowCount(); i++)
    {
      IResultRow row = result.getRow(i);
      ConfigurationNode rowValue = new ConfigurationNode(API_ROWNODE);
      for (String columnName : resultColumns)
      {
        ConfigurationNode columnValue = new ConfigurationNode(API_COLUMNNODE);
        Object value = row.getValue(columnName);
        String valueToUse;
        if (value == null)
          valueToUse = "";
        else
          valueToUse = value.toString();
        ConfigurationNode nameNode = new ConfigurationNode(API_NAMENODE);
        nameNode.setValue(columnName);
        columnValue.addChild(columnValue.getChildCount(),nameNode);
        ConfigurationNode valueNode = new ConfigurationNode(API_VALUENODE);
        valueNode.setValue(valueToUse);
        columnValue.addChild(columnValue.getChildCount(),valueNode);
        rowValue.addChild(rowValue.getChildCount(),columnValue);
      }
      output.addChild(output.getChildCount(),rowValue);
    }
  }
 
  /** Read the activity list for a given connection name. */
  protected static int apiReadRepositoryConnectionActivities(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      String[] activities = getActivitiesList(tc,connectionName);
      if (activities == null)
      {
        createErrorNode(output,"Connection '"+connectionName+"' does not exist.");
        return READRESULT_NOTFOUND;
      }
      for (String activity : activities)
      {
        ConfigurationNode node = new ConfigurationNode(API_ACTIVITYNODE);
        node.setValue(activity);
        output.addChild(output.getChildCount(),node);
      }
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return READRESULT_FOUND;
  }
 
  /** Execute specified read command.
  *@param tc is the thread context.
  *@param output is the output object, to be filled in.
  *@param path is the object path.
  *@return read status - either found, not found, or bad args
  */
  public static int executeReadCommand(IThreadContext tc, Configuration output, String path,
    Map<String,List<String>> queryParameters) throws ManifoldCFException
  {
    if (path.equals("jobs"))
    {
      return apiReadJobs(tc,output);
    }
    else if (path.startsWith("jobs/"))
    {
      Long jobID = new Long(path.substring("jobs/".length()));
      return apiReadJob(tc,output,jobID);
    }
    else if (path.startsWith("repositoryconnectionactivities/"))
    {
      int firstSeparator = "repositoryconnectionactivities/".length();
      String connectionName = decodeAPIPathElement(path.substring(firstSeparator));
      return apiReadRepositoryConnectionActivities(tc,output,connectionName);
    }
    else if (path.startsWith("repositoryconnectionhistory/"))
    {
      int firstSeparator = "repositoryconnectionhistory/".length();
      String connectionName = decodeAPIPathElement(path.substring(firstSeparator));
      return apiReadRepositoryConnectionHistory(tc,output,connectionName,queryParameters);
    }
    else if (path.startsWith("repositoryconnectionqueue/"))
    {
      int firstSeparator = "repositoryconnectionqueue/".length();
      String connectionName = decodeAPIPathElement(path.substring(firstSeparator));
      return apiReadRepositoryConnectionQueue(tc,output,connectionName,queryParameters);
    }
    else if (path.startsWith("repositoryconnectionjobs/"))
    {
      int firstSeparator = "repositoryconnectionjobs/".length();
      String connectionName = decodeAPIPathElement(path.substring(firstSeparator));
      return apiReadRepositoryConnectionJobs(tc,output,connectionName);
    }
    else if (path.startsWith("status/"))
    {
      int firstSeparator = "status/".length();
      int secondSeparator = path.indexOf("/",firstSeparator);
      if (secondSeparator == -1)
      {
        createErrorNode(output,"Need connection name.");
        return READRESULT_NOTFOUND;
      }
     
      String connectionType = path.substring(firstSeparator,secondSeparator);
      String connectionName = decodeAPIPathElement(path.substring(secondSeparator+1));
     
      if (connectionType.equals("outputconnections"))
      {
        return apiReadOutputConnectionStatus(tc,output,connectionName);
      }
      else if (connectionType.equals("mappingconnections"))
      {
        return apiReadMappingConnectionStatus(tc,output,connectionName);
      }
      else if (connectionType.equals("authorityconnections"))
      {
        return apiReadAuthorityConnectionStatus(tc,output,connectionName);
      }
      else if (connectionType.equals("repositoryconnections"))
      {
        return apiReadRepositoryConnectionStatus(tc,output,connectionName);
      }
      else
      {
        createErrorNode(output,"Unknown connection type '"+connectionType+"'.");
        return READRESULT_NOTFOUND;
      }
    }
    else if (path.startsWith("info/"))
    {
      int firstSeparator = "info/".length();
      int secondSeparator = path.indexOf("/",firstSeparator);
      if (secondSeparator == -1)
      {
        createErrorNode(output,"Need connection type and connection name.");
        return READRESULT_NOTFOUND;
      }

      int thirdSeparator = path.indexOf("/",secondSeparator+1);
      if (thirdSeparator == -1)
      {
        createErrorNode(output,"Need connection name.");
        return READRESULT_NOTFOUND;
      }

      String connectionType = path.substring(firstSeparator,secondSeparator);
      String connectionName = decodeAPIPathElement(path.substring(secondSeparator+1,thirdSeparator));
      String command = path.substring(thirdSeparator+1);
     
      if (connectionType.equals("outputconnections"))
      {
        return apiReadOutputConnectionInfo(tc,output,connectionName,command);
      }
      else if (connectionType.equals("repositoryconnections"))
      {
        return apiReadRepositoryConnectionInfo(tc,output,connectionName,command);
      }
      else
      {
        createErrorNode(output,"Unknown connection type '"+connectionType+"'.");
        return READRESULT_NOTFOUND;
      }
    }
    else if (path.equals("jobstatuses"))
    {
      return apiReadJobStatuses(tc,output);
    }
    else if (path.startsWith("jobstatuses/"))
    {
      Long jobID = new Long(path.substring("jobstatuses/".length()));
      return apiReadJobStatus(tc,output,jobID);
    }
    else if (path.startsWith("jobstatusesnocounts/"))
    {
      Long jobID = new Long(path.substring("jobstatusesnocounts/".length()));
      return apiReadJobStatusNoCounts(tc,output,jobID);
    }
    else if (path.equals("outputconnections"))
    {
      return apiReadOutputConnections(tc,output);
    }
    else if (path.startsWith("outputconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("outputconnections/".length()));
      return apiReadOutputConnection(tc,output,connectionName);
    }
    else if (path.equals("mappingconnections"))
    {
      return apiReadMappingConnections(tc,output);
    }
    else if (path.startsWith("mappingconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("mappingconnections/".length()));
      return apiReadMappingConnection(tc,output,connectionName);
    }
    else if (path.equals("authorityconnections"))
    {
      return apiReadAuthorityConnections(tc,output);
    }
    else if (path.startsWith("authorityconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("authorityconnections/".length()));
      return apiReadAuthorityConnection(tc,output,connectionName);
    }
    else if (path.equals("repositoryconnections"))
    {
      return apiReadRepositoryConnections(tc,output);
    }
    else if (path.startsWith("repositoryconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("repositoryconnections/".length()));
      return apiReadRepositoryConnection(tc,output,connectionName);
    }
    else if (path.equals("outputconnectors"))
    {
      return apiReadOutputConnectors(tc,output);
    }
    else if (path.equals("mappingconnectors"))
    {
      return apiReadMappingConnectors(tc,output);
    }
    else if (path.equals("authorityconnectors"))
    {
      return apiReadAuthorityConnectors(tc,output);
    }
    else if (path.equals("repositoryconnectors"))
    {
      return apiReadRepositoryConnectors(tc,output);
    }  
    else
    {
      createErrorNode(output,"Unrecognized resource.");
      return READRESULT_NOTFOUND;
    }
  }
 
  // Post result codes
  public static final int POSTRESULT_NOTFOUND = 0;
  public static final int POSTRESULT_FOUND = 1;
  public static final int POSTRESULT_CREATED = 2;
 
  /** Post job.
  */
  protected static int apiPostJob(IThreadContext tc, Configuration output, Configuration input)
    throws ManifoldCFException
  {
    ConfigurationNode jobNode = findConfigurationNode(input,API_JOBNODE);
    if (jobNode == null)
      throw new ManifoldCFException("Input must have '"+API_JOBNODE+"' field");

    // Turn the configuration node into a JobDescription
    org.apache.manifoldcf.crawler.jobs.JobDescription job = new org.apache.manifoldcf.crawler.jobs.JobDescription();
    processJobDescription(job,jobNode);
     
    if (job.getID() != null)
      throw new ManifoldCFException("Input job cannot supply an ID field for create");
     
    try
    {
      Long jobID = new Long(IDFactory.make(tc));
      job.setID(jobID);
      job.setIsNew(true);
       
      // Save the job.
      IJobManager jobManager = JobManagerFactory.make(tc);
      jobManager.save(job);

      ConfigurationNode idNode = new ConfigurationNode(API_JOBIDNODE);
      idNode.setValue(jobID.toString());
      output.addChild(output.getChildCount(),idNode);
       
      return POSTRESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return POSTRESULT_FOUND;
  }
 
  /** Execute specified post command.
  *@param tc is the thread context.
  *@param output is the output object, to be filled in.
  *@param path is the object path.
  *@param input is the input object.
  *@return write result - either "not found", "found", or "created".
  */
  public static int executePostCommand(IThreadContext tc, Configuration output, String path, Configuration input)
    throws ManifoldCFException
  {
    if (path.equals("jobs"))
    {
      return apiPostJob(tc,output,input);
    }
    else
    {
      createErrorNode(output,"Unrecognized resource.");
      return POSTRESULT_NOTFOUND;
    }
  }

  // Write result codes
  public static final int WRITERESULT_NOTFOUND = 0;
  public static final int WRITERESULT_FOUND = 1;
  public static final int WRITERESULT_CREATED = 2;
 
  /** Start a job.
  */
  protected static int apiWriteStartJob(IThreadContext tc, Configuration output, Long jobID, boolean requestMinimum)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      jobManager.manualStart(jobID,requestMinimum);
      return WRITERESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }
 
  /** Abort a job.
  */
  protected static int apiWriteAbortJob(IThreadContext tc, Configuration output, Long jobID)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      jobManager.manualAbort(jobID);
      return WRITERESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }
 
  /** Restart a job.
  */
  protected static int apiWriteRestartJob(IThreadContext tc, Configuration output, Long jobID, boolean requestMinimum)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      jobManager.manualAbortRestart(jobID,requestMinimum);
      return WRITERESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }

  /** Pause a job.
  */
  protected static int apiWritePauseJob(IThreadContext tc, Configuration output, Long jobID)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      jobManager.pauseJob(jobID);
      return WRITERESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }

  /** Resume a job.
  */
  protected static int apiWriteResumeJob(IThreadContext tc, Configuration output, Long jobID)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      jobManager.restartJob(jobID);
      return WRITERESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }
 
  /** Write job.
  */
  protected static int apiWriteJob(IThreadContext tc, Configuration output, Configuration input, Long jobID)
    throws ManifoldCFException
  {
    ConfigurationNode jobNode = findConfigurationNode(input,API_JOBNODE);
    if (jobNode == null)
      throw new ManifoldCFException("Input must have '"+API_JOBNODE+"' field");

    // Turn the configuration node into a JobDescription
    org.apache.manifoldcf.crawler.jobs.JobDescription job = new org.apache.manifoldcf.crawler.jobs.JobDescription();
    processJobDescription(job,jobNode);
     
    try
    {
      if (job.getID() == null)
      {
        job.setID(jobID);
      }
      else
      {
        if (!job.getID().equals(jobID))
          throw new ManifoldCFException("Job identifier must agree within object and within path");
      }
       
      job.setIsNew(false);
       
      // Save the job.
      IJobManager jobManager = JobManagerFactory.make(tc);
      jobManager.save(job);
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }

  /** Write output connection.
  */
  protected static int apiWriteOutputConnection(IThreadContext tc, Configuration output, Configuration input, String connectionName)
    throws ManifoldCFException
  {
    ConfigurationNode connectionNode = findConfigurationNode(input,API_OUTPUTCONNECTIONNODE);
    if (connectionNode == null)
      throw new ManifoldCFException("Input argument must have '"+API_OUTPUTCONNECTIONNODE+"' field");
     
    // Turn the configuration node into an OutputConnection
    org.apache.manifoldcf.agents.outputconnection.OutputConnection outputConnection = new org.apache.manifoldcf.agents.outputconnection.OutputConnection();
    processOutputConnection(outputConnection,connectionNode);
     
    if (outputConnection.getName() == null)
      outputConnection.setName(connectionName);
    else
    {
      if (!outputConnection.getName().equals(connectionName))
        throw new ManifoldCFException("Connection name in path and in object must agree");
    }
     
    try
    {
      // Save the connection.
      IOutputConnectionManager connectionManager = OutputConnectionManagerFactory.make(tc);
      if (connectionManager.save(outputConnection))
        return WRITERESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }
 
  /** Write authority connection.
  */
  protected static int apiWriteAuthorityConnection(IThreadContext tc, Configuration output, Configuration input, String connectionName)
    throws ManifoldCFException
  {
    ConfigurationNode connectionNode = findConfigurationNode(input,API_AUTHORITYCONNECTIONNODE);
    if (connectionNode == null)
      throw new ManifoldCFException("Input argument must have '"+API_AUTHORITYCONNECTIONNODE+"' field");
     
    // Turn the configuration node into an OutputConnection
    org.apache.manifoldcf.authorities.authority.AuthorityConnection authorityConnection = new org.apache.manifoldcf.authorities.authority.AuthorityConnection();
    processAuthorityConnection(authorityConnection,connectionNode);
     
    if (authorityConnection.getName() == null)
      authorityConnection.setName(connectionName);
    else
    {
      if (!authorityConnection.getName().equals(connectionName))
        throw new ManifoldCFException("Connection name in path and in object must agree");
    }
     
    try
    {
      // Save the connection.
      IAuthorityConnectionManager connectionManager = AuthorityConnectionManagerFactory.make(tc);
      if (connectionManager.save(authorityConnection))
        return WRITERESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }
 
  /** Write mapping connection.
  */
  protected static int apiWriteMappingConnection(IThreadContext tc, Configuration output, Configuration input, String connectionName)
    throws ManifoldCFException
  {
    ConfigurationNode connectionNode = findConfigurationNode(input,API_MAPPINGCONNECTIONNODE);
    if (connectionNode == null)
      throw new ManifoldCFException("Input argument must have '"+API_MAPPINGCONNECTIONNODE+"' field");
     
    // Turn the configuration node into an OutputConnection
    org.apache.manifoldcf.authorities.mapping.MappingConnection mappingConnection = new org.apache.manifoldcf.authorities.mapping.MappingConnection();
    processMappingConnection(mappingConnection,connectionNode);
     
    if (mappingConnection.getName() == null)
      mappingConnection.setName(connectionName);
    else
    {
      if (!mappingConnection.getName().equals(connectionName))
        throw new ManifoldCFException("Connection name in path and in object must agree");
    }
     
    try
    {
      // Save the connection.
      IMappingConnectionManager connectionManager = MappingConnectionManagerFactory.make(tc);
      if (connectionManager.save(mappingConnection))
        return WRITERESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }

  /** Write repository connection.
  */
  protected static int apiWriteRepositoryConnection(IThreadContext tc, Configuration output, Configuration input, String connectionName)
    throws ManifoldCFException
  {
    ConfigurationNode connectionNode = findConfigurationNode(input,API_REPOSITORYCONNECTIONNODE);
    if (connectionNode == null)
      throw new ManifoldCFException("Input argument must have '"+API_REPOSITORYCONNECTIONNODE+"' field");
     
    // Turn the configuration node into an OutputConnection
    org.apache.manifoldcf.crawler.repository.RepositoryConnection repositoryConnection = new org.apache.manifoldcf.crawler.repository.RepositoryConnection();
    processRepositoryConnection(repositoryConnection,connectionNode);
     
    if (repositoryConnection.getName() == null)
      repositoryConnection.setName(connectionName);
    else
    {
      if (!repositoryConnection.getName().equals(connectionName))
        throw new ManifoldCFException("Connection name in path and in object must agree");
    }

    try
    {
      // Save the connection.
      IRepositoryConnectionManager connectionManager = RepositoryConnectionManagerFactory.make(tc);
      if (connectionManager.save(repositoryConnection))
        return WRITERESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }

  /** Reset output connection.
  */
  protected static int apiWriteResetOutputConnection(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      signalOutputConnectionRedo(tc,connectionName);
      return WRITERESULT_CREATED;
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return WRITERESULT_FOUND;
  }
 
  /** Execute specified write command.
  *@param tc is the thread context.
  *@param output is the output object, to be filled in.
  *@param path is the object path.
  *@param input is the input object.
  *@return write result - either "not found", "found", or "created".
  */
  public static int executeWriteCommand(IThreadContext tc, Configuration output, String path, Configuration input)
    throws ManifoldCFException
  {
    if (path.startsWith("start/"))
    {
      Long jobID = new Long(path.substring("start/".length()));
      return apiWriteStartJob(tc,output,jobID,false);
    }
    else if (path.startsWith("startminimal/"))
    {
      Long jobID = new Long(path.substring("startminimal/".length()));
      return apiWriteStartJob(tc,output,jobID,true);
    }
    else if (path.startsWith("abort/"))
    {
      Long jobID = new Long(path.substring("abort/".length()));
      return apiWriteAbortJob(tc,output,jobID);
    }
    else if (path.startsWith("restart/"))
    {
      Long jobID = new Long(path.substring("restart/".length()));
      return apiWriteRestartJob(tc,output,jobID,false);
    }
    else if (path.startsWith("restartminimal/"))
    {
      Long jobID = new Long(path.substring("restartminimal/".length()));
      return apiWriteRestartJob(tc,output,jobID,true);
    }
    else if (path.startsWith("pause/"))
    {
      Long jobID = new Long(path.substring("pause/".length()));
      return apiWritePauseJob(tc,output,jobID);
    }
    else if (path.startsWith("resume/"))
    {
      Long jobID = new Long(path.substring("resume/".length()));
      return apiWriteResumeJob(tc,output,jobID);
    }
    else if (path.startsWith("jobs/"))
    {
      Long jobID = new Long(path.substring("jobs/".length()));
      return apiWriteJob(tc,output,input,jobID);
    }
    else if (path.startsWith("outputconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("outputconnections/".length()));
      return apiWriteOutputConnection(tc,output,input,connectionName);
    }
    else if (path.startsWith("mappingconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("mappingconnections/".length()));
      return apiWriteMappingConnection(tc,output,input,connectionName);
    }
    else if (path.startsWith("authorityconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("authorityconnections/".length()));
      return apiWriteAuthorityConnection(tc,output,input,connectionName);
    }
    else if (path.startsWith("repositoryconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("repositoryconnections/".length()));
      return apiWriteRepositoryConnection(tc,output,input,connectionName);
    }
    else if (path.startsWith("reset/"))
    {
      int firstSeparator = "reset/".length();
      int secondSeparator = path.indexOf("/",firstSeparator);
      if (secondSeparator == -1)
      {
        createErrorNode(output,"Need connection name.");
        return WRITERESULT_NOTFOUND;
      }
     
      String connectionType = path.substring(firstSeparator,secondSeparator);
      String connectionName = decodeAPIPathElement(path.substring(secondSeparator+1));
     
      if (connectionType.equals("outputconnections"))
      {
        return apiWriteResetOutputConnection(tc,output,connectionName);
      }
      else
      {
        createErrorNode(output,"Unknown connection type '"+connectionType+"'.");
        return WRITERESULT_NOTFOUND;
      }
    }
    else
    {
      createErrorNode(output,"Unrecognized resource.");
      return WRITERESULT_NOTFOUND;
    }
  }
 
  // Delete result codes
  public static final int DELETERESULT_NOTFOUND = 0;
  public static final int DELETERESULT_FOUND = 1;

  /** Delete a job.
  */
  protected static int apiDeleteJob(IThreadContext tc, Configuration output, Long jobID)
    throws ManifoldCFException
  {
    try
    {
      IJobManager jobManager = JobManagerFactory.make(tc);
      jobManager.deleteJob(jobID);
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return DELETERESULT_FOUND;
  }
 
  /** Delete output connection.
  */
  protected static int apiDeleteOutputConnection(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IOutputConnectionManager connectionManager = OutputConnectionManagerFactory.make(tc);
      connectionManager.delete(connectionName);
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return DELETERESULT_FOUND;
  }

  /** Delete authority connection.
  */
  protected static int apiDeleteAuthorityConnection(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IAuthorityConnectionManager connectionManager = AuthorityConnectionManagerFactory.make(tc);
      connectionManager.delete(connectionName);
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return DELETERESULT_FOUND;
  }

  /** Delete repository connection.
  */
  protected static int apiDeleteRepositoryConnection(IThreadContext tc, Configuration output, String connectionName)
    throws ManifoldCFException
  {
    try
    {
      IRepositoryConnectionManager connectionManager = RepositoryConnectionManagerFactory.make(tc);
      connectionManager.delete(connectionName);
    }
    catch (ManifoldCFException e)
    {
      createErrorNode(output,e);
    }
    return DELETERESULT_FOUND;
  }
 
  /** Execute specified delete command.
  *@param tc is the thread context.
  *@param output is the output object, to be filled in.
  *@param path is the object path.
  *@return delete result code
  */
  public static int executeDeleteCommand(IThreadContext tc, Configuration output, String path)
    throws ManifoldCFException
  {
    if (path.startsWith("jobs/"))
    {
      Long jobID = new Long(path.substring("jobs/".length()));
      return apiDeleteJob(tc,output,jobID);
    }
    else if (path.startsWith("outputconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("outputconnections/".length()));
      return apiDeleteOutputConnection(tc,output,connectionName);
    }
    else if (path.startsWith("authorityconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("authorityconnections/".length()));
      return apiDeleteAuthorityConnection(tc,output,connectionName);
    }
    else if (path.startsWith("repositoryconnections/"))
    {
      String connectionName = decodeAPIPathElement(path.substring("repositoryconnections/".length()));
      return apiDeleteRepositoryConnection(tc,output,connectionName);
    }
    else
    {
      createErrorNode(output,"Unrecognized resource.");
      return DELETERESULT_NOTFOUND;
    }
  }
 
  // The following chunk of code is responsible for formatting a job description into a set of nodes, and for reading back a formatted job description.
  // This is needed to support the job-related API methods, above.
 
  // Job node types
  protected static final String JOBNODE_ID = "id";
  protected static final String JOBNODE_DESCRIPTION = "description";
  protected static final String JOBNODE_CONNECTIONNAME = "repository_connection";
  protected static final String JOBNODE_OUTPUTNAME = "output_connection";
  protected static final String JOBNODE_DOCUMENTSPECIFICATION = "document_specification";
  protected static final String JOBNODE_OUTPUTSPECIFICATION = "output_specification";
  protected static final String JOBNODE_STARTMODE = "start_mode";
  protected static final String JOBNODE_RUNMODE = "run_mode";
  protected static final String JOBNODE_HOPCOUNTMODE = "hopcount_mode";
  protected static final String JOBNODE_PRIORITY = "priority";
  protected static final String JOBNODE_RECRAWLINTERVAL = "recrawl_interval";
  protected static final String JOBNODE_EXPIRATIONINTERVAL = "expiration_interval";
  protected static final String JOBNODE_RESEEDINTERVAL = "reseed_interval";
  protected static final String JOBNODE_HOPCOUNT = "hopcount";
  protected static final String JOBNODE_SCHEDULE = "schedule";
  protected static final String JOBNODE_LINKTYPE = "link_type";
  protected static final String JOBNODE_COUNT = "count";
  protected static final String JOBNODE_REQUESTMINIMUM = "requestminimum";
  protected static final String JOBNODE_TIMEZONE = "timezone";
  protected static final String JOBNODE_DURATION = "duration";
  protected static final String JOBNODE_DAYOFWEEK = "dayofweek";
  protected static final String JOBNODE_MONTHOFYEAR = "monthofyear";
  protected static final String JOBNODE_DAYOFMONTH = "dayofmonth";
  protected static final String JOBNODE_YEAR = "year";
  protected static final String JOBNODE_HOUROFDAY = "hourofday";
  protected static final String JOBNODE_MINUTESOFHOUR = "minutesofhour";
  protected static final String JOBNODE_ENUMVALUE = "value";
  protected static final String JOBNODE_FORCEDPARAM = "forcedparam";
  protected static final String JOBNODE_PARAMNAME = "paramname";
  protected static final String JOBNODE_PARAMVALUE = "paramvalue";

  /** Convert a node into a job description.
  *@param jobDescription is the job to be filled in.
  *@param jobNode is the configuration node corresponding to the whole job itself.
  */
  protected static void processJobDescription(org.apache.manifoldcf.crawler.jobs.JobDescription jobDescription, ConfigurationNode jobNode)
    throws ManifoldCFException
  {
    // Walk through the node's children
    int i = 0;
    while (i < jobNode.getChildCount())
    {
      ConfigurationNode child = jobNode.findChild(i++);
      String childType = child.getType();
      if (childType.equals(JOBNODE_ID))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Job id node requires a value");
        jobDescription.setID(new Long(child.getValue()));
      }
      else if (childType.equals(JOBNODE_DESCRIPTION))
      {
        jobDescription.setDescription(child.getValue());
      }
      else if (childType.equals(JOBNODE_CONNECTIONNAME))
      {
        jobDescription.setConnectionName(child.getValue());
      }
      else if (childType.equals(JOBNODE_OUTPUTNAME))
      {
        jobDescription.setOutputConnectionName(child.getValue());
      }
      else if (childType.equals(JOBNODE_DOCUMENTSPECIFICATION))
      {
        // Get the job's document specification, clear out the children, and copy new ones from the child.
        DocumentSpecification ds = jobDescription.getSpecification();
        ds.clearChildren();
        int j = 0;
        while (j < child.getChildCount())
        {
          ConfigurationNode cn = child.findChild(j++);
          ds.addChild(ds.getChildCount(),new SpecificationNode(cn));
        }
      }
      else if (childType.equals(JOBNODE_OUTPUTSPECIFICATION))
      {
        // Get the job's output specification, clear out the children, and copy new ones from the child.
        OutputSpecification os = jobDescription.getOutputSpecification();
        os.clearChildren();
        int j = 0;
        while (j < child.getChildCount())
        {
          ConfigurationNode cn = child.findChild(j++);
          os.addChild(os.getChildCount(),new SpecificationNode(cn));
        }
      }
      else if (childType.equals(JOBNODE_STARTMODE))
      {
        jobDescription.setStartMethod(mapToStartMode(child.getValue()));
      }
      else if (childType.equals(JOBNODE_RUNMODE))
      {
        jobDescription.setType(mapToRunMode(child.getValue()));
      }
      else if (childType.equals(JOBNODE_HOPCOUNTMODE))
      {
        jobDescription.setHopcountMode(mapToHopcountMode(child.getValue()));
      }
      else if (childType.equals(JOBNODE_PRIORITY))
      {
        try
        {
          jobDescription.setPriority(Integer.parseInt(child.getValue()));
        }
        catch (NumberFormatException e)
        {
          throw new ManifoldCFException(e.getMessage(),e);
        }
      }
      else if (childType.equals(JOBNODE_RECRAWLINTERVAL))
      {
        jobDescription.setInterval(interpretInterval(child.getValue()));
      }
      else if (childType.equals(JOBNODE_EXPIRATIONINTERVAL))
      {
        jobDescription.setExpiration(interpretInterval(child.getValue()));
      }
      else if (childType.equals(JOBNODE_RESEEDINTERVAL))
      {
        jobDescription.setReseedInterval(interpretInterval(child.getValue()));
      }
      else if (childType.equals(JOBNODE_HOPCOUNT))
      {
        // Read the hopcount values
        String linkType = null;
        String hopCount = null;
       
        int q = 0;
        while (q < child.getChildCount())
        {
          ConfigurationNode cn = child.findChild(q++);
          if (cn.getType().equals(JOBNODE_LINKTYPE))
            linkType = cn.getValue();
          else if (cn.getType().equals(JOBNODE_COUNT))
            hopCount = cn.getValue();
          else
            throw new ManifoldCFException("Found an unexpected node type: '"+cn.getType()+"'");
        }
        if (linkType == null)
          throw new ManifoldCFException("Missing required field: '"+JOBNODE_LINKTYPE+"'");
        if (hopCount == null)
          throw new ManifoldCFException("Missing required field: '"+JOBNODE_COUNT+"'");
        jobDescription.addHopCountFilter(linkType,new Long(hopCount));
      }
      else if (childType.equals(JOBNODE_FORCEDPARAM))
      {
        // Read the forced parameter values
        String paramName = null;
        String paramValue = null;
        for (int q = 0; q < child.getChildCount(); q++)
        {
          ConfigurationNode cn = child.findChild(q);
          if (cn.getType().equals(JOBNODE_PARAMNAME))
            paramName = cn.getValue();
          else if (cn.getType().equals(JOBNODE_PARAMVALUE))
            paramValue = cn.getValue();
          else
            throw new ManifoldCFException("Found an unexpected node type: '"+cn.getType()+"'");
        }
        if (paramName == null)
          throw new ManifoldCFException("Missing required field: '"+JOBNODE_PARAMNAME+"'");
        if (paramValue == null)
          throw new ManifoldCFException("Missing required field: '"+JOBNODE_PARAMVALUE+"'");
        jobDescription.addForcedMetadataValue(paramName,paramValue);
      }
      else if (childType.equals(JOBNODE_SCHEDULE))
      {
        // Create a schedule record.
        String timezone = null;
        Long duration = null;
        boolean requestMinimum = false;
        EnumeratedValues dayOfWeek = null;
        EnumeratedValues monthOfYear = null;
        EnumeratedValues dayOfMonth = null;
        EnumeratedValues year = null;
        EnumeratedValues hourOfDay = null;
        EnumeratedValues minutesOfHour = null;
           
        // Now, walk through children of the schedule node.
        int q = 0;
        while (q < child.getChildCount())
        {
          ConfigurationNode scheduleField = child.findChild(q++);
          String fieldType = scheduleField.getType();
          if (fieldType.equals(JOBNODE_REQUESTMINIMUM))
          {
            requestMinimum = scheduleField.getValue().equals("true");
          }
          else if (fieldType.equals(JOBNODE_TIMEZONE))
          {
            timezone = scheduleField.getValue();
          }
          else if (fieldType.equals(JOBNODE_DURATION))
          {
            duration = new Long(scheduleField.getValue());
          }
          else if (fieldType.equals(JOBNODE_DAYOFWEEK))
          {
            dayOfWeek = processEnumeratedValues(scheduleField);
          }
          else if (fieldType.equals(JOBNODE_MONTHOFYEAR))
          {
            monthOfYear = processEnumeratedValues(scheduleField);
          }
          else if (fieldType.equals(JOBNODE_YEAR))
          {
            year = processEnumeratedValues(scheduleField);
          }
          else if (fieldType.equals(JOBNODE_DAYOFMONTH))
          {
            dayOfMonth = processEnumeratedValues(scheduleField);
          }
          else if (fieldType.equals(JOBNODE_HOUROFDAY))
          {
            hourOfDay = processEnumeratedValues(scheduleField);
          }
          else if (fieldType.equals(JOBNODE_MINUTESOFHOUR))
          {
            minutesOfHour = processEnumeratedValues(scheduleField);
          }
          else
            throw new ManifoldCFException("Unrecognized field in schedule record: '"+fieldType+"'");
        }
        ScheduleRecord sr = new ScheduleRecord(dayOfWeek,monthOfYear,dayOfMonth,year,hourOfDay,minutesOfHour,timezone,duration,requestMinimum);
        // Add the schedule record to the job.
        jobDescription.addScheduleRecord(sr);
      }
      else
        throw new ManifoldCFException("Unrecognized job field: '"+childType+"'");
    }
  }

  /** Convert a job description into a ConfigurationNode.
  *@param jobNode is the node to be filled in.
  *@param job is the job description.
  */
  protected static void formatJobDescription(ConfigurationNode jobNode, IJobDescription job)
  {
    // For each field of the job, add an appropriate child node, with value.
    ConfigurationNode child;
    int j;
   
    // id
    if (job.getID() != null)
    {
      child = new ConfigurationNode(JOBNODE_ID);
      child.setValue(job.getID().toString());
      jobNode.addChild(jobNode.getChildCount(),child);
    }
   
    // description
    if (job.getDescription() != null)
    {
      child = new ConfigurationNode(JOBNODE_DESCRIPTION);
      child.setValue(job.getDescription());
      jobNode.addChild(jobNode.getChildCount(),child);
    }
   
    // connection
    if (job.getConnectionName() != null)
    {
      child = new ConfigurationNode(JOBNODE_CONNECTIONNAME);
      child.setValue(job.getConnectionName());
      jobNode.addChild(jobNode.getChildCount(),child);
    }

    // output connection
    if (job.getOutputConnectionName() != null)
    {
      child = new ConfigurationNode(JOBNODE_OUTPUTNAME);
      child.setValue(job.getOutputConnectionName());
      jobNode.addChild(jobNode.getChildCount(),child);
    }

    // Document specification
    DocumentSpecification ds = job.getSpecification();
    child = new ConfigurationNode(JOBNODE_DOCUMENTSPECIFICATION);
    j = 0;
    while (j < ds.getChildCount())
    {
      ConfigurationNode cn = ds.getChild(j++);
      child.addChild(child.getChildCount(),cn);
    }
    jobNode.addChild(jobNode.getChildCount(),child);

    // Output specification
    OutputSpecification os = job.getOutputSpecification();
    child = new ConfigurationNode(JOBNODE_OUTPUTSPECIFICATION);
    j = 0;
    while (j < os.getChildCount())
    {
      ConfigurationNode cn = os.getChild(j++);
      child.addChild(child.getChildCount(),cn);
    }
    jobNode.addChild(jobNode.getChildCount(),child);

    // Start mode
    child = new ConfigurationNode(JOBNODE_STARTMODE);
    child.setValue(startModeMap(job.getStartMethod()));
    jobNode.addChild(jobNode.getChildCount(),child);

    // Run mode
    child = new ConfigurationNode(JOBNODE_RUNMODE);
    child.setValue(runModeMap(job.getType()));
    jobNode.addChild(jobNode.getChildCount(),child);

    // Hopcount mode
    child = new ConfigurationNode(JOBNODE_HOPCOUNTMODE);
    child.setValue(hopcountModeMap(job.getHopcountMode()));
    jobNode.addChild(jobNode.getChildCount(),child);

    // Priority
    child = new ConfigurationNode(JOBNODE_PRIORITY);
    child.setValue(Integer.toString(job.getPriority()));
    jobNode.addChild(jobNode.getChildCount(),child);

    // Recrawl interval
    child = new ConfigurationNode(JOBNODE_RECRAWLINTERVAL);
    child.setValue((job.getInterval()==null)?"infinite":job.getInterval().toString());
    jobNode.addChild(jobNode.getChildCount(),child);

    child = new ConfigurationNode(JOBNODE_EXPIRATIONINTERVAL);
    child.setValue((job.getExpiration()==null)?"infinite":job.getExpiration().toString());
    jobNode.addChild(jobNode.getChildCount(),child);

    child = new ConfigurationNode(JOBNODE_RESEEDINTERVAL);
    child.setValue((job.getReseedInterval()==null)?"infinite":job.getReseedInterval().toString());
    jobNode.addChild(jobNode.getChildCount(),child);

    // Hopcount records
    Map filters = job.getHopCountFilters();
    Iterator iter = filters.keySet().iterator();
    while (iter.hasNext())
    {
      String linkType = (String)iter.next();
      Long hopCount = (Long)filters.get(linkType);
      child = new ConfigurationNode(JOBNODE_HOPCOUNT);
      ConfigurationNode cn;
      cn = new ConfigurationNode(JOBNODE_LINKTYPE);
      cn.setValue(linkType);
      child.addChild(child.getChildCount(),cn);
      cn = new ConfigurationNode(JOBNODE_COUNT);
      cn.setValue(hopCount.toString());
      child.addChild(child.getChildCount(),cn);
      jobNode.addChild(jobNode.getChildCount(),child);
    }
   
    // Forced metadata records
    Map<String,Set<String>> forcedMetadata = job.getForcedMetadata();
    for (String paramName : forcedMetadata.keySet())
    {
      Set<String> values = forcedMetadata.get(paramName);
      for (String paramValue : values)
      {
        child = new ConfigurationNode(JOBNODE_FORCEDPARAM);
        ConfigurationNode cn;
        cn = new ConfigurationNode(JOBNODE_PARAMNAME);
        cn.setValue(paramName);
        child.addChild(child.getChildCount(),cn);
        cn = new ConfigurationNode(JOBNODE_PARAMVALUE);
        cn.setValue(paramValue);
        child.addChild(child.getChildCount(),cn);
        jobNode.addChild(jobNode.getChildCount(),child);
      }
    }
   
    // Schedule records
    j = 0;
    while (j < job.getScheduleRecordCount())
    {
      ScheduleRecord sr = job.getScheduleRecord(j++);
      child = new ConfigurationNode(JOBNODE_SCHEDULE);
      ConfigurationNode recordChild;
     
      // requestminimum
      recordChild = new ConfigurationNode(JOBNODE_REQUESTMINIMUM);
      recordChild.setValue(sr.getRequestMinimum()?"true":"false");
      child.addChild(child.getChildCount(),recordChild);
     
      // timezone
      if (sr.getTimezone() != null)
      {
        recordChild = new ConfigurationNode(JOBNODE_TIMEZONE);
        recordChild.setValue(sr.getTimezone());
        child.addChild(child.getChildCount(),recordChild);
      }

      // duration
      if (sr.getDuration() != null)
      {
        recordChild = new ConfigurationNode(JOBNODE_DURATION);
        recordChild.setValue(sr.getDuration().toString());
        child.addChild(child.getChildCount(),recordChild);
      }
     
      // Schedule specification values
     
      // day of week
      if (sr.getDayOfWeek() != null)
        formatEnumeratedValues(child,JOBNODE_DAYOFWEEK,sr.getDayOfWeek());
      if (sr.getMonthOfYear() != null)
        formatEnumeratedValues(child,JOBNODE_MONTHOFYEAR,sr.getMonthOfYear());
      if (sr.getDayOfMonth() != null)
        formatEnumeratedValues(child,JOBNODE_DAYOFMONTH,sr.getDayOfMonth());
      if (sr.getYear() != null)
        formatEnumeratedValues(child,JOBNODE_YEAR,sr.getYear());
      if (sr.getHourOfDay() != null)
        formatEnumeratedValues(child,JOBNODE_HOUROFDAY,sr.getHourOfDay());
      if (sr.getMinutesOfHour() != null)
        formatEnumeratedValues(child,JOBNODE_MINUTESOFHOUR,sr.getMinutesOfHour());
     
      jobNode.addChild(jobNode.getChildCount(),child);
    }
  }

  protected static void formatEnumeratedValues(ConfigurationNode recordNode, String childType, EnumeratedValues value)
  {
    ConfigurationNode child = new ConfigurationNode(childType);
    Iterator iter = value.getValues();
    while (iter.hasNext())
    {
      Integer theValue = (Integer)iter.next();
      ConfigurationNode valueNode = new ConfigurationNode(JOBNODE_ENUMVALUE);
      valueNode.setValue(theValue.toString());
      child.addChild(child.getChildCount(),valueNode);
    }
    recordNode.addChild(recordNode.getChildCount(),child);
  }
 
  protected static EnumeratedValues processEnumeratedValues(ConfigurationNode fieldNode)
    throws ManifoldCFException
  {
    ArrayList values = new ArrayList();
    int i = 0;
    while (i < fieldNode.getChildCount())
    {
      ConfigurationNode cn = fieldNode.findChild(i++);
      if (cn.getType().equals(JOBNODE_ENUMVALUE))
      {
        try
        {
          values.add(new Integer(cn.getValue()));
        }
        catch (NumberFormatException e)
        {
          throw new ManifoldCFException("Error processing enumerated value node: "+e.getMessage(),e);
        }
      }
      else
        throw new ManifoldCFException("Error processing enumerated value nodes: Unrecognized node type '"+cn.getType()+"'");
    }
    return new EnumeratedValues(values);
  }
 
  protected static String presentInterval(Long interval)
  {
    if (interval == null)
      return "infinite";
    return interval.toString();
  }

  protected static Long interpretInterval(String interval)
    throws ManifoldCFException
  {
    if (interval == null || interval.equals("infinite"))
      return null;
    else
      return new Long(interval);
  }
 
  protected static String startModeMap(int startMethod)
  {
    switch (startMethod)
    {
    case IJobDescription.START_WINDOWBEGIN:
      return "schedule window start";
    case IJobDescription.START_WINDOWINSIDE:
      return "schedule window anytime";
    case IJobDescription.START_DISABLE:
      return "manual";
    default:
      return "unknown";
    }
  }

  protected static int mapToStartMode(String startMethod)
    throws ManifoldCFException
  {
    if (startMethod.equals("schedule window start"))
      return IJobDescription.START_WINDOWBEGIN;
    else if (startMethod.equals("schedule window anytime"))
      return IJobDescription.START_WINDOWINSIDE;
    else if (startMethod.equals("manual"))
      return IJobDescription.START_DISABLE;
    else
      throw new ManifoldCFException("Unrecognized start method: '"+startMethod+"'");
  }
 
  protected static String runModeMap(int type)
  {
    switch (type)
    {
    case IJobDescription.TYPE_CONTINUOUS:
      return "continuous";
    case IJobDescription.TYPE_SPECIFIED:
      return "scan once";
    default:
      return "unknown";
    }
  }

  protected static int mapToRunMode(String mode)
    throws ManifoldCFException
  {
    if (mode.equals("continuous"))
      return IJobDescription.TYPE_CONTINUOUS;
    else if (mode.equals("scan once"))
      return IJobDescription.TYPE_SPECIFIED;
    else
      throw new ManifoldCFException("Unrecognized run method: '"+mode+"'");
  }
 
  protected static String hopcountModeMap(int mode)
  {
    switch (mode)
    {
    case IJobDescription.HOPCOUNT_ACCURATE:
      return "accurate";
    case IJobDescription.HOPCOUNT_NODELETE:
      return "no delete";
    case IJobDescription.HOPCOUNT_NEVERDELETE:
      return "never delete";
    default:
      return "unknown";
    }
  }

  protected static int mapToHopcountMode(String mode)
    throws ManifoldCFException
  {
    if (mode.equals("accurate"))
      return IJobDescription.HOPCOUNT_ACCURATE;
    else if (mode.equals("no delete"))
      return IJobDescription.HOPCOUNT_NODELETE;
    else if (mode.equals("never delete"))
      return IJobDescription.HOPCOUNT_NEVERDELETE;
    else
      throw new ManifoldCFException("Unrecognized hopcount method: '"+mode+"'");
  }
 
  // End of job API support code.
 
  // The following chunk of code supports job statuses in the API.  Only a formatting method is required, since we never "save" a status.

  // Node types used to handle job statuses.
  protected static final String JOBSTATUSNODE_JOBID = "job_id";
  protected static final String JOBSTATUSNODE_STATUS = "status";
  protected static final String JOBSTATUSNODE_ERRORTEXT = "errortext";
  protected static final String JOBSTATUSNODE_STARTTIME = "start_time";
  protected static final String JOBSTATUSNODE_ENDTIME = "end_time";
  protected static final String JOBSTATUSNODE_DOCUMENTSINQUEUE = "documents_in_queue";
  protected static final String JOBSTATUSNODE_DOCUMENTSOUTSTANDING = "documents_outstanding";
  protected static final String JOBSTATUSNODE_DOCUMENTSPROCESSED = "documents_processed";
 
  /** Format a job status.
  */
  protected static void formatJobStatus(ConfigurationNode jobStatusNode, JobStatus jobStatus)
  {
    // For each field of the job, add an appropriate child node, with value.
    ConfigurationNode child;
    int j;
   
    // id
    child = new ConfigurationNode(JOBSTATUSNODE_JOBID);
    child.setValue(jobStatus.getJobID().toString());
    jobStatusNode.addChild(jobStatusNode.getChildCount(),child);

    // status
    child = new ConfigurationNode(JOBSTATUSNODE_STATUS);
    child.setValue(statusMap(jobStatus.getStatus()));
    jobStatusNode.addChild(jobStatusNode.getChildCount(),child);

    // error text
    if (jobStatus.getErrorText() != null)
    {
      child = new ConfigurationNode(JOBSTATUSNODE_ERRORTEXT);
      child.setValue(jobStatus.getErrorText());
      jobStatusNode.addChild(jobStatusNode.getChildCount(),child);
    }
   
    // start time
    if (jobStatus.getStartTime() != -1L)
    {
      child = new ConfigurationNode(JOBSTATUSNODE_STARTTIME);
      child.setValue(new Long(jobStatus.getStartTime()).toString());
      jobStatusNode.addChild(jobStatusNode.getChildCount(),child);
    }
   
    // end time
    if (jobStatus.getEndTime() != -1L)
    {
      child = new ConfigurationNode(JOBSTATUSNODE_ENDTIME);
      child.setValue(new Long(jobStatus.getEndTime()).toString());
      jobStatusNode.addChild(jobStatusNode.getChildCount(),child);
    }

    // documents in queue
    child = new ConfigurationNode(JOBSTATUSNODE_DOCUMENTSINQUEUE);
    child.setValue(new Long(jobStatus.getDocumentsInQueue()).toString());
    jobStatusNode.addChild(jobStatusNode.getChildCount(),child);

    // documents outstanding
    child = new ConfigurationNode(JOBSTATUSNODE_DOCUMENTSOUTSTANDING);
    child.setValue(new Long(jobStatus.getDocumentsOutstanding()).toString());
    jobStatusNode.addChild(jobStatusNode.getChildCount(),child);

    // documents processed
    child = new ConfigurationNode(JOBSTATUSNODE_DOCUMENTSPROCESSED);
    child.setValue(new Long(jobStatus.getDocumentsProcessed()).toString());
    jobStatusNode.addChild(jobStatusNode.getChildCount(),child);

  }

  protected static String statusMap(int status)
  {
    switch (status)
    {
    case JobStatus.JOBSTATUS_NOTYETRUN:
      return "not yet run";
    case JobStatus.JOBSTATUS_RUNNING:
      return "running";
    case JobStatus.JOBSTATUS_STOPPING:
      return "stopping";
    case JobStatus.JOBSTATUS_RESUMING:
      return "resuming";
    case JobStatus.JOBSTATUS_PAUSED:
      return "paused";
    case JobStatus.JOBSTATUS_COMPLETED:
      return "done";
    case JobStatus.JOBSTATUS_WINDOWWAIT:
      return "waiting";
    case JobStatus.JOBSTATUS_STARTING:
      return "starting up";
    case JobStatus.JOBSTATUS_DESTRUCTING:
      return "cleaning up";
    case JobStatus.JOBSTATUS_ERROR:
      return "error";
    case JobStatus.JOBSTATUS_ABORTING:
      return "aborting";
    case JobStatus.JOBSTATUS_RESTARTING:
      return "restarting";
    case JobStatus.JOBSTATUS_RUNNING_UNINSTALLED:
      return "running no connector";
    case JobStatus.JOBSTATUS_JOBENDCLEANUP:
      return "terminating";
    case JobStatus.JOBSTATUS_JOBENDNOTIFICATION:
      return "notifying";
    default:
      return "unknown";
    }
  }

  // End of jobstatus API support.
 
  // Connection API
 
  protected static final String CONNECTIONNODE_ISNEW = "isnew";
  protected static final String CONNECTIONNODE_NAME = "name";
  protected static final String CONNECTIONNODE_CLASSNAME = "class_name";
  protected static final String CONNECTIONNODE_MAXCONNECTIONS = "max_connections";
  protected static final String CONNECTIONNODE_DESCRIPTION = "description";
  protected static final String CONNECTIONNODE_PREREQUISITE = "prerequisite";
  protected static final String CONNECTIONNODE_CONFIGURATION = "configuration";
  protected static final String CONNECTIONNODE_ACLAUTHORITY = "acl_authority";
  protected static final String CONNECTIONNODE_THROTTLE = "throttle";
  protected static final String CONNECTIONNODE_MATCH = "match";
  protected static final String CONNECTIONNODE_MATCHDESCRIPTION = "match_description";
  protected static final String CONNECTIONNODE_RATE = "rate";
 
  // Output connection API support.
 
  /** Convert input hierarchy into an OutputConnection object.
  */
  protected static void processOutputConnection(org.apache.manifoldcf.agents.outputconnection.OutputConnection connection, ConfigurationNode connectionNode)
    throws ManifoldCFException
  {
    // Walk through the node's children
    int i = 0;
    while (i < connectionNode.getChildCount())
    {
      ConfigurationNode child = connectionNode.findChild(i++);
      String childType = child.getType();
      if (childType.equals(CONNECTIONNODE_ISNEW))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection isnew node requires a value");
        connection.setIsNew(child.getValue().equals("true"));
      }
      else if (childType.equals(CONNECTIONNODE_NAME))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection name node requires a value");
        connection.setName(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_CLASSNAME))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection classname node requires a value");
        connection.setClassName(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_MAXCONNECTIONS))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection maxconnections node requires a value");
        try
        {
          connection.setMaxConnections(Integer.parseInt(child.getValue()));
        }
        catch (NumberFormatException e)
        {
          throw new ManifoldCFException("Error parsing max connections: "+e.getMessage(),e);
        }
      }
      else if (childType.equals(CONNECTIONNODE_DESCRIPTION))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection description node requires a value");
        connection.setDescription(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_CONFIGURATION))
      {
        // Get the connection's configuration, clear out the children, and copy new ones from the child.
        ConfigParams cp = connection.getConfigParams();
        cp.clearChildren();
        int j = 0;
        while (j < child.getChildCount())
        {
          ConfigurationNode cn = child.findChild(j++);
          cp.addChild(cp.getChildCount(),new ConfigNode(cn));
        }
      }
      else
        throw new ManifoldCFException("Unrecognized output connection field: '"+childType+"'");
    }
    if (connection.getClassName() == null)
      throw new ManifoldCFException("Missing connection field: '"+CONNECTIONNODE_CLASSNAME+"'");

  }
 
  /** Format an output connection.
  */
  protected static void formatOutputConnection(ConfigurationNode connectionNode, IOutputConnection connection)
  {
    ConfigurationNode child;
    int j;

    child = new ConfigurationNode(CONNECTIONNODE_ISNEW);
    child.setValue(connection.getIsNew()?"true":"false");
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_NAME);
    child.setValue(connection.getName());
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_CLASSNAME);
    child.setValue(connection.getClassName());
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_MAXCONNECTIONS);
    child.setValue(Integer.toString(connection.getMaxConnections()));
    connectionNode.addChild(connectionNode.getChildCount(),child);

    if (connection.getDescription() != null)
    {
      child = new ConfigurationNode(CONNECTIONNODE_DESCRIPTION);
      child.setValue(connection.getDescription());
      connectionNode.addChild(connectionNode.getChildCount(),child);
    }
   
    ConfigParams cp = connection.getConfigParams();
    child = new ConfigurationNode(CONNECTIONNODE_CONFIGURATION);
    j = 0;
    while (j < cp.getChildCount())
    {
      ConfigurationNode cn = cp.findChild(j++);
      child.addChild(child.getChildCount(),cn);
    }
    connectionNode.addChild(connectionNode.getChildCount(),child);

  }

  // Authority connection API support
 
  /** Convert input hierarchy into an AuthorityConnection object.
  */
  protected static void processAuthorityConnection(org.apache.manifoldcf.authorities.authority.AuthorityConnection connection, ConfigurationNode connectionNode)
    throws ManifoldCFException
  {
    // Walk through the node's children
    int i = 0;
    while (i < connectionNode.getChildCount())
    {
      ConfigurationNode child = connectionNode.findChild(i++);
      String childType = child.getType();
      if (childType.equals(CONNECTIONNODE_ISNEW))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection isnew node requires a value");
        connection.setIsNew(child.getValue().equals("true"));
      }
      else if (childType.equals(CONNECTIONNODE_NAME))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection name node requires a value");
        connection.setName(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_CLASSNAME))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection classname node requires a value");
        connection.setClassName(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_MAXCONNECTIONS))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection maxconnections node requires a value");
        try
        {
          connection.setMaxConnections(Integer.parseInt(child.getValue()));
        }
        catch (NumberFormatException e)
        {
          throw new ManifoldCFException("Error parsing max connections: "+e.getMessage(),e);
        }
      }
      else if (childType.equals(CONNECTIONNODE_PREREQUISITE))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection prerequisite node requires a value");
        connection.setPrerequisiteMapping(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_DESCRIPTION))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection description node requires a value");
        connection.setDescription(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_CONFIGURATION))
      {
        // Get the connection's configuration, clear out the children, and copy new ones from the child.
        ConfigParams cp = connection.getConfigParams();
        cp.clearChildren();
        int j = 0;
        while (j < child.getChildCount())
        {
          ConfigurationNode cn = child.findChild(j++);
          cp.addChild(cp.getChildCount(),new ConfigNode(cn));
        }
      }
      else
        throw new ManifoldCFException("Unrecognized authority connection field: '"+childType+"'");
    }
    if (connection.getClassName() == null)
      throw new ManifoldCFException("Missing connection field: '"+CONNECTIONNODE_CLASSNAME+"'");

  }


  /** Format an authority connection.
  */
  protected static void formatAuthorityConnection(ConfigurationNode connectionNode, IAuthorityConnection connection)
  {
    ConfigurationNode child;
    int j;
   
    child = new ConfigurationNode(CONNECTIONNODE_ISNEW);
    child.setValue(connection.getIsNew()?"true":"false");
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_NAME);
    child.setValue(connection.getName());
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_CLASSNAME);
    child.setValue(connection.getClassName());
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_MAXCONNECTIONS);
    child.setValue(Integer.toString(connection.getMaxConnections()));
    connectionNode.addChild(connectionNode.getChildCount(),child);

    if (connection.getPrerequisiteMapping() != null)
    {
      child = new ConfigurationNode(CONNECTIONNODE_PREREQUISITE);
      child.setValue(connection.getPrerequisiteMapping());
      connectionNode.addChild(connectionNode.getChildCount(),child);
    }

    if (connection.getDescription() != null)
    {
      child = new ConfigurationNode(CONNECTIONNODE_DESCRIPTION);
      child.setValue(connection.getDescription());
      connectionNode.addChild(connectionNode.getChildCount(),child);
    }
   
    ConfigParams cp = connection.getConfigParams();
    child = new ConfigurationNode(CONNECTIONNODE_CONFIGURATION);
    j = 0;
    while (j < cp.getChildCount())
    {
      ConfigurationNode cn = cp.findChild(j++);
      child.addChild(child.getChildCount(),cn);
    }
    connectionNode.addChild(connectionNode.getChildCount(),child);

  }

  // Mapping connection API methods
 
  /** Convert input hierarchy into an MappingConnection object.
  */
  protected static void processMappingConnection(org.apache.manifoldcf.authorities.mapping.MappingConnection connection, ConfigurationNode connectionNode)
    throws ManifoldCFException
  {
    // Walk through the node's children
    int i = 0;
    while (i < connectionNode.getChildCount())
    {
      ConfigurationNode child = connectionNode.findChild(i++);
      String childType = child.getType();
      if (childType.equals(CONNECTIONNODE_ISNEW))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection isnew node requires a value");
        connection.setIsNew(child.getValue().equals("true"));
      }
      else if (childType.equals(CONNECTIONNODE_NAME))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection name node requires a value");
        connection.setName(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_CLASSNAME))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection classname node requires a value");
        connection.setClassName(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_MAXCONNECTIONS))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection maxconnections node requires a value");
        try
        {
          connection.setMaxConnections(Integer.parseInt(child.getValue()));
        }
        catch (NumberFormatException e)
        {
          throw new ManifoldCFException("Error parsing max connections: "+e.getMessage(),e);
        }
      }
      else if (childType.equals(CONNECTIONNODE_PREREQUISITE))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection prerequisite node requires a value");
        connection.setPrerequisiteMapping(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_DESCRIPTION))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection description node requires a value");
        connection.setDescription(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_CONFIGURATION))
      {
        // Get the connection's configuration, clear out the children, and copy new ones from the child.
        ConfigParams cp = connection.getConfigParams();
        cp.clearChildren();
        int j = 0;
        while (j < child.getChildCount())
        {
          ConfigurationNode cn = child.findChild(j++);
          cp.addChild(cp.getChildCount(),new ConfigNode(cn));
        }
      }
      else
        throw new ManifoldCFException("Unrecognized mapping connection field: '"+childType+"'");
    }
    if (connection.getClassName() == null)
      throw new ManifoldCFException("Missing connection field: '"+CONNECTIONNODE_CLASSNAME+"'");

  }

  /** Format a mapping connection.
  */
  protected static void formatMappingConnection(ConfigurationNode connectionNode, IMappingConnection connection)
  {
    ConfigurationNode child;
    int j;
   
    child = new ConfigurationNode(CONNECTIONNODE_ISNEW);
    child.setValue(connection.getIsNew()?"true":"false");
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_NAME);
    child.setValue(connection.getName());
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_CLASSNAME);
    child.setValue(connection.getClassName());
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_MAXCONNECTIONS);
    child.setValue(Integer.toString(connection.getMaxConnections()));
    connectionNode.addChild(connectionNode.getChildCount(),child);

    if (connection.getPrerequisiteMapping() != null)
    {
      child = new ConfigurationNode(CONNECTIONNODE_PREREQUISITE);
      child.setValue(connection.getPrerequisiteMapping());
      connectionNode.addChild(connectionNode.getChildCount(),child);
    }
   
    if (connection.getDescription() != null)
    {
      child = new ConfigurationNode(CONNECTIONNODE_DESCRIPTION);
      child.setValue(connection.getDescription());
      connectionNode.addChild(connectionNode.getChildCount(),child);
    }
   
    ConfigParams cp = connection.getConfigParams();
    child = new ConfigurationNode(CONNECTIONNODE_CONFIGURATION);
    j = 0;
    while (j < cp.getChildCount())
    {
      ConfigurationNode cn = cp.findChild(j++);
      child.addChild(child.getChildCount(),cn);
    }
    connectionNode.addChild(connectionNode.getChildCount(),child);

  }

  // Repository connection API support methods
 
  /** Convert input hierarchy into a RepositoryConnection object.
  */
  protected static void processRepositoryConnection(org.apache.manifoldcf.crawler.repository.RepositoryConnection connection, ConfigurationNode connectionNode)
    throws ManifoldCFException
  {
    // Walk through the node's children
    int i = 0;
    while (i < connectionNode.getChildCount())
    {
      ConfigurationNode child = connectionNode.findChild(i++);
      String childType = child.getType();
      if (childType.equals(CONNECTIONNODE_ISNEW))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection isnew node requires a value");
        connection.setIsNew(child.getValue().equals("true"));
      }
      else if (childType.equals(CONNECTIONNODE_NAME))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection name node requires a value");
        connection.setName(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_CLASSNAME))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection classname node requires a value");
        connection.setClassName(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_MAXCONNECTIONS))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection maxconnections node requires a value");
        try
        {
          connection.setMaxConnections(Integer.parseInt(child.getValue()));
        }
        catch (NumberFormatException e)
        {
          throw new ManifoldCFException("Error parsing max connections: "+e.getMessage(),e);
        }
      }
      else if (childType.equals(CONNECTIONNODE_DESCRIPTION))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection description node requires a value");
        connection.setDescription(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_CONFIGURATION))
      {
        // Get the connection's configuration, clear out the children, and copy new ones from the child.
        ConfigParams cp = connection.getConfigParams();
        cp.clearChildren();
        int j = 0;
        while (j < child.getChildCount())
        {
          ConfigurationNode cn = child.findChild(j++);
          cp.addChild(cp.getChildCount(),new ConfigNode(cn));
        }
      }
      else if (childType.equals(CONNECTIONNODE_ACLAUTHORITY))
      {
        if (child.getValue() == null)
          throw new ManifoldCFException("Connection aclauthority node requires a value");
        connection.setACLAuthority(child.getValue());
      }
      else if (childType.equals(CONNECTIONNODE_THROTTLE))
      {
        String match = null;
        String description = null;
        Float rate = null;
           
        int q = 0;
        while (q < child.getChildCount())
        {
          ConfigurationNode throttleField = child.findChild(q++);
          String fieldType = throttleField.getType();
          if (fieldType.equals(CONNECTIONNODE_MATCH))
          {
            match = throttleField.getValue();
          }
          else if (fieldType.equals(CONNECTIONNODE_MATCHDESCRIPTION))
          {
            description = throttleField.getValue();
          }
          else if (fieldType.equals(CONNECTIONNODE_RATE))
          {
            rate = new Float(throttleField.getValue());
          }
          else
            throw new ManifoldCFException("Unrecognized throttle field: '"+fieldType+"'");
        }
        if (match == null)
          throw new ManifoldCFException("Missing throttle field: '"+CONNECTIONNODE_MATCH+"'");
        if (rate == null)
          throw new ManifoldCFException("Missing throttle field: '"+CONNECTIONNODE_RATE+"'");
        connection.addThrottleValue(match,description,rate.floatValue());
      }
      else
        throw new ManifoldCFException("Unrecognized repository connection field: '"+childType+"'");
    }
    if (connection.getClassName() == null)
      throw new ManifoldCFException("Missing connection field: '"+CONNECTIONNODE_CLASSNAME+"'");

  }
 
  /** Format a repository connection.
  */
  protected static void formatRepositoryConnection(ConfigurationNode connectionNode, IRepositoryConnection connection)
  {
    ConfigurationNode child;
    int j;

    child = new ConfigurationNode(CONNECTIONNODE_ISNEW);
    child.setValue(connection.getIsNew()?"true":"false");
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_NAME);
    child.setValue(connection.getName());
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_CLASSNAME);
    child.setValue(connection.getClassName());
    connectionNode.addChild(connectionNode.getChildCount(),child);

    child = new ConfigurationNode(CONNECTIONNODE_MAXCONNECTIONS);
    child.setValue(Integer.toString(connection.getMaxConnections()));
    connectionNode.addChild(connectionNode.getChildCount(),child);

    if (connection.getDescription() != null)
    {
      child = new ConfigurationNode(CONNECTIONNODE_DESCRIPTION);
      child.setValue(connection.getDescription());
      connectionNode.addChild(connectionNode.getChildCount(),child);
    }
   
    ConfigParams cp = connection.getConfigParams();
    child = new ConfigurationNode(CONNECTIONNODE_CONFIGURATION);
    j = 0;
    while (j < cp.getChildCount())
    {
      ConfigurationNode cn = cp.findChild(j++);
      child.addChild(child.getChildCount(),cn);
    }
    connectionNode.addChild(connectionNode.getChildCount(),child);

    if (connection.getACLAuthority() != null)
    {
      child = new ConfigurationNode(CONNECTIONNODE_ACLAUTHORITY);
      child.setValue(connection.getACLAuthority());
      connectionNode.addChild(connectionNode.getChildCount(),child);
    }
   
    String[] throttles = connection.getThrottles();
    j = 0;
    while (j < throttles.length)
    {
      String match = throttles[j++];
      String description = connection.getThrottleDescription(match);
      float rate = connection.getThrottleValue(match);
      child = new ConfigurationNode(CONNECTIONNODE_THROTTLE);
      ConfigurationNode throttleChildNode;
     
      throttleChildNode = new ConfigurationNode(CONNECTIONNODE_MATCH);
      throttleChildNode.setValue(match);
      child.addChild(child.getChildCount(),throttleChildNode);
     
      if (description != null)
      {
        throttleChildNode = new ConfigurationNode(CONNECTIONNODE_MATCHDESCRIPTION);
        throttleChildNode.setValue(description);
        child.addChild(child.getChildCount(),throttleChildNode);
      }

      throttleChildNode = new ConfigurationNode(CONNECTIONNODE_RATE);
      throttleChildNode.setValue(new Float(rate).toString());
      child.addChild(child.getChildCount(),throttleChildNode);

      connectionNode.addChild(connectionNode.getChildCount(),child);
    }
   
  }

  // End of connection API code

}

TOP

Related Classes of org.apache.manifoldcf.crawler.system.ManifoldCF

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.