You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@manifoldcf.apache.org by kw...@apache.org on 2010/07/15 20:48:03 UTC

svn commit: r964536 - in /incubator/lcf/trunk/modules: filesystem-tests/org/apache/lcf/filesystem_tests/APISanity.java framework/core/org/apache/lcf/core/interfaces/ConfigurationNode.java framework/pull-agent/org/apache/lcf/crawler/system/LCF.java

Author: kwright
Date: Thu Jul 15 18:48:02 2010
New Revision: 964536

URL: http://svn.apache.org/viewvc?rev=964536&view=rev
Log:
Fix a couple problems in the API proper, and finish the simple test that exercises a good chunk of that API.

Modified:
    incubator/lcf/trunk/modules/filesystem-tests/org/apache/lcf/filesystem_tests/APISanity.java
    incubator/lcf/trunk/modules/framework/core/org/apache/lcf/core/interfaces/ConfigurationNode.java
    incubator/lcf/trunk/modules/framework/pull-agent/org/apache/lcf/crawler/system/LCF.java

Modified: incubator/lcf/trunk/modules/filesystem-tests/org/apache/lcf/filesystem_tests/APISanity.java
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/modules/filesystem-tests/org/apache/lcf/filesystem_tests/APISanity.java?rev=964536&r1=964535&r2=964536&view=diff
==============================================================================
--- incubator/lcf/trunk/modules/filesystem-tests/org/apache/lcf/filesystem_tests/APISanity.java (original)
+++ incubator/lcf/trunk/modules/filesystem-tests/org/apache/lcf/filesystem_tests/APISanity.java Thu Jul 15 18:48:02 2010
@@ -73,10 +73,16 @@ public class APISanity extends TestBase
       // Hey, we were able to install the file system connector etc.
       // Now, create a local test job and run it.
       IThreadContext tc = ThreadContextFactory.make();
-      
+      int i;
+      IJobManager jobManager = JobManagerFactory.make(tc);
+
       // Create a basic file system connection, and save it.
-      ConfigurationNode connectionObject = new ConfigurationNode("repositoryconnection");
+      ConfigurationNode connectionObject;
       ConfigurationNode child;
+      Configuration requestObject;
+      Configuration result;
+      
+      connectionObject = new ConfigurationNode("repositoryconnection");
       
       child = new ConfigurationNode("name");
       child.setValue("File Connection");
@@ -94,105 +100,160 @@ public class APISanity extends TestBase
       child.setValue("100");
       connectionObject.addChild(connectionObject.getChildCount(),child);
 
-      Configuration requestObject = new Configuration();
+      requestObject = new Configuration();
       requestObject.addChild(0,connectionObject);
       
-      Configuration result = performAPIOperationViaNodes("repositoryconnection/save",requestObject);
+      result = performAPIOperationViaNodes("repositoryconnection/save",requestObject);
       
-      int i = 0;
+      i = 0;
       while (i < result.getChildCount())
       {
         ConfigurationNode resultNode = result.findChild(i++);
-        if (resultNode.getType() == "error")
+        if (resultNode.getType().equals("error"))
           throw new Exception(resultNode.getValue());
       }
       
       // Create a basic null output connection, and save it.
-      IOutputConnectionManager outputMgr = OutputConnectionManagerFactory.make(tc);
-      IOutputConnection outputConn = outputMgr.create();
-      outputConn.setName("Null Connection");
-      outputConn.setDescription("Null Connection");
-      outputConn.setClassName("org.apache.lcf.agents.output.nullconnector.NullConnector");
-      outputConn.setMaxConnections(100);
-      // Now, save
-      outputMgr.save(outputConn);
+      connectionObject = new ConfigurationNode("outputconnection");
+      
+      child = new ConfigurationNode("name");
+      child.setValue("Null Connection");
+      connectionObject.addChild(connectionObject.getChildCount(),child);
+      
+      child = new ConfigurationNode("class_name");
+      child.setValue("org.apache.lcf.agents.output.nullconnector.NullConnector");
+      connectionObject.addChild(connectionObject.getChildCount(),child);
+      
+      child = new ConfigurationNode("description");
+      child.setValue("Null Connection");
+      connectionObject.addChild(connectionObject.getChildCount(),child);
+
+      child = new ConfigurationNode("max_connections");
+      child.setValue("100");
+      connectionObject.addChild(connectionObject.getChildCount(),child);
+
+      requestObject = new Configuration();
+      requestObject.addChild(0,connectionObject);
+      
+      result = performAPIOperationViaNodes("outputconnection/save",requestObject);
+      
+      i = 0;
+      while (i < result.getChildCount())
+      {
+        ConfigurationNode resultNode = result.findChild(i++);
+        if (resultNode.getType().equals("error"))
+          throw new Exception(resultNode.getValue());
+      }
 
       // Create a job.
