Specify socket timeout and read time out for SharePoint web services call to avoid hanged threads from unstable SharePoint env.
https://codereview.appspot.com/99080044/
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java b/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
index 01de037..06e03fb 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
@@ -266,6 +266,9 @@
 
   private static final String SITE_COLLECTION_ADMIN_FRAGMENT = "admin";
 
+  private int socketTimeoutMillis;
+  private int readTimeOutMillis;
+
   /**
    * Mapping of mime-types used by SharePoint to ones that the GSA comprehends.
    */
@@ -488,6 +491,10 @@
     String stsrealm = config.getValue("sharepoint.sts.realm");
     boolean useLiveAuthentication = Boolean.parseBoolean(
         config.getValue("sharepoint.useLiveAuthentication"));
+    socketTimeoutMillis = Integer.parseInt(
+        config.getValue("adaptor.docHeaderTimeoutSecs")) * 1000;
+    readTimeOutMillis = Integer.parseInt(
+        config.getValue("adaptor.docContentTimeoutSecs")) * 1000;
 
     log.log(Level.CONFIG, "VirtualServer: {0}", virtualServer);
     log.log(Level.CONFIG, "Username: {0}", username);
@@ -521,6 +528,7 @@
     } else {    
       AuthenticationSoap authenticationSoap = authenticationClientFactory
           .newSharePointFormsAuthentication(virtualServer, username, password);
+      addSocketTimeoutConfiguration((BindingProvider) authenticationSoap);
       authenticationHandler = new SharePointFormsAuthenticationHandler
           .Builder(username, password, scheduledExecutor, authenticationSoap)
           .build();
@@ -968,13 +976,15 @@
       String endpointPeople = spUrlToUri(site + "/_vti_bin/People.asmx")
           .toString();
       PeopleSoap peopleSoap = soapFactory.newPeople(endpointPeople);
-      // JAX-WS RT 2.1.4 doesn't handle headers correctly and always assumes the
-      // list contains precisely one entry, so we work around it here.
-      if (authenticationHandler.isFormsAuthentication()) {
-        addFormsAuthenticationCookies((BindingProvider) siteDataSoap);
-        addFormsAuthenticationCookies((BindingProvider) userGroupSoap);
-        addFormsAuthenticationCookies((BindingProvider) peopleSoap);
-      }
+
+      addFormsAuthenticationCookies((BindingProvider) siteDataSoap);
+      addFormsAuthenticationCookies((BindingProvider) userGroupSoap);
+      addFormsAuthenticationCookies((BindingProvider) peopleSoap);
+
+      addSocketTimeoutConfiguration((BindingProvider) siteDataSoap);
+      addSocketTimeoutConfiguration((BindingProvider) userGroupSoap);
+      addSocketTimeoutConfiguration((BindingProvider) peopleSoap);
+
       siteAdaptor = new SiteAdaptor(site, web, siteDataSoap, userGroupSoap,
           peopleSoap, new MemberIdMappingCallable(site),
           new SiteUserIdMappingCallable(site));
@@ -996,6 +1006,17 @@
             authenticationHandler.getAuthenticationCookies()));
   }
   
