You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2015/07/30 16:28:21 UTC

svn commit: r1693432 - in /lucene/dev/trunk/solr: ./ core/src/java/org/apache/solr/request/ core/src/java/org/apache/solr/search/ core/src/java/org/apache/solr/servlet/ core/src/java/org/apache/solr/util/ solrj/src/java/org/apache/solr/client/solrj/imp...

Author: noble
Date: Thu Jul 30 14:28:20 2015
New Revision: 1693432

URL: http://svn.apache.org/r1693432
Log:
SOLR-6625: Enable registering interceptors for the calls made using HttpClient and make the
  request object available at the interceptor context

Added:
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SolrQueryRequestContext.java   (with props)
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/SolrHttpClient.java   (with props)
    lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrHttpContext.java   (with props)
Modified:
    lucene/dev/trunk/solr/CHANGES.txt
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
    lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientConfigurer.java
    lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientUtil.java
    lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/util/ExecutorUtil.java
    lucene/dev/trunk/solr/solrj/src/test/org/apache/solr/client/solrj/impl/BasicHttpSolrClientTest.java
    lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
    lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/SolrJettyTestBase.java
    lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/BaseTestHarness.java

Modified: lucene/dev/trunk/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/CHANGES.txt (original)
+++ lucene/dev/trunk/solr/CHANGES.txt Thu Jul 30 14:28:20 2015
@@ -376,6 +376,9 @@ Other Changes
   server/solr-webapp, solr.war is no longer included in the distribution
   bundles. (Timothy Potter, Uwe Schindler)
 
+* SOLR-6625: Enable registering interceptors for the calls made using HttpClient and make the
+  request object available at the interceptor context ( Ishan Chattopadhyay, Gregory Chanan, noble, Anshum Gupta)
+
 ==================  5.2.1 ==================
 
 Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SolrQueryRequestContext.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SolrQueryRequestContext.java?rev=1693432&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SolrQueryRequestContext.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SolrQueryRequestContext.java Thu Jul 30 14:28:20 2015
@@ -0,0 +1,45 @@
+package org.apache.solr.request;
+
+
+import org.apache.solr.client.solrj.impl.SolrHttpContext;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A HttpContext derivative to encapsulate a server-side SolrQueryRequest
+ * object.
+ */
+public class SolrQueryRequestContext extends SolrHttpContext {
+  final private SolrQueryRequest solrQueryRequest;
+  
+  public SolrQueryRequestContext(SolrQueryRequest solrQueryRequest) {
+    this.solrQueryRequest = solrQueryRequest;
+    setAttribute(SolrHttpContext.class.getName(), this);
+  }
+  
+  public SolrQueryRequest getSolrQueryRequest() {
+    return solrQueryRequest;
+  }
+  
+  @Override
+  public String toString() {
+    return "[SolrQueryRequestContext contains: "+solrQueryRequest+"]";
+  }
+}
+
+

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java Thu Jul 30 14:28:20 2015
@@ -17,19 +17,21 @@
 
 package org.apache.solr.request;
 
+import java.io.Closeable;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicReference;
+
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.component.ResponseBuilder;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.util.TimeZoneUtils;
 
-import java.io.Closeable;
-import java.util.Date;
-import java.util.TimeZone;
-import java.util.LinkedList;
-import java.util.List;
-
 
 public class SolrRequestInfo {
   protected final static ThreadLocal<SolrRequestInfo> threadLocal = new ThreadLocal<>();
@@ -50,7 +52,8 @@ public class SolrRequestInfo {
     // TODO: temporary sanity check... this can be changed to just an assert in the future
     SolrRequestInfo prev = threadLocal.get();
     if (prev != null) {
-      SolrCore.log.error("Previous SolrRequestInfo was not closed!  req=" + prev.req.getOriginalParams().toString());  
+      SolrCore.log.error("Previous SolrRequestInfo was not closed!  req=" + prev.req.getOriginalParams().toString());
+      SolrCore.log.error("prev == info : {}", prev.req == info.req);
     }
     assert prev == null;
 
@@ -137,4 +140,28 @@ public class SolrRequestInfo {
       closeHooks.add(hook);
     }
   }
+
+  public static ExecutorUtil.InheritableThreadLocalProvider getInheritableThreadLocalProvider() {
+    return new ExecutorUtil.InheritableThreadLocalProvider() {
+      @Override
+      public void store(AtomicReference ctx) {
+        SolrRequestInfo me = threadLocal.get();
+        if (me != null) ctx.set(me);
+      }
+
+      @Override
+      public void set(AtomicReference ctx) {
+        SolrRequestInfo me = (SolrRequestInfo) ctx.get();
+        if (me != null) {
+          ctx.set(null);
+          threadLocal.set(me);
+        }
+      }
+
+      @Override
+      public void clean(AtomicReference ctx) {
+        threadLocal.remove();
+      }
+    };
+  }
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java Thu Jul 30 14:28:20 2015
@@ -2203,6 +2203,7 @@ public class SolrIndexSearcher extends I
       };
 
       SolrQueryResponse rsp = new SolrQueryResponse();