-      IJobManager jobManager = JobManagerFactory.make(tc);
-      IJobDescription job = jobManager.createJob();
-      job.setDescription("Test Job");
-      job.setConnectionName("File Connection");
-      job.setOutputConnectionName("Null Connection");
-      job.setType(job.TYPE_SPECIFIED);
-      job.setStartMethod(job.START_DISABLE);
-      job.setHopcountMode(job.HOPCOUNT_ACCURATE);
+      ConfigurationNode jobObject = new ConfigurationNode("job");
       
-      // Now, set up the document specification.
-      DocumentSpecification ds = job.getSpecification();
+      child = new ConfigurationNode("description");
+      child.setValue("Test Job");
+      jobObject.addChild(jobObject.getChildCount(),child);
+
+      child = new ConfigurationNode("repository_connection");
+      child.setValue("File Connection");
+      jobObject.addChild(jobObject.getChildCount(),child);
+
+      child = new ConfigurationNode("output_connection");
+      child.setValue("Null Connection");
+      jobObject.addChild(jobObject.getChildCount(),child);
+
+      child = new ConfigurationNode("run_mode");
+      child.setValue("scan once");
+      jobObject.addChild(jobObject.getChildCount(),child);
+
+      child = new ConfigurationNode("start_mode");
+      child.setValue("manual");
+      jobObject.addChild(jobObject.getChildCount(),child);
+
+      child = new ConfigurationNode("hopcount_mode");
+      child.setValue("accurate");
+      jobObject.addChild(jobObject.getChildCount(),child);
+
+      child = new ConfigurationNode("document_specification");
       // Crawl everything underneath the 'testdata' area
       File testDataFile = new File("testdata").getCanonicalFile();
       if (!testDataFile.exists())
         throw new LCFException("Test data area not found!  Looking in "+testDataFile.toString());
       if (!testDataFile.isDirectory())
         throw new LCFException("Test data area not a directory!  Looking in "+testDataFile.toString());
-      SpecificationNode sn = new SpecificationNode("startpoint");
+      ConfigurationNode sn = new ConfigurationNode("startpoint");
       sn.setAttribute("path",testDataFile.toString());
-      SpecificationNode n = new SpecificationNode("include");
+      ConfigurationNode n = new ConfigurationNode("include");
       n.setAttribute("type","file");
       n.setAttribute("match","*");
       sn.addChild(sn.getChildCount(),n);
-      n = new SpecificationNode("include");
+      n = new ConfigurationNode("include");
       n.setAttribute("type","directory");
       n.setAttribute("match","*");
       sn.addChild(sn.getChildCount(),n);
-      ds.addChild(ds.getChildCount(),sn);
+      child.addChild(child.getChildCount(),sn);
+      jobObject.addChild(jobObject.getChildCount(),child);
       
-      // Set up the output specification.
-      OutputSpecification os = job.getOutputSpecification();
-      // Null output connections have no output specification, so this is a no-op.
+      requestObject = new Configuration();
+      requestObject.addChild(0,jobObject);
+      
+      result = performAPIOperationViaNodes("job/save",requestObject);
+      
+      String jobIDString = null;
+      i = 0;
+      while (i < result.getChildCount())
+      {
+        ConfigurationNode resultNode = result.findChild(i++);
+        if (resultNode.getType().equals("error"))
+          throw new Exception(resultNode.getValue());
+        else if (resultNode.getType().equals("job_id"))
+          jobIDString = resultNode.getValue();
+      }
+      if (jobIDString == null)
+        throw new Exception("Missing job_id from return!");
+      
+      Long jobID = new Long(jobIDString);
       
