Have AdServer/AdAdaptor use new Startup exceptions
diff --git a/src/com/google/enterprise/adaptor/ad/AdAdaptor.java b/src/com/google/enterprise/adaptor/ad/AdAdaptor.java
index 70ba0e0..2db0331 100644
--- a/src/com/google/enterprise/adaptor/ad/AdAdaptor.java
+++ b/src/com/google/enterprise/adaptor/ad/AdAdaptor.java
@@ -20,10 +20,12 @@
 import com.google.enterprise.adaptor.Config;
 import com.google.enterprise.adaptor.DocIdPusher;
 import com.google.enterprise.adaptor.GroupPrincipal;
+import com.google.enterprise.adaptor.InvalidConfigurationException;
 import com.google.enterprise.adaptor.PollingIncrementalLister;
 import com.google.enterprise.adaptor.Principal;
 import com.google.enterprise.adaptor.Request;
 import com.google.enterprise.adaptor.Response;
+import com.google.enterprise.adaptor.StartupException;
 import com.google.enterprise.adaptor.UserPrincipal;
 
 import java.io.IOException;
@@ -112,7 +114,8 @@
         if ("ssl".equals(methodStr)) {
           method = Method.SSL;
         } else if (!"standard".equals(methodStr)) {
-          throw new IllegalArgumentException("invalid method: " + methodStr);
+          throw new InvalidConfigurationException("invalid method: "
+              + methodStr);
         }
       }
       String principal = singleServerConfig.get("user");
@@ -120,7 +123,8 @@
         principal = defaultUser;
       }
       if (principal.isEmpty()) {
-        throw new IllegalStateException("user not specified for host " + host);
+        throw new InvalidConfigurationException("user not specified for host "
+              + host);
       }
       String passwd = singleServerConfig.get("password");
       if (null == passwd) {
@@ -129,8 +133,8 @@
         passwd = context.getSensitiveValueDecoder().decodeValue(passwd);
       }
       if (passwd.isEmpty()) {
-        throw new IllegalStateException("password not specified for host "
-            + host);
+        throw new InvalidConfigurationException("password not specified for "
+            + "host " + host);
       }
       AdServer adServer = newAdServer(method, host, port, principal, passwd,
           ldapTimeoutInMillis);
@@ -149,12 +153,14 @@
    */
   @VisibleForTesting
   AdServer newAdServer(Method method, String host, int port,
-      String principal, String passwd, String ldapTimeoutInMillis) {
+      String principal, String passwd, String ldapTimeoutInMillis)
+      throws StartupException {
     return new AdServer(method, host, port, principal, passwd,
         ldapTimeoutInMillis);
   }
 
