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 2013/12/21 14:38:35 UTC

svn commit: r1552900 [1/2] - in /manifoldcf/trunk: ./ connectors/ connectors/alfresco/ connectors/email/ connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/ connectors/email/connector/src/main/native2ascii/org/apach...

Author: kwright
Date: Sat Dec 21 13:38:34 2013
New Revision: 1552900

URL: http://svn.apache.org/r1552900
Log:
Finish CONNECTORS-553.  Add email connector

Added:
    manifoldcf/trunk/connectors/email/   (props changed)
      - copied from r1552345, manifoldcf/branches/CONNECTORS-553-2/connectors/email/
    manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailSession.java
      - copied unchanged from r1552899, manifoldcf/branches/CONNECTORS-553/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailSession.java
    manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/Configuration_URL.html
      - copied unchanged from r1552899, manifoldcf/branches/CONNECTORS-553/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/Configuration_URL.html
Modified:
    manifoldcf/trunk/   (props changed)
    manifoldcf/trunk/CHANGES.txt
    manifoldcf/trunk/build.xml
    manifoldcf/trunk/connectors/alfresco/build.xml
    manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailConfig.java
    manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailConnector.java
    manifoldcf/trunk/connectors/email/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/email/common_en_US.properties
    manifoldcf/trunk/connectors/email/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/email/common_ja_JP.properties
    manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/ConfigurationHeader.js
    manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/ConfigurationView.html
    manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/SpecificationView.html
    manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/Specification_Filter.html
    manifoldcf/trunk/connectors/pom.xml
    manifoldcf/trunk/framework/build.xml
    manifoldcf/trunk/tests/alfresco/build.xml

Propchange: manifoldcf/trunk/
------------------------------------------------------------------------------
  Merged /manifoldcf/branches/CONNECTORS-553-2:r1550121-1550124,1550464-1550466,1552306-1552345
  Merged /manifoldcf/branches/CONNECTORS-553:r1534730-1550120,1550125-1550463,1550467-1552343,1552346-1552899

Modified: manifoldcf/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/CHANGES.txt?rev=1552900&r1=1552899&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/CHANGES.txt (original)
+++ manifoldcf/trunk/CHANGES.txt Sat Dec 21 13:38:34 2013
@@ -3,6 +3,9 @@ $Id$
 
 ======================= 1.5-dev =====================
 
+CONNECTORS-553: Add email connector.
+(Tishan DahanaYakage, Piergiorgio Lucidi, Karl Wright)
+
 CONNECTORS-846: Once a service had grabbed all connector
 instances, but was not using them any more, it would not release
 them for other cluster members to use.

Modified: manifoldcf/trunk/build.xml
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/build.xml?rev=1552900&r1=1552899&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/build.xml (original)
+++ manifoldcf/trunk/build.xml Sat Dec 21 13:38:34 2013
@@ -59,6 +59,7 @@
         <ant dir="connectors/alfresco" target="clean"/>
         <ant dir="connectors/cmis" target="clean"/>
         <ant dir="connectors/dropbox" target="clean"/>
+        <ant dir="connectors/email" target="clean"/>
         <ant dir="connectors/generic" target="clean"/>
         <ant dir="connectors/googledrive" target="clean"/>
         <ant dir="connectors/jira" target="clean"/>
@@ -116,6 +117,7 @@
         <ant dir="connectors/alfresco" target="clean"/>
         <ant dir="connectors/cmis" target="clean"/>
         <ant dir="connectors/dropbox" target="clean"/>
+        <ant dir="connectors/email" target="clean"/>
         <ant dir="connectors/generic" target="clean"/>
         <ant dir="connectors/googledrive" target="clean"/>
         <ant dir="connectors/jira" target="clean"/>
@@ -1127,6 +1129,41 @@
         <ant dir="connectors/wiki" target="run-tests-HSQLDB"/>
     </target>
 
+    <target name="setup-email-connector" depends="build-framework" if="downloaded"/>
+    
+    <target name="setup-email-connector-tests" depends="build-tests-framework" if="downloaded"/>
+
+    <target name="build-email-connector" depends="setup-email-connector" if="downloaded">
+        <ant dir="connectors/email" target="build"/>
+    </target>
+
+    <target name="doc-email-connector" depends="setup-email-connector" if="downloaded">
+        <ant dir="connectors/email" target="doc"/>
+    </target>
+
+    <target name="build-tests-email-connector" depends="setup-email-connector,setup-email-connector-tests" if="downloaded">
+        <ant dir="connectors/email" target="build-tests"/>
+    </target>
+
+    <target name="run-tests-email-connector" depends="setup-email-connector,setup-email-connector-tests" if="downloaded">
+        <ant dir="connectors/email" target="run-tests"/>
+    </target>
+
+    <target name="run-tests-derby-email-connector" depends="setup-email-connector,setup-email-connector-tests" if="downloaded">
+        <ant dir="connectors/email" target="run-tests-derby"/>
+    </target>
+
+    <target name="run-tests-postgresql-email-connector" depends="setup-email-connector,setup-email-connector-tests" if="downloaded">
+        <ant dir="connectors/email" target="run-tests-postgresql"/>
+    </target>
+
+    <target name="run-tests-mysql-email-connector" depends="setup-email-connector,setup-email-connector-tests" if="downloaded">
+        <ant dir="connectors/email" target="run-tests-mysql"/>
+    </target>
+
+    <target name="run-tests-HSQLDB-email-connector" depends="setup-email-connector,setup-email-connector-tests" if="downloaded">
+        <ant dir="connectors/email" target="run-tests-HSQLDB"/>
+    </target>
 
     <target name="deliver-site-doc" depends="presite-check" if="site-exists">
           <mkdir dir="dist/doc"/>
@@ -2518,6 +2555,43 @@
         </antcall>
     </target>
 
+    <target name="calculate-email-condition" depends="build-email-connector">
+        <available file="connectors/email/dist/lib" type="dir" property="email.exists"/>
+        <condition property="email.include">
+            <and>
+                <isset property="email.exists"/>
+                <isset property="downloaded"/>
+            </and>
+        </condition>
+    </target>
+
+    <target name="calculate-email-doc-condition" depends="doc-email-connector">
+        <available file="connectors/email/dist/doc" type="dir" property="email-doc.exists"/>
+        <condition property="email-doc.include">
+            <and>
+                <isset property="email-doc.exists"/>
+                <isset property="downloaded"/>
+            </and>
+        </condition>
+    </target>
+
+    <target name="deliver-email-connector" depends="calculate-email-condition" if="email.include">
+        <antcall target="general-connector-delivery">
+            <param name="connector-name" value="email"/>
+        </antcall>
+        <antcall target="general-add-repository-connector">
+            <param name="connector-name" value="email"/>
+            <param name="connector-label" value="EMail"/>
+            <param name="connector-class" value="org.apache.manifoldcf.crawler.connectors.email.EmailConnector"/>
+        </antcall>
+    </target>
+    
+    <target name="deliver-email-connector-doc" depends="calculate-email-doc-condition" if="email-doc.include">
+        <antcall target="general-connector-doc-delivery">
+            <param name="connector-name" value="email"/>
+        </antcall>
+    </target>
+
     <target name="calculate-filesystem-tests-condition" depends="calculate-filesystem-condition,calculate-nulloutput-condition">
       <condition property="filesystem-tests.include">
         <and>
@@ -2995,8 +3069,8 @@
 
     <target name="end-to-end-loadtests-HSQLDB" depends="run-filesystem-loadtests-HSQLDB,run-hdfs-loadtests-HSQLDB,run-rss-loadtests-HSQLDB,run-wiki-loadtests-HSQLDB,run-alfresco-loadtests-HSQLDB,run-cmis-loadtests-HSQLDB,run-sharepoint-loadtests-HSQLDB"/>
 
-    <target name="deliver-open-connectors" depends="deliver-generic-connector,deliver-jira-connector,deliver-googledrive-connector,deliver-dropbox-connector,deliver-nullauthority-connector,deliver-activedirectory-connector,deliver-ldap-connector,deliver-alfresco-connector,deliver-cmis-connector,deliver-filesystem-connector,deliver-hdfs-connector,deliver-rss-connector,deliver-webcrawler-connector,deliver-wiki-connector,deliver-jdbc-connector"/>
-    <target name="deliver-open-connectors-doc" depends="deliver-generic-connector-doc,deliver-jira-connector-doc,deliver-googledrive-connector-doc,deliver-dropbox-connector-doc,deliver-nullauthority-connector-doc,deliver-activedirectory-connector-doc,deliver-ldap-connector-doc,deliver-alfresco-connector-doc,deliver-cmis-connector-doc,deliver-filesystem-connector-doc,deliver-hdfs-connector-doc,deliver-rss-connector-doc,deliver-webcrawler-connector-doc,deliver-wiki-connector-doc,deliver-jdbc-connector-doc"/>
+    <target name="deliver-open-connectors" depends="deliver-email-connector,deliver-generic-connector,deliver-jira-connector,deliver-googledrive-connector,deliver-dropbox-connector,deliver-nullauthority-connector,deliver-activedirectory-connector,deliver-ldap-connector,deliver-alfresco-connector,deliver-cmis-connector,deliver-filesystem-connector,deliver-hdfs-connector,deliver-rss-connector,deliver-webcrawler-connector,deliver-wiki-connector,deliver-jdbc-connector"/>
+    <target name="deliver-open-connectors-doc" depends="deliver-email-connector-doc,deliver-generic-connector-doc,deliver-jira-connector-doc,deliver-googledrive-connector-doc,deliver-dropbox-connector-doc,deliver-nullauthority-connector-doc,deliver-activedirectory-connector-doc,deliver-ldap-connector-doc,deliver-alfresco-connector-doc,deliver-cmis-connector-doc,deliver-filesystem-connector-doc,deliver-hdfs-connector-doc,deliver-rss-connector-doc,deliver-webcrawler-connector-doc,deliver-wiki-connector-doc,deliver-jdbc-connector-doc"/>
 
     <target name="deliver-output-connectors" depends="deliver-gts-connector,deliver-solr-connector,deliver-nulloutput-connector,deliver-opensearchserver-connector,deliver-elasticsearch-connector"/>
     <target name="deliver-output-connectors-doc" depends="deliver-gts-connector-doc,deliver-solr-connector-doc,deliver-nulloutput-connector-doc,deliver-opensearchserver-connector-doc,deliver-elasticsearch-connector-doc"/>