-      // Save the job.
-      jobManager.save(job);
-
       // Create the test data files.
       createFile(new File("testdata/test1.txt"),"This is a test file");
       createFile(new File("testdata/test2.txt"),"This is another test file");
       createDirectory(new File("testdata/testdir"));
       createFile(new File("testdata/testdir/test3.txt"),"This is yet another test file");
       
+      ConfigurationNode requestNode;
+      
       // Now, start the job, and wait until it completes.
-      jobManager.manualStart(job.getID());
-      waitJobInactive(jobManager,job.getID());
+      startJob(jobIDString);
+      waitJobInactive(jobIDString);
 
       // Check to be sure we actually processed the right number of documents.
-      JobStatus status = jobManager.getStatus(job.getID());
       // The test data area has 3 documents and one directory, and we have to count the root directory too.
-      if (status.getDocumentsProcessed() != 5)
-        throw new LCFException("Wrong number of documents processed - expected 5, saw "+new Long(status.getDocumentsProcessed()).toString());
+      long count;
+      count = getJobDocumentsProcessed(jobIDString);
+      if (count != 5)
+        throw new LCFException("Wrong number of documents processed - expected 5, saw "+new Long(count).toString());
       
       // Add a file and recrawl
       createFile(new File("testdata/testdir/test4.txt"),"Added file");
 
       // Now, start the job, and wait until it completes.
-      jobManager.manualStart(job.getID());
-      waitJobInactive(jobManager,job.getID());
+      startJob(jobIDString);
+      waitJobInactive(jobIDString);
 
-      status = jobManager.getStatus(job.getID());
       // The test data area has 4 documents and one directory, and we have to count the root directory too.
-      if (status.getDocumentsProcessed() != 6)
-        throw new LCFException("Wrong number of documents processed after add - expected 6, saw "+new Long(status.getDocumentsProcessed()).toString());
+      count = getJobDocumentsProcessed(jobIDString);
+      if (count != 6)
+        throw new LCFException("Wrong number of documents processed after add - expected 6, saw "+new Long(count).toString());
 
       // Change a file, and recrawl
       changeFile(new File("testdata/test1.txt"),"Modified contents");
       
       // Now, start the job, and wait until it completes.
-      jobManager.manualStart(job.getID());
-      waitJobInactive(jobManager,job.getID());
+      startJob(jobIDString);
+      waitJobInactive(jobIDString);
 
-      status = jobManager.getStatus(job.getID());
       // The test data area has 4 documents and one directory, and we have to count the root directory too.
-      if (status.getDocumentsProcessed() != 6)
-        throw new LCFException("Wrong number of documents processed after change - expected 6, saw "+new Long(status.getDocumentsProcessed()).toString());
+      count = getJobDocumentsProcessed(jobIDString);
+      if (count != 6)
+        throw new LCFException("Wrong number of documents processed after change - expected 6, saw "+new Long(count).toString());
       // We also need to make sure the new document was indexed.  Have to think about how to do this though.
       // MHL
       
@@ -200,18 +261,19 @@ public class APISanity extends TestBase
       removeFile(new File("testdata/test2.txt"));
       
       // Now, start the job, and wait until it completes.
-      jobManager.manualStart(job.getID());
-      waitJobInactive(jobManager,job.getID());
+      startJob(jobIDString);
+      waitJobInactive(jobIDString);
 
       // Check to be sure we actually processed the right number of documents.
-      status = jobManager.getStatus(job.getID());
       // The test data area has 3 documents and one directory, and we have to count the root directory too.