+  private void addSocketTimeoutConfiguration(BindingProvider port) {
+    port.getRequestContext().put("com.sun.xml.internal.ws.connect.timeout",
+        socketTimeoutMillis);
+    port.getRequestContext().put("com.sun.xml.internal.ws.request.timeout",
+        readTimeOutMillis);
+    port.getRequestContext().put("com.sun.xml.ws.connect.timeout",
+        socketTimeoutMillis);
+    port.getRequestContext().put("com.sun.xml.ws.request.timeout",
+        readTimeOutMillis);
+  }
+
   private void disableFormsAuthentication(BindingProvider port) {
     port.getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS, 
         Collections.singletonMap("X-FORMS_BASED_AUTH_ACCEPTED", 
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java b/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java
index 6e91ec1..db23326 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java
@@ -123,6 +123,9 @@
   public static final String GSA_PROPNAME_COLLEAGUES =
       "google_social_user_colleagues";
 
+ private static int socketTimeoutMillis;
+ private static int readTimeOutMillis;
+
   // Mapping for SharePoint user profile properties to
   // GSA Expert Search properties
   static {
@@ -232,6 +235,11 @@
     boolean useLiveAuthentication = Boolean.parseBoolean(
         config.getValue("sharepoint.useLiveAuthentication"));
 
+    socketTimeoutMillis = Integer.parseInt(
+        config.getValue("adaptor.docHeaderTimeoutSecs")) * 1000;
+    readTimeOutMillis = Integer.parseInt(
+        config.getValue("adaptor.docContentTimeoutSecs")) * 1000;
+
     log.log(Level.CONFIG, "virtualServer: {0}", virtualServer);
     log.log(Level.CONFIG, "Username: {0}", username);
     log.log(Level.CONFIG, "setAcl: {0}", setAcl);
@@ -272,6 +280,7 @@
     } else {    
       AuthenticationSoap authenticationSoap = authenticationClientFactory
           .newSharePointFormsAuthentication(virtualServer, username, password);
+      addSocketTimeoutConfiguration((BindingProvider) authenticationSoap);
       authenticationHandler = new SharePointFormsAuthenticationHandler
           .Builder(username, password, scheduledExecutor, authenticationSoap)
           .build();
@@ -321,6 +330,17 @@
         userProfileChangeToken);
   }
 
+  private static void addSocketTimeoutConfiguration(BindingProvider port) {
+    port.getRequestContext().put("com.sun.xml.internal.ws.connect.timeout",
+        socketTimeoutMillis);
+    port.getRequestContext().put("com.sun.xml.internal.ws.request.timeout",
+        readTimeOutMillis);
+    port.getRequestContext().put("com.sun.xml.ws.connect.timeout",
+        socketTimeoutMillis);
+    port.getRequestContext().put("com.sun.xml.ws.request.timeout",
+        readTimeOutMillis);
+  }
+
   private static class NtlmAuthenticator extends Authenticator {
     private final String username;
     private final char[] password;
@@ -375,14 +395,15 @@
       UserProfileChangeServiceSoap inUserProfileChangeServiceSoap 
           = userProfileChangeServiceSoap.getPort(
               endpointChangeRef, UserProfileChangeServiceSoap.class);
-      // JAX-WS RT 2.1.4 doesn't handle headers correctly and always assumes the
-      // list contains precisely one entry, so we work around it here.
-      if (!cookies.isEmpty()) {
-        addFormsAuthenticationCookies(
-            (BindingProvider) inUserProfileServiceSoap, cookies);
-        addFormsAuthenticationCookies(
-            (BindingProvider) inUserProfileChangeServiceSoap, cookies);
-      }
+   
+      addFormsAuthenticationCookies(
+          (BindingProvider) inUserProfileServiceSoap, cookies);
+      addFormsAuthenticationCookies(
+          (BindingProvider) inUserProfileChangeServiceSoap, cookies);
+      addSocketTimeoutConfiguration((BindingProvider) inUserProfileServiceSoap);
+      addSocketTimeoutConfiguration(
+          (BindingProvider) inUserProfileChangeServiceSoap);
+
       return new SharePointUserProfileServiceWS(inUserProfileServiceSoap,
           inUserProfileChangeServiceSoap);
     }
diff --git a/test/com/google/enterprise/adaptor/sharepoint/DelegatingSiteData.java b/test/com/google/enterprise/adaptor/sharepoint/DelegatingSiteData.java
index 75043f0..3e67ac3 100644
--- a/test/com/google/enterprise/adaptor/sharepoint/DelegatingSiteData.java
+++ b/test/com/google/enterprise/adaptor/sharepoint/DelegatingSiteData.java
@@ -25,10 +25,14 @@
 import com.microsoft.schemas.sharepoint.soap.SSiteMetadata;
 import com.microsoft.schemas.sharepoint.soap.SWebMetadata;
 import com.microsoft.schemas.sharepoint.soap.SiteDataSoap;
+import java.util.Map;
+import javax.xml.ws.Binding;
+import javax.xml.ws.BindingProvider;
+import javax.xml.ws.EndpointReference;
 
 import javax.xml.ws.Holder;
 
-abstract class DelegatingSiteData implements SiteDataSoap {
+abstract class DelegatingSiteData implements SiteDataSoap, BindingProvider {
   protected abstract SiteDataSoap delegate();
 
   @Override
@@ -127,6 +131,31 @@
   public String getChangesEx(int version, String xmlInput) {
     return delegate().getChangesEx(version, xmlInput);
   }
+
+  @Override
+  public Map<String, Object> getRequestContext() {
+   return ((BindingProvider) delegate()).getRequestContext();
+  }
+
+  @Override
+  public Map<String, Object> getResponseContext() {
+    return ((BindingProvider) delegate()).getResponseContext();
+  }
+
+  @Override
+  public Binding getBinding() {
+   return ((BindingProvider) delegate()).getBinding();
+  }
+
+  @Override
+  public EndpointReference getEndpointReference() {
+    return ((BindingProvider) delegate()).getEndpointReference();
+  }
+
+  @Override
+  public <T extends EndpointReference> T getEndpointReference(Class<T> clazz) {
+    return ((BindingProvider) delegate()).getEndpointReference(clazz);
+  }
 }
 
 
diff --git a/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java b/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
index 4162017..d5cd8e3 100644
--- a/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
+++ b/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
@@ -123,6 +123,7 @@
 
 import javax.xml.ws.Holder;
 import javax.xml.ws.WebServiceException;
+import javax.xml.ws.handler.MessageContext;
 
 /**
  * Test cases for {@link SharePointAdaptor}.
@@ -348,6 +349,57 @@
   }
   
   @Test
+  public void testAdaptorWithSocketTimeoutConfiguration() throws Exception {
+    Map<String, Object> goldenRequestContext;
+    Map<String, Object> goldenRequestContextAuth;
+    {
+      Map<String, Object> tmp = new HashMap<String, Object>();
+      tmp.put("com.sun.xml.internal.ws.connect.timeout", 20000);
+      tmp.put("com.sun.xml.internal.ws.request.timeout", 180000);
+      tmp.put("com.sun.xml.ws.connect.timeout", 20000);
+      tmp.put("com.sun.xml.ws.request.timeout", 180000);
+      goldenRequestContextAuth 
+          = Collections.unmodifiableMap(new HashMap<String, Object>(tmp));
+      // Disabling forms authentication
+      tmp.put(MessageContext.HTTP_REQUEST_HEADERS, 
+        Collections.singletonMap("X-FORMS_BASED_AUTH_ACCEPTED",
+          Collections.singletonList("f")));
+      goldenRequestContext = Collections.unmodifiableMap(tmp);
+    }
+
+    MockSiteData siteDataSoap = new MockSiteData()
+        .register(VS_CONTENT_EXCHANGE).register(CD_CONTENT_EXCHANGE);
+    MockPeopleSoap peopleSoap = new MockPeopleSoap();
+    MockUserGroupSoap userGroupSoap = new MockUserGroupSoap(null);
+    final MockAuthenticationSoap authenticationSoap 
+        = new MockAuthenticationSoap();
+    SoapFactory siteDataFactory = MockSoapFactory.blank()
+        .endpoint(VS_ENDPOINT, siteDataSoap)
+        .endpoint("http://localhost:1/_vti_bin/People.asmx", peopleSoap)
+        .endpoint("http://localhost:1/_vti_bin/UserGroup.asmx", userGroupSoap);
+
+    adaptor = new SharePointAdaptor(siteDataFactory,
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms() {
+          @Override
+          public AuthenticationSoap newSharePointFormsAuthentication(
+              String virtualServer, String username, String password)
+              throws IOException {
+            return authenticationSoap;
+          }
+        });
+    config.overrideKey("adaptor.docHeaderTimeoutSecs", "20");    
+    adaptor.init(new MockAdaptorContext(config, pusher));
+    assertEquals(goldenRequestContext, siteDataSoap.getRequestContext());
+    assertEquals(goldenRequestContext, peopleSoap.getRequestContext());
+    assertEquals(goldenRequestContext, userGroupSoap.getRequestContext());
+    assertEquals(goldenRequestContextAuth,
+        authenticationSoap.getRequestContext());
+    adaptor.destroy();
+    adaptor = null;
+  }
+
+  @Test
   public void testAdaptorInitWithAdfs() throws Exception {
     SoapFactory siteDataFactory = MockSoapFactory.blank()
         .endpoint(VS_ENDPOINT, MockSiteData.blank()
@@ -2050,7 +2102,9 @@
   
   private static class UnsupportedPeopleSoap extends DelegatingPeopleSoap
       implements BindingProvider {
-    private final String endpoint; 
+    private final String endpoint;
+    private final Map<String, Object> requestContext
+        = new HashMap<String, Object>();
 
     public UnsupportedPeopleSoap() {
       this(null);
@@ -2071,7 +2125,7 @@
 
     @Override
     public Map<String, Object> getRequestContext() {
-       throw new UnsupportedOperationException();
+       return requestContext;
     }
 
     @Override
@@ -2156,7 +2210,9 @@
 
   private static class UnsupportedUserGroupSoap
       extends DelegatingUserGroupSoap  implements BindingProvider {
-    private final String endpoint;   
+    private final String endpoint;
+    private final Map<String, Object> requestContext
+        = new HashMap<String, Object>();
 
     public UnsupportedUserGroupSoap() {
       this(null);
@@ -2177,7 +2233,7 @@
 
     @Override
     public Map<String, Object> getRequestContext() {
-       throw new UnsupportedOperationException();
+       return requestContext;
     }
 
     @Override
@@ -2461,6 +2517,8 @@
   
   private static class MockAuthenticationSoap extends 
       UnsupportedAuthenticationSoap {
+    private final Map<String, Object> requestContext
+        = new HashMap<String, Object>();
     @Override
     public LoginResult login(String string, String string1) {
       throw new UnsupportedOperationException();
@@ -2470,6 +2528,11 @@
     public AuthenticationMode mode() {
       return AuthenticationMode.WINDOWS;
     }
+
+    @Override
+    public Map<String, Object> getRequestContext() {
+      return requestContext;
+    }
   }
   
   private static class UnsupportedAuthenticationSoap 
@@ -2492,11 +2555,37 @@
         throw new UnsupportedOperationException("Endpoint: " + endpoint);
       }
     }
+
+    @Override
+    public Map<String, Object> getRequestContext() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, Object> getResponseContext() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Binding getBinding() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EndpointReference getEndpointReference() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EndpointReference> T getEndpointReference(
+        Class<T> clazz) {
+      throw new UnsupportedOperationException();
+    }
   }
 
 
   private abstract static class DelegatingAuthenticationSoap 
-      implements AuthenticationSoap {
+      implements AuthenticationSoap, BindingProvider {
     protected abstract AuthenticationSoap delegate();
 
     @Override
@@ -2508,13 +2597,41 @@
     public AuthenticationMode mode() {
       return delegate().mode();
     }    
+    
+    @Override
+    public Map<String, Object> getRequestContext() {
+      return ((BindingProvider) delegate()).getRequestContext();
+    }
+
+    @Override
+    public Map<String, Object> getResponseContext() {
+      return ((BindingProvider) delegate()).getResponseContext();
+    }
+
+    @Override
+    public Binding getBinding() {
+      return ((BindingProvider) delegate()).getBinding();
+    }
+
+    @Override
+    public EndpointReference getEndpointReference() {
+      return ((BindingProvider) delegate()).getEndpointReference();
+    }
+
+    @Override
+    public <T extends EndpointReference> T getEndpointReference(
+        Class<T> clazz) {
+      return ((BindingProvider) delegate()).getEndpointReference(clazz);
+    }
   }
 
   /**
    * Throw UnsupportedOperationException for all calls.
    */
   private static class UnsupportedSiteData extends DelegatingSiteData
-      implements BindingProvider{   
+      implements BindingProvider {
+    private final Map<String, Object> requestContext
+        = new HashMap<String, Object>();
     @Override
     protected SiteDataSoap delegate() {
       throw new UnsupportedOperationException();
@@ -2522,7 +2639,7 @@
 
     @Override
     public Map<String, Object> getRequestContext() {
-       throw new UnsupportedOperationException();
+       return requestContext;
     }
 
     @Override
@@ -2650,7 +2767,6 @@
     private final List<ContentExchange> contentList;
     private final List<ChangesExchange> changesList;
     private final List<SiteAndWebExchange> siteAndWebList;
-    private Map<String, Object> requestContext = new HashMap<String, Object>();
 
     private MockSiteData() {
       this.urlSegmentsList = Collections.emptyList();
@@ -2746,11 +2862,6 @@
       }
       fail("Could not find " + strUrl);
     }
-    
-    @Override
-    public Map<String, Object> getRequestContext() {
-      return requestContext;
-    }
 
     public static MockSiteData blank() {
       return new MockSiteData();
diff --git a/test/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptorTest.java b/test/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptorTest.java
index 7b2bd1d..5df3fc0 100644
--- a/test/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptorTest.java
+++ b/test/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptorTest.java
@@ -68,6 +68,9 @@
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.ws.Binding;
+import javax.xml.ws.BindingProvider;
+import javax.xml.ws.EndpointReference;
 
 import javax.xml.ws.WebServiceException;
 
@@ -666,7 +669,10 @@
     }
   }
   
-  private static class MockAuthenticationSoap implements AuthenticationSoap {
+  private static class MockAuthenticationSoap
+      implements AuthenticationSoap, BindingProvider {
+    private final Map<String, Object> requestContext 
+        = new HashMap<String, Object>();
     @Override
     public LoginResult login(String string, String string1) {
       throw new UnsupportedOperationException();
@@ -676,6 +682,32 @@
     public AuthenticationMode mode() {
       return AuthenticationMode.WINDOWS;
     }    
+
+    @Override
+    public Map<String, Object> getRequestContext() {
+      return requestContext;
+    }
+
+    @Override
+    public Map<String, Object> getResponseContext() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Binding getBinding() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EndpointReference getEndpointReference() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EndpointReference> T getEndpointReference(
+        Class<T> clazz) {
+      throw new UnsupportedOperationException();
+    }
   }
   
   private static class UnsupportedAuthenticationClientFactory