@@ -4184,6 +4258,7 @@ Use Apache Forrest version forrest-0.9-d
         <ant dir="connectors/activedirectory" target="download-dependencies"/>
         <ant dir="connectors/ldap" target="download-dependencies"/>
         <ant dir="connectors/documentum" target="download-dependencies"/>
+        <ant dir="connectors/email" target="download-dependencies"/>
         <ant dir="connectors/filenet" target="download-dependencies"/>
         <ant dir="connectors/filesystem" target="download-dependencies"/>
         <ant dir="connectors/gts" target="download-dependencies"/>

Modified: manifoldcf/trunk/connectors/alfresco/build.xml
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/connectors/alfresco/build.xml?rev=1552900&r1=1552899&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/connectors/alfresco/build.xml (original)
+++ manifoldcf/trunk/connectors/alfresco/build.xml Sat Dec 21 13:38:34 2013
@@ -50,7 +50,6 @@
             <include name="saaj*.jar"/>	
             <include name="commons-discovery*.jar"/>
             <include name="jaxrpc*.jar"/>
-            <include name="mail*.jar"/>
             <include name="opensaml*.jar"/>
             <include name="wsdl4j*.jar"/>
             <include name="wss4j*.jar"/>
@@ -73,7 +72,6 @@
                 <include name="saaj*.jar"/>	
                 <include name="commons-discovery*.jar"/>
                 <include name="jaxrpc*.jar"/>
-                <include name="mail*.jar"/>
                 <include name="opensaml*.jar"/>
                 <include name="wsdl4j*.jar"/>
                 <include name="wss4j*.jar"/>

Propchange: manifoldcf/trunk/connectors/email/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Sat Dec 21 13:38:34 2013
@@ -0,0 +1,10 @@
+build
+dist
+doc
+target
+test-output
+test-derby-output
+test-postgresql-output
+test-HSQLDB-output
+test-HSQLDBext-output
+test-mysql-output

Modified: manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailConfig.java
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailConfig.java?rev=1552900&r1=1552345&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailConfig.java (original)
+++ manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailConfig.java Sat Dec 21 13:38:34 2013
@@ -49,51 +49,65 @@ public class EmailConfig {
   public static final String PORT_PARAM = "port";
 
   /**
-  * Properties
+  * URL template
   */
-  public static final String PROPERTIES_PARAM = "properties";
-
+  public static final String URL_PARAM = "url";
+  
+  // Protocol options
+  
   public static final String PROTOCOL_IMAP = "IMAP";
-  public static final String PROTOCOL_IMAP_PROVIDER = "imap";
   public static final String PROTOCOL_IMAPS = "IMAP-SSL";
-  public static final String PROTOCOL_IMAPS_PROVIDER = "imaps";
   public static final String PROTOCOL_POP3 = "POP3";
-  public static final String PROTOCOL_POP3_PROVIDER = "pop3";
   public static final String PROTOCOL_POP3S = "POP3-SSL";
+  
+  // Protocol providers
+  
+  public static final String PROTOCOL_IMAP_PROVIDER = "imap";
+  public static final String PROTOCOL_IMAPS_PROVIDER = "imaps";
+  public static final String PROTOCOL_POP3_PROVIDER = "pop3";
   public static final String PROTOCOL_POP3S_PROVIDER = "pop3s";
   
+  // Default values and various other constants
+  
   public static final String PROTOCOL_DEFAULT_VALUE = "IMAP";
-  public static final String PORT_DEFAULT_VALUE = "25";
-
+  public static final String PORT_DEFAULT_VALUE = "";
+  public static final String[] BASIC_METADATA = {"To","From","Subject","Body","Date","Encoding of Attachment","MIME type of attachment"};
+  public static final String[] BASIC_SEARCHABLE_ATTRIBUTES = {"To","From","Subject","Body","Date"};
 
+  // Specification nodes
+  
   public static final String NODE_PROPERTIES = "properties";
   public static final String NODE_METADATA = "metadata";
   public static final String NODE_FILTER = "filter";
-  public static final String SERVER_PROPERTY = "serverproperty";
-  public static final String VALUE = "p_value";
+  public static final String NODE_FOLDER = "folder";
+  
   public static final String ATTRIBUTE_NAME = "name";
-
   public static final String ATTRIBUTE_VALUE = "value";
-  public static final String[] BASIC_METADATA = {"To","From","Subject","Body","Date","Encoding of Attachment","MIME type of attachment"};
-  public static final String[] BASIC_SEARCHABLE_ATTRIBUTES = {"To","From","Subject","Body","Date","Folder"};
-  protected final static String ACTIVITY_FETCH = "fetch";
 
-
-  public static final String RELATIONSHIP_CHILD = "child";
-  public static final String FOLDER_INBOX = "INBOX";
+  // Metadata field names
+  
   public static final String EMAIL_SUBJECT = "subject";
   public static final String EMAIL_FROM = "from";
   public static final String EMAIL_TO = "to";
   public static final String EMAIL_BODY = "body";
-  public static final String ATTRIBUTE_FOLDER = "Folder";
   public static final String EMAIL_DATE = "date";
   public static final String EMAIL_ATTACHMENT_ENCODING = "encoding of attachment";
   public static final String EMAIL_ATTACHMENT_MIMETYPE = "mime type of attachment";
   public static final String EMAIL_VERSION = "1.0";
+  
+  // Mime types
+  
   public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
   public static final String MIMETYPE_HTML = "text/html";
+  
+  // Fields
+  
   public static final String ENCODING_FIELD = "encoding";
   public static final String MIMETYPE_FIELD = "mimetype";
-  protected final static long SESSION_EXPIRATION_MILLISECONDS = 300000L;
   //public static final String TO = "To";
+  
+  // Activity names
+  
+  public final static String ACTIVITY_FETCH = "fetch";
+
 }
\ No newline at end of file