-      if (status.getDocumentsProcessed() != 5)
-        throw new LCFException("Wrong number of documents processed after delete - expected 5, saw "+new Long(status.getDocumentsProcessed()).toString());
+      count = getJobDocumentsProcessed(jobIDString);
+      if (count != 5)
+        throw new LCFException("Wrong number of documents processed after delete - expected 5, saw "+new Long(count).toString());
 
       // Now, delete the job.
-      jobManager.deleteJob(job.getID());
-      waitJobDeleted(jobManager,job.getID());
+      deleteJob(jobIDString);
+
+      waitJobDeleted(jobIDString);
       
       // Cleanup is automatic by the base class, so we can feel free to leave jobs and connections lying around.
     }
@@ -222,37 +284,130 @@ public class APISanity extends TestBase
     }
   }
   
-  protected void waitJobInactive(IJobManager jobManager, Long jobID)
-    throws LCFException, InterruptedException
+  protected void startJob(String jobIDString)
+    throws Exception
   {
-    while (true)
+    ConfigurationNode requestNode = new ConfigurationNode("job_id");
+    requestNode.setValue(jobIDString);
+    Configuration requestObject = new Configuration();
+    requestObject.addChild(0,requestNode);
+    
+    Configuration result = performAPIOperationViaNodes("jobstatus/start",requestObject);
+    int i = 0;
+    while (i < result.getChildCount())
     {
-      JobStatus status = jobManager.getStatus(jobID);
-      if (status == null)
-        throw new LCFException("No such job: '"+jobID+"'");
-      int statusValue = status.getStatus();
-      switch (statusValue)
+      ConfigurationNode resultNode = result.findChild(i++);
+      if (resultNode.getType().equals("error"))
+        throw new Exception(resultNode.getValue());
+    }
+  }
+  
+  protected void deleteJob(String jobIDString)
+    throws Exception
+  {
+    ConfigurationNode requestNode = new ConfigurationNode("job_id");
+    requestNode.setValue(jobIDString);
+    Configuration requestObject = new Configuration();
+    requestObject.addChild(0,requestNode);
+      
+    Configuration result = performAPIOperationViaNodes("job/delete",requestObject);
+    int i = 0;
+    while (i < result.getChildCount())
+    {
+      ConfigurationNode resultNode = result.findChild(i++);
+      if (resultNode.getType().equals("error"))
+        throw new Exception(resultNode.getValue());
+    }
+
+  }
+  
+  protected String getJobStatus(String jobIDString)
+    throws Exception
+  {
+    ConfigurationNode requestNode = new ConfigurationNode("job_id");
+    requestNode.setValue(jobIDString);
+    Configuration requestObject = new Configuration();
+    requestObject.addChild(0,requestNode);
+    
+    Configuration result = performAPIOperationViaNodes("jobstatus/get",requestObject);
+    String status = null;
+    int i = 0;
+    while (i < result.getChildCount())
+    {
+      ConfigurationNode resultNode = result.findChild(i++);
+      if (resultNode.getType().equals("error"))
+        throw new Exception(resultNode.getValue());
+      else if (resultNode.getType().equals("jobstatus"))
+      {
+        int j = 0;
+        while (j < resultNode.getChildCount())
+        {
+          ConfigurationNode childNode = resultNode.findChild(j++);
+          if (childNode.getType().equals("status"))
+            status = childNode.getValue();
+        }
+      }
+    }
+    return status;
+  }
+
+  protected long getJobDocumentsProcessed(String jobIDString)
+    throws Exception
+  {
+    ConfigurationNode requestNode = new ConfigurationNode("job_id");
+    requestNode.setValue(jobIDString);
+    Configuration requestObject = new Configuration();
+    requestObject.addChild(0,requestNode);
+    
+    Configuration result = performAPIOperationViaNodes("jobstatus/get",requestObject);
+    String documentsProcessed = null;
+    int i = 0;
+    while (i < result.getChildCount())
+    {
+      ConfigurationNode resultNode = result.findChild(i++);
+      if (resultNode.getType().equals("error"))
+        throw new Exception(resultNode.getValue());
+      else if (resultNode.getType().equals("jobstatus"))
       {
-        case JobStatus.JOBSTATUS_NOTYETRUN:
-          throw new LCFException("Job was never started.");
-        case JobStatus.JOBSTATUS_COMPLETED:
-          break;
-        case JobStatus.JOBSTATUS_ERROR:
-          throw new LCFException("Job reports error status: "+status.getErrorText());
-        default:
-          LCF.sleep(10000L);
-          continue;
+        int j = 0;
+        while (j < resultNode.getChildCount())
+        {
+          ConfigurationNode childNode = resultNode.findChild(j++);
+          if (childNode.getType().equals("documents_processed"))
+            documentsProcessed = childNode.getValue();
+        }
       }
-      break;
+    }
+    if (documentsProcessed == null)
+      throw new Exception("Expected a documents_processed field, didn't find it");
+    return new Long(documentsProcessed).longValue();
+  }
+
+  protected void waitJobInactive(String jobIDString)
+    throws Exception
+  {
+    while (true)
+    {
+      String status = getJobStatus(jobIDString);
+      if (status == null)
+        throw new Exception("No such job: '"+jobIDString+"'");
+      if (status.equals("not yet run"))
+        throw new Exception("Job was never started.");
+      if (status.equals("done"))
+        break;
+      if (status.equals("error"))
+        throw new Exception("Job reports error.");
+      LCF.sleep(10000L);
+      continue;
     }
   }
   