-  private static String parseLdapTimeoutInMillis(String timeInSeconds) {
+  private static String parseLdapTimeoutInMillis(String timeInSeconds)
+      throws InvalidConfigurationException {
     if (timeInSeconds.equals("0") || timeInSeconds.trim().equals("")) {
       timeInSeconds = "90";
       log.log(Level.CONFIG, "ad.ldapReadTimeoutSecs set to default of 90 sec.");
@@ -162,8 +168,8 @@
     try {
       return String.valueOf(1000L * Integer.parseInt(timeInSeconds));
     } catch (NumberFormatException e) {
-      throw new IllegalArgumentException("invalid ad.ldapReadTimeoutSecs: " +
-          timeInSeconds);
+      throw new InvalidConfigurationException("invalid value for "
+          + "ad.ldapReadTimeoutSecs: " + timeInSeconds);
     }
   }
 
diff --git a/src/com/google/enterprise/adaptor/ad/AdServer.java b/src/com/google/enterprise/adaptor/ad/AdServer.java
index 658a46c..5d5aaa1 100644
--- a/src/com/google/enterprise/adaptor/ad/AdServer.java
+++ b/src/com/google/enterprise/adaptor/ad/AdServer.java
@@ -16,6 +16,8 @@
 
 import com.google.common.annotations.VisibleForTesting;
 
+import com.google.enterprise.adaptor.StartupException;
+
 import java.io.IOException;
 import java.net.ConnectException;
 import java.util.HashSet;
@@ -67,7 +69,8 @@
   private String ldapTimeoutInMillis;
 
   public AdServer(Method connectMethod, String hostName,
-      int port, String principal, String password, String ldapTimeoutInMillis) {
+      int port, String principal, String password, String ldapTimeoutInMillis)
+      throws StartupException {
     this(hostName, createLdapContext(connectMethod, hostName, port,
         principal, password, ldapTimeoutInMillis));
     this.connectMethod = connectMethod;
@@ -90,7 +93,7 @@
    */
   private static LdapContext createLdapContext(Method connectMethod,
       String hostName, int port, String principal, String password,
-      String ldapTimeoutInMillis) {
+      String ldapTimeoutInMillis) throws StartupException {
     Hashtable<String, String> env = new Hashtable<String, String>();
     if (null == connectMethod || null == hostName
         || null == principal || null == password) {
@@ -129,6 +132,7 @@
       // a ConnectException (wrong hostname).
       Throwable cause = ne.getCause();
       boolean replaceException = false;
+      boolean abortStartup = false;
       if (cause instanceof ConnectException) {
         ConnectException ce = (ConnectException) cause;
         if (ce.getMessage() != null
@@ -137,7 +141,9 @@
           replaceException = true;
         }
       } else if (ne instanceof AuthenticationException) {
+        // this is the only exception we flag as a StartupException.
         replaceException = true;
+        abortStartup = true;
       } else if (ne instanceof CommunicationException) {
         replaceException = true;
       }
@@ -146,7 +152,11 @@
             + "user \"%s\" with the specified password.  Please make sure "
             + "they are specified correctly.  If the AD server is currently "
             + "down, please try again later.", hostName, principal);
-        throw new RuntimeException(warning, ne);
+        if (abortStartup) {
+          throw new StartupException(warning, ne);
+        } else {
+          throw new RuntimeException(warning, ne);
+        }
       }
       // wasn't the specific error we're looking for -- rethrow it.
       // <code>RuntimeException</code> is caught by the library, and retried.
@@ -155,7 +165,7 @@
   }
 
   @VisibleForTesting
-  void recreateLdapContext() {
+  void recreateLdapContext() throws StartupException {
     ldapContext = createLdapContext(connectMethod, hostName, port, principal,
         password, ldapTimeoutInMillis);
   }
@@ -176,7 +186,14 @@
     } catch (CommunicationException ce) {
       LOGGER.log(Level.FINER,
           "Reconnecting to AdServer after detecting issue", ce);
-      recreateLdapContext();
+      try {
+        recreateLdapContext();
+      } catch (StartupException se) {
+        // authentication issues
+        NamingException ne = new NamingException("recreateLdapContext problem");
+        ne.setRootCause(se);
+        throw ne;
+      }
       attributes = ldapContext.getAttributes("");
     } catch (NamingException ne) {
       if (ne.getMessage() != null
diff --git a/test/com/google/enterprise/adaptor/ad/AdAdaptorTest.java b/test/com/google/enterprise/adaptor/ad/AdAdaptorTest.java
index 8a3c507..dabe94b 100644
--- a/test/com/google/enterprise/adaptor/ad/AdAdaptorTest.java
+++ b/test/com/google/enterprise/adaptor/ad/AdAdaptorTest.java
@@ -27,6 +27,7 @@
 import com.google.enterprise.adaptor.Config;
 import com.google.enterprise.adaptor.DocIdPusher;
 import com.google.enterprise.adaptor.GroupPrincipal;
+import com.google.enterprise.adaptor.InvalidConfigurationException;
 import com.google.enterprise.adaptor.Principal;
 import com.google.enterprise.adaptor.Response;
 import com.google.enterprise.adaptor.TestHelper;
@@ -854,7 +855,7 @@
     try {
       initializeAdaptorConfig(adAdaptor, configEntries);
       fail("Did not catch expected exception");
-    } catch (IllegalStateException e) {
+    } catch (InvalidConfigurationException e) {
       assertTrue(e.toString().contains("user not specified"));
     }
   }
@@ -874,7 +875,7 @@
     try {
       initializeAdaptorConfig(adAdaptor, configEntries);
       fail("Did not catch expected exception");
-    } catch (IllegalStateException e) {
+    } catch (InvalidConfigurationException e) {
       assertTrue(e.toString().contains("password not specified"));
     }
   }
@@ -895,8 +896,8 @@
     try {
       initializeAdaptorConfig(adAdaptor, configEntries);
       fail("Did not catch expected exception");
-    } catch (IllegalArgumentException iae) {
-      assertTrue(iae.toString().contains("invalid method: https"));
+    } catch (InvalidConfigurationException ice) {
+      assertTrue(ice.toString().contains("invalid method: https"));
     }
   }
 
@@ -916,8 +917,9 @@
     try {
       initializeAdaptorConfig(adAdaptor, configEntries);
       fail("Did not catch expected exception");
-    } catch (IllegalArgumentException iae) {
-      assertTrue(iae.toString().contains("invalid ad.ldapReadTimeoutSecs"));
+    } catch (InvalidConfigurationException ice) {
+      assertTrue(ice.toString().contains(
+          "invalid value for ad.ldapReadTimeoutSecs"));
     }
   }
 