Modified: manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailConnector.java
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailConnector.java?rev=1552900&r1=1552345&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailConnector.java (original)
+++ manifoldcf/trunk/connectors/email/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/email/EmailConnector.java Sat Dec 21 13:38:34 2013
@@ -68,17 +68,23 @@ import javax.mail.search.*;
 
 public class EmailConnector extends org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector {
 
+  protected final static long SESSION_EXPIRATION_MILLISECONDS = 300000L;
+  
   // Local variables.
   protected long sessionExpiration = -1L;
+  
+  // Parameters for establishing a session
+  
   protected String server = null;
-  protected String port = null;
+  protected String portString = null;
   protected String username = null;
   protected String password = null;
   protected String protocol = null;
-  protected Map<String, String> properties = new HashMap<String,String>();
-  private String folderName = null;
-  private Folder folder;
-  private Store store;
+  protected Properties properties = null;
+  protected String urlTemplate = null;
+  
+  // Local session handle
+  protected EmailSession session = null;
 
   private static Map<String,String> providerMap;
   static
@@ -101,10 +107,12 @@ public class EmailConnector extends org.
   public void connect(ConfigParams configParameters) {
     super.connect(configParameters);
     this.server = configParameters.getParameter(EmailConfig.SERVER_PARAM);
-    this.port = configParameters.getParameter(EmailConfig.PORT_PARAM);
+    this.portString = configParameters.getParameter(EmailConfig.PORT_PARAM);
     this.protocol = configParameters.getParameter(EmailConfig.PROTOCOL_PARAM);
     this.username = configParameters.getParameter(EmailConfig.USERNAME_PARAM);
-    this.password = configParameters.getParameter(EmailConfig.PASSWORD_PARAM);
+    this.password = configParameters.getObfuscatedParameter(EmailConfig.PASSWORD_PARAM);
+    this.urlTemplate = configParameters.getParameter(EmailConfig.URL_PARAM);
+    this.properties = new Properties();
     int i = 0;
     while (i < configParameters.getChildCount()) //In post property set is added as a configuration node
     {
@@ -112,7 +120,7 @@ public class EmailConnector extends org.
       if (cn.getType().equals(EmailConfig.NODE_PROPERTIES)) {
         String findParameterName = cn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
         String findParameterValue = cn.getAttributeValue(EmailConfig.ATTRIBUTE_VALUE);
-        this.properties.put(findParameterName, findParameterValue);
+        this.properties.setProperty(findParameterName, findParameterValue);
       }
     }
   }
@@ -124,8 +132,9 @@ public class EmailConnector extends org.
   @Override
   public void disconnect()
     throws ManifoldCFException {
+    this.urlTemplate = null;
     this.server = null;
-    this.port = null;
+    this.portString = null;
     this.protocol = null;
     this.username = null;
     this.password = null;
@@ -140,7 +149,7 @@ public class EmailConnector extends org.
   */
   @Override
   public void poll() throws ManifoldCFException {
-    if (store != null)
+    if (session != null)
     {
       if (System.currentTimeMillis() >= sessionExpiration)
         finalizeConnection();
@@ -152,6 +161,7 @@ public class EmailConnector extends org.
   *
   * @return the connection's status as a displayable string.
   */
+  @Override
   public String check()
       throws ManifoldCFException {
     try {
@@ -165,23 +175,17 @@ public class EmailConnector extends org.
   }
 
   protected void checkConnection() throws ManifoldCFException, ServiceInterruption {
-    while (true) {
-      try {
-        store = getSession().getStore(providerMap.get(protocol));
-        store.connect(server, username, password);
-        Folder defaultFolder = store.getDefaultFolder();
-        if (defaultFolder == null) {
-          Logging.connectors.warn("Email: Error checking the connection: No default folder.");
-          throw new ManifoldCFException("Error checking the connection: No default folder.");
-        }
-
-      } catch (Exception e) {
-        Logging.connectors.warn(
-            "Email: Error checking the connection: "+e.getMessage(),e);
-        throw new ManifoldCFException("Error checking the connection: "+e.getMessage(),e);
-      }
-      store = null;
-      return;
+    // Force a re-connection
+    finalizeConnection();
+    getSession();
+    try {
+      CheckConnectionThread cct = new CheckConnectionThread(session);
+      cct.start();
+      cct.finishUp();
+    } catch (InterruptedException e) {
+      throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
+    } catch (MessagingException e) {
+      handleMessagingException(e,"checking the connection");
     }
   }
 
@@ -189,7 +193,7 @@ public class EmailConnector extends org.
 
   //////////////////////////////Start of Repository Connector Method///////////////////////////////////
 
-
+  @Override
   public int getConnectorModel() {
     return MODEL_ADD; //Change is not applicable in context of email
   }
@@ -199,22 +203,12 @@ public class EmailConnector extends org.
   *
   * @return the list.
   */
+  @Override
   public String[] getActivitiesList() {
     return new String[]{EmailConfig.ACTIVITY_FETCH};
   }
 
   /**
-  * Return the list of relationship types that this connector recognizes.
-  *
-  * @return the list.
-  */
-  public String[] getRelationshipTypes() {
-    String[] relationships = new String[1];
-    relationships[0] = EmailConfig.RELATIONSHIP_CHILD;
-    return relationships;
-  }
-
-  /**
   * Get the bin name strings for a document identifier. The bin name describes the queue to which the
   * document will be assigned for throttling purposes. Throttling controls the rate at which items in a
   * given queue are fetched; it does not say anything about the overall fetch rate, which may operate on
@@ -226,6 +220,7 @@ public class EmailConnector extends org.
   * @return the set of bin names. If an empty array is returned, it is equivalent to there being no request
   * rate throttling available for this identifier.
   */
+  @Override
   public String[] getBinNames(String documentIdentifier) {
     return new String[]{server};
   }
@@ -235,8 +230,9 @@ public class EmailConnector extends org.
   *
   * @return the maximum number. 0 indicates "unlimited".
   */
+  @Override
   public int getMaxDocumentRequest() {
-    return 50;
+    return 10;
   }
 
   /**
@@ -271,117 +267,169 @@ public class EmailConnector extends org.
   public void addSeedDocuments(ISeedingActivity activities,
     DocumentSpecification spec, long startTime, long endTime, int jobMode)
     throws ManifoldCFException, ServiceInterruption {
-    Session session = getSession();
-    int i = 0, j = 0;
-    Map findMap;
+
+    getSession();
+
+    int i = 0;
+    Map<String,String> findMap = new HashMap<String,String>();
+    List<String> folderNames = new ArrayList<String>();
     while (i < spec.getChildCount()) {
       SpecificationNode sn = spec.getChild(i++);
-      if (sn.getType().equals(EmailConfig.NODE_FILTER) && sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME).equals(EmailConfig.ATTRIBUTE_FOLDER)) {
-        folderName = sn.getAttributeValue(EmailConfig.ATTRIBUTE_VALUE);
-      }
-    }
-    while (j < spec.getChildCount()) {
-      SpecificationNode sn = spec.getChild(j++);
-      if (sn.getType().equals(EmailConfig.NODE_FILTER)) {
+      if (sn.getType().equals(EmailConfig.NODE_FOLDER)) {
+        folderNames.add(sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME));
+      } else if (sn.getType().equals(EmailConfig.NODE_FILTER)) {
         String findParameterName, findParameterValue;
         findParameterName = sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
         findParameterValue = sn.getAttributeValue(EmailConfig.ATTRIBUTE_VALUE);
-        findMap = new HashMap();
         findMap.put(findParameterName, findParameterValue);
-        try {
-          Message[] messages = findMessages(startTime, endTime, findMap);
+
+      }
+
+    }
+    
+    for (String folderName : folderNames)
+    {
+      try {
+        OpenFolderThread oft = new OpenFolderThread(session, folderName);
+        oft.start();
+        Folder folder = oft.finishUp();
+        try
+        {
+          Message[] messages = findMessages(folder, startTime, endTime, findMap);
           for (Message message : messages) {
             String emailID = ((MimeMessage) message).getMessageID();
-            activities.addSeedDocument(emailID);
+            activities.addSeedDocument(createDocumentIdentifier(folderName,emailID));
           }
-        } catch (MessagingException e) {
-          Logging.connectors.warn("Email: Error finding emails: " + e.getMessage(), e);
-          throw new ManifoldCFException(e.getMessage(), e);
-        } catch (Exception e) {
-          Logging.connectors.warn("Email: Error finding emails: " + e.getMessage(), e);
-          throw new ManifoldCFException(e.getMessage(), e);
-        }finally {
-          finalizeConnection();
         }
-
+        finally
+        {
+          CloseFolderThread cft = new CloseFolderThread(session, folder);
+          cft.start();
+          cft.finishUp();
+        }
+      } catch (InterruptedException e) {
+        throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
+      } catch (MessagingException e) {
+        handleMessagingException(e, "finding emails");
       }
-
     }
+
   }
 
   /*
   This method will return the list of messages which matches the given criteria
   */
-  private Message[] findMessages(long startTime, long endTime, Map findMap) throws MessagingException {
-    Message[] result;
+  private Message[] findMessages(Folder folder, long startTime, long endTime, Map<String,String> findMap)
+    throws MessagingException, InterruptedException {
     String findParameterName;
     String findParameterValue;
-    initializeConnection();
-    if (findMap.size() > 0) {
-      result = null;
-      Iterator it = findMap.entrySet().iterator();
-      while (it.hasNext()) {
-        Map.Entry pair = (Map.Entry) it.next();
-        findParameterName = ((String) pair.getKey()).toLowerCase();
-        findParameterValue = (String) pair.getValue();
-        it.remove();
-        if (Logging.connectors.isDebugEnabled())
-          Logging.connectors.debug("Email: Finding emails where " + findParameterName +
-              "= '" + findParameterValue + "'");
-        if (findParameterName.equals(EmailConfig.EMAIL_SUBJECT)) {
-          SubjectTerm subjectTerm = new SubjectTerm(findParameterValue);
-          result = folder.search(subjectTerm);
-        } else if (findParameterName.equals(EmailConfig.EMAIL_FROM)) {
-          FromStringTerm fromTerm = new FromStringTerm(findParameterValue);
-          result = folder.search(fromTerm);
-        } else if (findParameterName.equals(EmailConfig.EMAIL_TO)) {
-          RecipientStringTerm recipientTerm = new RecipientStringTerm(Message.RecipientType.TO, findParameterValue);
-          result = folder.search(recipientTerm);
-        } else if (findParameterName.equals(EmailConfig.EMAIL_BODY)) {
-          BodyTerm bodyTerm = new BodyTerm(findParameterValue);
-          result = folder.search(bodyTerm);
-        }
+    
+    SearchTerm searchTerm = null;
+    
+    Iterator<Map.Entry<String,String>> it = findMap.entrySet().iterator();
+    while (it.hasNext()) {
+      Map.Entry<String,String> pair = it.next();
+      findParameterName = pair.getKey().toLowerCase();
+      findParameterValue = pair.getValue();
+      if (Logging.connectors.isDebugEnabled())
+        Logging.connectors.debug("Email: Finding emails where '" + findParameterName +
+            "' = '" + findParameterValue + "'");
+      SearchTerm searchClause = null;
+      if (findParameterName.equals(EmailConfig.EMAIL_SUBJECT)) {
+        searchClause = new SubjectTerm(findParameterValue);
+      } else if (findParameterName.equals(EmailConfig.EMAIL_FROM)) {
+        searchClause = new FromStringTerm(findParameterValue);
+      } else if (findParameterName.equals(EmailConfig.EMAIL_TO)) {
+        searchClause = new RecipientStringTerm(Message.RecipientType.TO, findParameterValue);
+      } else if (findParameterName.equals(EmailConfig.EMAIL_BODY)) {
+        searchClause = new BodyTerm(findParameterValue);
+      }
+      
+      if (searchClause != null)
+      {
+        if (searchTerm == null)
+          searchTerm = searchClause;
+        else
+          searchTerm = new AndTerm(searchTerm, searchClause);
+      }
+      else
+      {
+        Logging.connectors.warn("Email: Unknown filter parameter name: '"+findParameterName+"'");
       }
-    } else {
-      result = folder.getMessages();
     }
-
+    
+    Message[] result;
+    if (searchTerm == null)
+    {
+      GetMessagesThread gmt = new GetMessagesThread(session, folder);
+      gmt.start();
+      result = gmt.finishUp();
+    }
+    else
+    {
+      SearchMessagesThread smt = new SearchMessagesThread(session, folder, searchTerm);
+      smt.start();
+      result = smt.finishUp();
+    }
     return result;
   }
 
-  private Session getSession() {
-    // Create empty properties
-    Properties props = new Properties();
-    // Get session
-    Session session = Session.getDefaultInstance(props, null);
-    sessionExpiration = System.currentTimeMillis() + EmailConfig.SESSION_EXPIRATION_MILLISECONDS;
-    return session;
-  }
-
-  private void initializeConnection() throws MessagingException {
-    store = getSession().getStore(providerMap.get(protocol));
-    store.connect(server, username, password);
+  protected void getSession()
+    throws ManifoldCFException, ServiceInterruption {
+    if (session == null) {
+      
+      // Check that all the required parameters are there.
+      if (urlTemplate == null)
+        throw new ManifoldCFException("Missing url parameter");
+      if (server == null)
+        throw new ManifoldCFException("Missing server parameter");
+      if (properties == null)
+        throw new ManifoldCFException("Missing server properties");
+      if (protocol == null)
+        throw new ManifoldCFException("Missing protocol parameter");
+      
+      // Create a session.
+      int port;
+      if (portString != null && portString.length() > 0)
+      {
+        try
+        {
+          port = Integer.parseInt(portString);
+        }
+        catch (NumberFormatException e)
+        {
+          throw new ManifoldCFException("Port number has bad format: "+e.getMessage(),e);
+        }
+      }
+      else
+        port = -1;
 
-    if (protocol.equals(EmailConfig.PROTOCOL_IMAP)) {
-      folder = store.getFolder(folderName);
-    } else {
-      folder = store.getFolder(EmailConfig.FOLDER_INBOX);
+      try {
+        ConnectThread connectThread = new ConnectThread(server, port, username, password,
+          providerMap.get(protocol), properties);
+        connectThread.start();
+        session = connectThread.finishUp();
+      } catch (InterruptedException e) {
+        throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
+      } catch (MessagingException e) {
+        handleMessagingException(e, "connecting");
+      }
     }
-    folder.open(Folder.READ_ONLY);
+    sessionExpiration = System.currentTimeMillis() + SESSION_EXPIRATION_MILLISECONDS;
   }
 
-  private void finalizeConnection() {
-    try {
-      if (folder != null)
-        folder.close(false);
-      if (store != null)
-        store.close();
-    } catch (MessagingException e) {
-      if (Logging.connectors.isDebugEnabled())
-        Logging.connectors.debug("Error while closing connection to server" + e.getMessage());
-    } finally {
-      folder = null;
-      store = null;
+  protected void finalizeConnection() {
+    if (session != null) {
+      try {
+        CloseSessionThread closeSessionThread = new CloseSessionThread(session);
+        closeSessionThread.start();
+        closeSessionThread.finishUp();
+      } catch (InterruptedException e) {
+      } catch (MessagingException e) {
+        Logging.connectors.warn("Error while closing connection to server: " + e.getMessage(),e);
+      } finally {
+        session = null;
+      }
     }
   }
 
@@ -410,20 +458,12 @@ public class EmailConnector extends org.
     DocumentSpecification spec, int jobMode, boolean usesDefaultAuthority)
     throws ManifoldCFException, ServiceInterruption {
 
-    String[] result = null;
-    if (documentIdentifiers.length > 0) {
-      result = new String[documentIdentifiers.length];
-      //Since visioning is not applicable in the current context.
-      if (result != null) {
-        for (int i=0;i<documentIdentifiers.length;i++) {
-          result[i]=EmailConfig.EMAIL_VERSION;
-        }
-      }
-      return result;
-
-    } else {
-      return new String[]{EmailConfig.EMAIL_VERSION};
+    String[] result = new String[documentIdentifiers.length];
+    for (int i = 0; i < documentIdentifiers.length; i++)
+    {
+      result[i] = "_" + urlTemplate;   // NOT empty; we need to make ManifoldCF understand that this is a document that never will change.
     }
+    return result;
 
   }
 
@@ -448,122 +488,160 @@ public class EmailConnector extends org.
   public void processDocuments(String[] documentIdentifiers, String[] versions, IProcessActivity activities,
     DocumentSpecification spec, boolean[] scanOnly, int jobMode)
     throws ManifoldCFException, ServiceInterruption {
-    int i = 0, count=0;
+    getSession();
+    int i = 0;
     List<String> requiredMetadata = new ArrayList<String>();
+    while (i < spec.getChildCount()) {
+      SpecificationNode sn = spec.getChild(i++);
+      if (sn.getType().equals(EmailConfig.NODE_METADATA)) {
+        String metadataAttribute = sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
+        requiredMetadata.add(metadataAttribute);
+      }
+    }
+    
+    // Keep a cached set of open folders
+    Map<String,Folder> openFolders = new HashMap<String,Folder>();
     try {
-      initializeConnection();
+      i = 0;
+      while (i < documentIdentifiers.length) {
+        String compositeID = documentIdentifiers[i];
+        String version = versions[i];
+        String folderName = extractFolderNameFromDocumentIdentifier(compositeID);
+        String id = extractEmailIDFromDocumentIdentifier(compositeID);
+        try {
+          Folder folder = openFolders.get(folderName);
+          if (folder == null)
+          {
+            OpenFolderThread oft = new OpenFolderThread(session, folderName);
+            oft.start();
+            folder = oft.finishUp();
+            openFolders.put(folderName,folder);
+          }
+          
+          long startTime = System.currentTimeMillis();
+          InputStream is = null;
+          if (Logging.connectors.isDebugEnabled())
+            Logging.connectors.debug("Email: Processing document identifier '"
+              + compositeID + "'");
+          SearchTerm messageIDTerm = new MessageIDTerm(id);
+          
+          SearchMessagesThread smt = new SearchMessagesThread(session, folder, messageIDTerm);
+          smt.start();
+          Message[] message = smt.finishUp();
+
+          for (Message msg : message) {
+            RepositoryDocument rd = new RepositoryDocument();
+            Date setDate = msg.getSentDate();
+            rd.setFileName(msg.getFileName());
+            is = msg.getInputStream();
+            rd.setBinary(is, msg.getSize());
+            String subject = StringUtils.EMPTY;
+            for (String metadata : requiredMetadata) {
+              if (metadata.toLowerCase().equals(EmailConfig.EMAIL_TO)) {
+                Address[] to = msg.getRecipients(Message.RecipientType.TO);
+                String[] toStr = new String[to.length];
+                int j = 0;
+                for (Address address : to) {
+                  toStr[j] = address.toString();
+                }
+                rd.addField(EmailConfig.EMAIL_TO, toStr);
+              } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_FROM)) {
+                Address[] from = msg.getFrom();
+                String[] fromStr = new String[from.length];
+                int j = 0;
+                for (Address address : from) {
+                  fromStr[j] = address.toString();
+                }
+                rd.addField(EmailConfig.EMAIL_TO, fromStr);
 
-      while (i < spec.getChildCount()) {
-        SpecificationNode sn = spec.getChild(i++);
-        if (sn.getType().equals(EmailConfig.NODE_METADATA)) {
-          String metadataAttribute = sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
-          requiredMetadata.add(metadataAttribute);
-        }
-      }
-      for (String id : documentIdentifiers) {
-        long startTime = System.currentTimeMillis();
-        String msgId = documentIdentifiers[count];
-        InputStream is = null;
-        if (Logging.connectors.isDebugEnabled())
-          Logging.connectors.debug("Email: Processing document identifier '"
-              + msgId + "'");
-        MessageIDTerm messageIDTerm = new MessageIDTerm(id);
-        Message[] message = null;
-
-        message = folder.search(messageIDTerm);
-        for (Message msg : message) {
-          RepositoryDocument rd = new RepositoryDocument();
-          Date setDate = msg.getSentDate();
-          rd.setFileName(msg.getFileName());
-          is = msg.getInputStream();
-          rd.setBinary(is, msg.getSize());
-          String subject = StringUtils.EMPTY;
-          for (String metadata : requiredMetadata) {
-            if (metadata.toLowerCase().equals(EmailConfig.EMAIL_TO)) {
-              Address[] to = msg.getRecipients(Message.RecipientType.TO);
-              String[] toStr = new String[to.length];
-              int j = 0;
-              for (Address address : to) {
-                toStr[j] = address.toString();
-              }
-              rd.addField(EmailConfig.EMAIL_TO, toStr);
-            } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_FROM)) {
-              Address[] from = msg.getFrom();
-              String[] fromStr = new String[from.length];
-              int j = 0;
-              for (Address address : from) {
-                fromStr[j] = address.toString();
-              }
-              rd.addField(EmailConfig.EMAIL_TO, fromStr);
+              } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_SUBJECT)) {
+                subject = msg.getSubject();
+                rd.addField(EmailConfig.EMAIL_SUBJECT, subject);
+              } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_BODY)) {
+                Multipart mp = (Multipart) msg.getContent();
+                for (int j = 0, n = mp.getCount(); i < n; i++) {
+                  Part part = mp.getBodyPart(i);
+                  String disposition = part.getDisposition();
+                  if ((disposition == null)) {
+                    MimeBodyPart mbp = (MimeBodyPart) part;
+                    if (mbp.isMimeType(EmailConfig.MIMETYPE_TEXT_PLAIN)) {
+                      rd.addField(EmailConfig.EMAIL_BODY, mbp.getContent().toString());
+                    } else if (mbp.isMimeType(EmailConfig.MIMETYPE_HTML)) {
+                      rd.addField(EmailConfig.EMAIL_BODY, mbp.getContent().toString()); //handle html accordingly. Returns content with html tags
+                    }
+                  }
+                }
+              } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_DATE)) {
+                Date sentDate = msg.getSentDate();
+                rd.addField(EmailConfig.EMAIL_DATE, sentDate.toString());
+              } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_ATTACHMENT_ENCODING)) {
+                Multipart mp = (Multipart) msg.getContent();
+                if (mp != null) {
+                  String[] encoding = new String[mp.getCount()];
+                  for (int k = 0, n = mp.getCount(); i < n; i++) {
+                    Part part = mp.getBodyPart(i);
+                    String disposition = part.getDisposition();
+                    if ((disposition != null) &&
+                        ((disposition.equals(Part.ATTACHMENT) ||
+                            (disposition.equals(Part.INLINE))))) {
+                      encoding[k] = part.getFileName().split("\\?")[1];
 
-            } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_SUBJECT)) {
-              subject = msg.getSubject();
-              rd.addField(EmailConfig.EMAIL_SUBJECT, subject);
-            } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_BODY)) {
-              Multipart mp = (Multipart) msg.getContent();
-              for (int j = 0, n = mp.getCount(); i < n; i++) {
-                Part part = mp.getBodyPart(i);
-                String disposition = part.getDisposition();
-                if ((disposition == null)) {
-                  MimeBodyPart mbp = (MimeBodyPart) part;
-                  if (mbp.isMimeType(EmailConfig.MIMETYPE_TEXT_PLAIN)) {
-                    rd.addField(EmailConfig.EMAIL_BODY, mbp.getContent().toString());
-                  } else if (mbp.isMimeType(EmailConfig.MIMETYPE_HTML)) {
-                    rd.addField(EmailConfig.EMAIL_BODY, mbp.getContent().toString()); //handle html accordingly. Returns content with html tags
+                    }
                   }
+                  rd.addField(EmailConfig.ENCODING_FIELD, encoding);
                 }
-              }
-            } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_DATE)) {
-              Date sentDate = msg.getSentDate();
-              rd.addField(EmailConfig.EMAIL_DATE, sentDate.toString());
-            } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_ATTACHMENT_ENCODING)) {
-              Multipart mp = (Multipart) msg.getContent();
-              if (mp != null) {
-                String[] encoding = new String[mp.getCount()];
+              } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_ATTACHMENT_MIMETYPE)) {
+                Multipart mp = (Multipart) msg.getContent();
+                String[] MIMEType = new String[mp.getCount()];
                 for (int k = 0, n = mp.getCount(); i < n; i++) {
                   Part part = mp.getBodyPart(i);
                   String disposition = part.getDisposition();
                   if ((disposition != null) &&
                       ((disposition.equals(Part.ATTACHMENT) ||
                           (disposition.equals(Part.INLINE))))) {
-                    encoding[k] = part.getFileName().split("\\?")[1];
+                    MIMEType[k] = part.getContentType();
 
                   }
                 }
-                rd.addField(EmailConfig.ENCODING_FIELD, encoding);
+                rd.addField(EmailConfig.MIMETYPE_FIELD, MIMEType);
               }
-            } else if (metadata.toLowerCase().equals(EmailConfig.EMAIL_ATTACHMENT_MIMETYPE)) {
-              Multipart mp = (Multipart) msg.getContent();
-              String[] MIMEType = new String[mp.getCount()];
-              for (int k = 0, n = mp.getCount(); i < n; i++) {
-                Part part = mp.getBodyPart(i);
-                String disposition = part.getDisposition();
-                if ((disposition != null) &&
-                    ((disposition.equals(Part.ATTACHMENT) ||
-                        (disposition.equals(Part.INLINE))))) {
-                  MIMEType[k] = part.getContentType();
-
-                }
-              }
-              rd.addField(EmailConfig.MIMETYPE_FIELD, MIMEType);
             }
-          }
-          String documentURI = subject + messageIDTerm;
-          String version = versions[count++];
-          activities.ingestDocument(id, version, documentURI, rd);
+            String documentURI = makeDocumentURI(urlTemplate, folderName, id);
+            activities.ingestDocument(id, version, documentURI, rd);
 
+          }
+        } catch (InterruptedException e) {
+          throw new ManifoldCFException(e.getMessage(), ManifoldCFException.INTERRUPTED);
+        } catch (MessagingException e) {
+          handleMessagingException(e, "processing email");
+        } catch (IOException e) {
+          handleIOException(e, "processing email");
+          throw new ManifoldCFException(e.getMessage(), e);
+        }
+        
+        i++;
+      }
+    }
+    finally
+    {
+      for (Folder f : openFolders.values())
+      {
+        try
+        {
+          CloseFolderThread cft = new CloseFolderThread(session, f);
+          cft.start();
+          cft.finishUp();
+        }
+        catch (InterruptedException e)
+        {
+          throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
+        }
+        catch (MessagingException e)
+        {
+          handleMessagingException(e, "closing folders");
         }
       }
-
-    } catch (MessagingException e) {
-
-    } catch (IOException e) {
-      throw new ManifoldCFException(e.getMessage(), e,
-          ManifoldCFException.INTERRUPTED);
-    } finally {
-      finalizeConnection();
     }