-  protected void waitJobDeleted(IJobManager jobManager, Long jobID)
-    throws LCFException, InterruptedException
+  protected void waitJobDeleted(String jobIDString)
+    throws Exception
   {
     while (true)
     {
-      JobStatus status = jobManager.getStatus(jobID);
+      String status = getJobStatus(jobIDString);
       if (status == null)
         break;
       LCF.sleep(10000L);

Modified: incubator/lcf/trunk/modules/framework/core/org/apache/lcf/core/interfaces/ConfigurationNode.java
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/modules/framework/core/org/apache/lcf/core/interfaces/ConfigurationNode.java?rev=964536&r1=964535&r2=964536&view=diff
==============================================================================
--- incubator/lcf/trunk/modules/framework/core/org/apache/lcf/core/interfaces/ConfigurationNode.java (original)
+++ incubator/lcf/trunk/modules/framework/core/org/apache/lcf/core/interfaces/ConfigurationNode.java Thu Jul 15 18:48:02 2010
@@ -50,12 +50,17 @@ public class ConfigurationNode
     this.type = source.type;
     this.value = source.value;
     this.readOnly = source.readOnly;
-    Iterator iter = source.attributes.keySet().iterator();
-    while (iter.hasNext())
+    if (source.attributes != null)
     {
-      String attribute = (String)iter.next();
-      String attrValue = (String)source.attributes.get(attribute);
-      this.attributes.put(attribute,attrValue);
+      Iterator iter = source.attributes.keySet().iterator();
+      while (iter.hasNext())
+      {
+        String attribute = (String)iter.next();
+        String attrValue = (String)source.attributes.get(attribute);
+        if (this.attributes == null)
+          this.attributes = new HashMap();
+        this.attributes.put(attribute,attrValue);
+      }
     }
     int i = 0;
     while (i < source.getChildCount())

Modified: incubator/lcf/trunk/modules/framework/pull-agent/org/apache/lcf/crawler/system/LCF.java
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/modules/framework/pull-agent/org/apache/lcf/crawler/system/LCF.java?rev=964536&r1=964535&r2=964536&view=diff
==============================================================================
--- incubator/lcf/trunk/modules/framework/pull-agent/org/apache/lcf/crawler/system/LCF.java (original)
+++ incubator/lcf/trunk/modules/framework/pull-agent/org/apache/lcf/crawler/system/LCF.java Thu Jul 15 18:48:02 2010
@@ -1156,6 +1156,37 @@ public class LCF extends org.apache.lcf.
         rval.addChild(rval.getChildCount(),error);
       }
     }