diff --git a/test/com/google/enterprise/adaptor/ad/AdServerTest.java b/test/com/google/enterprise/adaptor/ad/AdServerTest.java
index 49665dc..349d0fc 100644
--- a/test/com/google/enterprise/adaptor/ad/AdServerTest.java
+++ b/test/com/google/enterprise/adaptor/ad/AdServerTest.java
@@ -38,28 +38,28 @@
   }
 
   @Test
-  public void testNPEOnNullConnectMethod() {
+  public void testNPEOnNullConnectMethod() throws Exception {
     thrown.expect(NullPointerException.class);
     AdServer adServer = new AdServer(null, "hostname", 1234, "principal", "pw",
         "90000");
   }
 
   @Test
-  public void testNPEOnNullHostname() {
+  public void testNPEOnNullHostname() throws Exception {
     thrown.expect(NullPointerException.class);
     AdServer adServer = new AdServer(Method.SSL, null, 1234, "principal", "pw",
         "90000");
   }
 
   @Test
-  public void testIAEOnEmptyHostname() {
+  public void testIAEOnEmptyHostname() throws Exception {
     thrown.expect(IllegalArgumentException.class);
     AdServer adServer = new AdServer(Method.SSL, "", 1234, "principal", "pw",
         "90000");
   }
 
   @Test
-  public void testNPEOnNullPrincipal() {
+  public void testNPEOnNullPrincipal() throws Exception {
     thrown.expect(NullPointerException.class);
     AdServer adServer = new AdServer(Method.SSL, "hostname", 1234, null, "pw",
         "90000");
@@ -73,35 +73,35 @@
   }
 
   @Test
-  public void testNPEOnNullPassword() {
+  public void testNPEOnNullPassword() throws Exception {
     thrown.expect(NullPointerException.class);
     AdServer adServer = new AdServer(Method.SSL, "host", 1234, "princ", null,
         "90000");
   }
 
   @Test
-  public void testIAEOnEmptyPassword() {
+  public void testIAEOnEmptyPassword() throws Exception {
     thrown.expect(IllegalArgumentException.class);
     AdServer adServer = new AdServer(Method.SSL, "hostname", 1234, "princ", "",
         "90000");
   }
 
   @Test
-  public void testIAEOnBogusTimeout() {
+  public void testIAEOnBogusTimeout() throws Exception {
     thrown.expect(IllegalArgumentException.class);
     AdServer adServer = new AdServer(Method.SSL, "", 1234, "principal", "pw",
         "bogusTimeout");
   }
 
   @Test
-  public void testPublicSSLConstructor() {
+  public void testPublicSSLConstructor() throws Exception {
     thrown.expect(RuntimeException.class);
     AdServer adServer = new AdServer(Method.SSL, "localhost", 389, " ", " ",
         "90000");
   }
 
   @Test
-  public void testPublicStandardConstructor() {
+  public void testPublicStandardConstructor() throws Exception {
     thrown.expect(RuntimeException.class);
     AdServer adServer =
         new AdServer(Method.STANDARD, "localhost", 389, " ", " ", "90000");