-
   }
 
   //////////////////////////////End of Repository Connector Methods///////////////////////////////////
@@ -589,11 +667,13 @@ public class EmailConnector extends org.
     Locale locale, ConfigParams parameters, List<String> tabsArray)
     throws ManifoldCFException, IOException {
     tabsArray.add(Messages.getString(locale, "EmailConnector.Server"));
+    tabsArray.add(Messages.getString(locale, "EmailConnector.URL"));
     // Map the parameters
     Map<String, Object> paramMap = new HashMap<String, Object>();
 
     // Fill in the parameters from each tab
-    fillInServerConfigurationMap(paramMap, parameters);
+    fillInServerConfigurationMap(paramMap, out, parameters);
+    fillInURLConfigurationMap(paramMap, out, parameters);
 
     // Output the Javascript - only one Velocity template for all tabs
     Messages.outputResourceWithVelocity(out, locale, "ConfigurationHeader.js", paramMap);
@@ -608,14 +688,16 @@ public class EmailConnector extends org.
     // Set the tab name
     paramMap.put("TabName", tabName);
     // Fill in the parameters
-    fillInServerConfigurationMap(paramMap, parameters);
+    fillInServerConfigurationMap(paramMap, out, parameters);
+    fillInURLConfigurationMap(paramMap, out, parameters);
     Messages.outputResourceWithVelocity(out, locale, "Configuration_Server.html", paramMap);
+    Messages.outputResourceWithVelocity(out, locale, "Configuration_URL.html", paramMap);
   }
 