+    else if (command.equals("jobstatus/get"))
+    {
+      // Get the job id from the argument
+      if (inputArgument == null)
+        throw new LCFException("Input argument required");
+      
+      String jobID = getRootArgument(inputArgument,API_JOBIDNODE);
+      if (jobID == null)
+        throw new LCFException("Input argument must have '"+API_JOBIDNODE+"' field");
+
+      try
+      {
+        IJobManager jobManager = JobManagerFactory.make(tc);
+        JobStatus status = jobManager.getStatus(new Long(jobID));
+	if (status != null)
+        {
+          ConfigurationNode jobStatusNode = new ConfigurationNode(API_JOBSTATUSNODE);
+          formatJobStatus(jobStatusNode,status);
+          rval.addChild(rval.getChildCount(),jobStatusNode);
+        }
+      }
+      catch (LCFException e)
+      {
+        if (e.getErrorCode() == LCFException.INTERRUPTED)
+          throw e;
+        Logging.api.error(e.getMessage(),e);
+        ConfigurationNode error = new ConfigurationNode(API_ERRORNODE);
+        error.setValue(e.getMessage());
+        rval.addChild(rval.getChildCount(),error);
+      }
+    }
     else if (command.equals("jobstatus/start"))
     {
       // Get the job id from the argument
@@ -1845,8 +1876,10 @@ public class LCF extends org.apache.lcf.
   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_RECORD = "record";
+  protected static final String JOBNODE_LINKTYPE = "link_type";
+  protected static final String JOBNODE_COUNT = "count";
   protected static final String JOBNODE_TIMEZONE = "timezone";
   protected static final String JOBNODE_DURATION = "duration";
   protected static final String JOBNODE_DAYOFWEEK = "dayofweek";
@@ -1947,73 +1980,85 @@ public class LCF extends org.apache.lcf.
       {
         jobDescription.setReseedInterval(interpretInterval(child.getValue()));
       }
-      else if (childType.equals(JOBNODE_SCHEDULE))
+      else if (childType.equals(JOBNODE_HOPCOUNT))
       {
-        // The children of this node should be schedule records.  Walk through them.
-        int k = 0;
-        while (k < child.getChildCount())
+        // Read the hopcount values
+        String linkType = null;
+        String hopCount = null;
+        
+        int q = 0;
+        while (q < child.getChildCount())
         {
-          ConfigurationNode scheduleNode = child.findChild(k++);
-          if (scheduleNode.getType().equals(JOBNODE_RECORD))
-          {
-            // Create a schedule record.
-            String timezone = null;
-            Long duration = null;
-            EnumeratedValues dayOfWeek = null;
-            EnumeratedValues monthOfYear = null;
-            EnumeratedValues dayOfMonth = null;
-            EnumeratedValues year = null;
-            EnumeratedValues hourOfDay = null;
-            EnumeratedValues minutesOfHour = null;
+          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 LCFException("Found an unexpected node type: '"+cn.getType()+"'");
+        }
+        if (linkType == null)
+          throw new LCFException("Missing required field: '"+JOBNODE_LINKTYPE+"'");
+        if (hopCount == null)
+          throw new LCFException("Missing required field: '"+JOBNODE_COUNT+"'");
+        jobDescription.addHopCountFilter(linkType,new Long(hopCount));
+      }
+      else if (childType.equals(JOBNODE_SCHEDULE))
+      {
+        // Create a schedule record.
+        String timezone = null;
+        Long duration = null;
+        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 < scheduleNode.getChildCount())
-            {
-              ConfigurationNode scheduleField = scheduleNode.findChild(q++);
-              String fieldType = scheduleField.getType();
-              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 LCFException("Unrecognized field in schedule record: '"+fieldType+"'");
-            }
-            ScheduleRecord sr = new ScheduleRecord(dayOfWeek,monthOfYear,dayOfMonth,year,hourOfDay,minutesOfHour,timezone,duration);
-            // Add the schedule record to the job.
-            jobDescription.addScheduleRecord(sr);
+        // 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_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 LCFException("Encountered an unexpected node type in schedule: '"+scheduleNode.getType()+"'");
+            throw new LCFException("Unrecognized field in schedule record: '"+fieldType+"'");
         }
+        ScheduleRecord sr = new ScheduleRecord(dayOfWeek,monthOfYear,dayOfMonth,year,hourOfDay,minutesOfHour,timezone,duration);
+        // Add the schedule record to the job.
+        jobDescription.addScheduleRecord(sr);
       }
       else
         throw new LCFException("Unrecognized job field: '"+childType+"'");
