Don't use instanceof to infer Adaptor features

Instead, the Adaptor explicitly provides the way to handle a feature via
AdaptorContext.
diff --git a/src/com/google/enterprise/adaptor/Adaptor.java b/src/com/google/enterprise/adaptor/Adaptor.java
index d0b0380..b25e764 100644
--- a/src/com/google/enterprise/adaptor/Adaptor.java
+++ b/src/com/google/enterprise/adaptor/Adaptor.java
@@ -34,7 +34,7 @@
  *
  * @see com.google.enterprise.adaptor.examples.AdaptorTemplate
  * @see AbstractAdaptor
- * @see PollingIncrementalAdaptor
+ * @see PollingIncrementalLister
  */
 public interface Adaptor {
   /**
diff --git a/src/com/google/enterprise/adaptor/AdaptorContext.java b/src/com/google/enterprise/adaptor/AdaptorContext.java
index c22f93f..a1e01be 100644
--- a/src/com/google/enterprise/adaptor/AdaptorContext.java
+++ b/src/com/google/enterprise/adaptor/AdaptorContext.java
@@ -103,4 +103,16 @@
    *     and {@code create = false}
    */
   public Session getUserSession(HttpExchange ex, boolean create);
+
+  /**
+   * Register a polling incremental lister, so that it can be called when
+   * appropriate. Registration may not occur after {@link Adaptor#init}.
+   */
+  public void setPollingIncrementalLister(PollingIncrementalLister lister);
+
+  /**
+   * Register an authentication provider, so it can authenticate users for the
+   * GSA. Registration may not occur after {@link Adaptor#init}.
+   */
+  public void setAuthnAuthority(AuthnAuthority authnAuthority);
 }
diff --git a/src/com/google/enterprise/adaptor/AuthnAdaptor.java b/src/com/google/enterprise/adaptor/AuthnAuthority.java
similarity index 95%
rename from src/com/google/enterprise/adaptor/AuthnAdaptor.java
rename to src/com/google/enterprise/adaptor/AuthnAuthority.java
index 9e26b85..79d3904 100644
--- a/src/com/google/enterprise/adaptor/AuthnAdaptor.java
+++ b/src/com/google/enterprise/adaptor/AuthnAuthority.java
@@ -20,8 +20,11 @@
 
 /**
  * Interface for adaptors capable of authenticating users.
+ *
+ * <p>Instances of this interface are typically registered with {@link
+ * AdaptorContext#setAuthnAuthority}.
  */