-  private void fillInServerConfigurationMap(Map<String, Object> paramMap, ConfigParams parameters) {
+  private static void fillInServerConfigurationMap(Map<String, Object> paramMap, IPasswordMapperActivity mapper, ConfigParams parameters) {
     int i = 0;
     String username = parameters.getParameter(EmailConfig.USERNAME_PARAM);
-    String password = parameters.getParameter(EmailConfig.PASSWORD_PARAM);
+    String password = parameters.getObfuscatedParameter(EmailConfig.PASSWORD_PARAM);
     String protocol = parameters.getParameter(EmailConfig.PROTOCOL_PARAM);
     String server = parameters.getParameter(EmailConfig.SERVER_PARAM);
     String port = parameters.getParameter(EmailConfig.PORT_PARAM);
@@ -637,6 +719,8 @@ public class EmailConnector extends org.
       username = StringUtils.EMPTY;
     if (password == null)
       password = StringUtils.EMPTY;
+    else
+      password = mapper.mapPasswordToKey(password);
     if (protocol == null)
       protocol = EmailConfig.PROTOCOL_DEFAULT_VALUE;
     if (server == null)
@@ -653,6 +737,15 @@ public class EmailConnector extends org.
 
   }
 
+  private static void fillInURLConfigurationMap(Map<String, Object> paramMap, IPasswordMapperActivity mapper, ConfigParams parameters) {
+    String urlTemplate = parameters.getParameter(EmailConfig.URL_PARAM);
+
+    if (urlTemplate == null)
+      urlTemplate = "http://sampleserver/$(FOLDERNAME)?id=$(MESSAGEID)";
+
+    paramMap.put("URL", urlTemplate);
+  }
+
   /**
   * Process a configuration post.
   * This method is called at the start of the connector's configuration page, whenever there is a possibility
@@ -671,13 +764,17 @@ public class EmailConnector extends org.
   public String processConfigurationPost(IThreadContext threadContext, IPostParameters variableContext,
     ConfigParams parameters) throws ManifoldCFException {
 
+    String urlTemplate = variableContext.getParameter("url");
+    if (urlTemplate != null)
+      parameters.setParameter(EmailConfig.URL_PARAM, urlTemplate);
+
     String userName = variableContext.getParameter("username");
     if (userName != null)
       parameters.setParameter(EmailConfig.USERNAME_PARAM, userName);
 
     String password = variableContext.getParameter("password");
     if (password != null)
-      parameters.setParameter(EmailConfig.PASSWORD_PARAM, password);
+      parameters.setObfuscatedParameter(EmailConfig.PASSWORD_PARAM, variableContext.mapKeyToPassword(password));
 
     String protocol = variableContext.getParameter("protocol");
     if (protocol != null)
@@ -723,26 +820,6 @@ public class EmailConnector extends org.
     return null;
   }
 
-  private void addFindParameterNode(ConfigParams parameters, String findParameterName, String findParameterValue) {
-    ConfigNode cn = new ConfigNode(EmailConfig.NODE_PROPERTIES);
-    cn.setAttribute(EmailConfig.ATTRIBUTE_NAME, findParameterName);
-    cn.setAttribute(EmailConfig.ATTRIBUTE_VALUE, findParameterValue);
-    // Add to the end
-    parameters.addChild(parameters.getChildCount(), cn);
-  }
-
-  protected static void removeNodes(ConfigParams parameters,
-                    String nodeTypeName) {
-    int i = 0;
-    while (i < parameters.getChildCount()) {
-      ConfigNode cn = parameters.getChild(i);
-      if (cn.getType().equals(nodeTypeName))
-        parameters.removeChild(i);
-      else
-        i++;
-    }
-  }
-
   /**
   * View configuration. This method is called in the body section of the
   * connector's view configuration page. Its purpose is to present the
@@ -761,7 +838,8 @@ public class EmailConnector extends org.
     Map<String, Object> paramMap = new HashMap<String, Object>();
 
     // Fill in map from each tab
-    fillInServerConfigurationMap(paramMap, parameters);
+    fillInServerConfigurationMap(paramMap, out, parameters);
+    fillInURLConfigurationMap(paramMap, out, parameters);
 
     Messages.outputResourceWithVelocity(out, locale, "ConfigurationView.html", paramMap);
   }
@@ -859,18 +937,35 @@ public class EmailConnector extends org.
     Map<String, Object> paramMap = new HashMap<String, Object>();
     paramMap.put("TabName", tabName);
     fillInFilterTab(paramMap, ds);
-    fillInSearchableAttributes(paramMap);
+    if (tabName.equals(Messages.getString(locale, "EmailConnector.Filter")))
+      fillInSearchableAttributes(paramMap);
     Messages.outputResourceWithVelocity(out, locale, "Specification_Filter.html", paramMap);
   }
 
-  private void fillInSearchableAttributes(Map<String, Object> paramMap) {
+  private void fillInSearchableAttributes(Map<String, Object> paramMap)
+  {
     String[] attributes = EmailConfig.BASIC_SEARCHABLE_ATTRIBUTES;
     paramMap.put("SEARCHABLEATTRIBUTES", attributes);
+    try
+    {
+      String[] folderNames = getFolderNames();
+      paramMap.put("FOLDERNAMES", folderNames);
+      paramMap.put("EXCEPTION", "");
+    }
+    catch (ManifoldCFException e)
+    {
+      paramMap.put("EXCEPTION", e.getMessage());
+    }
+    catch (ServiceInterruption e)
+    {
+      paramMap.put("EXCEPTION", e.getMessage());
+    }
   }
 
   protected static void fillInFilterTab(Map<String, Object> paramMap,
     DocumentSpecification ds) {
-    List<Map<String, String>> list = new ArrayList<Map<String, String>>();
+    List<Map<String, String>> filterList = new ArrayList<Map<String, String>>();
+    Set<String> folders = new HashSet<String>();
     int i = 0;
     while (i < ds.getChildCount()) {
       SpecificationNode sn = ds.getChild(i++);
@@ -881,10 +976,15 @@ public class EmailConnector extends org.
         Map<String, String> row = new HashMap<String, String>();
         row.put("name", findParameterName);
         row.put("value", findParameterValue);
-        list.add(row);
+        filterList.add(row);
+      }
+      else if (sn.getType().equals(EmailConfig.NODE_FOLDER)) {
+        String folderName = sn.getAttributeValue(EmailConfig.ATTRIBUTE_NAME);
+        folders.add(folderName);
       }
     }
-    paramMap.put("MATCHES", list);
+    paramMap.put("MATCHES", filterList);
+    paramMap.put("FOLDERS", folders);
   }
 
   /**
@@ -913,12 +1013,13 @@ public class EmailConnector extends org.
 
   protected String processFilterTab(IPostParameters variableContext, DocumentSpecification ds)
       throws ManifoldCFException {
-    // Remove old find parameter document specification information
-    removeNodes(ds, EmailConfig.NODE_FILTER);
 
     String findCountString = variableContext.getParameter("findcount");
     if (findCountString != null) {
       int findCount = Integer.parseInt(findCountString);
+      
+      // Remove old find parameter document specification information
+      removeNodes(ds, EmailConfig.NODE_FILTER);
 
       int i = 0;
       while (i < findCount) {
@@ -931,15 +1032,24 @@ public class EmailConnector extends org.
           addFindParameterNode(ds, findParameterName, findParameterValue);
         }
       }
-    }
 
-    String operation = variableContext.getParameter("findop");
-    if (operation != null && operation.equals("Add")) {
-      String findParameterName = variableContext.getParameter("findname");
-      String findParameterValue = variableContext.getParameter("findvalue");
-      addFindParameterNode(ds, findParameterName, findParameterValue);
+      String operation = variableContext.getParameter("findop");
+      if (operation != null && operation.equals("Add")) {
+        String findParameterName = variableContext.getParameter("findname");
+        String findParameterValue = variableContext.getParameter("findvalue");
+        addFindParameterNode(ds, findParameterName, findParameterValue);
+      }
+    }
+    
+    String[] folders = variableContext.getParameterValues("folders");
+    if (folders != null)
+    {
+      removeNodes(ds, EmailConfig.NODE_FOLDER);
+      for (String folder : folders)
+      {
+        addFolderNode(ds, folder);
+      }
     }
-
     return null;
   }
 
@@ -963,6 +1073,117 @@ public class EmailConnector extends org.
     return null;
   }
 
+  /**
+  * View specification.
+  * This method is called in the body section of a job's view page. Its purpose is to present the document
+  * specification information to the user. The coder can presume that the HTML that is output from
+  * this configuration will be within appropriate <html> and <body> tags.
+  * The connector will be connected before this method can be called.
+  *
+  * @param out is the output to which any HTML should be sent.
+  * @param locale is the desired locale.
+  * @param ds is the current document specification for this job.
+*/
+  @Override
+  public void viewSpecification(IHTTPOutput out, Locale locale, DocumentSpecification ds)
+      throws ManifoldCFException, IOException {
+    Map<String, Object> paramMap = new HashMap<String, Object>();
+    fillInFilterTab(paramMap, ds);
+    fillInMetadataTab(paramMap, ds);
+    Messages.outputResourceWithVelocity(out, locale, "SpecificationView.html", paramMap);
+  }
+
+  ///////////////////////////////////////End of specification UI///////////////////////////////////////////////
+  
+  /** Get a sorted list of folder names */
+  protected String[] getFolderNames()
+    throws ManifoldCFException, ServiceInterruption
+  {
+    getSession();
+    try
+    {
+      ListFoldersThread lft = new ListFoldersThread(session);
+      lft.start();
+      return lft.finishUp();
+    }
+    catch (InterruptedException e)
+    {
+      throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
+    }
+    catch (MessagingException e)
+    {
+      handleMessagingException(e,"getting folder list");
+      return null;
+    }
+  }
+
+  /** Create a document's URI given a template, a folder name, and a message ID */
+  protected static String makeDocumentURI(String urlTemplate, String folderName, String id)
+  {
+    try {
+      // First, URL encode folder name and id
+      String encodedFolderName = java.net.URLEncoder.encode(folderName, "utf-8");
+      String encodedId = java.net.URLEncoder.encode(id, "utf-8");
+      // The template is already URL encoded, except for the substitution points
+      Map<String,String> subsMap = new HashMap<String,String>();
+      subsMap.put("FOLDERNAME", encodedFolderName);
+      subsMap.put("MESSAGEID", encodedId);
+      return substitute(urlTemplate, subsMap);
+    } catch (UnsupportedEncodingException e) {
+      throw new RuntimeException("No utf-8 encoder found: "+e.getMessage(), e);
+    }
+  }
+
+  protected static String substitute(String template, Map<String,String> map)
+  {
+    StringBuilder sb = new StringBuilder();
+    int index = 0;
+    while (true)
+    {
+      int newIndex = template.indexOf("$(",index);
+      if (newIndex == -1)
+      {
+        sb.append(template.substring(index));
+        break;
+      }
+      sb.append(template.substring(index, newIndex));
+      int endIndex = template.indexOf(")",newIndex+2);
+      String varName;
+      if (endIndex == -1)
+        varName = template.substring(newIndex + 2);
+      else
+        varName = template.substring(newIndex + 2, endIndex);
+      String subsValue = map.get(varName);
+      if (subsValue == null)
+        subsValue = "";
+      sb.append(subsValue);
+      if (endIndex == -1)
+        break;
+      index = endIndex+1;
+    }
+    return sb.toString();
+  }
+  
+  protected static void addFindParameterNode(ConfigParams parameters, String findParameterName, String findParameterValue) {
+    ConfigNode cn = new ConfigNode(EmailConfig.NODE_PROPERTIES);
+    cn.setAttribute(EmailConfig.ATTRIBUTE_NAME, findParameterName);
+    cn.setAttribute(EmailConfig.ATTRIBUTE_VALUE, findParameterValue);
+    // Add to the end
+    parameters.addChild(parameters.getChildCount(), cn);
+  }
+
+  protected static void removeNodes(ConfigParams parameters,
+                    String nodeTypeName) {
+    int i = 0;
+    while (i < parameters.getChildCount()) {
+      ConfigNode cn = parameters.getChild(i);
+      if (cn.getType().equals(nodeTypeName))
+        parameters.removeChild(i);
+      else
+        i++;
+    }
+  }
+
   protected static void removeNodes(DocumentSpecification ds,
                     String nodeTypeName) {
     int i = 0;
@@ -984,7 +1205,7 @@ public class EmailConnector extends org.
     ds.addChild(ds.getChildCount(), sn);
   }
 
-  private void addFindParameterNode(DocumentSpecification ds, String findParameterName, String findParameterValue) {
+  protected static void addFindParameterNode(DocumentSpecification ds, String findParameterName, String findParameterValue) {
     SpecificationNode sn = new SpecificationNode(EmailConfig.NODE_FILTER);
     sn.setAttribute(EmailConfig.ATTRIBUTE_NAME, findParameterName);
     sn.setAttribute(EmailConfig.ATTRIBUTE_VALUE, findParameterValue);
@@ -992,25 +1213,542 @@ public class EmailConnector extends org.
     ds.addChild(ds.getChildCount(), sn);
   }
 
-  /**
-  * View specification.
-  * This method is called in the body section of a job's view page. Its purpose is to present the document
-  * specification information to the user. The coder can presume that the HTML that is output from
-  * this configuration will be within appropriate <html> and <body> tags.
-  * The connector will be connected before this method can be called.
-  *
-  * @param out is the output to which any HTML should be sent.
-  * @param locale is the desired locale.
-  * @param ds is the current document specification for this job.
-*/
-  @Override
-  public void viewSpecification(IHTTPOutput out, Locale locale, DocumentSpecification ds)
-      throws ManifoldCFException, IOException {
-    Map<String, Object> paramMap = new HashMap<String, Object>();
-    fillInFilterTab(paramMap, ds);
-    fillInMetadataTab(paramMap, ds);
-    Messages.outputResourceWithVelocity(out, locale, "SpecificationView.html", paramMap);
+  protected static void addFolderNode(DocumentSpecification ds, String folderName)
+  {
+    SpecificationNode sn = new SpecificationNode(EmailConfig.NODE_FOLDER);
+    sn.setAttribute(EmailConfig.ATTRIBUTE_NAME, folderName);
+    ds.addChild(ds.getChildCount(), sn);
+  }
+  
+
+  /** Create a document identifier from a folder name and an email ID */
+  protected static String createDocumentIdentifier(String folderName, String emailID)
+  {
+    return makeSafeFolderName(folderName) + ":" + emailID;
+  }
+  
+  /** Find a folder name in a document identifier */
+  protected static String extractFolderNameFromDocumentIdentifier(String di)
+  {
+    int index = di.indexOf(":");
+    if (index == -1)
+      throw new RuntimeException("Bad document identifier: '"+di+"'");
+    return di.substring(0,index);
+  }
+
+  /** Find an email ID in a document identifier */
+  protected static String extractEmailIDFromDocumentIdentifier(String di)
+  {
+    int index = di.indexOf(":");
+    if (index == -1)
+      throw new RuntimeException("Bad document identifier: '"+di+"'");
+    return di.substring(index+1);
+  }
+  
+  /** Create a safe folder name (which doesn't contain colons) */
+  protected static String makeSafeFolderName(String folderName)
+  {
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < folderName.length(); i++)
+    {
+      char x = folderName.charAt(i);
+      if (x == '\\')
+        sb.append('\\').append('\\');
+      else if (x == ':')
+        sb.append('\\').append('0');
+      else
+        sb.append(x);
+    }
+    return sb.toString();
+  }
+  
+  /** Unpack a safe folder name */
+  protected static String unpackSafeFolderName(String packedFolderName)
+  {
+    StringBuilder sb = new StringBuilder();
+    int i = 0;
+    while (i < packedFolderName.length())
+    {
+      char x = packedFolderName.charAt(i++);
+      if (x == '\\')
+      {
+        if (i == packedFolderName.length())
+          throw new RuntimeException("Illegal packed folder name: '"+packedFolderName+"'");
+        x = packedFolderName.charAt(i++);
+        if (x == '\\')
+          sb.append('\\');
+        else if (x == '0')
+          sb.append(':');
+        else
+          throw new RuntimeException("Illegal packed folder name: '"+packedFolderName+"'");
+      }
+      else
+        sb.append(x);
+    }
+    return sb.toString();
+  }
+  
+  /** Handle Messaging exceptions in a consistent global manner */
+  protected static void handleMessagingException(MessagingException e, String context)
+    throws ManifoldCFException, ServiceInterruption
+  {
+    Logging.connectors.error("Email: Error "+context+": "+e.getMessage(),e);
+    throw new ManifoldCFException("Error "+context+": "+e.getMessage(),e);
+  }
+  
+  /** Handle IO Exception */
+  protected static void handleIOException(IOException e, String context)
+    throws ManifoldCFException, ServiceInterruption
+  {
+    if (e instanceof java.net.SocketTimeoutException)
+    {
+      Logging.connectors.error("Email: Socket timeout "+context+": "+e.getMessage(),e);
+      throw new ManifoldCFException("Socket timeout: "+e.getMessage(),e);
+    }
+    else if (e instanceof InterruptedIOException)
+    {
+      throw new ManifoldCFException("Interrupted: "+e.getMessage(),ManifoldCFException.INTERRUPTED);
+    }
+    else
+    {
+      Logging.connectors.error("Email: IO error "+context+": "+e.getMessage(),e);
+      throw new ManifoldCFException("IO error "+context+": "+e.getMessage(),e);
+    }
+  }
+
+  /** Class to set up connection.
+  */
+  protected static class ConnectThread extends Thread
+  {
+    protected final String server;
+    protected final int port;
+    protected final String username;
+    protected final String password;
+    protected final String protocol;
+    protected final Properties properties;
+    
+    // Local session handle
+    protected EmailSession session = null;
+    protected Throwable exception = null;
+    
+    public ConnectThread(String server, int port, String username, String password, String protocol, Properties properties)
+    {
+      this.server = server;
+      this.port = port;
+      this.username = username;
+      this.password = password;
+      this.protocol = protocol;
+      this.properties = properties;
+      setDaemon(true);
+    }
+    
+    public void run()
+    {
+      try
+      {
+        session = new EmailSession(server, port, username, password, protocol, properties);
+      }
+      catch (Throwable e)
+      {
+        exception = e;
+      }
+    }
+    
+    public EmailSession finishUp()
+      throws MessagingException, InterruptedException
+    {
+      try
+      {
+        join();
+        if (exception != null)
+        {
+          if (exception instanceof RuntimeException)
+            throw (RuntimeException)exception;
+          else if (exception instanceof Error)
+            throw (Error)exception;
+          else if (exception instanceof MessagingException)
+            throw (MessagingException)exception;
+          else
+            throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
+        }
+        return session;
+      } catch (InterruptedException e) {
+        this.interrupt();
+        throw e;
+      }
+    }
+  }
+
+  /** Class to close the session.
+  */
+  protected static class CloseSessionThread extends Thread
+  {
+    protected final EmailSession session;
+    
+    protected Throwable exception = null;
+    
+    public CloseSessionThread(EmailSession session)
+    {
+      this.session = session;
+      setDaemon(true);
+    }
+    
+    public void run()
+    {
+      try
+      {
+        session.close();
+      }
+      catch (Throwable e)
+      {
+        exception = e;
+      }
+    }
+    
+    public void finishUp()
+      throws MessagingException, InterruptedException
+    {
+      try
+      {
+        join();
+        if (exception != null)
+        {
+          if (exception instanceof RuntimeException)
+            throw (RuntimeException)exception;
+          else if (exception instanceof Error)
+            throw (Error)exception;
+          else if (exception instanceof MessagingException)
+            throw (MessagingException)exception;
+          else
+            throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
+        }
+      } catch (InterruptedException e) {
+        this.interrupt();
+        throw e;
+      }
+    }
+  }
+
+  /** Class to list all folders.
+  */
+  protected static class ListFoldersThread extends Thread
+  {
+    protected final EmailSession session;
+    
+    protected String[] rval = null;
+    protected Throwable exception = null;
+    
+    public ListFoldersThread(EmailSession session)
+    {
+      this.session = session;
+      setDaemon(true);
+    }
+    
+    public void run()
+    {
+      try
+      {
+        rval = session.listFolders();
+      }
+      catch (Throwable e)
+      {
+        exception = e;
+      }
+    }
+    
+    public String[] finishUp()
+      throws MessagingException, InterruptedException
+    {
+      try
+      {
+        join();
+        if (exception != null)
+        {
+          if (exception instanceof RuntimeException)
+            throw (RuntimeException)exception;
+          else if (exception instanceof Error)
+            throw (Error)exception;
+          else if (exception instanceof MessagingException)
+            throw (MessagingException)exception;
+          else
+            throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
+        }
+        return rval;
+      } catch (InterruptedException e) {
+        this.interrupt();
+        throw e;
+      }
+    }
+  }
+
+  /** Class to check the connection.
+  */
+  protected static class CheckConnectionThread extends Thread
+  {
+    protected final EmailSession session;
+    
+    protected Throwable exception = null;
+    
+    public CheckConnectionThread(EmailSession session)
+    {
+      this.session = session;
+      setDaemon(true);
+    }
+    
+    public void run()
+    {
+      try
+      {
+        session.checkConnection();
+      }
+      catch (Throwable e)
+      {
+        exception = e;
+      }
+    }
+    
+    public void finishUp()
+      throws MessagingException, InterruptedException
+    {
+      try
+      {
+        join();
+        if (exception != null)
+        {
+          if (exception instanceof RuntimeException)
+            throw (RuntimeException)exception;
+          else if (exception instanceof Error)
+            throw (Error)exception;
+          else if (exception instanceof MessagingException)
+            throw (MessagingException)exception;
+          else
+            throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
+        }
+      } catch (InterruptedException e) {
+        this.interrupt();
+        throw e;
+      }
+    }
+  }
+
+  /** Class to open a folder.
+  */
+  protected static class OpenFolderThread extends Thread
+  {
+    protected final EmailSession session;
+    protected final String folderName;
+    
+    // Local folder
+    protected Folder folder = null;
+    protected Throwable exception = null;
+    
+    public OpenFolderThread(EmailSession session, String folderName)
+    {
+      this.session = session;
+      this.folderName = folderName;
+      setDaemon(true);
+    }
+    
+    public void run()
+    {
+      try
+      {
+        folder = session.openFolder(folderName);
+      }
+      catch (Throwable e)
+      {
+        exception = e;
+      }
+    }
+    
+    public Folder finishUp()
+      throws MessagingException, InterruptedException
+    {
+      try
+      {
+        join();
+        if (exception != null)
+        {
+          if (exception instanceof RuntimeException)
+            throw (RuntimeException)exception;
+          else if (exception instanceof Error)
+            throw (Error)exception;
+          else if (exception instanceof MessagingException)
+            throw (MessagingException)exception;
+          else
+            throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
+        }
+        return folder;
+      } catch (InterruptedException e) {
+        this.interrupt();
+        throw e;
+      }
+    }
+  }
+  
+  /** Class to close a folder.
+  */
+  protected static class CloseFolderThread extends Thread
+  {
+    protected final EmailSession session;
+    protected final Folder folder;
+    
+    // Local folder
+    protected Throwable exception = null;
+    
+    public CloseFolderThread(EmailSession session, Folder folder)
+    {
+      this.session = session;
+      this.folder = folder;
+      setDaemon(true);
+    }
+    
+    public void run()
+    {
+      try
+      {
+        session.closeFolder(folder);
+      }
+      catch (Throwable e)
+      {
+        exception = e;
+      }
+    }
+    
+    public void finishUp()
+      throws MessagingException, InterruptedException
+    {
+      try
+      {
+        join();
+        if (exception != null)
+        {
+          if (exception instanceof RuntimeException)
+            throw (RuntimeException)exception;
+          else if (exception instanceof Error)
+            throw (Error)exception;
+          else if (exception instanceof MessagingException)
+            throw (MessagingException)exception;
+          else
+            throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
+        }
+      } catch (InterruptedException e) {
+        this.interrupt();
+        throw e;
+      }
+    }
+  }
+
+  /** Class to get all messages from a folder.
+  */
+  protected static class GetMessagesThread extends Thread
+  {
+    protected final EmailSession session;
+    protected final Folder folder;
+    
+    // Local messages
+    protected Message[] messages = null;
+    protected Throwable exception = null;
+    
+    public GetMessagesThread(EmailSession session, Folder folder)
+    {
+      this.session = session;
+      this.folder = folder;
+      setDaemon(true);
+    }
+    
+    public void run()
+    {
+      try
+      {
+        messages = session.getMessages(folder);
+      }
+      catch (Throwable e)
+      {
+        exception = e;
+      }
+    }
+    
+    public Message[] finishUp()
+      throws MessagingException, InterruptedException
+    {
+      try
+      {
+        join();
+        if (exception != null)
+        {
+          if (exception instanceof RuntimeException)
+            throw (RuntimeException)exception;
+          else if (exception instanceof Error)
+            throw (Error)exception;
+          else if (exception instanceof MessagingException)
+            throw (MessagingException)exception;
+          else
+            throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
+        }
+        return messages;
+      } catch (InterruptedException e) {
+        this.interrupt();
+        throw e;
+      }
+    }
+  }
+
+  /** Class to search for messages in a folder.
+  */
+  protected static class SearchMessagesThread extends Thread
+  {
+    protected final EmailSession session;
+    protected final Folder folder;
+    protected final SearchTerm searchTerm;
+    
+    // Local messages
+    protected Message[] messages = null;
+    protected Throwable exception = null;
+    
+    public SearchMessagesThread(EmailSession session, Folder folder, SearchTerm searchTerm)
+    {
+      this.session = session;
+      this.folder = folder;
+      this.searchTerm = searchTerm;
+      setDaemon(true);
+    }
+    
+    public void run()
+    {
+      try
+      {
+        messages = session.search(folder, searchTerm);
+      }
+      catch (Throwable e)
+      {
+        exception = e;
+      }
+    }
+    
+    public Message[] finishUp()
+      throws MessagingException, InterruptedException
+    {
+      try
+      {
+        join();
+        if (exception != null)
+        {
+          if (exception instanceof RuntimeException)
+            throw (RuntimeException)exception;
+          else if (exception instanceof Error)
+            throw (Error)exception;
+          else if (exception instanceof MessagingException)
+            throw (MessagingException)exception;
+          else
+            throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
+        }
+        return messages;
+      } catch (InterruptedException e) {
+        this.interrupt();
+        throw e;
+      }
+    }
   }
 
-  ///////////////////////////////////////End of specification UI///////////////////////////////////////////////
 }
\ No newline at end of file

Modified: manifoldcf/trunk/connectors/email/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/email/common_en_US.properties
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/connectors/email/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/email/common_en_US.properties?rev=1552900&r1=1552345&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/connectors/email/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/email/common_en_US.properties (original)
+++ manifoldcf/trunk/connectors/email/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/email/common_en_US.properties Sat Dec 21 13:38:34 2013
@@ -14,6 +14,7 @@
 # limitations under the License.
 
 EmailConnector.Server=Server
+EmailConnector.URL=URL
 EmailConnector.Metadata=Metadata
 EmailConnector.Filter=Filter
 
@@ -21,7 +22,9 @@ EmailConnector.EnterAMailServerHostName=
 EmailConnector.PleaseSelectAConfigurationParameterName=Please select a configuration parameter name
 EmailConnector.PleaseSelectAMetadataName=Please select a metadata name
 EmailConnector.ValueCannotBeBlank=Value cannot be blank
+EmailConnector.URLTemplateCannotBeBlank=URL template cannot be blank
 
+EmailConnector.URLTemplateColon=URL template:
 EmailConnector.ConfigurationPropertiesColon=Configuration properties:
 EmailConnector.ProtocolColon=Protocol:
 EmailConnector.HostNameColon=Host name:
@@ -29,6 +32,7 @@ EmailConnector.PortColon=Port:
 EmailConnector.UserNameColon=User name:
 EmailConnector.PasswordColon=Password:
 EmailConnector.MatchesColon=Matches:
+EmailConnector.FoldersColon=Folders:
 EmailConnector.RecordFilterColon=Record filter:
 EmailConnector.ServerProperty=Server property
 EmailConnector.Value=Value

Modified: manifoldcf/trunk/connectors/email/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/email/common_ja_JP.properties
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/connectors/email/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/email/common_ja_JP.properties?rev=1552900&r1=1552345&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/connectors/email/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/email/common_ja_JP.properties (original)
+++ manifoldcf/trunk/connectors/email/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/email/common_ja_JP.properties Sat Dec 21 13:38:34 2013
@@ -14,6 +14,7 @@
 # limitations under the License.
 
 EmailConnector.Server=Server
+EmailConnector.URL=URL
 EmailConnector.Metadata=Metadata
 EmailConnector.Filter=Filter
 
@@ -21,7 +22,9 @@ EmailConnector.EnterAMailServerHostName=
 EmailConnector.PleaseSelectAConfigurationParameterName=Please select a configuration parameter name
 EmailConnector.PleaseSelectAMetadataName=Please select a metadata name
 EmailConnector.ValueCannotBeBlank=Value cannot be blank
+EmailConnector.URLTemplateCannotBeBlank=URL template cannot be blank
 
+EmailConnector.URLTemplateColon=URL template:
 EmailConnector.ConfigurationPropertiesColon=Configuration properties:
 EmailConnector.ProtocolColon=Protocol:
 EmailConnector.HostNameColon=Host name:
@@ -29,6 +32,7 @@ EmailConnector.PortColon=Port:
 EmailConnector.UserNameColon=User name:
 EmailConnector.PasswordColon=Password:
 EmailConnector.MatchesColon=Matches:
+EmailConnector.FoldersColon=Folders:
 EmailConnector.RecordFilterColon=Record filter:
 EmailConnector.ServerProperty=Server property
 EmailConnector.Value=Value

Modified: manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/ConfigurationHeader.js
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/ConfigurationHeader.js?rev=1552900&r1=1552345&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/ConfigurationHeader.js (original)
+++ manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/ConfigurationHeader.js Sat Dec 21 13:38:34 2013
@@ -17,6 +17,17 @@
 
 <script type="text/javascript">
 <!--
+function checkConfig()
+{
+  if (editconnection.port.value != "" && !isInteger(editconnection.port.value))
+  {
+    alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('EmailConnector.PortMustBeIntegerOrBlank'))");
+    editconnection.port.focus();
+    return false;
+  }
+  return true;
+}
+
 function checkConfigForSave()
 {
   if (editconnection.server.value == "")
@@ -26,6 +37,20 @@ function checkConfigForSave()
     editconnection.server.focus();
     return false;
   }