@@ -2128,13 +2173,30 @@ public class LCF extends org.apache.lcf.
       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);
+    }
+    
     // Schedule records
     child = new ConfigurationNode(JOBNODE_SCHEDULE);
     j = 0;
     while (j < job.getScheduleRecordCount())
     {
       ScheduleRecord sr = job.getScheduleRecord(j++);
-      ConfigurationNode recordNode = new ConfigurationNode(JOBNODE_RECORD);
       ConfigurationNode recordChild;
       
       // timezone
@@ -2142,7 +2204,7 @@ public class LCF extends org.apache.lcf.
       {
         recordChild = new ConfigurationNode(JOBNODE_TIMEZONE);
         recordChild.setValue(sr.getTimezone());
-        recordNode.addChild(recordNode.getChildCount(),recordChild);
+        child.addChild(child.getChildCount(),recordChild);
       }
 
       // duration
@@ -2150,26 +2212,24 @@ public class LCF extends org.apache.lcf.
       {
         recordChild = new ConfigurationNode(JOBNODE_DURATION);
         recordChild.setValue(sr.getDuration().toString());
-        recordNode.addChild(recordNode.getChildCount(),recordChild);
+        child.addChild(child.getChildCount(),recordChild);
       }
       
       // Schedule specification values
       
       // day of week
       if (sr.getDayOfWeek() != null)
-        formatEnumeratedValues(recordNode,JOBNODE_DAYOFWEEK,sr.getDayOfWeek());
+        formatEnumeratedValues(child,JOBNODE_DAYOFWEEK,sr.getDayOfWeek());
       if (sr.getMonthOfYear() != null)
-        formatEnumeratedValues(recordNode,JOBNODE_MONTHOFYEAR,sr.getMonthOfYear());
+        formatEnumeratedValues(child,JOBNODE_MONTHOFYEAR,sr.getMonthOfYear());
       if (sr.getDayOfMonth() != null)
-        formatEnumeratedValues(recordNode,JOBNODE_DAYOFMONTH,sr.getDayOfMonth());
+        formatEnumeratedValues(child,JOBNODE_DAYOFMONTH,sr.getDayOfMonth());
       if (sr.getYear() != null)
-        formatEnumeratedValues(recordNode,JOBNODE_YEAR,sr.getYear());
+        formatEnumeratedValues(child,JOBNODE_YEAR,sr.getYear());
       if (sr.getHourOfDay() != null)
-        formatEnumeratedValues(recordNode,JOBNODE_HOUROFDAY,sr.getHourOfDay());
+        formatEnumeratedValues(child,JOBNODE_HOUROFDAY,sr.getHourOfDay());
       if (sr.getMinutesOfHour() != null)
-        formatEnumeratedValues(recordNode,JOBNODE_MINUTESOFHOUR,sr.getMinutesOfHour());
-
-      child.addChild(child.getChildCount(),recordNode);
+        formatEnumeratedValues(child,JOBNODE_MINUTESOFHOUR,sr.getMinutesOfHour());
     }
     jobNode.addChild(jobNode.getChildCount(),child);
   }
@@ -2425,7 +2485,6 @@ public class LCF extends org.apache.lcf.
   protected static final String CONNECTIONNODE_DESCRIPTION = "description";
   protected static final String CONNECTIONNODE_CONFIGURATION = "configuration";
   protected static final String CONNECTIONNODE_ACLAUTHORITY = "acl_authority";
-  protected static final String CONNECTIONNODE_THROTTLES = "throttles";
   protected static final String CONNECTIONNODE_THROTTLE = "throttle";
   protected static final String CONNECTIONNODE_MATCH = "match";
   protected static final String CONNECTIONNODE_MATCHDESCRIPTION = "match_description";