-public interface AuthnAdaptor extends Adaptor {
+public interface AuthnAuthority {
   /**
    * Authenticate the user connected via {@code ex}. After attempting to
    * authenticate the user the implementation should respond by calling {@link
diff --git a/src/com/google/enterprise/adaptor/Config.java b/src/com/google/enterprise/adaptor/Config.java
index 056397d..4333a44 100644
--- a/src/com/google/enterprise/adaptor/Config.java
+++ b/src/com/google/enterprise/adaptor/Config.java
@@ -41,8 +41,8 @@
  *     hour,  day of month, month, day of week).  Defaults to 0 3 * * *
  * <tr><td> </td><td>adaptor.incrementalPollPeriodSecs </td><td> number
  *     of seconds between invocations of {@link
- *     PollingIncrementalAdaptor#getModifiedDocIds
- *     PollingIncrementalAdaptor.getModifiedDocIds}.    Defaults to 900
+ *     PollingIncrementalLister#getModifiedDocIds
+ *     PollingIncrementalLister.getModifiedDocIds}.    Defaults to 900
  * <tr><td> </td><td>adaptor.docContentTimeoutSecs </td><td> number of seconds
  *     adaptor has to complete sending content before it is interrupted. Timing
  *     starts when sending content starts. Defaults to 180
diff --git a/src/com/google/enterprise/adaptor/Dashboard.java b/src/com/google/enterprise/adaptor/Dashboard.java
index 7ed959c..3fa2e8a 100644
--- a/src/com/google/enterprise/adaptor/Dashboard.java
+++ b/src/com/google/enterprise/adaptor/Dashboard.java
@@ -39,6 +39,7 @@
   private final GsaCommunicationHandler gsaCommHandler;
   private final SessionManager<HttpExchange> sessionManager;
   private final RpcHandler rpcHandler;
+  private final Adaptor adaptor;
 
   public Dashboard(Config config, GsaCommunicationHandler gsaCommHandler,
                    Journal journal, SessionManager<HttpExchange> sessionManager,
@@ -47,6 +48,7 @@
     this.gsaCommHandler = gsaCommHandler;
     this.journal = journal;
     this.sessionManager = sessionManager;
+    this.adaptor = adaptor;
 
     rpcHandler = new RpcHandler(sessionManager);
     rpcHandler.registerRpcMethod("startFeedPush", new StartFeedPushRpcMethod());
@@ -55,8 +57,6 @@
     circularLogRpcMethod = new CircularLogRpcMethod();
     rpcHandler.registerRpcMethod("getLog", circularLogRpcMethod);
     rpcHandler.registerRpcMethod("getConfig", new ConfigRpcMethod(config));
-    rpcHandler.registerRpcMethod("getStats",
-        new StatRpcMethod(journal, adaptor));
     rpcHandler.registerRpcMethod("getStatuses", new StatusRpcMethod(monitor));
     rpcHandler.registerRpcMethod("checkForUpdatedConfig",
         new CheckForUpdatedConfigRpcMethod(gsaCommHandler));
@@ -71,6 +71,9 @@
   /** Starts listening for connections to the dashboard. */
   public void start(HttpServer dashboardServer, String contextPrefix)
       throws IOException {
+    rpcHandler.registerRpcMethod("getStats", new StatRpcMethod(journal, adaptor,
+        gsaCommHandler.isAdaptorIncremental()));
+
     this.scope = new HttpServerScope(dashboardServer, contextPrefix);
     int dashboardPort = dashboardServer.getAddress().getPort();
     if (dashboardPort != config.getServerDashboardPort()) {
diff --git a/src/com/google/enterprise/adaptor/DocIdSender.java b/src/com/google/enterprise/adaptor/DocIdSender.java
index cd9ddd0..34b97fc 100644
--- a/src/com/google/enterprise/adaptor/DocIdSender.java
+++ b/src/com/google/enterprise/adaptor/DocIdSender.java
@@ -84,8 +84,8 @@
    * Calls {@link Adaptor#getModifiedDocIds}. This method blocks until all
    * DocIds are sent or retrying failed.
    */
-  public void pushIncrementalDocIdsFromAdaptor(ExceptionHandler handler)
-      throws InterruptedException {
+  public void pushIncrementalDocIdsFromAdaptor(PollingIncrementalLister lister,
+      ExceptionHandler handler) throws InterruptedException {
     if (handler == null) {
       throw new NullPointerException();
     }
@@ -94,7 +94,7 @@
     for (int ntries = 1;; ntries++) {
       boolean keepGoing = true;
       try {
-        ((PollingIncrementalAdaptor) adaptor).getModifiedDocIds(this);
+        lister.getModifiedDocIds(this);
         break; // Success
       } catch (InterruptedException ex) {
         // Stop early.
diff --git a/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java b/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java
index 6c883c9..05c2a0b 100644
--- a/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java
+++ b/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java
@@ -45,6 +45,9 @@
   private final Adaptor adaptor;
   private final Config config;
   private final Journal journal;
+  private boolean afterInit;
+  private PollingIncrementalLister pollingIncrementalLister;
+  private AuthnAuthority authnAuthority;
   /**
    * Cron-style scheduler. Available for other uses, but necessary for
    * scheduling {@link docIdFullPusher}. Tasks should execute quickly, to allow
@@ -254,6 +257,7 @@
 
     // Since the Adaptor has been started, we can now issue other calls to it.
     // Usages of 'adaptor' are completely safe after this point.
+    afterInit = true;
 
     // Since we are white-listing particular keys for auto-update, things aren't
     // ready enough to expose to adaptors.
@@ -269,16 +273,14 @@
           config.getServerPort(), config.getGsaHostname(),
           config.getGsaSamlEntityId(), config.getServerSamlEntityId());
 
-      if (adaptor instanceof AuthnAdaptor) {
-        log.config("Adaptor is an AuthnAdaptor; enabling adaptor-based "
-            + "authentication");
+      if (authnAuthority != null) {
+        log.config("Adaptor-based authentication supported");
         samlIdentityProvider = new SamlIdentityProvider(
-            (AuthnAdaptor) adaptor, metadata, key);
+            authnAuthority, metadata, key);
         addFilters(scope.createContext("/samlip",
             samlIdentityProvider.getSingleSignOnHandler()));
       } else {
-        log.config("Adaptor is not an AuthnAdaptor; not enabling adaptor-based "
-            + "authentication");
+        log.config("Adaptor-based authentication not supported");
       }
       samlServiceProvider
           = new SamlServiceProvider(sessionManager, metadata, key);
@@ -323,9 +325,9 @@
       checkAndScheduleImmediatePushOfDocIds();
     }
 
-    if (adaptor instanceof PollingIncrementalAdaptor) {
+    if (pollingIncrementalLister != null) {
       docIdIncrementalPusher = new OneAtATimeRunnable(
-          new IncrementalPushRunnable((PollingIncrementalAdaptor) adaptor),
+          new IncrementalPushRunnable(pollingIncrementalLister),
           new AlreadyRunningRunnable());
 
       scheduleExecutor.scheduleAtFixedRate(
@@ -493,6 +495,7 @@
       scope.close();
     }
     if (dashboard != null) {
+      dashboard.clearStatusSources();
       dashboard.stop();
     }
     if (scheduleExecutor != null) {
@@ -508,7 +511,6 @@
         Thread.currentThread().interrupt();
       }
     }
-    dashboard.clearStatusSources();
     try {
       adaptor.destroy();
     } finally {
@@ -523,6 +525,9 @@
       waiter = null;
       sessionManager = null;
       docIdCodec = null;
+      afterInit = false;
+      pollingIncrementalLister = null;
+      authnAuthority = null;
     }
   }
 
@@ -544,7 +549,7 @@
 
   /**
    * Perform an push of incremental changes. This works only for adaptors that
-   * support incremental polling (implements {@link PollingIncrementalAdaptor}.
+   * support incremental polling (implements {@link PollingIncrementalLister}.
    */
   public synchronized boolean checkAndScheduleIncrementalPushOfDocIds() {
     if (docIdIncrementalPusher == null) {
@@ -562,6 +567,10 @@
     return true;
   }
 
+  boolean isAdaptorIncremental() {
+    return pollingIncrementalLister != null;
+  }
+
   boolean ensureLatestConfigLoaded() {
     try {
       return config.ensureLatestConfigLoaded();
@@ -617,16 +626,17 @@
   private class IncrementalPushRunnable implements Runnable {
     private volatile ExceptionHandler handler
         = ExceptionHandlers.defaultHandler();
-    private PollingIncrementalAdaptor adaptor;
+    private PollingIncrementalLister adaptor;
 
-    public IncrementalPushRunnable(PollingIncrementalAdaptor adaptor) {
+    public IncrementalPushRunnable(PollingIncrementalLister adaptor) {
       this.adaptor = adaptor;
     }
 
     @Override
     public void run() {
       try {
-        docIdSender.pushIncrementalDocIdsFromAdaptor(handler);
+        docIdSender.pushIncrementalDocIdsFromAdaptor(
+            pollingIncrementalLister, handler);
       } catch (InterruptedException ex) {
         Thread.currentThread().interrupt();
       } catch (Exception ex) {
@@ -795,5 +805,21 @@
       }
       return nsSession;
     }
+
+    @Override
+    public void setPollingIncrementalLister(PollingIncrementalLister lister) {
+      if (afterInit) {
+        throw new IllegalStateException("After init()");
+      }
+      pollingIncrementalLister = lister;
+    }
+
+    @Override
+    public void setAuthnAuthority(AuthnAuthority authnAuthority) {
+      if (afterInit) {
+        throw new IllegalStateException("After init()");
+      }
+      authnAuthority = authnAuthority;
+    }
   }
 }
diff --git a/src/com/google/enterprise/adaptor/PollingIncrementalAdaptor.java b/src/com/google/enterprise/adaptor/PollingIncrementalLister.java
similarity index 97%
rename from src/com/google/enterprise/adaptor/PollingIncrementalAdaptor.java
rename to src/com/google/enterprise/adaptor/PollingIncrementalLister.java
index d070213..6f23396 100644
--- a/src/com/google/enterprise/adaptor/PollingIncrementalAdaptor.java
+++ b/src/com/google/enterprise/adaptor/PollingIncrementalLister.java
@@ -33,7 +33,7 @@
  * adaptor for the user. Thus, adaptors are encouraged to implement this
  * interface if it is applicable.
  */
-public interface PollingIncrementalAdaptor extends Adaptor {
+public interface PollingIncrementalLister {
   /**
    * Check for documents modified since the last call to the method. This method
    * is intended to provide little-effort updates to the GSA about recent
diff --git a/src/com/google/enterprise/adaptor/SamlIdentityProvider.java b/src/com/google/enterprise/adaptor/SamlIdentityProvider.java
index 852d43c..b4299e1 100644
--- a/src/com/google/enterprise/adaptor/SamlIdentityProvider.java
+++ b/src/com/google/enterprise/adaptor/SamlIdentityProvider.java
@@ -97,13 +97,13 @@
     }
   }
 
-  private final AuthnAdaptor adaptor;
+  private final AuthnAuthority adaptor;
   /** Credentials to use to sign messages. */
   private final Credential cred;
   private final SamlMetadata metadata;
   private final SsoHandler ssoHandler = new SsoHandler();
 
-  public SamlIdentityProvider(AuthnAdaptor adaptor, SamlMetadata metadata,
+  public SamlIdentityProvider(AuthnAuthority adaptor, SamlMetadata metadata,
       KeyPair key) {
     if (adaptor == null || metadata == null) {
       throw new NullPointerException();
@@ -274,7 +274,7 @@
     }
   }
 
-  private class AuthnCallback implements AuthnAdaptor.Callback {
+  private class AuthnCallback implements AuthnAuthority.Callback {
     private final SAMLMessageContext<AuthnRequest, Response, NameID> context;
 
     public AuthnCallback(
diff --git a/src/com/google/enterprise/adaptor/StatRpcMethod.java b/src/com/google/enterprise/adaptor/StatRpcMethod.java
index ca73c3d..9397697 100644
--- a/src/com/google/enterprise/adaptor/StatRpcMethod.java
+++ b/src/com/google/enterprise/adaptor/StatRpcMethod.java
@@ -29,9 +29,10 @@
   private Journal journal;
   private boolean isAdaptorIncremental;
 
-  public StatRpcMethod(Journal journal, Adaptor adaptor) {
+  public StatRpcMethod(Journal journal, Adaptor adaptor,
+      boolean isAdaptorIncremental) {
     this.journal = journal;
-    this.isAdaptorIncremental = adaptor instanceof PollingIncrementalAdaptor;
+    this.isAdaptorIncremental = isAdaptorIncremental;
 
     Class adaptorClass = adaptor.getClass();
     if (adaptorClass.getPackage() != null) {
diff --git a/src/com/google/enterprise/adaptor/WrapperAdaptor.java b/src/com/google/enterprise/adaptor/WrapperAdaptor.java
index 595694d..38dc91b 100644
--- a/src/com/google/enterprise/adaptor/WrapperAdaptor.java
+++ b/src/com/google/enterprise/adaptor/WrapperAdaptor.java
@@ -477,5 +477,15 @@
     public Session getUserSession(HttpExchange ex, boolean create) {
       return context.getUserSession(ex, create);
     }
+
+    @Override
+    public void setPollingIncrementalLister(PollingIncrementalLister lister) {
+      context.setPollingIncrementalLister(lister);
+    }
+
+    @Override
+    public void setAuthnAuthority(AuthnAuthority authnAuthority) {
+      context.setAuthnAuthority(authnAuthority);
+    }
   }
 }
diff --git a/test/com/google/enterprise/adaptor/GsaCommunicationHandlerTest.java b/test/com/google/enterprise/adaptor/GsaCommunicationHandlerTest.java
index a7364b1..3836651 100644
--- a/test/com/google/enterprise/adaptor/GsaCommunicationHandlerTest.java
+++ b/test/com/google/enterprise/adaptor/GsaCommunicationHandlerTest.java
@@ -115,11 +115,16 @@
   @Test
   public void testPollingIncrementalAdaptor() throws Exception {
     class PollingIncrNullAdaptor extends NullAdaptor
-        implements PollingIncrementalAdaptor {
+        implements PollingIncrementalLister {
       public final ArrayBlockingQueue<Object> queue
           = new ArrayBlockingQueue<Object>(1);
 
       @Override
+      public void init(AdaptorContext context) {
+        context.setPollingIncrementalLister(this);
+      }
+
+      @Override
       public void getModifiedDocIds(DocIdPusher pusher) {
         queue.offer(new Object());
       }
diff --git a/test/com/google/enterprise/adaptor/SamlIdentityProviderTest.java b/test/com/google/enterprise/adaptor/SamlIdentityProviderTest.java
index 4562ead..8b67eba 100644
--- a/test/com/google/enterprise/adaptor/SamlIdentityProviderTest.java
+++ b/test/com/google/enterprise/adaptor/SamlIdentityProviderTest.java
@@ -43,7 +43,7 @@
   private UserPrincipal user = new UserPrincipal("user1");
   private Set<GroupPrincipal> groups = GroupPrincipal.makeSet(Arrays.asList(
       "group1", "group2"));
-  private AuthnAdaptor adaptor = new AuthnAdaptorImpl();
+  private AuthnAuthority adaptor = new AuthnAuthorityImpl();
   private SamlIdentityProvider identityProvider = new SamlIdentityProvider(
       adaptor, metadata, null);
   private MockHttpContext httpContext = new MockHttpsContext(null, "/samlip");
@@ -112,17 +112,7 @@
     assertEquals(404, ex.getResponseCode());
   }
 
-  private class AuthnAdaptorImpl extends AbstractAdaptor
-      implements AuthnAdaptor {
-    @Override
-    public void getDocContent(Request request, Response response)
-        throws IOException {
-      response.respondNotFound();
-    }
-
-    @Override
-    public void getDocIds(DocIdPusher pusher) {}
-
+  private class AuthnAuthorityImpl implements AuthnAuthority {
     @Override
     public void authenticateUser(HttpExchange ex, Callback callback)
         throws IOException {
diff --git a/test/com/google/enterprise/adaptor/StatRpcMethodTest.java b/test/com/google/enterprise/adaptor/StatRpcMethodTest.java
index c865684..f1b642a 100644
--- a/test/com/google/enterprise/adaptor/StatRpcMethodTest.java
+++ b/test/com/google/enterprise/adaptor/StatRpcMethodTest.java
@@ -18,19 +18,14 @@
 
 import org.junit.Test;
 
-import java.io.IOException;
-
 import java.util.*;
 
 /**
  * Tests for {@link StatRpcMethod}.
  */
 public class StatRpcMethodTest {
-  private RpcHandler.RpcMethod method;
-
-  public StatRpcMethodTest() {
-    method = new StatRpcMethod(new SnapshotMockJournal(), new AdaptorMock());
-  }
+  private RpcHandler.RpcMethod method = new StatRpcMethod(
+      new SnapshotMockJournal(), new AdaptorMock(), false);
 
   @Test
   public void testStat() throws Exception {