+  if (editconnection.port.value != "" && !isInteger(editconnection.port.value))
+  {
+    alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('EmailConnector.PortMustBeIntegerOrBlank'))");
+    SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('EmailConnector.Server'))");
+    editconnection.port.focus();
+    return false;
+  }
+  if (editconnection.url.value == "")
+  {
+    alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('EmailConnector.URLTemplateCannotBeBlank'))");
+    SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('EmailConnector.URL'))");
+    editconnection.url.focus();
+    return false;
+  }
   return true;
 }
 

Modified: manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/ConfigurationView.html
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/ConfigurationView.html?rev=1552900&r1=1552345&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/ConfigurationView.html (original)
+++ manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/ConfigurationView.html Sat Dec 21 13:38:34 2013
@@ -81,5 +81,16 @@ limitations under the License.
     </td>
   </tr>
   
+  <tr><td  class="separator" colspan="2"><hr/></td></tr>
+
+  <tr>
+    <td class="description">
+      <nobr>$Encoder.bodyEscape($ResourceBundle.getString('EmailConnector.URLTemplateColon'))</nobr>
+    </td>
+    <td class="value">
+      <nobr>$Encoder.bodyEscape($URL)</nobr>
+    </td>
+  </tr>
+
 </table>
 

Modified: manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/SpecificationView.html
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/SpecificationView.html?rev=1552900&r1=1552345&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/SpecificationView.html (original)
+++ manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/SpecificationView.html Sat Dec 21 13:38:34 2013
@@ -17,6 +17,15 @@ limitations under the License.
 
 <table class="displaytable">
   <tr>