@@ -2701,49 +2760,37 @@ public class LCF extends org.apache.lcf.
           throw new LCFException("Connection aclauthority node requires a value");
         connection.setACLAuthority(child.getValue());
       }
-      else if (childType.equals(CONNECTIONNODE_THROTTLES))
+      else if (childType.equals(CONNECTIONNODE_THROTTLE))
       {
-        // Go through children
-        connection.clearThrottleValues();
-        int k = 0;
-        while (k < child.getChildCount())
+        String match = null;
+        String description = null;
+        Float rate = null;
+            
+        int q = 0;
+        while (q < child.getChildCount())
         {
-          ConfigurationNode throttleNode = child.findChild(k++);
-          if (throttleNode.getType().equals(CONNECTIONNODE_THROTTLE))
+          ConfigurationNode throttleField = child.findChild(q++);
+          String fieldType = throttleField.getType();
+          if (fieldType.equals(CONNECTIONNODE_MATCH))
           {
-            String match = null;
-            String description = null;
-            Float rate = null;
-            
-            int q = 0;
-            while (q < throttleNode.getChildCount())
-            {
-              ConfigurationNode throttleField = throttleNode.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 LCFException("Unrecognized throttle field: '"+fieldType+"'");
-            }
-            if (match == null)
-              throw new LCFException("Missing throttle field: '"+CONNECTIONNODE_MATCH+"'");
-            if (rate == null)
-              throw new LCFException("Missing throttle field: '"+CONNECTIONNODE_RATE+"'");
-            connection.addThrottleValue(match,description,rate.floatValue());
+            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 LCFException("Unrecognized throttles node: '"+throttleNode.getType()+"'");
+            throw new LCFException("Unrecognized throttle field: '"+fieldType+"'");
         }
+        if (match == null)
+          throw new LCFException("Missing throttle field: '"+CONNECTIONNODE_MATCH+"'");
+        if (rate == null)
+          throw new LCFException("Missing throttle field: '"+CONNECTIONNODE_RATE+"'");
+        connection.addThrottleValue(match,description,rate.floatValue());
       }
       else
         throw new LCFException("Unrecognized repository connection field: '"+childType+"'");
@@ -2798,7 +2845,6 @@ public class LCF extends org.apache.lcf.
       connectionNode.addChild(connectionNode.getChildCount(),child);
     }
     
-    child = new ConfigurationNode(CONNECTIONNODE_THROTTLES);
     String[] throttles = connection.getThrottles();
     j = 0;
     while (j < throttles.length)
@@ -2806,25 +2852,25 @@ public class LCF extends org.apache.lcf.
       String match = throttles[j++];
       String description = connection.getThrottleDescription(match);
       float rate = connection.getThrottleValue(match);
-      ConfigurationNode throttleNode = new ConfigurationNode(CONNECTIONNODE_THROTTLE);
+      child = new ConfigurationNode(CONNECTIONNODE_THROTTLE);
       ConfigurationNode throttleChildNode;
       
       throttleChildNode = new ConfigurationNode(CONNECTIONNODE_MATCH);
       throttleChildNode.setValue(match);
-      throttleNode.addChild(throttleNode.getChildCount(),throttleChildNode);
+      child.addChild(child.getChildCount(),throttleChildNode);
       
       if (description != null)
       {
         throttleChildNode = new ConfigurationNode(CONNECTIONNODE_MATCHDESCRIPTION);
         throttleChildNode.setValue(description);
-        throttleNode.addChild(throttleNode.getChildCount(),throttleChildNode);
+        child.addChild(child.getChildCount(),throttleChildNode);
       }
 
       throttleChildNode = new ConfigurationNode(CONNECTIONNODE_RATE);
       throttleChildNode.setValue(new Float(rate).toString());
-      throttleNode.addChild(throttleNode.getChildCount(),throttleChildNode);
+      child.addChild(child.getChildCount(),throttleChildNode);
 
-      child.addChild(child.getChildCount(),throttleNode);
+      connectionNode.addChild(connectionNode.getChildCount(),child);
     }
     
   }