+      SolrRequestInfo.clearRequestInfo();
       SolrRequestInfo.setRequestInfo(new SolrRequestInfo(req, rsp));
       try {
         this.cacheList[i].warm(this, old.cacheList[i]);

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java Thu Jul 30 14:28:20 2015
@@ -44,12 +44,15 @@ import org.apache.solr.common.SolrExcept
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.NodeConfig;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.core.SolrXmlConfig;
+import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.security.AuthenticationPlugin;
+import org.apache.solr.util.SolrHttpClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -90,6 +93,9 @@ public class SolrDispatchFilter extends
   public void init(FilterConfig config) throws ServletException
   {
     log.info("SolrDispatchFilter.init(): {}", this.getClass().getClassLoader());
+
+    HttpClientUtil.HttpClientFactory.setHttpClientImpl(SolrHttpClient.SolrDefaultHttpClient.class, SolrHttpClient.SolrSystemDefaultHttpClient.class);
+
     String exclude = config.getInitParameter("excludePatterns");
     if(exclude != null) {
       String[] excludeArray = exclude.split(",");
@@ -106,6 +112,7 @@ public class SolrDispatchFilter extends
       String solrHome = (String) config.getServletContext().getAttribute(SOLRHOME_ATTRIBUTE);
       if (solrHome == null)
         solrHome = SolrResourceLoader.locateSolrHome();
+      ExecutorUtil.addThreadLocalProvider(SolrRequestInfo.getInheritableThreadLocalProvider());
 
       this.cores = createCoreContainer(solrHome, extraProperties);
 

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/SolrHttpClient.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/SolrHttpClient.java?rev=1693432&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/SolrHttpClient.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/SolrHttpClient.java Thu Jul 30 14:28:20 2015
@@ -0,0 +1,97 @@
+package org.apache.solr.util;
+
+import java.io.IOException;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.SystemDefaultHttpClient;
+import org.apache.http.protocol.HttpContext;
+import org.apache.solr.client.solrj.impl.SolrHttpContext;
+import org.apache.solr.request.SolrQueryRequestContext;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.request.SolrRequestInfo;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class SolrHttpClient {
+  
+  private static HttpContext getHttpContext() {
+    SolrRequestInfo requestInfo = SolrRequestInfo.getRequestInfo();
+    SolrQueryRequest request = requestInfo == null ? null : requestInfo.getReq();
+    return request == null ? SolrHttpContext.EMPTY_CONTEXT: new SolrQueryRequestContext(request);
+  }
+
+  public static class SolrSystemDefaultHttpClient extends SystemDefaultHttpClient {
+    
+    public SolrSystemDefaultHttpClient() {
+      super();
+    }
+    
+    @Override
+    public CloseableHttpResponse execute(HttpUriRequest request)
+        throws IOException {
+      return super.execute(request, getHttpContext());
+    }
+    
+    @Override
+    public CloseableHttpResponse execute(HttpHost target, HttpRequest request)
+        throws IOException, ClientProtocolException {
+      return super.execute(target, request, getHttpContext());
+    }
+    
+    @Override
+    public <T> T execute(HttpUriRequest request,
+        ResponseHandler<? extends T> responseHandler) throws IOException,
+        ClientProtocolException {
+      return super.execute(request, responseHandler, getHttpContext());
+    }
+  }
+  
+  public static class SolrDefaultHttpClient extends DefaultHttpClient {
+    
+    public SolrDefaultHttpClient(ClientConnectionManager cm) {
+      super(cm);
+    }
+    
+    @Override
+    public CloseableHttpResponse execute(HttpUriRequest request)
+        throws IOException {
+      return super.execute(request, getHttpContext());
+    }
+    
+    @Override
+    public CloseableHttpResponse execute(HttpHost target, HttpRequest request)
+        throws IOException, ClientProtocolException {
+      return super.execute(target, request, getHttpContext());
+    }
+    
+    @Override
+    public <T> T execute(HttpUriRequest request,
+        ResponseHandler<? extends T> responseHandler) throws IOException,
+        ClientProtocolException {
+      return super.execute(request, responseHandler, getHttpContext());
+    }
+  }
+}
+

Modified: lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientConfigurer.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientConfigurer.java?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientConfigurer.java (original)
+++ lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientConfigurer.java Thu Jul 30 14:28:20 2015
@@ -18,8 +18,13 @@ package org.apache.solr.client.solrj.imp
  */
 
 
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
 import org.apache.http.conn.ssl.SSLSocketFactory;
 import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.protocol.HttpContext;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.params.SolrParams;
 
 /**
@@ -76,6 +81,18 @@ public class HttpClientConfigurer {
     if(sslCheckPeerName == false) {
       HttpClientUtil.setHostNameVerifier(httpClient, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
     }
+
+    // Intercept every request made through httpclient and validate it has a SolrHttpContext object.
+    httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
+      @Override
+      public void process(final HttpRequest request, final HttpContext context) {
+        // Verify that a context object was passed in
+        final Object solrContext = context.getAttribute(SolrHttpContext.SOLR_CONTEXT_KEY);
+        if (solrContext == null || solrContext instanceof SolrHttpContext == false) {
+          throw new SolrException(ErrorCode.BAD_REQUEST, "A SolrHttpContext object must be passed in as context. Context: " + context);
+        }
+      }
+    });
   }
   
   public static boolean toBooleanDefaultIfNull(Boolean bool, boolean valueIfNull) {

Modified: lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientUtil.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientUtil.java?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientUtil.java (original)
+++ lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientUtil.java Thu Jul 30 14:28:20 2015
@@ -18,6 +18,11 @@ package org.apache.solr.client.solrj.imp
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.InflaterInputStream;
 
@@ -46,6 +51,8 @@ import org.apache.http.impl.conn.Pooling
 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; // jdoc
 import org.apache.http.params.HttpConnectionParams;
 import org.apache.http.protocol.HttpContext;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.slf4j.Logger;
@@ -87,8 +94,8 @@ public class HttpClientUtil {
       0, false);
 
   private static HttpClientConfigurer configurer = new HttpClientConfigurer();
-  
-  private HttpClientUtil(){}
+
+  private static final List<HttpRequestInterceptor> interceptors = Collections.synchronizedList(new ArrayList<HttpRequestInterceptor>());
   
   /**
    * Replace the {@link HttpClientConfigurer} class used in configuring the http
@@ -114,7 +121,7 @@ public class HttpClientUtil {
     if (logger.isDebugEnabled()) {
       logger.debug("Creating new http client, config:" + config);
     }
-    final DefaultHttpClient httpClient = new SystemDefaultHttpClient();
+    final DefaultHttpClient httpClient = HttpClientFactory.createHttpClient();
     configureClient(httpClient, config);
     return httpClient;
   }
@@ -128,7 +135,7 @@ public class HttpClientUtil {
     if (logger.isDebugEnabled()) {
       logger.debug("Creating new http client, config:" + config);
     }
-    final DefaultHttpClient httpClient = new DefaultHttpClient(cm);
+    final DefaultHttpClient httpClient = HttpClientFactory.createHttpClient(cm);
     configureClient(httpClient, config);
     return httpClient;
   }
@@ -140,6 +147,11 @@ public class HttpClientUtil {
   public static void configureClient(final DefaultHttpClient httpClient,
       SolrParams config) {
     configurer.configure(httpClient,  config);
+    synchronized(interceptors) {
+      for(HttpRequestInterceptor interceptor: interceptors) {
+        httpClient.addRequestInterceptor(interceptor);
+      }
+    }
   }
   
   public static void close(HttpClient httpClient) { 
@@ -150,6 +162,14 @@ public class HttpClientUtil {
     }
   }
 
+  public static void addRequestInterceptor(HttpRequestInterceptor interceptor) {
+    interceptors.add(interceptor);
+  }
+
+  public static void removeRequestInterceptor(HttpRequestInterceptor interceptor) {
+    interceptors.remove(interceptor);
+  }
+
   /**
    * Control HTTP payload compression.
    * 
@@ -358,5 +378,36 @@ public class HttpClientUtil {
       return new InflaterInputStream(wrappedEntity.getContent());
     }
   }
-  
+
+  public static class HttpClientFactory {
+    private static Class<? extends DefaultHttpClient> defaultHttpClientClass = DefaultHttpClient.class;
+    private static Class<? extends SystemDefaultHttpClient> systemDefaultHttpClientClass = SystemDefaultHttpClient.class;
+
+
+    public static SystemDefaultHttpClient createHttpClient() {
+      Constructor<? extends SystemDefaultHttpClient> constructor;
+      try {
+        constructor = systemDefaultHttpClientClass.getDeclaredConstructor();
+          return constructor.newInstance();
+      } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Unable to create HttpClient instance. ", e);
+      }
+    }
+
+    public static DefaultHttpClient createHttpClient(ClientConnectionManager cm) {
+      Constructor<? extends DefaultHttpClient> productConstructor;
+      try {
+        productConstructor = defaultHttpClientClass.getDeclaredConstructor(new Class[] { ClientConnectionManager.class });
+          return productConstructor.newInstance(new Object[] { cm });
+      } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Unable to create HttpClient instance, registered class is: "+defaultHttpClientClass, e);
+      }
+    }
+
+    public static void setHttpClientImpl(Class<? extends DefaultHttpClient> defaultHttpClient, Class<? extends SystemDefaultHttpClient> systemDefaultHttpClient) {
+      defaultHttpClientClass = defaultHttpClient;
+      systemDefaultHttpClientClass = systemDefaultHttpClient;
+    }
+  }
+
 }

Added: lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrHttpContext.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrHttpContext.java?rev=1693432&view=auto
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrHttpContext.java (added)
+++ lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrHttpContext.java Thu Jul 30 14:28:20 2015
@@ -0,0 +1,52 @@
+package org.apache.solr.client.solrj.impl;
+
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.solr.client.solrj.SolrRequest;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A HttpContext derivative to encapsulate a client-side SolrRequest
+ * object.
+ */
+public class SolrHttpContext extends HttpClientContext {
+  final protected static String SOLR_CONTEXT_KEY = "solr.context";
+  
+  private SolrRequest solrRequest;
+  
+  public static HttpContext EMPTY_CONTEXT = new SolrHttpContext();
+  
+  protected SolrHttpContext() {
+    setAttribute(SOLR_CONTEXT_KEY, this);
+  }
+  
+  public SolrHttpContext(SolrRequest request) {
+    this.solrRequest = request;
+    setAttribute(SOLR_CONTEXT_KEY, this);
+  }
+  
+  public SolrRequest getSolrRequest() {
+    return solrRequest;
+  }
+  
+  @Override
+  public String toString() {
+    return "[SolrHttpContext contains: "+solrRequest+"]";
+  }
+}

Modified: lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/util/ExecutorUtil.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/util/ExecutorUtil.java?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/util/ExecutorUtil.java (original)
+++ lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/util/ExecutorUtil.java Thu Jul 30 14:28:20 2015
@@ -1,5 +1,6 @@
 package org.apache.solr.common.util;
 
+import java.util.ArrayList;
 import java.util.Collection;
 
 /*
@@ -19,8 +20,15 @@ import java.util.Collection;
  * limitations under the License.
  */
 
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.RejectedExecutionHandler;
@@ -28,6 +36,7 @@ import java.util.concurrent.SynchronousQ
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -36,7 +45,39 @@ import org.slf4j.MDC;
 
 public class ExecutorUtil {
   public static Logger log = LoggerFactory.getLogger(ExecutorUtil.class);
-  
+
+  private static volatile List<InheritableThreadLocalProvider> providers = new ArrayList<>();
+
+  public synchronized static void addThreadLocalProvider(InheritableThreadLocalProvider provider) {
+    for (InheritableThreadLocalProvider p : providers) {//this is to avoid accidental multiple addition of providers in tests
+      if (p.getClass().equals(provider.getClass())) return;
+    }
+    List<InheritableThreadLocalProvider> copy = new ArrayList<>(providers);
+    copy.add(provider);
+    providers = copy;
+  }
+
+  /** Any class which wants to carry forward the threadlocal values to the threads run
+   * by threadpools must implement this interface and the implementation should be
+   * registered here
+   */
+  public interface InheritableThreadLocalProvider {
+    /**This is invoked in the parent thread which submitted a task.
+     * copy the necessary Objects to the ctx. The object that is passed is same
+     * across all three methods
+     */
+    public void store(AtomicReference<?> ctx);
+
+    /**This is invoked in the Threadpool thread. set the appropriate values in the threadlocal
+     * of this thread.     */
+    public void set(AtomicReference<?> ctx);
+
+    /**This method is invoked in the threadpool thread after the execution
+     * clean all the variables set in the set method
+     */
+    public void clean(AtomicReference<?> ctx);
+  }
+
   // this will interrupt the threads! Lucene and Solr do not like this because it can close channels, so only use
   // this if you know what you are doing - you probably want shutdownAndAwaitTermination
   public static void shutdownNowAndAwaitTermination(ExecutorService pool) {
@@ -140,9 +181,21 @@ public class ExecutorUtil {
       String ctxStr = contextString.toString().replace("/", "//");
       final String submitterContextStr = ctxStr.length() <= MAX_THREAD_NAME_LEN ? ctxStr : ctxStr.substring(0, MAX_THREAD_NAME_LEN);
       final Exception submitterStackTrace = new Exception("Submitter stack trace");
+      final List<InheritableThreadLocalProvider> providersCopy = providers;
+      final ArrayList<AtomicReference> ctx = providersCopy.isEmpty()? null: new ArrayList<>(providersCopy.size());
+      if(ctx != null) {
+        for (int i = 0; i < providers.size(); i++) {
+          AtomicReference reference = new AtomicReference();
+          ctx.add(reference);
+          providersCopy.get(i).store(reference);
+        }
+      }
       super.execute(new Runnable() {
         @Override
         public void run() {
+          if(ctx != null) {
+            for (int i = 0; i < providersCopy.size(); i++) providersCopy.get(i).set(ctx.get(i));
+          }
           Map<String, String> threadContext = MDC.getCopyOfContextMap();
           final Thread currentThread = Thread.currentThread();
           final String oldName = currentThread.getName();
@@ -166,6 +219,9 @@ public class ExecutorUtil {
             } else {
               MDC.clear();
             }
+            if(ctx != null) {
+              for (int i = 0; i < providersCopy.size(); i++) providersCopy.get(i).clean(ctx.get(i));
+            }
             currentThread.setName(oldName);
           }
         }

Modified: lucene/dev/trunk/solr/solrj/src/test/org/apache/solr/client/solrj/impl/BasicHttpSolrClientTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/test/org/apache/solr/client/solrj/impl/BasicHttpSolrClientTest.java?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/test/org/apache/solr/client/solrj/impl/BasicHttpSolrClientTest.java (original)
+++ lucene/dev/trunk/solr/solrj/src/test/org/apache/solr/client/solrj/impl/BasicHttpSolrClientTest.java Thu Jul 30 14:28:20 2015
@@ -21,23 +21,39 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URISyntaxException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 
 import org.apache.http.Header;
 import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
 import org.apache.http.HttpResponse;
 import org.apache.http.ParseException;
+import org.apache.http.client.CookieStore;
 import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.cookie.CookieSpec;
+import org.apache.http.cookie.CookieSpecRegistry;
+import org.apache.http.impl.client.BasicCookieStore;
 import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.RequestWrapper;
+import org.apache.http.impl.cookie.BasicClientCookie;
+import org.apache.http.protocol.HttpContext;
 import org.apache.solr.SolrJettyTestBase;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest;
@@ -57,9 +73,13 @@ import org.apache.solr.util.SSLTestConfi
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class BasicHttpSolrClientTest extends SolrJettyTestBase {
-  
+
+  private static Logger log = LoggerFactory.getLogger(BasicHttpSolrClientTest.class);
+
   public static class RedirectServlet extends HttpServlet {
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp)
@@ -85,6 +105,7 @@ public class BasicHttpSolrClientTest ext
       parameters = null;
       errorCode = null;
       queryString = null;
+      cookies = null;
     }
     
     public static Integer errorCode = null;
@@ -92,12 +113,19 @@ public class BasicHttpSolrClientTest ext
     public static HashMap<String,String> headers = null;
     public static Map<String,String[]> parameters = null;
     public static String queryString = null;
+    public static javax.servlet.http.Cookie[] cookies = null;
     
     public static void setErrorCode(Integer code) {
       errorCode = code;
     }
     
-
+    @Override
+    protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+      lastMethod = "delete";
+      recordRequest(req, resp);
+    }
+    
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp)
         throws ServletException, IOException {
@@ -105,6 +133,13 @@ public class BasicHttpSolrClientTest ext
       recordRequest(req, resp);
     }
     
+    @Override
+    protected void doHead(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+      lastMethod = "head";
+      recordRequest(req, resp);
+    }
+    
     private void setHeaders(HttpServletRequest req) {
       Enumeration<String> headerNames = req.getHeaderNames();
       headers = new HashMap<>();
@@ -123,6 +158,11 @@ public class BasicHttpSolrClientTest ext
       queryString = req.getQueryString();
     }
 
+    private void setCookies(HttpServletRequest req) {
+      javax.servlet.http.Cookie[] ck = req.getCookies();
+      cookies = req.getCookies();
+    }
+
     @Override
     protected void doPost(HttpServletRequest req, HttpServletResponse resp)
         throws ServletException, IOException {
@@ -141,6 +181,7 @@ public class BasicHttpSolrClientTest ext
       setHeaders(req);
       setParameters(req);
       setQueryString(req);
+      setCookies(req);
       if (null != errorCode) {
         try { 
           resp.sendError(errorCode); 
@@ -593,6 +634,97 @@ public class BasicHttpSolrClientTest ext
                   SSLTestConfig.TEST_KEYSTORE);
   }
 
+  /**
+   * An interceptor changing the request
+   */
+  HttpRequestInterceptor changeRequestInterceptor = new HttpRequestInterceptor() {
+
+    @Override
+    public void process(HttpRequest request, HttpContext context) throws HttpException,
+    IOException {
+      log.info("Intercepted params: "+context);
+
+      RequestWrapper wrapper = (RequestWrapper) request;
+      URIBuilder uribuilder = new URIBuilder(wrapper.getURI());
+      uribuilder.addParameter("b", "\u4321");
+      try {
+        wrapper.setURI(uribuilder.build());
+      } catch (URISyntaxException ex) {
+        throw new HttpException("Invalid request URI", ex);
+      }
+    }
+  };
+
+  public static final String cookieName = "cookieName";
+  public static final String cookieValue = "cookieValue";
+
+  /**
+   * An interceptor setting a cookie
+   */
+  HttpRequestInterceptor cookieSettingRequestInterceptor = new HttpRequestInterceptor() {    
+    @Override
+    public void process(HttpRequest request, HttpContext context) throws HttpException,
+    IOException {
+      BasicClientCookie cookie = new BasicClientCookie(cookieName, cookieValue);
+      cookie.setVersion(0);
+      cookie.setPath("/");
+      cookie.setDomain(jetty.getBaseUrl().getHost());
+
+      CookieStore cookieStore = new BasicCookieStore();        
+      CookieSpecRegistry registry = (CookieSpecRegistry) context.getAttribute(ClientContext.COOKIESPEC_REGISTRY);
+      String policy = HttpClientParams.getCookiePolicy(request.getParams());
+      CookieSpec cookieSpec = registry.getCookieSpec(policy, request.getParams());
+      // Add the cookies to the request
+      List<Header> headers = cookieSpec.formatCookies(Collections.singletonList(cookie));
+      for (Header header : headers) {
+        request.addHeader(header);
+      }
+      context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
+      context.setAttribute(ClientContext.COOKIE_SPEC, cookieSpec);
+    }
+  };
+
+
+  /**
+   * Set cookies via interceptor
+   * Change the request via an interceptor
+   * Ensure cookies are actually set and that request is actually changed
+   */
+  @Test
+  public void testInterceptors() {
+    DebugServlet.clear();
+    HttpClientUtil.addRequestInterceptor(changeRequestInterceptor);
+    HttpClientUtil.addRequestInterceptor(cookieSettingRequestInterceptor);    
+
+    try(HttpSolrClient server = new HttpSolrClient(jetty.getBaseUrl().toString() +
+        "/debug/foo")) {
+
+      SolrQuery q = new SolrQuery("foo");
+      q.setParam("a", "\u1234");
+      try {
+        server.query(q, random().nextBoolean()?METHOD.POST:METHOD.GET);
+      } catch (Throwable t) {}
+
+      // Assert cookies from UseContextCallback 
+      assertNotNull(DebugServlet.cookies);
+      boolean foundCookie = false;
+      for (javax.servlet.http.Cookie cookie : DebugServlet.cookies) {
+        if (cookieName.equals(cookie.getName())
+            && cookieValue.equals(cookie.getValue())) {
+          foundCookie = true;
+          break;
+        }
+      }
+      assertTrue(foundCookie);
+
+      // Assert request changes by ChangeRequestCallback
+      assertEquals("\u1234", DebugServlet.parameters.get("a")[0]);
+      assertEquals("\u4321", DebugServlet.parameters.get("b")[0]);
+
+    } catch (IOException ex) {
+      throw new RuntimeException(ex);
+    }
+  }
 
   private Set<String> setOf(String... keys) {
     Set<String> set = new TreeSet<>();

Modified: lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java (original)
+++ lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java Thu Jul 30 14:28:20 2015
@@ -27,6 +27,7 @@ import org.apache.solr.client.solrj.Solr
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.embedded.JettyConfig;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.impl.HttpClientUtil;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
@@ -37,8 +38,8 @@ import org.apache.solr.common.SolrInputD
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.schema.TrieDateField;
 import org.apache.solr.util.DateFormatUtil;
+import org.apache.solr.util.SolrHttpClient;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -103,6 +104,8 @@ public abstract class BaseDistributedSea
   public static void initialize() {
     assumeFalse("SOLR-4147: ibm 64bit has jvm bugs!", Constants.JRE_IS_64BIT && Constants.JAVA_VENDOR.startsWith("IBM"));
     r = new Random(random().nextLong());
+
+    HttpClientUtil.HttpClientFactory.setHttpClientImpl(SolrHttpClient.SolrDefaultHttpClient.class, SolrHttpClient.SolrSystemDefaultHttpClient.class);
   }
   
   /**

Modified: lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/SolrJettyTestBase.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/SolrJettyTestBase.java?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/SolrJettyTestBase.java (original)
+++ lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/SolrJettyTestBase.java Thu Jul 30 14:28:20 2015
@@ -23,8 +23,10 @@ import org.apache.solr.client.solrj.Solr
 import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
 import org.apache.solr.client.solrj.embedded.JettyConfig;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.impl.HttpClientUtil;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.util.ExternalPaths;
+import org.apache.solr.util.SolrHttpClient;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -42,6 +44,9 @@ abstract public class SolrJettyTestBase
 {
   private static Logger log = LoggerFactory.getLogger(SolrJettyTestBase.class);
 
+  static {
+    HttpClientUtil.HttpClientFactory.setHttpClientImpl(SolrHttpClient.SolrDefaultHttpClient.class, SolrHttpClient.SolrSystemDefaultHttpClient.class);
+  }
 
   @BeforeClass
   public static void beforeSolrJettyTestBase() throws Exception {

Modified: lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/BaseTestHarness.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/BaseTestHarness.java?rev=1693432&r1=1693431&r2=1693432&view=diff
==============================================================================
--- lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/BaseTestHarness.java (original)
+++ lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/BaseTestHarness.java Thu Jul 30 14:28:20 2015
@@ -16,6 +16,7 @@ package org.apache.solr.util;
  * limitations under the License.
  */
 
+import org.apache.solr.client.solrj.impl.HttpClientUtil;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.XML;
 import org.w3c.dom.Document;
@@ -39,6 +40,10 @@ abstract public class BaseTestHarness {
   private static final ThreadLocal<DocumentBuilder> builderTL = new ThreadLocal<>();
   private static final ThreadLocal<XPath> xpathTL = new ThreadLocal<>();
 
+  static {
+    HttpClientUtil.HttpClientFactory.setHttpClientImpl(SolrHttpClient.SolrDefaultHttpClient.class, SolrHttpClient.SolrSystemDefaultHttpClient.class);
+  }
+
   public static DocumentBuilder getXmlDocumentBuilder() {
     try {
       DocumentBuilder builder = builderTL.get();