+    <td class="description"><nobr>$Encoder.bodyEscape($ResourceBundle.getString('EmailConnector.FoldersColon'))</nobr></td>
+    <td class="value">
+  #foreach($folder in $FOLDERS)
+      <nobr>$Encoder.bodyEscape($folder)</nobr><br/>
+  #end
+    </td>
+  </tr>
+  
+  <tr>
     <td class="description"><nobr>$Encoder.bodyEscape($ResourceBundle.getString('EmailConnector.RecordFilterColon'))</nobr></td>
     <td class="boxcell">
       <table class="formtable">

Modified: manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/Specification_Filter.html
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/Specification_Filter.html?rev=1552900&r1=1552345&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/Specification_Filter.html (original)
+++ manifoldcf/trunk/connectors/email/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/email/Specification_Filter.html Sat Dec 21 13:38:34 2013
@@ -20,6 +20,32 @@ limitations under the License.
 <table class="displaytable">
   <tr><td class="separator" colspan="2"><hr/></td></tr>
   <tr>
+  #if($EXCEPTION == '')
+    <td class="description">
+      <nobr>$Encoder.bodyEscape($ResourceBundle.getString('EmailConnector.FoldersColon'))</nobr>
+    </td>
+    <td class="value">
+      <select name="folders" multiple="true" size="4">
+    #foreach($name in $FOLDERNAMES)
+      #if($FOLDERS.contains($name))
+        <option value="$Encoder.attributeEscape($name)" selected="true">$Encoder.bodyEscape($name)</option>
+      #else
+        <option value="$Encoder.attributeEscape($name)">$Encoder.bodyEscape($name)</option>
+      #end
+    #end
+      </select>
+    </td>
+  #else
+    <td class="message" colspan="2">
+    #foreach($name in $FOLDERS)
+      <input type="hidden" name="folders" value="$Encoder.attributeEscape($name)"/>
+    #end
+      $Encoder.bodyEscape($EXCEPTION)
+    </td>
+  #end
+  </tr>
+  <tr><td class="separator" colspan="2"><hr/></td></tr>
+  <tr>
     <td class="description">
       <nobr>$Encoder.bodyEscape($ResourceBundle.getString('EmailConnector.RecordFilterColon'))</nobr>
     </td>
@@ -101,6 +127,10 @@ limitations under the License.
 
 #else
 
+  #foreach($name in $FOLDERS)
+<input type="hidden" name="folders" value="$Encoder.attributeEscape($name)"/>
+  #end
+
   #set($k = 0)
   #foreach($match in $MATCHES)
 

Modified: manifoldcf/trunk/connectors/pom.xml
URL: http://svn.apache.org/viewvc/manifoldcf/trunk/connectors/pom.xml?rev=1552900&r1=1552899&r2=1552900&view=diff
==============================================================================
--- manifoldcf/trunk/connectors/pom.xml (original)
+++ manifoldcf/trunk/connectors/pom.xml Sat Dec 21 13:38:34 2013
@@ -56,6 +56,7 @@
     <module>jira</module>
     <module>generic</module>
     <module>regexpmapper</module>
+    <module>email</module>
   </modules>
 
 </project>