You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by is...@apache.org on 2017/02/12 13:18:25 UTC

[01/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Repository: lucene-solr
Updated Branches:
  refs/heads/jira/solr-5944 b7d78e3ab -> 4fc5a9f05


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/webapp/build.xml
----------------------------------------------------------------------
diff --git a/solr/webapp/build.xml b/solr/webapp/build.xml
index c7c5c85..2ebe084 100644
--- a/solr/webapp/build.xml
+++ b/solr/webapp/build.xml
@@ -35,6 +35,7 @@
   <target name="compile-core"/>
   <target name="compile-test"/>
   <target name="test"/>
+  <target name="test-nocompile"/>
 
   <target name="dist"
           description="Creates the Webapp folder for distribution."

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/webapp/web/js/angular/controllers/dataimport.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/controllers/dataimport.js b/solr/webapp/web/js/angular/controllers/dataimport.js
index d8fbc4f..3a26324 100644
--- a/solr/webapp/web/js/angular/controllers/dataimport.js
+++ b/solr/webapp/web/js/angular/controllers/dataimport.js
@@ -22,7 +22,7 @@ solrAdminApp.controller('DataImportController',
         $scope.resetMenu("dataimport", Constants.IS_COLLECTION_PAGE);
 
         $scope.refresh = function () {
-            Mbeans.info({core: $routeParams.core, cat: 'QUERYHANDLER'}, function (data) {
+            Mbeans.info({core: $routeParams.core, cat: 'QUERY'}, function (data) {
                 var mbeans = data['solr-mbeans'][1];
                 $scope.handlers = [];
                 for (var key in mbeans) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/webapp/web/js/angular/controllers/plugins.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/controllers/plugins.js b/solr/webapp/web/js/angular/controllers/plugins.js
index 9070b84..a537b37 100644
--- a/solr/webapp/web/js/angular/controllers/plugins.js
+++ b/solr/webapp/web/js/angular/controllers/plugins.js
@@ -88,6 +88,7 @@ var getPluginTypes = function(data, selected) {
         var key = mbeans[i];
         var lower = key.toLowerCase();
         var plugins = getPlugins(mbeans[i+1]);
+        if (plugins.length == 0) continue;
         keys.push({name: key,
                    selected: lower == selected,
                    changes: 0,

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/webapp/web/js/scripts/dataimport.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/scripts/dataimport.js b/solr/webapp/web/js/scripts/dataimport.js
index ef2b896..20532c6 100644
--- a/solr/webapp/web/js/scripts/dataimport.js
+++ b/solr/webapp/web/js/scripts/dataimport.js
@@ -28,7 +28,7 @@ sammy.bind
     $.ajax
     (
       {
-        url : core_basepath + '/admin/mbeans?cat=QUERYHANDLER&wt=json',
+        url : core_basepath + '/admin/mbeans?cat=QUERY&wt=json',
         dataType : 'json',
         beforeSend : function( xhr, settings )
         {


[09/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java b/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java
index 46f8009..d5f9f9d 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java
@@ -96,7 +96,7 @@ public class SearchGroupsFieldCommand implements Command<SearchGroupsFieldComman
     final List<Collector> collectors = new ArrayList<>(2);
     final FieldType fieldType = field.getType();
     if (topNGroups > 0) {
-      if (fieldType.getNumericType() != null) {
+      if (fieldType.getNumberType() != null) {
         ValueSource vs = fieldType.getValueSource(field, null);
         firstPassGroupingCollector = new FunctionFirstPassGroupingCollector(vs, new HashMap<Object,Object>(), groupSort, topNGroups);
       } else {
@@ -105,7 +105,7 @@ public class SearchGroupsFieldCommand implements Command<SearchGroupsFieldComman
       collectors.add(firstPassGroupingCollector);
     }
     if (includeGroupCount) {
-      if (fieldType.getNumericType() != null) {
+      if (fieldType.getNumberType() != null) {
         ValueSource vs = fieldType.getValueSource(field, null);
         allGroupsCollector = new FunctionAllGroupsCollector(vs, new HashMap<Object,Object>());
       } else {
@@ -120,7 +120,7 @@ public class SearchGroupsFieldCommand implements Command<SearchGroupsFieldComman
   public SearchGroupsFieldCommandResult result() throws IOException {
     final Collection<SearchGroup<BytesRef>> topGroups;
     if (firstPassGroupingCollector != null) {
-      if (field.getType().getNumericType() != null) {
+      if (field.getType().getNumberType() != null) {
         topGroups = GroupConverter.fromMutable(field, firstPassGroupingCollector.getTopGroups(0, true));
       } else {
         topGroups = firstPassGroupingCollector.getTopGroups(0, true);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java b/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java
index 0bdb0ed..2c6c401 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java
@@ -132,7 +132,7 @@ public class TopGroupsFieldCommand implements Command<TopGroups<BytesRef>> {
 
     final List<Collector> collectors = new ArrayList<>(1);
     final FieldType fieldType = field.getType();
-    if (fieldType.getNumericType() != null) {
+    if (fieldType.getNumberType() != null) {
       ValueSource vs = fieldType.getValueSource(field, null);
       Collection<SearchGroup<MutableValue>> v = GroupConverter.toMutable(field, firstPhaseGroups);
       secondPassCollector = new FunctionSecondPassGroupingCollector(
@@ -155,7 +155,7 @@ public class TopGroupsFieldCommand implements Command<TopGroups<BytesRef>> {
     }
 
     FieldType fieldType = field.getType();
-    if (fieldType.getNumericType() != null) {
+    if (fieldType.getNumberType() != null) {
       return GroupConverter.fromMutable(field, secondPassCollector.getTopGroups(0));
     } else {
       return secondPassCollector.getTopGroups(0);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetAccsHolder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetAccsHolder.java b/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetAccsHolder.java
index 00b3ac4..769a9c8 100644
--- a/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetAccsHolder.java
+++ b/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetAccsHolder.java
@@ -19,11 +19,9 @@ package org.apache.solr.search.join;
 import java.io.IOException;
 
 import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.search.join.ToParentBlockJoinQuery.ChildrenMatchesScorer;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.search.join.BlockJoinFieldFacetAccumulator.AggregatableDocIter;
-import org.apache.solr.search.join.BlockJoinFieldFacetAccumulator.SortedIntsAggDocIterator;
 
 /**
  * For each collected parent document creates matched block, which is a docSet with matched children and parent doc
@@ -32,8 +30,6 @@ import org.apache.solr.search.join.BlockJoinFieldFacetAccumulator.SortedIntsAggD
 class BlockJoinFacetAccsHolder {
   private BlockJoinFieldFacetAccumulator[] blockJoinFieldFacetAccumulators;
   private boolean firstSegment = true;
-  private ChildrenMatchesScorer blockJoinScorer;
-  private int[] childDocs = new int[0];
   
   BlockJoinFacetAccsHolder(SolrQueryRequest req) throws IOException {
     String[] facetFieldNames = BlockJoinFacetComponentSupport.getChildFacetFields(req);
@@ -61,16 +57,6 @@ class BlockJoinFacetAccsHolder {
     }
   }
 
-  protected void incrementFacets(int parent) throws IOException {
-    final int[] docNums = blockJoinScorer.swapChildDocs(childDocs);
-    // now we don't
-    //includeParentDoc(parent);
-    //final int childCountPlusParent = childTracking.getChildCount()+1;
-    final int childCountNoParent = blockJoinScorer.getChildCount();
-    final SortedIntsAggDocIterator iter = new SortedIntsAggDocIterator(docNums, childCountNoParent, parent);
-    countFacets(iter);
-  }
-
   /** is not used 
   protected int[] includeParentDoc(int parent) {
     final int[] docNums = ArrayUtil.grow(childTracking.getChildDocs(), childTracking.getChildCount()+1);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/mlt/CloudMLTQParser.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/mlt/CloudMLTQParser.java b/solr/core/src/java/org/apache/solr/search/mlt/CloudMLTQParser.java
index 945047b..5975f8f 100644
--- a/solr/core/src/java/org/apache/solr/search/mlt/CloudMLTQParser.java
+++ b/solr/core/src/java/org/apache/solr/search/mlt/CloudMLTQParser.java
@@ -190,7 +190,7 @@ public class CloudMLTQParser extends QParser {
   }
 
   private Query createIdQuery(String defaultField, String uniqueValue) {
-    return new TermQuery(req.getSchema().getField(defaultField).getType().getNumericType() != null
+    return new TermQuery(req.getSchema().getField(defaultField).getType().getNumberType() != null
         ? createNumericTerm(defaultField, uniqueValue)
         : new Term(defaultField, uniqueValue));
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/mlt/SimpleMLTQParser.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/mlt/SimpleMLTQParser.java b/solr/core/src/java/org/apache/solr/search/mlt/SimpleMLTQParser.java
index de6eb58..dea161d 100644
--- a/solr/core/src/java/org/apache/solr/search/mlt/SimpleMLTQParser.java
+++ b/solr/core/src/java/org/apache/solr/search/mlt/SimpleMLTQParser.java
@@ -101,7 +101,7 @@ public class SimpleMLTQParser extends QParser {
         ArrayList<String> fields = new ArrayList();
         for (String fieldName : fieldDefinitions.keySet()) {
           if (fieldDefinitions.get(fieldName).indexed() && fieldDefinitions.get(fieldName).stored())
-            if (fieldDefinitions.get(fieldName).getType().getNumericType() == null)
+            if (fieldDefinitions.get(fieldName).getType().getNumberType() == null)
               fields.add(fieldName);
         }
         fieldNames = fields.toArray(new String[0]);
@@ -150,7 +150,7 @@ public class SimpleMLTQParser extends QParser {
   }
 
   private Query createIdQuery(String defaultField, String uniqueValue) {
-    return new TermQuery(req.getSchema().getField(defaultField).getType().getNumericType() != null
+    return new TermQuery(req.getSchema().getField(defaultField).getType().getNumberType() != null
         ? createNumericTerm(defaultField, uniqueValue)
         : new Term(defaultField, uniqueValue));
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
index 5c0717b..f1665c7 100644
--- a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
@@ -37,11 +37,13 @@ import org.apache.http.Header;
 import org.apache.http.auth.BasicUserPrincipal;
 import org.apache.http.message.BasicHeader;
 import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.util.CommandOperation;
+import org.apache.solr.api.SpecProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEditablePlugin {
+public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEditablePlugin , SpecProvider {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   private AuthenticationProvider authenticationProvider;
   private final static ThreadLocal<Header> authHeader = new ThreadLocal<>();
@@ -162,7 +164,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
     authHeader.remove();
   }
 
-  public interface AuthenticationProvider {
+  public interface AuthenticationProvider extends SpecProvider {
     void init(Map<String, Object> pluginConfig);
 
     boolean authenticate(String user, String pwd);
@@ -170,6 +172,10 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
     Map<String, String> getPromptHeaders();
   }
 
+  @Override
+  public ValidatingJsonMap getSpec() {
+    return authenticationProvider.getSpec();
+  }
   public boolean getBlockUnknown(){
     return blockUnknown;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
index 1f0d5ad..fa59d38 100644
--- a/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
@@ -34,6 +34,7 @@ import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
 
@@ -112,6 +113,12 @@ public class HadoopAuthPlugin extends AuthenticationPlugin {
    */
   public static final String PROXY_USER_CONFIGS = "proxyUserConfigs";
 
+  /**
+   * This parameter is used to debug the authentication related issues during development.
+   * This should not be used in production.
+   */
+  private static final boolean TRACE_HTTP = Boolean.getBoolean("hadoopauth.tracehttp");
+
   private AuthenticationFilter authFilter;
   protected final CoreContainer coreContainer;
 
@@ -204,6 +211,23 @@ public class HadoopAuthPlugin extends AuthenticationPlugin {
       throws Exception {
     final HttpServletResponse frsp = (HttpServletResponse)response;
 
+    if (TRACE_HTTP) {
+      HttpServletRequest req = (HttpServletRequest) request;
+      log.info("----------HTTP Request---------");
+      log.info("{} : {}", req.getMethod(), req.getRequestURI());
+      log.info("Query : {}", req.getQueryString());
+      log.info("Headers :");
+      Enumeration<String> headers = req.getHeaderNames();
+      while (headers.hasMoreElements()) {
+        String name = headers.nextElement();
+        Enumeration<String> hvals = req.getHeaders(name);
+        while (hvals.hasMoreElements()) {
+          log.info("{} : {}", name, hvals.nextElement());
+        }
+      }
+      log.info("-------------------------------");
+    }
+
     // Workaround until HADOOP-13346 is fixed.
     HttpServletResponse rspCloseShield = new HttpServletResponseWrapper(frsp) {
       @SuppressForbidden(reason = "Hadoop DelegationTokenAuthenticationFilter uses response writer, this" +
@@ -219,6 +243,19 @@ public class HadoopAuthPlugin extends AuthenticationPlugin {
     };
     authFilter.doFilter(request, rspCloseShield, filterChain);
 
+    if (TRACE_HTTP) {
+      log.info("----------HTTP Response---------");
+      log.info("Status : {}", frsp.getStatus());
+      log.info("Headers :");
+      for (String name : frsp.getHeaderNames()) {
+        for (String value : frsp.getHeaders(name)) {
+          log.info("{} : {}", name, value);
+        }
+      }
+      log.info("-------------------------------");
+    }
+
+
     if (authFilter instanceof HadoopAuthFilter) { // delegation token mgmt.
       String requestContinuesAttr = (String)request.getAttribute(REQUEST_CONTINUES_ATTR);
       if (requestContinuesAttr == null) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java
index ed5a05c..a8a97ed 100644
--- a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java
@@ -27,6 +27,9 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
 
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.api.SpecProvider;
+import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.util.CommandOperation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,7 +42,7 @@ import static org.apache.solr.handler.admin.SecurityConfHandler.getListValue;
 import static org.apache.solr.handler.admin.SecurityConfHandler.getMapValue;
 
 
-public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, ConfigEditablePlugin {
+public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, ConfigEditablePlugin, SpecProvider {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   private final Map<String, Set<String>> usersVsRoles = new HashMap<>();
@@ -232,4 +235,10 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
 
   private static final Map<String, AutorizationEditOperation> ops = unmodifiableMap(asList(AutorizationEditOperation.values()).stream().collect(toMap(AutorizationEditOperation::getOperationName, identity())));
 
+
+  @Override
+  public ValidatingJsonMap getSpec() {
+    return ApiBag.getSpec("cluster.security.RuleBasedAuthorization").getSpec();
+
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
index 69664fd..0cc58cd 100644
--- a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
+++ b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
@@ -30,7 +30,10 @@ import java.util.Set;
 
 import com.google.common.collect.ImmutableSet;
 import org.apache.commons.codec.binary.Base64;
+import org.apache.solr.common.util.ValidatingJsonMap;
+
 import org.apache.solr.util.CommandOperation;
+import org.apache.solr.api.ApiBag;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -152,5 +155,10 @@ public class Sha256AuthenticationProvider implements ConfigEditablePlugin,  Basi
     return latestConf;
   }
 
+  @Override
+  public ValidatingJsonMap getSpec() {
+    return ApiBag.getSpec("cluster.security.BasicAuth.Commands").getSpec();
+  }
+
   static final Set<String> supported_ops = ImmutableSet.of("set-user", "delete-user");
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
index 1f98da9..4f6bae0 100644
--- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
+++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
@@ -18,7 +18,6 @@ package org.apache.solr.servlet;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -37,6 +36,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.input.CloseShieldInputStream;
@@ -56,6 +56,7 @@ import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.client.methods.HttpRequestBase;
 import org.apache.http.entity.InputStreamEntity;
+import org.apache.solr.api.ApiBag;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.impl.HttpClientUtil;
 import org.apache.solr.common.SolrException;
@@ -71,15 +72,18 @@ import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.ContentStreamHandlerBase;
 import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequestBase;
 import org.apache.solr.request.SolrRequestHandler;
@@ -97,7 +101,10 @@ import org.apache.solr.servlet.SolrDispatchFilter.Action;
 import org.apache.solr.servlet.cache.HttpCacheHeaderUtil;
 import org.apache.solr.servlet.cache.Method;
 import org.apache.solr.update.processor.DistributingUpdateProcessorFactory;
+import org.apache.solr.util.CommandOperation;
+import org.apache.solr.util.JsonSchemaValidator;
 import org.apache.solr.util.RTimerTree;
+import org.apache.solr.util.TimeOut;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -106,9 +113,13 @@ import static org.apache.solr.common.cloud.ZkStateReader.BASE_URL_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.NODE_NAME_PROP;
+import static org.apache.solr.common.cloud.ZkStateReader.REPLICATION_FACTOR;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATE;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETE;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.RELOAD;
+import static org.apache.solr.common.params.CommonParams.NAME;
+import static org.apache.solr.common.params.CoreAdminParams.ACTION;
+import static org.apache.solr.handler.admin.CollectionsHandler.SYSTEM_COLL;
 import static org.apache.solr.servlet.SolrDispatchFilter.Action.ADMIN;
 import static org.apache.solr.servlet.SolrDispatchFilter.Action.FORWARD;
 import static org.apache.solr.servlet.SolrDispatchFilter.Action.PASSTHROUGH;
@@ -149,6 +160,13 @@ public class HttpSolrCall {
   protected String coreUrl;
   protected SolrConfig config;
   protected Map<String, Integer> invalidStates;
+  protected boolean usingAliases = false;
+
+  //The states of client that is invalid in this request
+  protected Aliases aliases = null;
+  protected String corename = "";
+  protected String origCorename = null;
+
 
   public RequestType getRequestType() {
     return requestType;
@@ -172,6 +190,16 @@ public class HttpSolrCall {
     this.retry = retry;
     this.requestType = RequestType.UNKNOWN;
     queryParams = SolrRequestParsers.parseQueryString(req.getQueryString());
+    // set a request timer which can be reused by requests if needed
+    req.setAttribute(SolrRequestParsers.REQUEST_TIMER_SERVLET_ATTRIBUTE, new RTimerTree());
+    // put the core container in request attribute
+    req.setAttribute("org.apache.solr.CoreContainer", cores);
+    path = req.getServletPath();
+    if (req.getPathInfo() != null) {
+      // this lets you handle /update/commit when /update is a servlet
+      path += req.getPathInfo();
+    }
+    req.setAttribute(HttpSolrCall.class.getName(), this);
   }
 
   public String getPath() {
@@ -190,21 +218,8 @@ public class HttpSolrCall {
   public SolrParams getQueryParams() {
     return queryParams;
   }
-  
-  void init() throws Exception {
-    //The states of client that is invalid in this request
-    Aliases aliases = null;
-    String corename = "";
-    String origCorename = null;
-    // set a request timer which can be reused by requests if needed
-    req.setAttribute(SolrRequestParsers.REQUEST_TIMER_SERVLET_ATTRIBUTE, new RTimerTree());
-    // put the core container in request attribute
-    req.setAttribute("org.apache.solr.CoreContainer", cores);
-    path = req.getServletPath();
-    if (req.getPathInfo() != null) {
-      // this lets you handle /update/commit when /update is a servlet
-      path += req.getPathInfo();
-    }
+
+  protected void init() throws Exception {
     // check for management path
     String alternate = cores.getManagementPath();
     if (alternate != null && path.startsWith(alternate)) {
@@ -259,7 +274,7 @@ public class HttpSolrCall {
           core = cores.getCore(corename);
           if (core != null) {
             path = path.substring(idx);
-          } 
+          }
         }
       }
       if (core == null) {
@@ -287,6 +302,9 @@ public class HttpSolrCall {
       // if we couldn't find it locally, look on other nodes
       extractRemotePath(corename, origCorename, idx);
       if (action != null) return;
+      //core is not available locally or remotely
+      autoCreateSystemColl(corename);
+      if(action != null) return;
     }
 
     // With a valid core...
@@ -321,13 +339,59 @@ public class HttpSolrCall {
 
     action = PASSTHROUGH;
   }
-  
+
+  protected void autoCreateSystemColl(String corename) throws Exception {
+    if (core == null &&
+        SYSTEM_COLL.equals(corename) &&
+        "POST".equals(req.getMethod()) &&
+        !cores.getZkController().getClusterState().hasCollection(SYSTEM_COLL)) {
+      log.info("Going to auto-create .system collection");
+      SolrQueryResponse rsp = new SolrQueryResponse();
+      String repFactor = String.valueOf(Math.min(3, cores.getZkController().getClusterState().getLiveNodes().size()));
+      cores.getCollectionsHandler().handleRequestBody(new LocalSolrQueryRequest(null,
+          new ModifiableSolrParams()
+              .add(ACTION, CREATE.toString())
+              .add( NAME, SYSTEM_COLL)
+              .add(REPLICATION_FACTOR, repFactor)), rsp);
+      if (rsp.getValues().get("success") == null) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Could not auto-create .system collection: "+ Utils.toJSONString(rsp.getValues()));
+      }
+      TimeOut timeOut = new TimeOut(3, TimeUnit.SECONDS);
+      for (; ; ) {
+        if (cores.getZkController().getClusterState().getCollectionOrNull(SYSTEM_COLL) != null) {
+          break;
+        } else {
+          if (timeOut.hasTimedOut()) {
+            throw new SolrException(ErrorCode.SERVER_ERROR, "Could not find .system collection even after 3 seconds");
+          }
+          Thread.sleep(50);
+        }
+      }
+
+      action = RETRY;
+    }
+  }
+
+  protected String lookupAliases(String collName) {
+    ZkStateReader reader = cores.getZkController().getZkStateReader();
+    aliases = reader.getAliases();
+    if (aliases != null && aliases.collectionAliasSize() > 0) {
+      usingAliases = true;
+      String alias = aliases.getCollectionAlias(collName);
+      if (alias != null) {
+        collectionsList = StrUtils.splitSmart(alias, ",", true);
+        return collectionsList.get(0);
+      }
+    }
+    return null;
+  }
+
   /**
    * Extract handler from the URL path if not set.
    * This returns true if the action is set.
    * 
    */
-  private void extractHandlerFromURLPath(SolrRequestParsers parser) throws Exception {
+  protected void extractHandlerFromURLPath(SolrRequestParsers parser) throws Exception {
     if (handler == null && path.length() > 1) { // don't match "" or "/" as valid path
       handler = core.getRequestHandler(path);
 
@@ -370,7 +434,7 @@ public class HttpSolrCall {
     }
   }
 
-  private void extractRemotePath(String corename, String origCorename, int idx) throws UnsupportedEncodingException, KeeperException, InterruptedException {
+  protected void extractRemotePath(String corename, String origCorename, int idx) throws UnsupportedEncodingException, KeeperException, InterruptedException {
     if (core == null && idx > 0) {
       coreUrl = getRemotCoreUrl(corename, origCorename);
       // don't proxy for internal update requests
@@ -468,7 +532,7 @@ public class HttpSolrCall {
               Map.Entry<String, String> entry = headers.next();
               resp.addHeader(entry.getKey(), entry.getValue());
             }
-            QueryResponseWriter responseWriter = core.getQueryResponseWriter(solrReq);
+            QueryResponseWriter responseWriter = getResponseWriter();
             if (invalidStates != null) solrReq.getContext().put(CloudSolrClient.STATE_VERSION, invalidStates);
             writeResponse(solrRsp, responseWriter, reqMethod);
           }
@@ -661,17 +725,29 @@ public class HttpSolrCall {
   private void handleAdminRequest() throws IOException {
     SolrQueryResponse solrResp = new SolrQueryResponse();
     SolrCore.preDecorateResponse(solrReq, solrResp);
-    handler.handleRequest(solrReq, solrResp);
+    handleAdmin(solrResp);
     SolrCore.postDecorateResponse(handler, solrReq, solrResp);
     if (log.isInfoEnabled() && solrResp.getToLog().size() > 0) {
       log.info(solrResp.getToLogAsString("[admin]"));
     }
     QueryResponseWriter respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get(solrReq.getParams().get(CommonParams.WT));
-    if (respWriter == null) respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get("standard");
+    if (respWriter == null) respWriter = getResponseWriter();
     writeResponse(solrResp, respWriter, Method.getMethod(req.getMethod()));
   }
 
-  private void processAliases(Aliases aliases,
+  protected QueryResponseWriter getResponseWriter() {
+    if (core != null) return core.getQueryResponseWriter(solrReq);
+    QueryResponseWriter respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get(solrReq.getParams().get(CommonParams.WT));
+    if (respWriter == null) respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get("standard");
+    return respWriter;
+
+  }
+
+  protected void handleAdmin(SolrQueryResponse solrResp) {
+    handler.handleRequest(solrReq, solrResp);
+  }
+
+  protected void processAliases(Aliases aliases,
                               List<String> collectionsList) {
     String collection = solrReq.getParams().get(COLLECTION_PROP);
     if (collection != null) {
@@ -757,7 +833,7 @@ public class HttpSolrCall {
     return result;
   }
 
-  private SolrCore getCoreByCollection(String collectionName, boolean isPreferLeader) {
+  protected SolrCore getCoreByCollection(String collectionName, boolean isPreferLeader) {
     ZkStateReader zkStateReader = cores.getZkController().getZkStateReader();
 
     ClusterState clusterState = zkStateReader.getClusterState();
@@ -898,6 +974,10 @@ public class HttpSolrCall {
     return null;
   }
 
+  protected Object _getHandler(){
+    return handler;
+  }
+
   private AuthorizationContext getAuthCtx() {
 
     String resource = getPath();
@@ -987,7 +1067,7 @@ public class HttpSolrCall {
 
       @Override
       public Object getHandler() {
-        return handler;
+        return _getHandler();
       }
 
       @Override
@@ -1021,6 +1101,32 @@ public class HttpSolrCall {
   static final String CONNECTION_HEADER = "Connection";
   static final String TRANSFER_ENCODING_HEADER = "Transfer-Encoding";
   static final String CONTENT_LENGTH_HEADER = "Content-Length";
+  List<CommandOperation> parsedCommands;
+
+  public List<CommandOperation> getCommands(boolean validateInput) {
+    if (parsedCommands == null) {
+      Iterable<ContentStream> contentStreams = solrReq.getContentStreams();
+      if (contentStreams == null) parsedCommands = Collections.EMPTY_LIST;
+      else {
+        for (ContentStream contentStream : contentStreams) {
+          try {
+            parsedCommands = ApiBag.getCommandOperations(contentStream.getReader(), getValidators(), validateInput);
+          } catch (IOException e) {
+            throw new SolrException(ErrorCode.BAD_REQUEST, "Error reading commands");
+          }
+          break;
+        }
+      }
+    }
+    return CommandOperation.clone(parsedCommands);
+  }
+  protected ValidatingJsonMap getSpec() {
+    return null;
+  }
+
+  protected Map<String, JsonSchemaValidator> getValidators(){
+    return Collections.EMPTY_MAP;
+  }
 
   /**
    * A faster method for randomly picking items when you do not need to

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java b/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
index a74fa8a..00733f5 100644
--- a/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
+++ b/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
@@ -15,8 +15,10 @@
  * limitations under the License.
  */
 package org.apache.solr.servlet;
+import org.apache.solr.api.ApiBag;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
+import org.apache.solr.util.CommandOperation;
 import org.slf4j.Logger;
 
 import java.io.PrintWriter;
@@ -48,6 +50,10 @@ public class ResponseUtils {
       errorMetadata.add(SolrException.ERROR_CLASS, ex.getClass().getName());
       errorMetadata.add(SolrException.ROOT_ERROR_CLASS, SolrException.getRootCause(ex).getClass().getName());
       info.add("metadata", errorMetadata);
+      if (ex instanceof ApiBag.ExceptionWithErrObject) {
+        ApiBag.ExceptionWithErrObject exception = (ApiBag.ExceptionWithErrObject) ex;
+        info.add(CommandOperation.ERR_MSGS, exception.getErrs() );
+      }
     }
     
     for (Throwable th = ex; th != null; th = th.getCause()) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
index a411bb3..ce65069 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
@@ -58,6 +58,7 @@ import org.apache.commons.io.output.CloseShieldOutputStream;
 import org.apache.commons.lang.StringUtils;
 import org.apache.http.client.HttpClient;
 import org.apache.lucene.util.Version;
+import org.apache.solr.api.V2HttpCall;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.SolrZkClient;
@@ -93,6 +94,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
   
   // Effectively immutable
   private Boolean testMode = null;
+  private boolean isV2Enabled = !"true".equals(System.getProperty("disable.v2.api", "false"));
 
   /**
    * Enum to define action that needs to be processed.
@@ -102,7 +104,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
    *  This is generally when an error is set and returned.
    * RETRY:Retry the request. In cases when a core isn't found to work with, this is set.
    */
-  enum Action {
+  public enum Action {
     PASSTHROUGH, FORWARD, RETURN, RETRY, ADMIN, REMOTEQUERY, PROCESS
   }
   
@@ -136,7 +138,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
     log.trace("SolrDispatchFilter.init(): {}", this.getClass().getClassLoader());
 
     SolrRequestParsers.fileCleaningTracker = new SolrFileCleaningTracker();
-    
+
     StartupLoggingUtils.checkLogDir();
     logWelcomeBanner();
     String muteConsole = System.getProperty(SOLR_LOG_MUTECONSOLE);
@@ -380,7 +382,17 @@ public class SolrDispatchFilter extends BaseSolrFilter {
    * want to add attributes to the request and send errors differently
    */
   protected HttpSolrCall getHttpSolrCall(HttpServletRequest request, HttpServletResponse response, boolean retry) {
-    return new HttpSolrCall(this, cores, request, response, retry);
+    String path = request.getServletPath();
+    if (request.getPathInfo() != null) {
+      // this lets you handle /update/commit when /update is a servlet
+      path += request.getPathInfo();
+    }
+
+    if (isV2Enabled && (path.startsWith("/v2/") || path.equals("/v2"))) {
+      return new V2HttpCall(this, cores, request, response, false);
+    } else {
+      return new HttpSolrCall(this, cores, request, response, retry);
+    }
   }
 
   private boolean authenticateRequest(ServletRequest request, ServletResponse response, final AtomicReference<ServletRequest> wrappedRequest) throws IOException {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java
index 968320e..c311d4a 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java
@@ -33,6 +33,7 @@ import java.security.Principal;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -45,6 +46,7 @@ import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import org.apache.commons.io.FileCleaningTracker;
 import org.apache.commons.io.input.CloseShieldInputStream;
 import org.apache.lucene.util.IOUtils;
+import org.apache.solr.api.V2HttpCall;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.params.CommonParams;
@@ -58,6 +60,7 @@ import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequestBase;
+import org.apache.solr.util.CommandOperation;
 import org.apache.solr.util.RTimerTree;
 import org.apache.solr.util.SolrFileCleaningTracker;
 
@@ -224,11 +227,33 @@ public class SolrRequestParsers
       }
     }
 
+    final HttpSolrCall httpSolrCall = req == null ? null : (HttpSolrCall) req.getAttribute(HttpSolrCall.class.getName());
     SolrQueryRequestBase q = new SolrQueryRequestBase(core, params, requestTimer) {
       @Override
       public Principal getUserPrincipal() {
         return req == null ? null : req.getUserPrincipal();
       }
+
+      @Override
+      public List<CommandOperation> getCommands(boolean validateInput) {
+        if (httpSolrCall != null) {
+          return httpSolrCall.getCommands(validateInput);
+        }
+        return Collections.emptyList();
+      }
+
+      @Override
+      public Map<String, String> getPathTemplateValues() {
+        if (httpSolrCall != null && httpSolrCall instanceof V2HttpCall) {
+          return ((V2HttpCall) httpSolrCall).getUrlParts();
+        }
+        return Collections.EMPTY_MAP;
+      }
+
+      @Override
+      public HttpSolrCall getHttpSolrCall() {
+        return httpSolrCall;
+      }
     };
     if( streams != null && streams.size() > 0 ) {
       q.setContentStreams( streams );
@@ -848,4 +873,4 @@ public class SolrRequestParsers
 
 
 
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/spelling/suggest/SolrSuggester.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/spelling/suggest/SolrSuggester.java b/solr/core/src/java/org/apache/solr/spelling/suggest/SolrSuggester.java
index b4ca4da..267d9ad 100644
--- a/solr/core/src/java/org/apache/solr/spelling/suggest/SolrSuggester.java
+++ b/solr/core/src/java/org/apache/solr/spelling/suggest/SolrSuggester.java
@@ -36,6 +36,7 @@ import org.apache.lucene.search.Query;
 import org.apache.lucene.search.spell.Dictionary;
 import org.apache.lucene.search.suggest.Lookup;
 import org.apache.lucene.search.suggest.Lookup.LookupResult;
+import org.apache.lucene.store.AlreadyClosedException;
 import org.apache.lucene.util.Accountable;
 import org.apache.lucene.util.IOUtils;
 import org.apache.solr.analysis.TokenizerChain;
@@ -43,6 +44,7 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.CloseHook;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.update.SolrCoreState;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -175,7 +177,14 @@ public class SolrSuggester implements Accountable {
     LOG.info("SolrSuggester.build(" + name + ")");
 
     dictionary = dictionaryFactory.create(core, searcher);
-    lookup.build(dictionary);
+    try {
+      lookup.build(dictionary);
+    } catch (AlreadyClosedException e) {
+      RuntimeException e2 = new SolrCoreState.CoreIsClosedException
+          ("Suggester build has been interrupted by a core reload or shutdown.");
+      e2.initCause(e);
+      throw e2;
+    }
     if (storeDir != null) {
       File target = getStoreFile();
       if(!lookup.store(new FileOutputStream(target))) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/store/blockcache/BlockCache.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/store/blockcache/BlockCache.java b/solr/core/src/java/org/apache/solr/store/blockcache/BlockCache.java
index 3014550..7a5c67c 100644
--- a/solr/core/src/java/org/apache/solr/store/blockcache/BlockCache.java
+++ b/solr/core/src/java/org/apache/solr/store/blockcache/BlockCache.java
@@ -101,7 +101,18 @@ public class BlockCache {
     metrics.blockCacheEviction.incrementAndGet();
     metrics.blockCacheSize.decrementAndGet();
   }
-  
+
+  /**
+   * This is only best-effort... it's possible for false to be returned.
+   * The blockCacheKey is cloned before it is inserted into the map, so it may be reused by clients if desired.
+   *
+   * @param blockCacheKey the key for the block
+   * @param blockOffset the offset within the block
+   * @param data source data to write to the block
+   * @param offset offset within the source data array
+   * @param length the number of bytes to write.
+   * @return true if the block was cached/updated
+   */
   public boolean store(BlockCacheKey blockCacheKey, int blockOffset,
       byte[] data, int offset, int length) {
     if (length + blockOffset > blockSize) {
@@ -115,12 +126,19 @@ public class BlockCache {
       newLocation = true;
       location = new BlockCacheLocation();
       if (!findEmptyLocation(location)) {
+        // YCS: it looks like when the cache is full (a normal scenario), then two concurrent writes will result in one of them failing
+        // because no eviction is done first.  The code seems to rely on leaving just a single block empty.
+        // TODO: simplest fix would be to leave more than one block empty
         return false;
       }
     }
+
+    // YCS: I think this means that the block existed, but it is in the process of being
+    // concurrently removed.  This flag is set in the releaseLocation eviction listener.
     if (location.isRemoved()) {
       return false;
     }
+
     int bankId = location.getBankId();
     int bankOffset = location.getBlock() * blockSize;
     ByteBuffer bank = getBank(bankId);
@@ -132,7 +150,15 @@ public class BlockCache {
     }
     return true;
   }
-  
+
+  /**
+   * @param blockCacheKey the key for the block
+   * @param buffer the target buffer for the read result
+   * @param blockOffset offset within the block
+   * @param off offset within the target buffer
+   * @param length the number of bytes to read
+   * @return true if the block was cached and the bytes were read
+   */
   public boolean fetch(BlockCacheKey blockCacheKey, byte[] buffer,
       int blockOffset, int off, int length) {
     BlockCacheLocation location = cache.getIfPresent(blockCacheKey);
@@ -140,13 +166,14 @@ public class BlockCache {
       return false;
     }
     if (location.isRemoved()) {
+      // location is in the process of being removed and the block may have already been reused by this point.
       return false;
     }
     int bankId = location.getBankId();
-    int offset = location.getBlock() * blockSize;
+    int bankOffset = location.getBlock() * blockSize;
     location.touch();
     ByteBuffer bank = getBank(bankId);
-    bank.position(offset + blockOffset);
+    bank.position(bankOffset + blockOffset);
     bank.get(buffer, off, length);
     return true;
   }
@@ -200,11 +227,13 @@ public class BlockCache {
           + "] got [" + buffer.length + "]");
     }
   }
-  
+
+  /** Returns a new copy of the ByteBuffer for the given bank, so it's safe to call position() on w/o additional synchronization */
   private ByteBuffer getBank(int bankId) {
     return banks[bankId].duplicate();
   }
-  
+
+  /** returns the number of elements in the cache */
   public int getSize() {
     return cache.asMap().size();
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/store/blockcache/BlockCacheLocation.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/store/blockcache/BlockCacheLocation.java b/solr/core/src/java/org/apache/solr/store/blockcache/BlockCacheLocation.java
index 36fb0a6..7792d90 100644
--- a/solr/core/src/java/org/apache/solr/store/blockcache/BlockCacheLocation.java
+++ b/solr/core/src/java/org/apache/solr/store/blockcache/BlockCacheLocation.java
@@ -35,6 +35,7 @@ public class BlockCacheLocation {
     touch();
   }
 
+  /** The block within the bank.  This has no relationship to the blockId in BlockCacheKey */
   public void setBlock(int block) {
     this.block = block;
   }
@@ -42,7 +43,8 @@ public class BlockCacheLocation {
   public void setBankId(int bankId) {
     this.bankId = bankId;
   }
-  
+
+  /** The block within the bank.  This has no relationship to the blockId in BlockCacheKey */
   public int getBlock() {
     return block;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/update/SolrCoreState.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/update/SolrCoreState.java b/solr/core/src/java/org/apache/solr/update/SolrCoreState.java
index 10d7553..f775b72 100644
--- a/solr/core/src/java/org/apache/solr/update/SolrCoreState.java
+++ b/solr/core/src/java/org/apache/solr/update/SolrCoreState.java
@@ -41,11 +41,17 @@ public abstract class SolrCoreState {
   
   protected boolean closed = false;
   private final Object updateLock = new Object();
+  private final Object reloadLock = new Object();
   
   public Object getUpdateLock() {
     return updateLock;
   }
   
+  public Object getReloadLock() {
+    return reloadLock;
+  }
+  
+  
   private int solrCoreStateRefCnt = 1;
 
   public void increfSolrCoreState() {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java b/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java
index 5affae6..9d4eb7d 100644
--- a/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java
+++ b/solr/core/src/java/org/apache/solr/update/UpdateShardHandler.java
@@ -178,8 +178,8 @@ public class UpdateShardHandler implements SolrMetricProducer, SolrInfoMBean {
 
   public void close() {
     try {
-      // we interrupt on purpose here, but this executor should not run threads that do disk IO!
-      ExecutorUtil.shutdownWithInterruptAndAwaitTermination(updateExecutor);
+      // do not interrupt, do not interrupt
+      ExecutorUtil.shutdownAndAwaitTermination(updateExecutor);
       ExecutorUtil.shutdownAndAwaitTermination(recoveryExecutor);
     } catch (Exception e) {
       SolrException.log(log, e);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/util/CommandOperation.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/util/CommandOperation.java b/solr/core/src/java/org/apache/solr/util/CommandOperation.java
index 6b8f14f..88dfbb9 100644
--- a/solr/core/src/java/org/apache/solr/util/CommandOperation.java
+++ b/solr/core/src/java/org/apache/solr/util/CommandOperation.java
@@ -50,6 +50,10 @@ public class CommandOperation {
     this.name = operationName;
   }
 
+  public Object getCommandData() {
+    return commandData;
+  }
+
   public String getStr(String key, String def) {
     if (ROOT_OBJ.equals(key)) {
       Object obj = getRootPrimitive();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/util/JsonSchemaValidator.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/util/JsonSchemaValidator.java b/solr/core/src/java/org/apache/solr/util/JsonSchemaValidator.java
new file mode 100644
index 0000000..1074ed8
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/JsonSchemaValidator.java
@@ -0,0 +1,370 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.util;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.common.util.Utils;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableMap;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+
+/**A very basic and lightweight json schema parsing and data validation tool. This custom tool is created
+ * because a) we need to support non json inputs b) to avoiding double parsing (this accepts an already parsed json as a map)
+ * It validates most aspects of json schema but it is NOT A FULLY COMPLIANT JSON schema parser or validator.
+ * What is supported ?
+ * a) all types and their validation (string, boolean, array, enum,object, integer, number)
+ * b) 'required' properties, 'additionalProperties'
+ *
+ *
+ */
+
+public class JsonSchemaValidator {
+  private final SchemaNode root;
+
+  public JsonSchemaValidator(String jsonString) {
+    this((Map) Utils.fromJSONString(jsonString));
+  }
+  public JsonSchemaValidator(Map jsonSchema) {
+    root = new SchemaNode(null);
+    root.isRequired = true;
+    List<String> errs = new LinkedList<>();
+    root.validateSchema(jsonSchema, errs);
+    if(!errs.isEmpty()){
+      throw new RuntimeException("Invalid schema. "+ StrUtils.join(errs,'|'));
+    }
+  }
+
+  private static class SchemaNode {
+    final SchemaNode parent;
+    Type type;
+    Type elementType;
+    boolean isRequired = false;
+    Object validationInfo;
+    Boolean additionalProperties;
+    Map<String, SchemaNode> children;
+
+    private SchemaNode(SchemaNode parent) {
+      this.parent = parent;
+    }
+
+    private void validateSchema(Map jsonSchema, List<String> errs) {
+      Object typeStr = jsonSchema.get("type");
+      if (typeStr == null) {
+        errs.add("'type' is missing ");
+      }
+      Type type = Type.get(typeStr);
+      if (type == null) {
+        errs.add ("Unknown type " + typeStr + " in object "+ Utils.toJSONString(jsonSchema));
+        return;
+      }
+      this.type = type;
+
+      for (SchemaAttribute schemaAttribute : SchemaAttribute.values()) {
+        schemaAttribute.validateSchema(jsonSchema, this, errs);
+      }
+      jsonSchema.keySet().forEach(o -> {
+        if (!knownAttributes.containsKey(o)) errs.add("Unknown key : " + o);
+      });
+      if (!errs.isEmpty()) return;
+
+      if (type == Type.OBJECT) {
+        Map m = (Map) jsonSchema.get("properties");
+        if (m != null) {
+          for (Object o : m.entrySet()) {
+            Map.Entry e = (Map.Entry) o;
+            if (e.getValue() instanceof Map) {
+              Map od = (Map) e.getValue();
+              if (children == null) children = new LinkedHashMap<>();
+              SchemaNode child = new SchemaNode(this);
+              children.put((String) e.getKey(), child);
+              child.validateSchema(od, errs);
+            } else {
+              errs.add("Invalid Object definition for field " + e.getKey());
+            }
+          }
+        } else {
+          additionalProperties = Boolean.TRUE;
+        }
+      }
+      for (SchemaAttribute attr : SchemaAttribute.values()) {
+        attr.postValidateSchema(jsonSchema, this, errs);
+      }
+
+    }
+
+    private void validate(String key, Object data, List<String> errs) {
+      if (data == null) {
+        if (isRequired) {
+          errs.add("Missing field '" + key+"'");
+          return;
+        }
+      } else {
+        type.validateData(key, data, this, errs);
+        if(!errs.isEmpty()) return;
+        if (children != null && type == Type.OBJECT) {
+          for (Map.Entry<String, SchemaNode> e : children.entrySet()) {
+            e.getValue().validate(e.getKey(), ((Map) data).get(e.getKey()), errs);
+          }
+          if (Boolean.TRUE != additionalProperties) {
+            for (Object o : ((Map) data).keySet()) {
+              if (!children.containsKey(o)) {
+                errs.add("Unknown field '" + o + "' in object : " + Utils.toJSONString(data));
+              }
+            }
+          }
+        }
+      }
+    }
+
+  }
+
+  public List<String> validateJson(Object data) {
+    List<String> errs = new LinkedList<>();
+    root.validate(null, data, errs);
+    return errs.isEmpty() ? null : errs;
+  }
+
+  /**represents an attribute in the schema definition
+   *
+   */
+  enum SchemaAttribute {
+    type(true, Type.STRING),
+    properties(false, Type.OBJECT) {
+      @Override
+      public void validateSchema(Map attrSchema, SchemaNode schemaNode, List<String> errors) {
+        super.validateSchema(attrSchema, schemaNode, errors);
+        if (schemaNode.type != Type.OBJECT) return;
+        Object val = attrSchema.get(key);
+        if (val == null) {
+          Object additional = attrSchema.get(additionalProperties.key);
+          if (Boolean.TRUE.equals(additional)) schemaNode.additionalProperties =  Boolean.TRUE;
+        }
+      }
+    },
+    additionalProperties(false, Type.BOOLEAN),
+    items(false, Type.OBJECT) {
+      @Override
+      public void validateSchema(Map attrSchema, SchemaNode schemaNode, List<String> errors) {
+        super.validateSchema(attrSchema, schemaNode, errors);
+        Object itemsVal = attrSchema.get(key);
+        if (itemsVal != null) {
+          if (schemaNode.type != Type.ARRAY) {
+            errors.add("Only 'array' can have 'items'");
+            return;
+          } else {
+            if (itemsVal instanceof Map) {
+              Map val = (Map) itemsVal;
+              Object value = val.get(type.key);
+              Type t = Type.get(String.valueOf(value));
+              if (t == null) {
+                errors.add("Unknown array type " + Utils.toJSONString(attrSchema));
+              } else {
+                schemaNode.elementType = t;
+              }
+            }
+          }
+        }
+      }
+    },
+    __default(false,Type.UNKNOWN),
+    description(false, Type.STRING),
+    documentation(false, Type.STRING),
+    oneOf(false, Type.ARRAY),
+    __enum(false, Type.ARRAY) {
+      @Override
+      void validateSchema(Map attrSchema, SchemaNode schemaNode, List<String> errors) {
+        if (attrSchema.get(Type.ENUM._name) != null) {
+          schemaNode.elementType = schemaNode.type;
+          schemaNode.type = Type.ENUM;
+        }
+      }
+
+      @Override
+      void postValidateSchema(Map attrSchema, SchemaNode schemaNode, List<String> errs) {
+        Object val = attrSchema.get(key);
+        if (val == null) return;
+        if (val instanceof List) {
+          List list = (List) val;
+          for (Object o : list) {
+            if (!schemaNode.elementType.validate(o)) {
+              errs.add("Invalid value : " + o + " Expected type : " + schemaNode.elementType._name);
+            }
+          }
+          if (!errs.isEmpty()) return;
+          schemaNode.validationInfo = new HashSet(list);
+        } else {
+          errs.add("'enum' should have a an array as value in Object " + Utils.toJSONString(attrSchema));
+        }
+      }
+    },
+    id(false, Type.STRING),
+    _ref(false, Type.STRING),
+    _schema(false, Type.STRING),
+    required(false, Type.ARRAY) {
+      @Override
+      public void postValidateSchema(Map attrSchema, SchemaNode attr, List<String> errors) {
+        Object val = attrSchema.get(key);
+        if (val instanceof List) {
+          List list = (List) val;
+          if (attr.children != null) {
+            for (Map.Entry<String, SchemaNode> e : attr.children.entrySet()) {
+              if (list.contains(e.getKey())) e.getValue().isRequired = true;
+            }
+          }
+        }
+      }
+    };
+
+    final String key;
+    final boolean _required;
+    final Type typ;
+
+    public String getKey() {
+      return key;
+    }
+
+    void validateSchema(Map attrSchema, SchemaNode schemaNode, List<String> errors) {
+      Object val = attrSchema.get(key);
+      if (val == null) {
+        if (_required)
+          errors.add("Missing required attribute '" + key + "' in object " + Utils.toJSONString(attrSchema));
+      } else {
+        if (!typ.validate(val)) errors.add(key + " should be of type " + typ._name);
+      }
+    }
+
+    void postValidateSchema(Map attrSchema, SchemaNode schemaNode, List<String> errs) {
+    }
+
+    SchemaAttribute(boolean required, Type type) {
+      this.key = name().replaceAll("__","").replace('_', '$');
+      this._required = required;
+      this.typ = type;
+    }
+  }
+
+  interface TypeValidator {
+    void validateData(String key, Object o, SchemaNode schemaNode, List<String> errs);
+  }
+
+  /**represents a type in json
+   *
+   */
+  enum Type {
+    STRING(o -> o instanceof String),
+    ARRAY(o -> o instanceof List, (key, o, schemaNode, errs) -> {
+      List l = o instanceof List ? (List) o : Collections.singletonList(o);
+      if (schemaNode.elementType != null) {
+        for (Object elem : l) {
+          if (!schemaNode.elementType.validate(elem)) {
+            errs.add("Expected elements of type : " + key + " but found : " + Utils.toJSONString(o));
+            break;
+          }
+        }
+      }
+    }),
+    NUMBER(o -> o instanceof Number, (key, o, schemaNode, errs) -> {
+      if (o instanceof String) {
+        try {
+          Double.parseDouble((String) o);
+        } catch (NumberFormatException e) {
+          errs.add(e.getClass().getName() + " " + e.getMessage());
+        }
+
+      }
+
+    }),
+    INTEGER(o -> o instanceof Integer, (key, o, schemaNode, errs) -> {
+      if (o instanceof String) {
+        try {
+          Integer.parseInt((String) o);
+        } catch (NumberFormatException e) {
+          errs.add(e.getClass().getName() + " " + e.getMessage());
+        }
+      }
+    }),
+    BOOLEAN(o -> o instanceof Boolean, (key, o, schemaNode, errs) -> {
+      if (o instanceof String) {
+        try {
+          Boolean.parseBoolean((String) o);
+        } catch (Exception e) {
+          errs.add(e.getClass().getName() + " " + e.getMessage());
+        }
+      }
+    }),
+    ENUM(o -> o instanceof List, (key, o, schemaNode, errs) -> {
+      if (schemaNode.validationInfo instanceof HashSet) {
+        HashSet enumVals = (HashSet) schemaNode.validationInfo;
+        if (!enumVals.contains(o)) {
+          errs.add("value of enum " + key + " must be one of" + enumVals);
+        }
+      }
+    }),
+    OBJECT(o -> o instanceof Map),
+    UNKNOWN((o -> true));
+    final String _name;
+
+    final java.util.function.Predicate typeValidator;
+    private final TypeValidator validator;
+
+    Type(java.util.function.Predicate validator) {
+      this(validator, null);
+
+    }
+
+    Type(java.util.function.Predicate validator, TypeValidator v) {
+      _name = this.name().toLowerCase(Locale.ROOT);
+      this.typeValidator = validator;
+      this.validator = v;
+    }
+
+    boolean validate(Object o) {
+      return typeValidator.test(o);
+    }
+
+    void validateData(String key, Object o, SchemaNode attr, List<String> errs) {
+      if (validator != null) {
+        validator.validateData(key, o, attr, errs);
+        return;
+      }
+      if (!typeValidator.test(o))
+        errs.add("Expected type : " + _name + " but found : " + o + "in object : " + Utils.toJSONString(o));
+    }
+
+    static Type get(Object type) {
+      for (Type t : Type.values()) {
+        if (t._name.equals(type)) return t;
+      }
+      return null;
+    }
+  }
+
+
+  static final Map<String, SchemaAttribute> knownAttributes = unmodifiableMap(asList(SchemaAttribute.values()).stream().collect(toMap(SchemaAttribute::getKey, identity())));
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/util/PathTrie.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/util/PathTrie.java b/solr/core/src/java/org/apache/solr/util/PathTrie.java
new file mode 100644
index 0000000..ceaa5de
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/PathTrie.java
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.util;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.solr.common.util.StrUtils;
+
+import static java.util.Collections.emptyList;
+
+/**A utility class to efficiently parse/store/lookup hierarchical paths which are templatized
+ * like /collections/{collection}/shards/{shard}/{replica}
+ */
+public class PathTrie<T> {
+  private final Set<String> reserved = new HashSet<>();
+  Node root = new Node(emptyList(), null);
+
+  public PathTrie() { }
+
+  public PathTrie(Set<String> reserved) {
+    this.reserved.addAll(reserved);
+  }
+
+
+
+  public void insert(String path, Map<String, String> replacements, T o) {
+    List<String> parts = getPathSegments(path);
+    insert(parts,replacements, o);
+  }
+
+  public void insert(List<String> parts, Map<String, String> replacements, T o) {
+    if (parts.isEmpty()) {
+      root.obj = o;
+      return;
+    }
+
+    for (int i = 0; i < parts.size(); i++) {
+      String part = parts.get(i);
+      if (part.charAt(0) == '$') {
+        String replacement = replacements.get(part.substring(1));
+        if (replacement == null) {
+          throw new RuntimeException(part + " is not provided");
+        }
+        replacement = replacement.charAt(0) == '/' ? replacement.substring(1) : replacement;
+        parts.set(i, replacement);
+      }
+    }
+
+    root.insert(parts, o);
+  }
+
+  // /a/b/c will be returned as ["a","b","c"]
+  public static List<String> getPathSegments(String path) {
+    if (path == null || path.isEmpty()) return emptyList();
+    List<String> parts = new ArrayList<String>() {
+      @Override
+      public boolean add(String s) {
+        if (s == null || s.isEmpty()) return false;
+        return super.add(s);
+      }
+    };
+    StrUtils.splitSmart(path, '/', parts);
+    return parts;
+  }
+
+
+  public T lookup(String path, Map<String, String> templateValues) {
+    return root.lookup(getPathSegments(path), 0, templateValues);
+  }
+
+  public T lookup(List<String> path, Map<String, String> templateValues) {
+    return root.lookup(path, 0, templateValues);
+  }
+
+  public T lookup(String path, Map<String, String> templateValues, Set<String> paths) {
+    return root.lookup(getPathSegments(path), 0, templateValues, paths);
+  }
+
+  public static String templateName(String templateStr) {
+    return templateStr.startsWith("{") && templateStr.endsWith("}") ?
+        templateStr.substring(1, templateStr.length() - 1) :
+        null;
+
+  }
+
+  class Node {
+    String name;
+    Map<String, Node> children;
+    T obj;
+    String templateName;
+
+    Node(List<String> path, T o) {
+      if (path.isEmpty()) {
+        obj = o;
+        return;
+      }
+      String part = path.get(0);
+      templateName = templateName(part);
+      name = part;
+      if (path.isEmpty()) obj = o;
+    }
+
+
+    private synchronized void insert(List<String> path, T o) {
+      String part = path.get(0);
+      Node matchedChild = null;
+      if (children == null) children = new ConcurrentHashMap<>();
+
+      String varName = templateName(part);
+      String key = varName == null ? part : "";
+
+      matchedChild = children.get(key);
+      if (matchedChild == null) {
+        children.put(key, matchedChild = new Node(path, o));
+      }
+      if (varName != null) {
+        if (!matchedChild.templateName.equals(varName)) {
+          throw new RuntimeException("wildcard name must be " + matchedChild.templateName);
+        }
+      }
+      path.remove(0);
+      if (!path.isEmpty()) {
+        matchedChild.insert(path, o);
+      } else {
+        matchedChild.obj = o;
+      }
+
+    }
+
+
+    void findAvailableChildren(String path, Set<String> availableSubPaths) {
+      if (availableSubPaths == null) return;
+      if (children != null) {
+        for (Node node : children.values()) {
+          if (node.obj != null) {
+            String s = path + "/" + node.name;
+            availableSubPaths.add(s);
+          }
+        }
+
+        for (Node node : children.values()) {
+          node.findAvailableChildren(path + "/" + node.name, availableSubPaths);
+        }
+      }
+    }
+
+
+    public T lookup(List<String> pieces, int i, Map<String, String> templateValues) {
+      return lookup(pieces, i, templateValues, null);
+
+    }
+
+    /**
+     *
+     * @param pathSegments pieces in the url /a/b/c has pieces as 'a' , 'b' , 'c'
+     * @param index current index of the pieces that we are looking at in /a/b/c 0='a' and 1='b'
+     * @param templateVariables The mapping of template variable to its value
+     * @param availableSubPaths If not null , available sub paths will be returned in this set
+     */
+    public T lookup(List<String> pathSegments, int index, Map<String, String> templateVariables, Set<String> availableSubPaths) {
+      if (templateName != null) templateVariables.put(templateName, pathSegments.get(index - 1));
+      if (pathSegments.size() < index + 1) {
+        findAvailableChildren("", availableSubPaths);
+        return obj;
+      }
+      String piece = pathSegments.get(index);
+      if (children == null) return null;
+      Node n = children.get(piece);
+      if (n == null && !reserved.contains(piece)) n = children.get("");
+      if (n == null) return null;
+      return n.lookup(pathSegments, index + 1, templateVariables, availableSubPaths);
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/util/SolrCLI.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/util/SolrCLI.java b/solr/core/src/java/org/apache/solr/util/SolrCLI.java
index bb2d554..4f6cf8d 100644
--- a/solr/core/src/java/org/apache/solr/util/SolrCLI.java
+++ b/solr/core/src/java/org/apache/solr/util/SolrCLI.java
@@ -60,7 +60,6 @@ import org.apache.commons.cli.Option;
 import org.apache.commons.cli.OptionBuilder;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
-import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.exec.DefaultExecuteResultHandler;
 import org.apache.commons.exec.DefaultExecutor;
 import org.apache.commons.exec.Executor;
@@ -80,7 +79,6 @@ import org.apache.http.client.methods.HttpHead;
 import org.apache.http.client.utils.URIBuilder;
 import org.apache.http.conn.ConnectTimeoutException;
 import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.message.BasicHeader;
 import org.apache.http.util.EntityUtils;
 import org.apache.lucene.util.Version;
 import org.apache.solr.client.solrj.SolrClient;
@@ -90,7 +88,6 @@ import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.impl.HttpClientUtil;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.HttpSolrClient.Builder;
-import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
 import org.apache.solr.client.solrj.impl.ZkClientClusterStateProvider;
 import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
@@ -106,7 +103,6 @@ import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.ContentStreamBase;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.StrUtils;
 import org.noggit.CharArr;
 import org.noggit.JSONParser;
 import org.noggit.JSONWriter;
@@ -153,7 +149,6 @@ public class SolrCLI {
 
       int toolExitStatus = 0;
       try {
-        setBasicAuth();
         runImpl(cli);
       } catch (Exception exc) {
         // since this is a CLI, spare the user the stacktrace
@@ -261,20 +256,6 @@ public class SolrCLI {
   }
 
   public static CommandLine parseCmdLine(String[] args, Option[] toolOptions) throws Exception {
-
-    String builderClassName = System.getProperty("solr.authentication.httpclient.builder");
-    if (builderClassName!=null) {
-      try {
-        Class c = Class.forName(builderClassName);
-        SolrHttpClientBuilder builder = (SolrHttpClientBuilder)c.newInstance();
-        HttpClientUtil.setHttpClientBuilder(builder);
-        log.info("Set SolrHttpClientBuilder from: "+builderClassName);
-      } catch (Exception ex) {
-        log.error(ex.getMessage());
-        throw new RuntimeException("Error during loading of builder '"+builderClassName+"'.", ex);
-      }
-    }
-
     // the parser doesn't like -D props
     List<String> toolArgList = new ArrayList<String>();
     List<String> dashDList = new ArrayList<String>();
@@ -532,25 +513,6 @@ public class SolrCLI {
   }
 
   /**
-   * Inspects system property basicauth and enables authentication for HttpClient
-   * @throws Exception if the basicauth SysProp has wrong format
-   */
-  protected static void setBasicAuth() throws Exception {
-    String basicauth = System.getProperty("basicauth", null);
-    if (basicauth != null) {
-      List<String> ss = StrUtils.splitSmart(basicauth, ':');
-      if (ss.size() != 2)
-        throw new Exception("Please provide 'basicauth' in the 'user:password' format");
-
-      HttpClientUtil.addRequestInterceptor((httpRequest, httpContext) -> {
-        String pair = ss.get(0) + ":" + ss.get(1);
-        byte[] encodedBytes = Base64.encodeBase64(pair.getBytes(UTF_8));
-        httpRequest.addHeader(new BasicHeader("Authorization", "Basic " + new String(encodedBytes, UTF_8)));
-      });
-    }
-  }
-  
-  /**
    * Determine if a request to Solr failed due to a communication error,
    * which is generally retry-able. 
    */
@@ -3341,7 +3303,6 @@ public class SolrCLI {
 
       int toolExitStatus = 0;
       try {
-        setBasicAuth();
         toolExitStatus = runAssert(cli);
       } catch (Exception exc) {
         // since this is a CLI, spare the user the stacktrace

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/ImplicitPlugins.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/ImplicitPlugins.json b/solr/core/src/resources/ImplicitPlugins.json
index 34e5c07..a1ddbe7 100644
--- a/solr/core/src/resources/ImplicitPlugins.json
+++ b/solr/core/src/resources/ImplicitPlugins.json
@@ -26,6 +26,10 @@
         "json.command": "false"
       }
     },
+    "update":{
+      "class":"solr.UpdateRequestHandlerApi",
+      "useParams": "_UPDATE_JSON_DOCS"
+    },
     "/config": {
       "useParams":"_CONFIG",
       "class": "solr.SolrConfigHandler"
@@ -159,4 +163,4 @@
       }
     }
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.Commands.json b/solr/core/src/resources/apispec/cluster.Commands.json
new file mode 100644
index 0000000..8983964
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.Commands.json
@@ -0,0 +1,74 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API",
+  "description": "Cluster-wide commands to assign roles to nodes, remove role assignments, or add, edit or remove a cluster-wide property.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/cluster"
+    ]
+  },
+  "commands": {
+    "add-role":{
+      "type":"object",
+      "documentation":"https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api15",
+      "description":"Assign a specific role to a node in the cluster.",
+      "properties": {
+        "role": {
+          "type": "string",
+          "description": "The name of the role. The only supported role is 'overseer'."
+
+        },
+        "node": {
+          "type": "string",
+          "description": "The name of the node. It is possible to assign a role even before that node is started."
+
+        }
+      },
+      "required": [
+        "role",
+        "node"
+      ]
+    },
+    "remove-role":{
+      "type":"object",
+      "documentation":"https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api16",
+      "description":"Unassign a role from a node in the cluster.",
+      "properties": {
+        "role": {
+          "type": "string",
+          "description": "The name of the role. The only supported role as of now is 'overseer'."
+
+        },
+        "node": {
+          "type": "string",
+          "description": "The name of the node where the role should be removed."
+        }
+      },
+      "required": [
+        "role",
+        "node"
+      ]
+    },
+    "set-property": {
+      "type": "object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api11",
+      "description": "Add, edit, or delete a cluster-wide property.",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The name of the property"
+        },
+        "val": {
+          "type": "string",
+          "description": "The value of the property. If the value is empty or null, the property is unset."
+        }
+      },
+      "required": [
+        "name",
+        "val"
+      ]
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.commandstatus.delete.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.commandstatus.delete.json b/solr/core/src/resources/apispec/cluster.commandstatus.delete.json
new file mode 100644
index 0000000..5576c42
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.commandstatus.delete.json
@@ -0,0 +1,10 @@
+{
+  "methods": [
+    "DELETE"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/command-status/{id}"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.commandstatus.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.commandstatus.json b/solr/core/src/resources/apispec/cluster.commandstatus.json
new file mode 100644
index 0000000..a8a402b
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.commandstatus.json
@@ -0,0 +1,20 @@
+{
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/command-status"
+    ],
+    "params": {
+      "flush": {
+        "type": "boolean",
+        "default": false
+      },
+      "id":{
+        "type":"string",
+        "description": "The command id"
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.configs.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.configs.Commands.json b/solr/core/src/resources/apispec/cluster.configs.Commands.json
new file mode 100644
index 0000000..d026cd5
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.configs.Commands.json
@@ -0,0 +1,34 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/ConfigSets+API",
+  "description": "Create ConfigSets.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/configs"]
+  },
+  "commands": {
+    "create": {
+      "type" :"object",
+      "description": "Create a ConfigSet, based on another ConfigSet already in ZooKeeper.",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/ConfigSets+API#ConfigSetsAPI-create",
+      "properties": {
+        "name" :{
+          "type" :"string",
+          "description" : "The name of the ConfigSet to be created."
+        },
+        "baseConfigSet":{
+          "type" : "string",
+          "description" :"The existing ConfigSet to copy as the basis for the new one."
+        },
+        "properties" : {
+          "type":"object",
+          "description": "Additional key-value pairs, in the form of 'ConfigSetProp.<key>=<value>', as needed. These properties will override the same properties in the base ConfigSet.",
+          "additionalProperties" : true
+        }
+      },
+      "required" : ["name", "baseConfigSet"]
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.configs.delete.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.configs.delete.json b/solr/core/src/resources/apispec/cluster.configs.delete.json
new file mode 100644
index 0000000..236d457
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.configs.delete.json
@@ -0,0 +1,12 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/ConfigSets+API#ConfigSetsAPI-delete",
+  "description": "Delete ConfigSets. The name of the ConfigSet to delete must be provided as a path parameter.",
+  "methods": [
+    "DELETE"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/configs/{name}"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.configs.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.configs.json b/solr/core/src/resources/apispec/cluster.configs.json
new file mode 100644
index 0000000..9a1443a
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.configs.json
@@ -0,0 +1,12 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/ConfigSets+API#ConfigSetsAPI-list",
+  "description": "List all ConfigSets in the cluster.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/configs"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.json b/solr/core/src/resources/apispec/cluster.json
new file mode 100644
index 0000000..0ec5b96
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.json
@@ -0,0 +1,14 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API",
+  "description": "General information about the cluster, including defined collections (with the 'cluster' endpoint), status of the overseer (with the 'cluster/overseer' endpoint), and available nodes (with the 'cluster/nodes' endpoint).",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/cluster",
+      "/cluster/overseer",
+      "/cluster/nodes"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.nodes.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.nodes.json b/solr/core/src/resources/apispec/cluster.nodes.json
new file mode 100644
index 0000000..f992f7f
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.nodes.json
@@ -0,0 +1,12 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API",
+  "description": "Provides general information about the available nodes of the cluster.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/nodes"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.security.BasicAuth.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.security.BasicAuth.Commands.json b/solr/core/src/resources/apispec/cluster.security.BasicAuth.Commands.json
new file mode 100644
index 0000000..da04c85
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.security.BasicAuth.Commands.json
@@ -0,0 +1,23 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Basic+Authentication+Plugin",
+  "description": "Modifies the configuration of Basic authentication, allowing you to add or remove users and their passwords.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/security/authentication"
+    ]
+  },
+  "commands": {
+    "set-user": {
+      "type":"object",
+      "description": "The set-user command allows you to add users and change their passwords. Usernames and passwords are expressed as key-value pairs in a JSON object.",
+      "additionalProperties": true
+    },
+    "delete-user": {
+      "description": "Delete a user or a list of users. Passwords do not need to be provided, simply list the users in a JSON array, separated by colons.",
+      "type":"string"
+    }
+  }
+}


[10/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index caa8906..cac5389 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -85,6 +85,7 @@ public class SolrMetricManager {
   private final Map<String, Map<String, SolrMetricReporter>> reporters = new HashMap<>();
 
   private final Lock reportersLock = new ReentrantLock();
+  private final Lock swapLock = new ReentrantLock();
 
   public SolrMetricManager() { }
 
@@ -178,18 +179,27 @@ public class SolrMetricManager {
     if (isSharedRegistry(registry)) {
       return SharedMetricRegistries.getOrCreate(registry);
     } else {
-      final MetricRegistry existing = registries.get(registry);
-      if (existing == null) {
-        final MetricRegistry created = new MetricRegistry();
-        final MetricRegistry raced = registries.putIfAbsent(registry, created);
-        if (raced == null) {
-          return created;
-        } else {
-          return raced;
-        }
+      swapLock.lock();
+      try {
+        return getOrCreate(registries, registry);
+      } finally {
+        swapLock.unlock();
+      }
+    }
+  }
+
+  private static MetricRegistry getOrCreate(ConcurrentMap<String, MetricRegistry> map, String registry) {
+    final MetricRegistry existing = map.get(registry);
+    if (existing == null) {
+      final MetricRegistry created = new MetricRegistry();
+      final MetricRegistry raced = map.putIfAbsent(registry, created);
+      if (raced == null) {
+        return created;
       } else {
-        return existing;
+        return raced;
       }
+    } else {
+      return existing;
     }
   }
 
@@ -205,34 +215,47 @@ public class SolrMetricManager {
     if (isSharedRegistry(registry)) {
       SharedMetricRegistries.remove(registry);
     } else {
-      registries.remove(registry);
+      swapLock.lock();
+      try {
+        registries.remove(registry);
+      } finally {
+        swapLock.unlock();
+      }
     }
   }
 
   /**
-   * Move all matching metrics from one registry to another. This is useful eg. during
-   * {@link org.apache.solr.core.SolrCore} rename or swap operations.
-   * @param fromRegistry source registry
-   * @param toRegistry target registry
-   * @param filter optional {@link MetricFilter} to select what metrics to move. If null
-   *               then all metrics will be moved.
+   * Swap registries. This is useful eg. during
+   * {@link org.apache.solr.core.SolrCore} rename or swap operations. NOTE:
+   * this operation is not supported for shared registries.
+   * @param registry1 source registry
+   * @param registry2 target registry. Note: when used after core rename the target registry doesn't
+   *                  exist, so the swap operation will only rename the existing registry without creating
+   *                  an empty one under the previous name.
    */
-  public void moveMetrics(String fromRegistry, String toRegistry, MetricFilter filter) {
-    MetricRegistry from = registry(fromRegistry);
-    MetricRegistry to = registry(toRegistry);
-    if (from == to) {
-      return;
+  public void swapRegistries(String registry1, String registry2) {
+    registry1 = overridableRegistryName(registry1);
+    registry2 = overridableRegistryName(registry2);
+    if (isSharedRegistry(registry1) || isSharedRegistry(registry2)) {
+      throw new UnsupportedOperationException("Cannot swap shared registry: " + registry1 + ", " + registry2);
     }
-    if (filter == null) {
-      to.registerAll(from);
-      from.removeMatching(MetricFilter.ALL);
-    } else {
-      for (Map.Entry<String, Metric> entry : from.getMetrics().entrySet()) {
-        if (filter.matches(entry.getKey(), entry.getValue())) {
-          to.register(entry.getKey(), entry.getValue());
-        }
+    swapLock.lock();
+    try {
+      MetricRegistry from = registries.get(registry1);
+      MetricRegistry to = registries.get(registry2);
+      if (from == to) {
+        return;
+      }
+      MetricRegistry reg1 = registries.remove(registry1);
+      MetricRegistry reg2 = registries.remove(registry2);
+      if (reg2 != null) {
+        registries.put(registry1, reg2);
       }
-      from.removeMatching(filter);
+      if (reg1 != null) {
+        registries.put(registry2, reg1);
+      }
+    } finally {
+      swapLock.unlock();
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/query/SolrRangeQuery.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/query/SolrRangeQuery.java b/solr/core/src/java/org/apache/solr/query/SolrRangeQuery.java
index bed0ad5..78c01f8 100644
--- a/solr/core/src/java/org/apache/solr/query/SolrRangeQuery.java
+++ b/solr/core/src/java/org/apache/solr/query/SolrRangeQuery.java
@@ -49,6 +49,7 @@ import org.apache.solr.search.BitDocSet;
 import org.apache.solr.search.DocSet;
 import org.apache.solr.search.DocSetBuilder;
 import org.apache.solr.search.DocSetProducer;
+import org.apache.solr.search.DocSetUtil;
 import org.apache.solr.search.ExtendedQueryBase;
 import org.apache.solr.search.Filter;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -168,7 +169,8 @@ public final class SolrRangeQuery extends ExtendedQueryBase implements DocSetPro
       maxTermsPerSegment = Math.max(maxTermsPerSegment, termsVisited);
     }
 
-    return maxTermsPerSegment <= 1 ? builder.buildUniqueInOrder(liveBits) : builder.build(liveBits);
+    DocSet set =  maxTermsPerSegment <= 1 ? builder.buildUniqueInOrder(liveBits) : builder.build(liveBits);
+    return DocSetUtil.getDocSet(set, searcher);
   }
 
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/request/DocValuesFacets.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/DocValuesFacets.java b/solr/core/src/java/org/apache/solr/request/DocValuesFacets.java
index 3714bf1..e9498f8 100644
--- a/solr/core/src/java/org/apache/solr/request/DocValuesFacets.java
+++ b/solr/core/src/java/org/apache/solr/request/DocValuesFacets.java
@@ -18,6 +18,7 @@ package org.apache.solr.request;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.function.Predicate;
 
 import org.apache.lucene.index.DocValues;
 import org.apache.lucene.index.LeafReaderContext;
@@ -57,8 +58,13 @@ import org.apache.solr.util.LongPriorityQueue;
  */
 public class DocValuesFacets {
   private DocValuesFacets() {}
-  
+
   public static NamedList<Integer> getCounts(SolrIndexSearcher searcher, DocSet docs, String fieldName, int offset, int limit, int mincount, boolean missing, String sort, String prefix, String contains, boolean ignoreCase, FacetDebugInfo fdebug) throws IOException {
+    final Predicate<BytesRef> termFilter = new SubstringBytesRefFilter(contains, ignoreCase);
+    return getCounts(searcher, docs, fieldName, offset, limit, mincount, missing, sort, prefix, termFilter, fdebug);
+  }
+  
+  public static NamedList<Integer> getCounts(SolrIndexSearcher searcher, DocSet docs, String fieldName, int offset, int limit, int mincount, boolean missing, String sort, String prefix, Predicate<BytesRef> termFilter, FacetDebugInfo fdebug) throws IOException {
     SchemaField schemaField = searcher.getSchema().getField(fieldName);
     FieldType ft = schemaField.getType();
     NamedList<Integer> res = new NamedList<>();
@@ -178,9 +184,9 @@ public class DocValuesFacets {
             // index order, so we already know that the keys are ordered.  This can be very
             // important if a lot of the counts are repeated (like zero counts would be).
 
-            if (contains != null) {
+            if (termFilter != null) {
               final BytesRef term = si.lookupOrd(startTermIndex+i);
-              if (!SimpleFacets.contains(term.utf8ToString(), contains, ignoreCase)) {
+              if (!termFilter.test(term)) {
                 continue;
               }
             }
@@ -213,8 +219,8 @@ public class DocValuesFacets {
       } else {
         // add results in index order
         int i=(startTermIndex==-1)?1:0;
-        if (mincount<=0 && contains == null) {
-          // if mincount<=0 and we're not examining the values for contains, then
+        if (mincount<=0 && termFilter == null) {
+          // if mincount<=0 and we're not examining the values for the term filter, then
           // we won't discard any terms and we know exactly where to start.
           i+=off;
           off=0;
@@ -224,9 +230,9 @@ public class DocValuesFacets {
           int c = counts[i];
           if (c<mincount) continue;
           BytesRef term = null;
-          if (contains != null) {
+          if (termFilter != null) {
             term = si.lookupOrd(startTermIndex+i);
-            if (!SimpleFacets.contains(term.utf8ToString(), contains, ignoreCase)) {
+            if (!termFilter.test(term)) {
               continue;
             }
           }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/IntervalFacets.java b/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
index 88e39fc..8b7cd3c 100644
--- a/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
+++ b/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
@@ -31,7 +31,6 @@ import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.NumericDocValues;
 import org.apache.lucene.index.SortedDocValues;
 import org.apache.lucene.index.SortedSetDocValues;
-import org.apache.lucene.legacy.LegacyNumericType;
 import org.apache.lucene.search.DocIdSet;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.util.Bits;
@@ -42,9 +41,9 @@ import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.request.IntervalFacets.FacetInterval;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.NumberType;
 import org.apache.solr.schema.PointField;
 import org.apache.solr.schema.SchemaField;
-import org.apache.solr.schema.TrieDateField;
 import org.apache.solr.search.DocIterator;
 import org.apache.solr.search.DocSet;
 import org.apache.solr.search.Filter;
@@ -175,7 +174,7 @@ public class IntervalFacets implements Iterable<FacetInterval> {
   }
 
   private void doCount() throws IOException {
-    if (schemaField.getType().getNumericType() != null && !schemaField.multiValued()) {
+    if (schemaField.getType().getNumberType() != null && !schemaField.multiValued()) {
       getCountNumeric();
     } else {
       getCountString();
@@ -185,7 +184,7 @@ public class IntervalFacets implements Iterable<FacetInterval> {
   private void getCountNumeric() throws IOException {
     final FieldType ft = schemaField.getType();
     final String fieldName = schemaField.getName();
-    final LegacyNumericType numericType = ft.getNumericType();
+    final NumberType numericType = ft.getNumberType();
     if (numericType == null) {
       throw new IllegalStateException();
     }
@@ -203,9 +202,8 @@ public class IntervalFacets implements Iterable<FacetInterval> {
         assert doc >= ctx.docBase;
         switch (numericType) {
           case LONG:
-            longs = DocValues.getNumeric(ctx.reader(), fieldName);
-            break;
-          case INT:
+          case DATE:
+          case INTEGER:
             longs = DocValues.getNumeric(ctx.reader(), fieldName);
             break;
           case FLOAT:
@@ -515,7 +513,7 @@ public class IntervalFacets implements Iterable<FacetInterval> {
       }
       // TODO: what about escaping star (*)?
       // TODO: escaping spaces on ends?
-      if (schemaField.getType().getNumericType() != null) {
+      if (schemaField.getType().getNumberType() != null) {
         setNumericLimits(schemaField);
       }
       if (start != null && end != null && start.compareTo(end) > 0) {
@@ -537,7 +535,7 @@ public class IntervalFacets implements Iterable<FacetInterval> {
      */
     public FacetInterval(SchemaField schemaField, String startStr, String endStr,
         boolean includeLower, boolean includeUpper, String key) {
-      assert schemaField.getType().getNumericType() != null: "Only numeric fields supported with this constructor";
+      assert schemaField.getType().getNumberType() != null: "Only numeric fields supported with this constructor";
       this.key = key;
       this.startOpen = !includeLower;
       this.endOpen = !includeUpper;
@@ -559,15 +557,14 @@ public class IntervalFacets implements Iterable<FacetInterval> {
       if (start == null) {
         startLimit = Long.MIN_VALUE;
       } else {
-        switch (schemaField.getType().getNumericType()) {
+        switch (schemaField.getType().getNumberType()) {
           case LONG:
-            if (schemaField.getType() instanceof TrieDateField) {
-              startLimit = ((Date) schemaField.getType().toObject(schemaField, start)).getTime();
-            } else {
-              startLimit = (long) schemaField.getType().toObject(schemaField, start);
-            }
+            startLimit = (long) schemaField.getType().toObject(schemaField, start);
             break;
-          case INT:
+          case DATE:
+            startLimit = ((Date) schemaField.getType().toObject(schemaField, start)).getTime();
+            break;
+          case INTEGER:
             startLimit = ((Integer) schemaField.getType().toObject(schemaField, start)).longValue();
             break;
           case FLOAT:
@@ -588,15 +585,14 @@ public class IntervalFacets implements Iterable<FacetInterval> {
       if (end == null) {
         endLimit = Long.MAX_VALUE;
       } else {
-        switch (schemaField.getType().getNumericType()) {
+        switch (schemaField.getType().getNumberType()) {
           case LONG:
-            if (schemaField.getType() instanceof TrieDateField) {
-              endLimit = ((Date) schemaField.getType().toObject(schemaField, end)).getTime();
-            } else {
-              endLimit = (long) schemaField.getType().toObject(schemaField, end);
-            }
+            endLimit = (long) schemaField.getType().toObject(schemaField, end);
+            break;
+          case DATE:
+            endLimit = ((Date) schemaField.getType().toObject(schemaField, end)).getTime();
             break;
-          case INT:
+          case INTEGER:
             endLimit = ((Integer) schemaField.getType().toObject(schemaField, end)).longValue();
             break;
           case FLOAT:

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/request/NumericFacets.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/NumericFacets.java b/solr/core/src/java/org/apache/solr/request/NumericFacets.java
index d292a88..9452c53 100644
--- a/solr/core/src/java/org/apache/solr/request/NumericFacets.java
+++ b/solr/core/src/java/org/apache/solr/request/NumericFacets.java
@@ -34,7 +34,6 @@ import org.apache.lucene.index.NumericDocValues;
 import org.apache.lucene.index.ReaderUtil;
 import org.apache.lucene.index.Terms;
 import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.legacy.LegacyNumericType;
 import org.apache.lucene.queries.function.FunctionValues;
 import org.apache.lucene.queries.function.ValueSource;
 import org.apache.lucene.util.BytesRef;
@@ -44,6 +43,7 @@ import org.apache.lucene.util.StringHelper;
 import org.apache.solr.common.params.FacetParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.NumberType;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.TrieField;
 import org.apache.solr.search.DocIterator;
@@ -133,7 +133,7 @@ final class NumericFacets {
     mincount = Math.max(mincount, 1);
     final SchemaField sf = searcher.getSchema().getField(fieldName);
     final FieldType ft = sf.getType();
-    final LegacyNumericType numericType = ft.getNumericType();
+    final NumberType numericType = ft.getNumberType();
     if (numericType == null) {
       throw new IllegalStateException();
     }
@@ -154,9 +154,9 @@ final class NumericFacets {
         assert doc >= ctx.docBase;
         switch (numericType) {
           case LONG:
-            longs = DocValues.getNumeric(ctx.reader(), fieldName);
-            break;
-          case INT:
+          case DATE:
+          case INTEGER:
+            // Long, Date and Integer
             longs = DocValues.getNumeric(ctx.reader(), fieldName);
             break;
           case FLOAT:
@@ -182,7 +182,7 @@ final class NumericFacets {
             };
             break;
           default:
-            throw new AssertionError();
+            throw new AssertionError("Unexpected type: " + numericType);
         }
       }
       int valuesDocID = longs.docID();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/request/PerSegmentSingleValuedFaceting.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/PerSegmentSingleValuedFaceting.java b/solr/core/src/java/org/apache/solr/request/PerSegmentSingleValuedFaceting.java
index c729b3b..48837e0 100644
--- a/solr/core/src/java/org/apache/solr/request/PerSegmentSingleValuedFaceting.java
+++ b/solr/core/src/java/org/apache/solr/request/PerSegmentSingleValuedFaceting.java
@@ -25,6 +25,7 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorCompletionService;
 import java.util.concurrent.Future;
+import java.util.function.Predicate;
 
 import org.apache.lucene.index.DocValues;
 import org.apache.lucene.index.LeafReaderContext;
@@ -62,14 +63,17 @@ class PerSegmentSingleValuedFaceting {
   String sort;
   String prefix;
 
-  private String contains;
-  private boolean ignoreCase;
+  private final Predicate<BytesRef> termFilter;
 
   Filter baseSet;
 
   int nThreads;
 
   public PerSegmentSingleValuedFaceting(SolrIndexSearcher searcher, DocSet docs, String fieldName, int offset, int limit, int mincount, boolean missing, String sort, String prefix, String contains, boolean ignoreCase) {
+    this(searcher, docs, fieldName, offset, limit, mincount, missing, sort, prefix, new SubstringBytesRefFilter(contains, ignoreCase));
+  }
+
+  public PerSegmentSingleValuedFaceting(SolrIndexSearcher searcher, DocSet docs, String fieldName, int offset, int limit, int mincount, boolean missing, String sort, String prefix, Predicate<BytesRef> filter) {
     this.searcher = searcher;
     this.docs = docs;
     this.fieldName = fieldName;
@@ -79,8 +83,7 @@ class PerSegmentSingleValuedFaceting {
     this.missing = missing;
     this.sort = sort;
     this.prefix = prefix;
-    this.contains = contains;
-    this.ignoreCase = ignoreCase;
+    this.termFilter = filter;
   }
 
   public void setNumThreads(int threads) {
@@ -183,8 +186,7 @@ class PerSegmentSingleValuedFaceting {
     while (queue.size() > 0) {
       SegFacet seg = queue.top();
       
-      // if facet.contains specified, only actually collect the count if substring contained
-      boolean collect = contains == null || SimpleFacets.contains(seg.tempBR.utf8ToString(), contains, ignoreCase);
+      boolean collect = termFilter == null || termFilter.test(seg.tempBR);
       
       // we will normally end up advancing the term enum for this segment
       // while still using "val", so we need to make a copy since the BytesRef

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/SimpleFacets.java b/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
index 0d9cb29..22a837a 100644
--- a/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
+++ b/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
@@ -21,9 +21,11 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -33,8 +35,8 @@ import java.util.concurrent.RunnableFuture;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
 
-import org.apache.commons.lang.StringUtils;
 import org.apache.lucene.index.Fields;
 import org.apache.lucene.index.LeafReader;
 import org.apache.lucene.index.LeafReaderContext;
@@ -169,26 +171,6 @@ public class SimpleFacets {
     this.fdebugParent = fdebugParent;
   }
 
-  /**
-   * Returns <code>true</code> if a String contains the given substring. Otherwise
-   * <code>false</code>.
-   *
-   * @param ref
-   *          the {@link String} to test
-   * @param substring
-   *          the substring to look for
-   * @param ignoreCase
-   *          whether the comparison should be case-insensitive
-   * @return Returns <code>true</code> iff the String contains the given substring.
-   *         Otherwise <code>false</code>.
-   */
-  public static boolean contains(String ref, String substring, boolean ignoreCase) {
-    if (ignoreCase)
-      return StringUtils.containsIgnoreCase(ref, substring);
-    return StringUtils.contains(ref, substring);
-  }
-
-
   protected ParsedParams parseParams(String type, String param) throws SyntaxError, IOException {
     SolrParams localParams = QueryParsing.getLocalParams(param, req.getParams());
     DocSet docs = docsOrig;
@@ -362,6 +344,48 @@ public class SimpleFacets {
     ENUM, FC, FCS, UIF;
   }
 
+  protected Predicate<BytesRef> newExcludeBytesRefFilter(String field, SolrParams params) {
+    final String exclude = params.getFieldParam(field, FacetParams.FACET_EXCLUDETERMS);
+    if (exclude == null) {
+      return null;
+    }
+
+    final Set<String> excludeTerms = new HashSet<>(StrUtils.splitSmart(exclude, ",", true));
+
+    return new Predicate<BytesRef>() {
+      @Override
+      public boolean test(BytesRef bytesRef) {
+        return !excludeTerms.contains(bytesRef.utf8ToString());
+      }
+    };
+  }
+
+  protected Predicate<BytesRef> newBytesRefFilter(String field, SolrParams params) {
+    final String contains = params.getFieldParam(field, FacetParams.FACET_CONTAINS);
+
+    final Predicate<BytesRef> containsFilter;
+    if (contains != null) {
+      final boolean containsIgnoreCase = params.getFieldBool(field, FacetParams.FACET_CONTAINS_IGNORE_CASE, false);
+      containsFilter = new SubstringBytesRefFilter(contains, containsIgnoreCase);
+    } else {
+      containsFilter = null;
+    }
+
+    final Predicate<BytesRef> excludeFilter = newExcludeBytesRefFilter(field, params);
+
+    if (containsFilter == null && excludeFilter == null) {
+      return null;
+    }
+
+    if (containsFilter != null && excludeFilter == null) {
+      return containsFilter;
+    } else if (containsFilter == null && excludeFilter != null) {
+      return excludeFilter;
+    }
+
+    return containsFilter.and(excludeFilter);
+  }
+
   /**
    * Term counts for use in pivot faceting that resepcts the appropriate mincount
    * @see FacetParams#FACET_PIVOT_MINCOUNT
@@ -405,8 +429,9 @@ public class SimpleFacets {
     // default to sorting if there is a limit.
     String sort = params.getFieldParam(field, FacetParams.FACET_SORT, limit>0 ? FacetParams.FACET_SORT_COUNT : FacetParams.FACET_SORT_INDEX);
     String prefix = params.getFieldParam(field, FacetParams.FACET_PREFIX);
-    String contains = params.getFieldParam(field, FacetParams.FACET_CONTAINS);
-    boolean ignoreCase = params.getFieldBool(field, FacetParams.FACET_CONTAINS_IGNORE_CASE, false);
+
+    final Predicate<BytesRef> termFilter = newBytesRefFilter(field, params);
+
     boolean exists = params.getFieldBool(field, FacetParams.FACET_EXISTS, false);
     
     NamedList<Integer> counts;
@@ -448,28 +473,30 @@ public class SimpleFacets {
     }
 
     if (params.getFieldBool(field, GroupParams.GROUP_FACET, false)) {
-      counts = getGroupedCounts(searcher, docs, field, multiToken, offset,limit, mincount, missing, sort, prefix, contains, ignoreCase);
+      counts = getGroupedCounts(searcher, docs, field, multiToken, offset,limit, mincount, missing, sort, prefix, termFilter);
     } else {
       assert appliedFacetMethod != null;
       switch (appliedFacetMethod) {
         case ENUM:
           assert TrieField.getMainValuePrefix(ft) == null;
-          counts = getFacetTermEnumCounts(searcher, docs, field, offset, limit, mincount,missing,sort,prefix, contains, ignoreCase, 
-                                          exists);
+          counts = getFacetTermEnumCounts(searcher, docs, field, offset, limit, mincount,missing,sort,prefix, termFilter, exists);
           break;
         case FCS:
           assert !multiToken;
-          if (ft.getNumericType() != null && !sf.multiValued()) {
+          if (ft.getNumberType() != null && !sf.multiValued()) {
             // force numeric faceting
             if (prefix != null && !prefix.isEmpty()) {
               throw new SolrException(ErrorCode.BAD_REQUEST, FacetParams.FACET_PREFIX + " is not supported on numeric types");
             }
-            if (contains != null && !contains.isEmpty()) {
-              throw new SolrException(ErrorCode.BAD_REQUEST, FacetParams.FACET_CONTAINS + " is not supported on numeric types");
+            if (termFilter != null) {
+              final boolean supportedOperation = (termFilter instanceof SubstringBytesRefFilter) && ((SubstringBytesRefFilter) termFilter).substring().isEmpty();
+              if (!supportedOperation) {
+                throw new SolrException(ErrorCode.BAD_REQUEST, FacetParams.FACET_CONTAINS + " is not supported on numeric types");
+              }
             }
             counts = NumericFacets.getCounts(searcher, docs, field, offset, limit, mincount, missing, sort);
           } else {
-            PerSegmentSingleValuedFaceting ps = new PerSegmentSingleValuedFaceting(searcher, docs, field, offset, limit, mincount, missing, sort, prefix, contains, ignoreCase);
+            PerSegmentSingleValuedFaceting ps = new PerSegmentSingleValuedFaceting(searcher, docs, field, offset, limit, mincount, missing, sort, prefix, termFilter);
             Executor executor = threads == 0 ? directExecutor : facetExecutor;
             ps.setNumThreads(threads);
             counts = ps.getFacetCounts(executor);
@@ -532,7 +559,7 @@ public class SimpleFacets {
             }
           break;
         case FC:
-          counts = DocValuesFacets.getCounts(searcher, docs, field, offset,limit, mincount, missing, sort, prefix, contains, ignoreCase, fdebug);
+          counts = DocValuesFacets.getCounts(searcher, docs, field, offset,limit, mincount, missing, sort, prefix, termFilter, fdebug);
           break;
         default:
           throw new AssertionError();
@@ -593,7 +620,7 @@ public class SimpleFacets {
        /* Always use filters for booleans if not DocValues only... we know the number of values is very small. */
        if (type instanceof BoolField && (field.indexed() == true || field.hasDocValues() == false)) {
          method = FacetMethod.ENUM;
-       } else if (type.getNumericType() != null && !field.multiValued()) {
+       } else if (type.getNumberType() != null && !field.multiValued()) {
         /* the per-segment approach is optimal for numeric field types since there
            are no global ords to merge and no need to create an expensive
            top-level reader */
@@ -606,7 +633,7 @@ public class SimpleFacets {
 
      /* FC without docValues does not support single valued numeric facets */
      if (method == FacetMethod.FC
-         && type.getNumericType() != null && !field.multiValued()) {
+         && type.getNumberType() != null && !field.multiValued()) {
        method = FacetMethod.FCS;
      }
 
@@ -644,8 +671,7 @@ public class SimpleFacets {
                                              boolean missing,
                                              String sort,
                                              String prefix,
-                                             String contains,
-                                             boolean ignoreCase) throws IOException {
+                                             Predicate<BytesRef> termFilter) throws IOException {
     GroupingSpecification groupingSpecification = rb.getGroupingSpec();
     final String groupField  = groupingSpecification != null ? groupingSpecification.getFields()[0] : null;
     if (groupField == null) {
@@ -675,8 +701,8 @@ public class SimpleFacets {
     List<TermGroupFacetCollector.FacetEntry> scopedEntries 
       = result.getFacetEntries(offset, limit < 0 ? Integer.MAX_VALUE : limit);
     for (TermGroupFacetCollector.FacetEntry facetEntry : scopedEntries) {
-      //:TODO:can we do contains earlier than this to make it more efficient?
-      if (contains != null && !contains(facetEntry.getValue().utf8ToString(), contains, ignoreCase)) {
+      //:TODO:can we filter earlier than this to make it more efficient?
+      if (termFilter != null && !termFilter.test(facetEntry.getValue())) {
         continue;
       }
       facetFieldType.indexedToReadable(facetEntry.getValue(), charsRef);
@@ -692,7 +718,7 @@ public class SimpleFacets {
   
   private Collector getInsanityWrapper(final String field, Collector collector) {
     SchemaField sf = searcher.getSchema().getFieldOrNull(field);
-    if (sf != null && !sf.hasDocValues() && !sf.multiValued() && sf.getType().getNumericType() != null) {
+    if (sf != null && !sf.hasDocValues() && !sf.multiValued() && sf.getType().getNumberType() != null) {
       // it's a single-valued numeric field: we must currently create insanity :(
       // there isn't a GroupedFacetCollector that works on numerics right now...
       return new FilterCollector(collector) {
@@ -852,6 +878,18 @@ public class SimpleFacets {
   }
 
   /**
+   *  Works like {@link #getFacetTermEnumCounts(SolrIndexSearcher, DocSet, String, int, int, int, boolean, String, String, Predicate, boolean)}
+   *  but takes a substring directly for the contains check rather than a {@link Predicate} instance.
+   */
+  public NamedList<Integer> getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int offset, int limit, int mincount, boolean missing,
+                                                   String sort, String prefix, String contains, boolean ignoreCase, boolean intersectsCheck)
+    throws IOException {
+
+    final Predicate<BytesRef> termFilter = new SubstringBytesRefFilter(contains, ignoreCase);
+    return getFacetTermEnumCounts(searcher, docs, field, offset, limit, mincount, missing, sort, prefix, termFilter, intersectsCheck);
+  }
+
+  /**
    * Returns a list of terms in the specified field along with the 
    * corresponding count of documents in the set that match that constraint.
    * This method uses the FilterCache to get the intersection count between <code>docs</code>
@@ -861,8 +899,8 @@ public class SimpleFacets {
    * @see FacetParams#FACET_ZEROS
    * @see FacetParams#FACET_MISSING
    */
-  public NamedList<Integer> getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int offset, int limit, int mincount, boolean missing, 
-                                      String sort, String prefix, String contains, boolean ignoreCase, boolean intersectsCheck)
+  public NamedList<Integer> getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int offset, int limit, int mincount, boolean missing,
+                                                   String sort, String prefix, Predicate<BytesRef> termFilter, boolean intersectsCheck)
     throws IOException {
     
     /* :TODO: potential optimization...
@@ -934,7 +972,7 @@ public class SimpleFacets {
         if (prefixTermBytes != null && !StringHelper.startsWith(term, prefixTermBytes))
           break;
 
-        if (contains == null || contains(term.utf8ToString(), contains, ignoreCase)) {
+        if (termFilter == null || termFilter.test(term)) {
           int df = termsEnum.docFreq();
 
           // If we are sorting, we can use df>min (rather than >=) since we
@@ -1148,4 +1186,4 @@ public class SimpleFacets {
   public ResponseBuilder getResponseBuilder() {
     return rb;
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java
index 35d04f6..a494ec4 100644
--- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java
+++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java
@@ -21,9 +21,13 @@ import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.core.SolrCore;
+import org.apache.solr.servlet.HttpSolrCall;
+import org.apache.solr.util.CommandOperation;
 import org.apache.solr.util.RTimerTree;
 
 import java.security.Principal;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -98,6 +102,26 @@ public interface SolrQueryRequest extends AutoCloseable {
   public void setJSON(Map<String,Object> json);
 
   public Principal getUserPrincipal();
+
+  default String getPath() {
+    return (String) getContext().get("path");
+  }
+
+  default Map<String, String> getPathTemplateValues() {
+    return Collections.emptyMap();
+  }
+
+  default List<CommandOperation> getCommands(boolean validateInput) {
+    return Collections.emptyList();
+  }
+
+  default String getHttpMethod() {
+    return (String) getContext().get("httpMethod");
+  }
+
+  default HttpSolrCall getHttpSolrCall() {
+    return null;
+  }
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java b/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java
index ebee2fc..4b0e4d6 100644
--- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java
+++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java
@@ -16,8 +16,13 @@
  */
 package org.apache.solr.request;
 
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.common.util.SuppressForbidden;
 import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.util.CommandOperation;
+import org.apache.solr.util.JsonSchemaValidator;
 import org.apache.solr.util.RTimerTree;
 import org.apache.solr.util.RefCounted;
 import org.apache.solr.schema.IndexSchema;
@@ -26,10 +31,16 @@ import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.core.SolrCore;
 
 import java.io.Closeable;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.security.Principal;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.HashMap;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 /**
  * Base implementation of <code>SolrQueryRequest</code> that provides some
  * convenience methods for accessing parameters, and manages an IndexSearcher
@@ -183,4 +194,28 @@ public abstract class SolrQueryRequestBase implements SolrQueryRequest, Closeabl
   public Principal getUserPrincipal() {
     return null;
   }
+
+  List<CommandOperation> parsedCommands;
+
+  public List<CommandOperation> getCommands(boolean validateInput) {
+    if (parsedCommands == null) {
+      Iterable<ContentStream> contentStreams = getContentStreams();
+      if (contentStreams == null) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No content stream");
+      for (ContentStream contentStream : contentStreams) {
+        parsedCommands = ApiBag.getCommandOperations(new InputStreamReader((InputStream) contentStream, UTF_8),
+            getValidators(), validateInput);
+      }
+
+    }
+    return CommandOperation.clone(parsedCommands);
+
+  }
+
+  protected ValidatingJsonMap getSpec() {
+    return null;
+  }
+
+  protected Map<String, JsonSchemaValidator> getValidators(){
+    return Collections.EMPTY_MAP;
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/request/SubstringBytesRefFilter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/SubstringBytesRefFilter.java b/solr/core/src/java/org/apache/solr/request/SubstringBytesRefFilter.java
new file mode 100644
index 0000000..623cb55
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/request/SubstringBytesRefFilter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+package org.apache.solr.request;
+
+import java.util.function.Predicate;
+
+import org.apache.lucene.util.BytesRef;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * An implementation of {@link Predicate} which returns true if the BytesRef contains a given substring.
+ */
+public class SubstringBytesRefFilter implements Predicate<BytesRef> {
+  final private String contains;
+  final private boolean ignoreCase;
+  
+  public SubstringBytesRefFilter(String contains, boolean ignoreCase) {
+    this.contains = contains;
+    this.ignoreCase = ignoreCase;
+  }
+
+  public String substring() {
+    return contains;
+  }
+
+  protected boolean includeString(String term) {
+    if (ignoreCase) {
+      return StringUtils.containsIgnoreCase(term, contains);
+    }
+
+    return StringUtils.contains(term, contains);
+  }
+
+  @Override
+  public boolean test(BytesRef term) {
+    return includeString(term.utf8ToString());
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/schema/EnumField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/EnumField.java b/solr/core/src/java/org/apache/solr/schema/EnumField.java
index 5723206..37cd24b 100644
--- a/solr/core/src/java/org/apache/solr/schema/EnumField.java
+++ b/solr/core/src/java/org/apache/solr/schema/EnumField.java
@@ -233,9 +233,18 @@ public class EnumField extends PrimitiveFieldType {
    * {@inheritDoc}
    */
   @Override
+  @Deprecated
   public LegacyNumericType getNumericType() {
     return LegacyNumericType.INT;
   }
+  
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public NumberType getNumberType() {
+    return NumberType.INTEGER;
+  }
 
   /**
    * {@inheritDoc}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/schema/FieldType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/FieldType.java b/solr/core/src/java/org/apache/solr/schema/FieldType.java
index 54f882f..7f44000 100644
--- a/solr/core/src/java/org/apache/solr/schema/FieldType.java
+++ b/solr/core/src/java/org/apache/solr/schema/FieldType.java
@@ -616,10 +616,21 @@ public abstract class FieldType extends FieldProperties {
 
 
   /** Return the numeric type of this field, or null if this field is not a
-   *  numeric field. */
+   *  numeric field. 
+   *  @deprecated Please use {@link FieldType#getNumberType()} instead
+   */
+  @Deprecated
   public LegacyNumericType getNumericType() {
     return null;
   }
+  
+  /**
+   * Return the numeric type of this field, or null if this field is not a
+   *  numeric field. 
+   */
+  public NumberType getNumberType() {
+    return null;
+  }
 
   /**
    * Sets the Similarity used when scoring fields of this type

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/schema/NumberType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/NumberType.java b/solr/core/src/java/org/apache/solr/schema/NumberType.java
new file mode 100644
index 0000000..2253d67
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/NumberType.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+package org.apache.solr.schema;
+
+public enum NumberType {
+  INTEGER,
+  LONG,
+  FLOAT,
+  DOUBLE,
+  DATE
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/schema/NumericFieldType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/NumericFieldType.java b/solr/core/src/java/org/apache/solr/schema/NumericFieldType.java
index 2d5412f..44066a2 100644
--- a/solr/core/src/java/org/apache/solr/schema/NumericFieldType.java
+++ b/solr/core/src/java/org/apache/solr/schema/NumericFieldType.java
@@ -28,20 +28,13 @@ import org.apache.solr.util.DateMathParser;
 
 public abstract class NumericFieldType extends PrimitiveFieldType {
 
-  public static enum NumberType {
-    INTEGER,
-    LONG,
-    FLOAT,
-    DOUBLE,
-    DATE
-  }
-
   protected NumberType type;
 
   /**
    * @return the type of this field
    */
-  final public NumberType getType() {
+  @Override
+  public NumberType getNumberType() {
     return type;
   }
 
@@ -58,7 +51,7 @@ public abstract class NumericFieldType extends PrimitiveFieldType {
       boolean minInclusive, boolean maxInclusive) {
     assert field.hasDocValues() && !field.multiValued();
     
-    switch (getType()) {
+    switch (getNumberType()) {
       case INTEGER:
         return numericDocValuesRangeQuery(field.getName(),
               min == null ? null : (long) Integer.parseInt(min),
@@ -87,18 +80,18 @@ public abstract class NumericFieldType extends PrimitiveFieldType {
     Query query;
     String fieldName = sf.getName();
 
-    Number minVal = min == null ? null : getType() == NumberType.FLOAT ? Float.parseFloat(min): Double.parseDouble(min);
-    Number maxVal = max == null ? null : getType() == NumberType.FLOAT ? Float.parseFloat(max): Double.parseDouble(max);
+    Number minVal = min == null ? null : getNumberType() == NumberType.FLOAT ? Float.parseFloat(min): Double.parseDouble(min);
+    Number maxVal = max == null ? null : getNumberType() == NumberType.FLOAT ? Float.parseFloat(max): Double.parseDouble(max);
     
     Long minBits = 
-        min == null ? null : getType() == NumberType.FLOAT ? (long) Float.floatToIntBits(minVal.floatValue()): Double.doubleToLongBits(minVal.doubleValue());
+        min == null ? null : getNumberType() == NumberType.FLOAT ? (long) Float.floatToIntBits(minVal.floatValue()): Double.doubleToLongBits(minVal.doubleValue());
     Long maxBits = 
-        max == null ? null : getType() == NumberType.FLOAT ? (long) Float.floatToIntBits(maxVal.floatValue()): Double.doubleToLongBits(maxVal.doubleValue());
+        max == null ? null : getNumberType() == NumberType.FLOAT ? (long) Float.floatToIntBits(maxVal.floatValue()): Double.doubleToLongBits(maxVal.doubleValue());
     
-    long negativeInfinityBits = getType() == NumberType.FLOAT ? FLOAT_NEGATIVE_INFINITY_BITS : DOUBLE_NEGATIVE_INFINITY_BITS;
-    long positiveInfinityBits = getType() == NumberType.FLOAT ? FLOAT_POSITIVE_INFINITY_BITS : DOUBLE_POSITIVE_INFINITY_BITS;
-    long minusZeroBits = getType() == NumberType.FLOAT ? FLOAT_MINUS_ZERO_BITS : DOUBLE_MINUS_ZERO_BITS;
-    long zeroBits = getType() == NumberType.FLOAT ? FLOAT_ZERO_BITS : DOUBLE_ZERO_BITS;
+    long negativeInfinityBits = getNumberType() == NumberType.FLOAT ? FLOAT_NEGATIVE_INFINITY_BITS : DOUBLE_NEGATIVE_INFINITY_BITS;
+    long positiveInfinityBits = getNumberType() == NumberType.FLOAT ? FLOAT_POSITIVE_INFINITY_BITS : DOUBLE_POSITIVE_INFINITY_BITS;
+    long minusZeroBits = getNumberType() == NumberType.FLOAT ? FLOAT_MINUS_ZERO_BITS : DOUBLE_MINUS_ZERO_BITS;
+    long zeroBits = getNumberType() == NumberType.FLOAT ? FLOAT_ZERO_BITS : DOUBLE_ZERO_BITS;
     
     // If min is negative (or -0d) and max is positive (or +0d), then issue a FunctionRangeQuery
     if ((minVal == null || minVal.doubleValue() < 0d || minBits == minusZeroBits) && 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/schema/SchemaManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/SchemaManager.java b/solr/core/src/java/org/apache/solr/schema/SchemaManager.java
index 8c3b5f0..7092c09 100644
--- a/solr/core/src/java/org/apache/solr/schema/SchemaManager.java
+++ b/solr/core/src/java/org/apache/solr/schema/SchemaManager.java
@@ -18,7 +18,6 @@ package org.apache.solr.schema;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.Reader;
 import java.io.StringWriter;
 import java.lang.invoke.MethodHandles;
 import java.nio.charset.StandardCharsets;
@@ -71,18 +70,10 @@ public class SchemaManager {
   /**
    * Take in a JSON command set and execute them. It tries to capture as many errors
    * as possible instead of failing at the first error it encounters
-   * @param reader The input as a Reader
    * @return List of errors. If the List is empty then the operation was successful.
    */
-  public List performOperations(Reader reader) throws Exception {
-    List<CommandOperation> ops;
-    try {
-      ops = CommandOperation.parse(reader);
-    } catch (Exception e) {
-      String msg = "Error parsing schema operations ";
-      log.warn(msg, e);
-      return Collections.singletonList(singletonMap(CommandOperation.ERR_MSGS, msg + ":" + e.getMessage()));
-    }
+  public List performOperations() throws Exception {
+    List<CommandOperation> ops = req.getCommands(false);
     List errs = CommandOperation.captureErrors(ops);
     if (!errs.isEmpty()) return errs;
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java b/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
index f6bb782..b4b3d2b 100644
--- a/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
+++ b/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
@@ -80,14 +80,20 @@ public class SpatialPointVectorFieldType extends AbstractSpatialFieldType<PointV
   }
 
   @Override
+  @Deprecated
   public LegacyNumericType getNumericType() {
     return LegacyNumericType.DOUBLE;
   }
+  
+  @Override
+  public NumberType getNumberType() {
+    return NumberType.DOUBLE;
+  }
 
   @Override
   protected PointVectorStrategy newSpatialStrategy(String fieldName) {
     // TODO update to how BBoxField does things
-    if (this.getNumericType() != null) {
+    if (this.getNumberType() != null) {
       // create strategy based on legacy numerics
       // todo remove in 7.0
       LegacyFieldType fieldType = new LegacyFieldType(PointVectorStrategy.LEGACY_FIELDTYPE);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/BitDocSet.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/BitDocSet.java b/solr/core/src/java/org/apache/solr/search/BitDocSet.java
index 317e976..a3141a7 100644
--- a/solr/core/src/java/org/apache/solr/search/BitDocSet.java
+++ b/solr/core/src/java/org/apache/solr/search/BitDocSet.java
@@ -261,7 +261,7 @@ public class BitDocSet extends DocSetBase {
   }
   
   @Override
-  protected BitDocSet clone() {
+  public BitDocSet clone() {
     return new BitDocSet(bits.clone(), size);
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/DocSet.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/DocSet.java b/solr/core/src/java/org/apache/solr/search/DocSet.java
index dd8f024..172cce7 100644
--- a/solr/core/src/java/org/apache/solr/search/DocSet.java
+++ b/solr/core/src/java/org/apache/solr/search/DocSet.java
@@ -31,7 +31,7 @@ import org.apache.solr.common.SolrException;
  *
  * @since solr 0.9
  */
-public interface DocSet extends Closeable, Accountable /* extends Collection<Integer> */ {
+public interface DocSet extends Closeable, Accountable, Cloneable /* extends Collection<Integer> */ {
   
   /**
    * Adds the specified document if it is not currently in the DocSet
@@ -131,5 +131,7 @@ public interface DocSet extends Closeable, Accountable /* extends Collection<Int
    */
   public void addAllTo(DocSet target);
 
+  public DocSet clone();
+
   public static DocSet EMPTY = new SortedIntDocSet(new int[0], 0);
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/DocSetBase.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/DocSetBase.java b/solr/core/src/java/org/apache/solr/search/DocSetBase.java
index a35c19f..465c208 100644
--- a/solr/core/src/java/org/apache/solr/search/DocSetBase.java
+++ b/solr/core/src/java/org/apache/solr/search/DocSetBase.java
@@ -23,8 +23,8 @@ import org.apache.lucene.index.LeafReader;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.search.DocIdSet;
 import org.apache.lucene.search.DocIdSetIterator;
-import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.BitDocIdSet;
+import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.FixedBitSet;
 import org.apache.solr.common.SolrException;
 
@@ -63,8 +63,21 @@ abstract class DocSetBase implements DocSet {
       // don't compare matches
     }
 
+    FixedBitSet bs1 = this.getBits();
+    FixedBitSet bs2 = toBitSet(other);
+
+// resize both BitSets to make sure they have the same amount of zero padding
+
+    int maxNumBits = bs1.length() > bs2.length() ? bs1.length() : bs2.length();
+    bs1 = FixedBitSet.ensureCapacity(bs1, maxNumBits);
+    bs2 = FixedBitSet.ensureCapacity(bs2, maxNumBits);
+
     // if (this.size() != other.size()) return false;
-    return this.getBits().equals(toBitSet(other));
+    return bs1.equals(bs2);
+  }
+
+  public DocSet clone() {
+    throw new RuntimeException(new CloneNotSupportedException());
   }
 
   /**
@@ -90,7 +103,7 @@ abstract class DocSetBase implements DocSet {
    * implementation.
    */
   protected FixedBitSet getBits() {
-    FixedBitSet bits = new FixedBitSet(64);
+    FixedBitSet bits = new FixedBitSet(size());
     for (DocIterator iter = iterator(); iter.hasNext();) {
       int nextDoc = iter.nextDoc();
       bits = FixedBitSet.ensureCapacity(bits, nextDoc);
@@ -193,7 +206,7 @@ abstract class DocSetBase implements DocSet {
 
               @Override
               public int nextDoc() {
-                pos = bs.nextSetBit(pos+1);
+                pos = bs.nextSetBit(pos+1);  // TODO: this is buggy if getBits() returns a bitset that does not have a capacity of maxDoc
                 return adjustedDoc = pos<max ? pos-base : NO_MORE_DOCS;
               }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/DocSetCollector.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/DocSetCollector.java b/solr/core/src/java/org/apache/solr/search/DocSetCollector.java
index 25b12c5..3b41c9a 100644
--- a/solr/core/src/java/org/apache/solr/search/DocSetCollector.java
+++ b/solr/core/src/java/org/apache/solr/search/DocSetCollector.java
@@ -72,10 +72,17 @@ public class DocSetCollector extends SimpleCollector {
     pos++;
   }
 
+  /** The number of documents that have been collected */
+  public int size() {
+    return pos;
+  }
+
   public DocSet getDocSet() {
     if (pos<=scratch.size()) {
       // assumes docs were collected in sorted order!
       return new SortedIntDocSet(scratch.toArray(), pos);
+//    } else if (pos == maxDoc) {
+//      return new MatchAllDocSet(maxDoc);  // a bunch of code currently relies on BitDocSet (either explicitly, or implicitly for performance)
     } else {
       // set the bits for ids that were collected in the array
       scratch.copyTo(bits);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/DocSetUtil.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/DocSetUtil.java b/solr/core/src/java/org/apache/solr/search/DocSetUtil.java
index b7545e6..a7c9bef 100644
--- a/solr/core/src/java/org/apache/solr/search/DocSetUtil.java
+++ b/solr/core/src/java/org/apache/solr/search/DocSetUtil.java
@@ -39,6 +39,7 @@ import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.FixedBitSet;
+import org.apache.solr.common.SolrException;
 
 /** @lucene.experimental */
 public class DocSetUtil {
@@ -71,6 +72,51 @@ public class DocSetUtil {
     }
   }
 
+  /**
+   * This variant of getDocSet will attempt to do some deduplication
+   * on certain DocSets such as DocSets that match numDocs.  This means it can return
+   * a cached version of the set, and the returned set should not be modified.
+   * @lucene.experimental
+   */
+  public static DocSet getDocSet(DocSetCollector collector, SolrIndexSearcher searcher) {
+    if (collector.size() == searcher.numDocs()) {
+      if (!searcher.isLiveDocsInstantiated()) {
+        searcher.setLiveDocs( collector.getDocSet() );
+      }
+      try {
+        return searcher.getLiveDocs();
+      } catch (IOException e) {
+        // should be impossible... liveDocs should exist, so no IO should be necessary
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+      }
+    }
+
+    return collector.getDocSet();
+  }
+
+  /**
+   * This variant of getDocSet maps all sets with size numDocs to searcher.getLiveDocs.
+   * The returned set should not be modified.
+   * @lucene.experimental
+   */
+  public static DocSet getDocSet(DocSet docs, SolrIndexSearcher searcher) {
+    if (docs.size() == searcher.numDocs()) {
+      if (!searcher.isLiveDocsInstantiated()) {
+        searcher.setLiveDocs( docs );
+      }
+      try {
+        // if this docset has the same cardinality as liveDocs, return liveDocs instead
+        // so this set will be short lived garbage.
+        return searcher.getLiveDocs();
+      } catch (IOException e) {
+        // should be impossible... liveDocs should exist, so no IO should be necessary
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+      }
+    }
+
+    return docs;
+  }
+
   // implementers of DocSetProducer should not call this with themselves or it will result in an infinite loop
   public static DocSet createDocSet(SolrIndexSearcher searcher, Query query, DocSet filter) throws IOException {
 
@@ -105,7 +151,7 @@ public class DocSetUtil {
     // but we should not catch it here, as we don't know how this DocSet will be used (it could be negated before use) or cached.
     searcher.search(query, collector);
 
-    return collector.getDocSet();
+    return getDocSet(collector, searcher);
   }
 
   public static DocSet createDocSet(SolrIndexSearcher searcher, Term term) throws IOException {
@@ -113,7 +159,6 @@ public class DocSetUtil {
     int maxDoc = searcher.getIndexReader().maxDoc();
     int smallSetSize = smallSetSize(maxDoc);
 
-
     String field = term.field();
     BytesRef termVal = term.bytes();
 
@@ -135,15 +180,16 @@ public class DocSetUtil {
       }
     }
 
+    DocSet answer = null;
     if (maxCount == 0) {
-      return DocSet.EMPTY;
-    }
-
-    if (maxCount <= smallSetSize) {
-      return createSmallSet(leaves, postList, maxCount, firstReader);
+      answer = DocSet.EMPTY;
+    } else if (maxCount <= smallSetSize) {
+      answer = createSmallSet(leaves, postList, maxCount, firstReader);
+    } else {
+      answer = createBigSet(leaves, postList, maxDoc, firstReader);
     }
 
-    return createBigSet(leaves, postList, maxDoc, firstReader);
+    return DocSetUtil.getDocSet( answer, searcher );
   }
 
   private static DocSet createSmallSet(List<LeafReaderContext> leaves, PostingsEnum[] postList, int maxPossible, int firstReader) throws IOException {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/DocSlice.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/DocSlice.java b/solr/core/src/java/org/apache/solr/search/DocSlice.java
index fd9553a..98de307 100644
--- a/solr/core/src/java/org/apache/solr/search/DocSlice.java
+++ b/solr/core/src/java/org/apache/solr/search/DocSlice.java
@@ -165,12 +165,8 @@ public class DocSlice extends DocSetBase implements DocList {
   }
 
   @Override
-  protected DocSlice clone() {
-    try {
-      // DocSlice is not currently mutable
-      DocSlice slice = (DocSlice) super.clone();
-    } catch (CloneNotSupportedException e) {}
-    return null;
+  public DocSlice clone() {
+    return (DocSlice) super.clone();
   }
 
   /** WARNING: this can over-estimate real memory use since backing arrays are shared with other DocSlice instances */

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/HashDocSet.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/HashDocSet.java b/solr/core/src/java/org/apache/solr/search/HashDocSet.java
index ee40f09..44aba6f 100644
--- a/solr/core/src/java/org/apache/solr/search/HashDocSet.java
+++ b/solr/core/src/java/org/apache/solr/search/HashDocSet.java
@@ -290,7 +290,7 @@ public final class HashDocSet extends DocSetBase {
   }
 
   @Override
-  protected HashDocSet clone() {
+  public HashDocSet clone() {
     return new HashDocSet(this);
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/SolrCoreParser.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/SolrCoreParser.java b/solr/core/src/java/org/apache/solr/search/SolrCoreParser.java
index 3f6596b..0a2cf58 100755
--- a/solr/core/src/java/org/apache/solr/search/SolrCoreParser.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrCoreParser.java
@@ -16,15 +16,20 @@
  */
 package org.apache.solr.search;
 
+import java.lang.invoke.MethodHandles;
 import java.util.Map;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.queryparser.xml.CoreParser;
 import org.apache.lucene.queryparser.xml.QueryBuilder;
+import org.apache.lucene.queryparser.xml.builders.SpanQueryBuilder;
+import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Assembles a QueryBuilder which uses Query objects from Solr's <code>search</code> module
@@ -32,6 +37,8 @@ import org.apache.solr.util.plugin.NamedListInitializedPlugin;
  */
 public class SolrCoreParser extends CoreParser implements NamedListInitializedPlugin {
 
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
   protected final SolrQueryRequest req;
 
   public SolrCoreParser(String defaultField, Analyzer analyzer,
@@ -58,14 +65,35 @@ public class SolrCoreParser extends CoreParser implements NamedListInitializedPl
       final String queryName = entry.getKey();
       final String queryBuilderClassName = (String)entry.getValue();
 
-      final SolrQueryBuilder queryBuilder = loader.newInstance(
-          queryBuilderClassName,
-          SolrQueryBuilder.class,
-          null,
-          new Class[] {String.class, Analyzer.class, SolrQueryRequest.class, QueryBuilder.class},
-          new Object[] {defaultField, analyzer, req, this});
+      try {
+        final SolrSpanQueryBuilder spanQueryBuilder = loader.newInstance(
+            queryBuilderClassName,
+            SolrSpanQueryBuilder.class,
+            null,
+            new Class[] {String.class, Analyzer.class, SolrQueryRequest.class, SpanQueryBuilder.class},
+            new Object[] {defaultField, analyzer, req, this});
+
+        this.addSpanQueryBuilder(queryName, spanQueryBuilder);
+      } catch (Exception outerException) {
+        try {
+        final SolrQueryBuilder queryBuilder = loader.newInstance(
+            queryBuilderClassName,
+            SolrQueryBuilder.class,
+            null,
+            new Class[] {String.class, Analyzer.class, SolrQueryRequest.class, QueryBuilder.class},
+            new Object[] {defaultField, analyzer, req, this});
 
-      this.queryFactory.addBuilder(queryName, queryBuilder);
+        this.addQueryBuilder(queryName, queryBuilder);
+        } catch (Exception innerException) {
+          log.error("Class {} not found or not suitable: {} {}",
+              queryBuilderClassName, outerException, innerException);
+          throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, "Cannot find suitable "
+                  + SolrSpanQueryBuilder.class.getCanonicalName() + " or "
+                  + SolrQueryBuilder.class.getCanonicalName() + " class: "
+                  + queryBuilderClassName + " in "
+                  + loader);
+        }
+      }
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
index 75d0998..820e1ba 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
@@ -34,7 +34,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
@@ -42,8 +41,28 @@ import java.util.concurrent.atomic.AtomicReference;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.DocumentStoredFieldVisitor;
 import org.apache.lucene.document.LazyDocument;
-import org.apache.lucene.index.*;
+import org.apache.lucene.index.BinaryDocValues;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.ExitableDirectoryReader;
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.index.FieldInfos;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.MultiPostingsEnum;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.PostingsEnum;
+import org.apache.lucene.index.ReaderUtil;
+import org.apache.lucene.index.SortedDocValues;
+import org.apache.lucene.index.SortedSetDocValues;
+import org.apache.lucene.index.StoredFieldVisitor;
 import org.apache.lucene.index.StoredFieldVisitor.Status;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermContext;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanClause.Occur;
 import org.apache.lucene.search.BooleanQuery;
@@ -98,8 +117,7 @@ import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.schema.BoolField;
 import org.apache.solr.schema.EnumField;
 import org.apache.solr.schema.IndexSchema;
-import org.apache.solr.schema.NumericFieldType;
-import org.apache.solr.schema.PointField;
+import org.apache.solr.schema.NumberType;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.TrieDateField;
 import org.apache.solr.schema.TrieDoubleField;
@@ -429,6 +447,10 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
     return reader.maxDoc();
   }
 
+  public final int numDocs() {
+    return reader.numDocs();
+  }
+
   public final int docFreq(Term term) throws IOException {
     return reader.docFreq(term);
   }
@@ -828,7 +850,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
             }
             Object newVal = val;
             if (schemaField.getType().isPointField()) {
-              NumericFieldType.NumberType type = ((PointField)schemaField.getType()).getType(); 
+              NumberType type = schemaField.getType().getNumberType(); 
               switch (type) {
                 case INTEGER:
                   newVal = val.intValue();
@@ -1063,19 +1085,24 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
     getDocSet(query);
   }
 
-  public BitDocSet getDocSetBits(Query q) throws IOException {
-    DocSet answer = getDocSet(q);
-    if (answer instanceof BitDocSet) {
-      return (BitDocSet) answer;
-    }
-
+  private BitDocSet makeBitDocSet(DocSet answer) {
+    // TODO: this should be implemented in DocSet, most likely with a getBits method that takes a maxDoc argument
+    // or make DocSet instances remember maxDoc
     FixedBitSet bs = new FixedBitSet(maxDoc());
     DocIterator iter = answer.iterator();
     while (iter.hasNext()) {
       bs.set(iter.nextDoc());
     }
 
-    BitDocSet answerBits = new BitDocSet(bs, answer.size());
+    return new BitDocSet(bs, answer.size());
+  }
+
+  public BitDocSet getDocSetBits(Query q) throws IOException {
+    DocSet answer = getDocSet(q);
+    if (answer instanceof BitDocSet) {
+      return (BitDocSet) answer;
+    }
+    BitDocSet answerBits = makeBitDocSet(answer);
     if (filterCache != null) {
       filterCache.put(q, answerBits);
     }
@@ -1138,14 +1165,34 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
   }
 
   private static Query matchAllDocsQuery = new MatchAllDocsQuery();
-  private BitDocSet liveDocs;
+  private volatile BitDocSet liveDocs;
 
+  /** @lucene.internal the type of DocSet returned may change in the future */
   public BitDocSet getLiveDocs() throws IOException {
-    // going through the filter cache will provide thread safety here
-    if (liveDocs == null) {
-      liveDocs = getDocSetBits(matchAllDocsQuery);
+    // Going through the filter cache will provide thread safety here if we only had getLiveDocs,
+    // but the addition of setLiveDocs means we needed to add volatile to "liveDocs".
+    BitDocSet docs = liveDocs;
+    if (docs == null) {
+      liveDocs = docs = getDocSetBits(matchAllDocsQuery);
+    }
+    assert docs.size() == numDocs();
+    return docs;
+  }
+
+  /** @lucene.internal */
+  public boolean isLiveDocsInstantiated() {
+    return liveDocs != null;
+  }
+
+  /** @lucene.internal */
+  public void setLiveDocs(DocSet docs) {
+    // a few places currently expect BitDocSet
+    assert docs.size() == numDocs();
+    if (docs instanceof BitDocSet) {
+      this.liveDocs = (BitDocSet)docs;
+    } else {
+      this.liveDocs = makeBitDocSet(docs);
     }
-    return liveDocs;
   }
 
   public static class ProcessedFilter {
@@ -1178,8 +1225,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
       ((DelegatingCollector) collector).finish();
     }
 
-    DocSet docSet = setCollector.getDocSet();
-    return docSet;
+    return DocSetUtil.getDocSet(setCollector, this);
   }
 
   /**
@@ -1251,7 +1297,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
       ((DelegatingCollector) collector).finish();
     }
 
-    return setCollector.getDocSet();
+    return DocSetUtil.getDocSet(setCollector, this);
   }
 
   public ProcessedFilter getProcessedFilter(DocSet setFilter, List<Query> queries) throws IOException {
@@ -1959,7 +2005,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
 
       buildAndRunCollectorChain(qr, query, collector, cmd, pf.postFilter);
 
-      set = setCollector.getDocSet();
+      set = DocSetUtil.getDocSet(setCollector, this);
 
       nDocsReturned = 0;
       ids = new int[nDocsReturned];
@@ -1976,7 +2022,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
 
       buildAndRunCollectorChain(qr, query, collector, cmd, pf.postFilter);
 
-      set = setCollector.getDocSet();
+      set = DocSetUtil.getDocSet(setCollector, this);
 
       totalHits = topCollector.getTotalHits();
       assert (totalHits == set.size());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/SolrSpanQueryBuilder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/SolrSpanQueryBuilder.java b/solr/core/src/java/org/apache/solr/search/SolrSpanQueryBuilder.java
new file mode 100644
index 0000000..2dea85c
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/search/SolrSpanQueryBuilder.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+package org.apache.solr.search;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.queryparser.xml.builders.SpanQueryBuilder;
+import org.apache.solr.request.SolrQueryRequest;
+
+public abstract class SolrSpanQueryBuilder extends SolrQueryBuilder implements SpanQueryBuilder {
+
+  protected final SpanQueryBuilder spanFactory;
+
+  public SolrSpanQueryBuilder(String defaultField, Analyzer analyzer,
+      SolrQueryRequest req, SpanQueryBuilder spanFactory) {
+    super(defaultField, analyzer, req, spanFactory);
+    this.spanFactory = spanFactory;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/SortedIntDocSet.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/SortedIntDocSet.java b/solr/core/src/java/org/apache/solr/search/SortedIntDocSet.java
index ba60707..aa96d8c 100644
--- a/solr/core/src/java/org/apache/solr/search/SortedIntDocSet.java
+++ b/solr/core/src/java/org/apache/solr/search/SortedIntDocSet.java
@@ -791,7 +791,7 @@ public class SortedIntDocSet extends DocSetBase {
   }
 
   @Override
-  protected SortedIntDocSet clone() {
+  public SortedIntDocSet clone() {
     return new SortedIntDocSet(docs.clone());
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/facet/FacetField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetField.java b/solr/core/src/java/org/apache/solr/search/facet/FacetField.java
index c2cf0c2..e8e624c 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetField.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetField.java
@@ -19,9 +19,9 @@ package org.apache.solr.search.facet;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.lucene.legacy.LegacyNumericType;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.NumberType;
 import org.apache.solr.schema.SchemaField;
 
 // Any type of facet request that generates a variable number of buckets
@@ -98,7 +98,7 @@ public class FacetField extends FacetRequestSorted {
     FieldType ft = sf.getType();
     boolean multiToken = sf.multiValued() || ft.multiValuedFieldCache();
 
-    LegacyNumericType ntype = ft.getNumericType();
+    NumberType ntype = ft.getNumberType();
     // ensure we can support the requested options for numeric faceting:
     if (ntype != null) {
       if (prefix != null) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByEnumTermsStream.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByEnumTermsStream.java b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByEnumTermsStream.java
index 2ab4713..2feff15 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByEnumTermsStream.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByEnumTermsStream.java
@@ -149,7 +149,7 @@ class FacetFieldProcessorByEnumTermsStream extends FacetFieldProcessor implement
     if (freq.prefix != null) {
       String indexedPrefix = sf.getType().toInternal(freq.prefix);
       startTermBytes = new BytesRef(indexedPrefix);
-    } else if (sf.getType().getNumericType() != null) {
+    } else if (sf.getType().getNumberType() != null) {
       String triePrefix = TrieField.getMainValuePrefix(sf.getType());
       if (triePrefix != null) {
         startTermBytes = new BytesRef(triePrefix);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByHashDV.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByHashDV.java b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByHashDV.java
index cf3971c..71ff690 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByHashDV.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByHashDV.java
@@ -215,7 +215,7 @@ class FacetFieldProcessorByHashDV extends FacetFieldProcessor {
 
   private SimpleOrderedMap<Object> calcFacets() throws IOException {
 
-    if (sf.getType().getNumericType() != null) {
+    if (sf.getType().getNumberType() != null) {
       calc = FacetRangeProcessor.getNumericCalc(sf);
     } else {
       calc = new TermOrdCalc(); // kind of a hack

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java b/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
index 900bbf7..276af5f 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
@@ -119,9 +119,7 @@ class FacetRangeProcessor extends FacetProcessor<FacetRange> {
     final FieldType ft = sf.getType();
 
     if (ft instanceof TrieField) {
-      final TrieField trie = (TrieField)ft;
-
-      switch (trie.getType()) {
+      switch (ft.getNumberType()) {
         case FLOAT:
           calc = new FloatCalc(sf);
           break;
@@ -143,9 +141,8 @@ class FacetRangeProcessor extends FacetProcessor<FacetRange> {
                   "Expected numeric field type :" + sf);
       }
     } else if (ft instanceof PointField) {
-      final PointField pfield = (PointField)ft;
-
-      switch (pfield.getType()) {
+      // TODO, this is the same in Trie and Point now
+      switch (ft.getNumberType()) {
         case FLOAT:
           calc = new FloatCalc(sf);
           break;
@@ -179,9 +176,7 @@ class FacetRangeProcessor extends FacetProcessor<FacetRange> {
     final FieldType ft = sf.getType();
 
     if (ft instanceof TrieField) {
-      final TrieField trie = (TrieField)ft;
-
-      switch (trie.getType()) {
+      switch (ft.getNumberType()) {
         case FLOAT:
           calc = new FloatCalc(sf);
           break;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/facet/HLLAgg.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/HLLAgg.java b/solr/core/src/java/org/apache/solr/search/facet/HLLAgg.java
index 72cdd27..28c95e8 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/HLLAgg.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/HLLAgg.java
@@ -57,7 +57,7 @@ public class HLLAgg extends StrAggValueSource {
         return new UniqueMultivaluedSlotAcc(fcontext, getArg(), numSlots, fcontext.isShard() ? factory : null);
       }
     } else {
-      if (sf.getType().getNumericType() != null) {
+      if (sf.getType().getNumberType() != null) {
         // always use hll here since we don't know how many values there are?
         return new NumericAcc(fcontext, getArg(), numSlots);
       } else {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/facet/UniqueAgg.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/UniqueAgg.java b/solr/core/src/java/org/apache/solr/search/facet/UniqueAgg.java
index a18eb0f..5e1e97c 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/UniqueAgg.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/UniqueAgg.java
@@ -48,7 +48,7 @@ public class UniqueAgg extends StrAggValueSource {
         return new UniqueMultivaluedSlotAcc(fcontext, getArg(), numSlots, null);
       }
     } else {
-      if (sf.getType().getNumericType() != null) {
+      if (sf.getType().getNumberType() != null) {
         return new NumericAcc(fcontext, getArg(), numSlots);
       } else {
         return new UniqueSinglevaluedSlotAcc(fcontext, getArg(), numSlots, null);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/function/OrdFieldSource.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/function/OrdFieldSource.java b/solr/core/src/java/org/apache/solr/search/function/OrdFieldSource.java
index 756a1a6..4637df6 100644
--- a/solr/core/src/java/org/apache/solr/search/function/OrdFieldSource.java
+++ b/solr/core/src/java/org/apache/solr/search/function/OrdFieldSource.java
@@ -77,7 +77,7 @@ public class OrdFieldSource extends ValueSource {
     if (o instanceof SolrIndexSearcher) {
       SolrIndexSearcher is = (SolrIndexSearcher) o;
       SchemaField sf = is.getSchema().getFieldOrNull(field);
-      if (sf != null && sf.hasDocValues() == false && sf.multiValued() == false && sf.getType().getNumericType() != null) {
+      if (sf != null && sf.hasDocValues() == false && sf.multiValued() == false && sf.getType().getNumberType() != null) {
         // it's a single-valued numeric field: we must currently create insanity :(
         List<LeafReaderContext> leaves = is.getIndexReader().leaves();
         LeafReader insaneLeaves[] = new LeafReader[leaves.size()];

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/function/ReverseOrdFieldSource.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/function/ReverseOrdFieldSource.java b/solr/core/src/java/org/apache/solr/search/function/ReverseOrdFieldSource.java
index 3ed13f0..f379913 100644
--- a/solr/core/src/java/org/apache/solr/search/function/ReverseOrdFieldSource.java
+++ b/solr/core/src/java/org/apache/solr/search/function/ReverseOrdFieldSource.java
@@ -77,7 +77,7 @@ public class ReverseOrdFieldSource extends ValueSource {
     if (o instanceof SolrIndexSearcher) {
       SolrIndexSearcher is = (SolrIndexSearcher) o;
       SchemaField sf = is.getSchema().getFieldOrNull(field);
-      if (sf != null && sf.hasDocValues() == false && sf.multiValued() == false && sf.getType().getNumericType() != null) {
+      if (sf != null && sf.hasDocValues() == false && sf.multiValued() == false && sf.getType().getNumberType() != null) {
         // it's a single-valued numeric field: we must currently create insanity :(
         List<LeafReaderContext> leaves = is.getIndexReader().leaves();
         LeafReader insaneLeaves[] = new LeafReader[leaves.size()];

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/grouping/CommandHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/CommandHandler.java b/solr/core/src/java/org/apache/solr/search/grouping/CommandHandler.java
index 74c2b70..2dd2291 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/CommandHandler.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/CommandHandler.java
@@ -40,6 +40,7 @@ import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.BitDocSet;
 import org.apache.solr.search.DocSet;
 import org.apache.solr.search.DocSetCollector;
+import org.apache.solr.search.DocSetUtil;
 import org.apache.solr.search.QueryCommand;
 import org.apache.solr.search.QueryResult;
 import org.apache.solr.search.QueryUtils;
@@ -171,7 +172,7 @@ public class CommandHandler {
     FieldType fieldType = sf.getType();
     
     final AllGroupHeadsCollector allGroupHeadsCollector;
-    if (fieldType.getNumericType() != null) {
+    if (fieldType.getNumberType() != null) {
       ValueSource vs = fieldType.getValueSource(sf, null);
       allGroupHeadsCollector = new FunctionAllGroupHeadsCollector(vs, new HashMap(), firstCommand.getSortWithinGroup());
     } else {
@@ -193,7 +194,7 @@ public class CommandHandler {
     List<Collector> allCollectors = new ArrayList<>(collectors);
     allCollectors.add(docSetCollector);
     searchWithTimeLimiter(query, filter, MultiCollector.wrap(allCollectors));
-    return docSetCollector.getDocSet();
+    return DocSetUtil.getDocSet( docSetCollector, searcher );
   }
 
   @SuppressWarnings("unchecked")

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/GroupConverter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/GroupConverter.java b/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/GroupConverter.java
index a9849d5..0a21a62 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/GroupConverter.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/GroupConverter.java
@@ -33,8 +33,8 @@ import org.apache.lucene.util.mutable.MutableValueFloat;
 import org.apache.lucene.util.mutable.MutableValueInt;
 import org.apache.lucene.util.mutable.MutableValueLong;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.NumberType;
 import org.apache.solr.schema.SchemaField;
-import org.apache.solr.schema.TrieField;
 
 /** 
  * this is a transition class: for numeric types we use function-based distributed grouping,
@@ -70,7 +70,7 @@ class GroupConverter {
     for (SearchGroup<BytesRef> original : values) {
       SearchGroup<MutableValue> converted = new SearchGroup<MutableValue>();
       converted.sortValues = original.sortValues; // ?
-      TrieField.NumberType type = ((TrieField)fieldType).getType();
+      NumberType type = fieldType.getNumberType();
       final MutableValue v;
       switch (type) {
         case INTEGER:


[06/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
new file mode 100644
index 0000000..1af5d93
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.TestSolrConfigHandler;
+import org.apache.solr.util.RESTfulServerProvider;
+import org.apache.solr.util.RestTestHarness;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class V2ApiIntegrationTest extends SolrCloudTestCase {
+  private List<RestTestHarness> restTestHarnesses = new ArrayList<>();
+
+  private static String COLL_NAME = "collection1";
+
+  private void setupHarnesses() {
+    for (final JettySolrRunner jettySolrRunner : cluster.getJettySolrRunners()) {
+      RestTestHarness harness = new RestTestHarness(new ServerProvider(jettySolrRunner));
+      restTestHarnesses.add(harness);
+    }
+  }
+  static class ServerProvider implements RESTfulServerProvider {
+
+    final JettySolrRunner jettySolrRunner;
+    String baseurl;
+
+    ServerProvider(JettySolrRunner jettySolrRunner) {
+      this.jettySolrRunner = jettySolrRunner;
+      baseurl = jettySolrRunner.getBaseUrl().toString() + "/" + COLL_NAME;
+    }
+
+    @Override
+    public String getBaseURL() {
+      return baseurl;
+    }
+
+  }
+
+  @BeforeClass
+  public static void createCluster() throws Exception {
+    System.setProperty("managed.schema.mutable", "true");
+    configureCluster(2)
+        .addConfig("conf1", TEST_PATH().resolve("configsets").resolve("cloud-managed").resolve("conf"))
+        .configure();
+    CollectionAdminRequest.createCollection(COLL_NAME, "conf1", 1, 2)
+        .process(cluster.getSolrClient());
+  }
+
+  @Test
+  public void test() throws Exception {
+    try {
+      setupHarnesses();
+      testApis();
+
+    } finally {
+      for (RestTestHarness r : restTestHarnesses) {
+        r.close();
+      }
+    }
+  }
+
+  private void testApis() throws Exception {
+    RestTestHarness restHarness = restTestHarnesses.get(0);
+    ServerProvider serverProvider = (ServerProvider) restHarness.getServerProvider();
+    serverProvider.baseurl = serverProvider.jettySolrRunner.getBaseUrl()+"/v2/c/"+ COLL_NAME;
+    Map result = TestSolrConfigHandler.getRespMap("/get/_introspect", restHarness);
+    assertEquals("/c/collection1/get", Utils.getObjectByPath(result, true, "/spec[0]/url/paths[0]"));
+    serverProvider.baseurl = serverProvider.jettySolrRunner.getBaseUrl()+"/v2/collections/"+ COLL_NAME;
+    result = TestSolrConfigHandler.getRespMap("/get/_introspect", restHarness);
+    assertEquals("/collections/collection1/get", Utils.getObjectByPath(result, true, "/spec[0]/url/paths[0]"));
+
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java
index 21634b7..84e2382 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/MBeansHandlerTest.java
@@ -18,6 +18,8 @@ package org.apache.solr.handler.admin;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.params.CommonParams;
@@ -59,7 +61,17 @@ public class MBeansHandlerTest extends SolrTestCaseJ4 {
     NamedList stats = (NamedList)diff.get("ADMIN").get("/admin/mbeans").get("stats");
     
     //System.out.println("stats:"+stats);
-    assertEquals("Was: 1, Now: 2, Delta: 1", stats.get("requests"));
+    Pattern p = Pattern.compile("Was: (?<was>[0-9]+), Now: (?<now>[0-9]+), Delta: (?<delta>[0-9]+)");
+    String response = stats.get("requests").toString();
+    Matcher m = p.matcher(response);
+    if (!m.matches()) {
+      fail("Response did not match pattern: " + response);
+    }
+
+    assertEquals(1, Integer.parseInt(m.group("delta")));
+    int was = Integer.parseInt(m.group("was"));
+    int now = Integer.parseInt(m.group("now"));
+    assertEquals(1, now - was);
 
     xml = h.query(req(
         CommonParams.QT,"/admin/mbeans",

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
new file mode 100644
index 0000000..b784f87
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
@@ -0,0 +1,219 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.api.V2HttpCall.CompositeApi;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.common.params.MapSolrParams;
+import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.PluginBag;
+import org.apache.solr.handler.PingRequestHandler;
+import org.apache.solr.handler.SchemaHandler;
+import org.apache.solr.handler.SolrConfigHandler;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.request.SolrRequestHandler;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.V2HttpCall;
+import org.apache.solr.util.CommandOperation;
+import org.apache.solr.util.PathTrie;
+
+import static org.apache.solr.api.ApiBag.EMPTY_SPEC;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH;
+import static org.apache.solr.common.params.CommonParams.CONFIGSETS_HANDLER_PATH;
+import static org.apache.solr.common.params.CommonParams.CORES_HANDLER_PATH;
+import static org.apache.solr.common.util.ValidatingJsonMap.NOT_NULL;
+
+public class TestApiFramework extends SolrTestCaseJ4 {
+
+  public void testFramework() {
+    Map<String, Object[]> calls = new HashMap<>();
+    Map<String, Object> out = new HashMap<>();
+    CoreContainer mockCC = TestCoreAdminApis.getCoreContainerMock(calls, out);
+    PluginBag<SolrRequestHandler> containerHandlers = new PluginBag<>(SolrRequestHandler.class, null, false);
+    containerHandlers.put(COLLECTIONS_HANDLER_PATH, new TestCollectionAPIs.MockCollectionsHandler());
+    containerHandlers.put(CORES_HANDLER_PATH, new CoreAdminHandler(mockCC));
+    containerHandlers.put(CONFIGSETS_HANDLER_PATH, new ConfigSetsHandler(mockCC));
+    out.put("getRequestHandlers", containerHandlers);
+
+    PluginBag<SolrRequestHandler> coreHandlers = new PluginBag<>(SolrRequestHandler.class, null, false);
+    coreHandlers.put("/schema", new SchemaHandler());
+    coreHandlers.put("/config", new SolrConfigHandler());
+    coreHandlers.put("/admin/ping", new PingRequestHandler());
+
+    Map<String, String> parts = new HashMap<>();
+    String fullPath = "/collections/hello/shards";
+    Api api = V2HttpCall.getApiInfo(containerHandlers, fullPath, "POST",
+       fullPath, parts);
+    assertNotNull(api);
+    assertConditions(api.getSpec(), Utils.makeMap(
+        "/methods[0]", "POST",
+        "/commands/create", NOT_NULL));
+    assertEquals("hello", parts.get("collection"));
+
+
+    parts = new HashMap<>();
+    api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello/shards", "POST",
+      null, parts);
+    assertConditions(api.getSpec(), Utils.makeMap(
+        "/methods[0]", "POST",
+        "/commands/split", NOT_NULL,
+        "/commands/add-replica", NOT_NULL
+    ));
+
+
+    parts = new HashMap<>();
+    api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello/shards/shard1", "POST",
+        null, parts);
+    assertConditions(api.getSpec(), Utils.makeMap(
+        "/methods[0]", "POST",
+        "/commands/force-leader", NOT_NULL
+    ));
+    assertEquals("hello", parts.get("collection"));
+    assertEquals("shard1", parts.get("shard"));
+
+
+    parts = new HashMap<>();
+    api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello", "POST",
+       null, parts);
+    assertConditions(api.getSpec(), Utils.makeMap(
+        "/methods[0]", "POST",
+        "/commands/add-replica-property", NOT_NULL,
+        "/commands/delete-replica-property", NOT_NULL
+    ));
+    assertEquals("hello", parts.get("collection"));
+
+    api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello/shards/shard1/replica1", "DELETE",
+       null, parts);
+    assertConditions(api.getSpec(), Utils.makeMap(
+        "/methods[0]", "DELETE",
+        "/url/params/onlyIfDown/type", "boolean"
+    ));
+    assertEquals("hello", parts.get("collection"));
+    assertEquals("shard1", parts.get("shard"));
+    assertEquals("replica1", parts.get("replica"));
+
+    SolrQueryResponse rsp = invoke(containerHandlers, null, "/collections/_introspect", GET, mockCC);
+
+    assertConditions(rsp.getValues().asMap(2), Utils.makeMap(
+        "/spec[0]/methods[0]", "DELETE",
+        "/spec[1]/methods[0]", "POST",
+        "/spec[2]/methods[0]", "GET"
+
+    ));
+
+    rsp = invoke(coreHandlers, "/schema/_introspect", "/collections/hello/schema/_introspect", GET, mockCC);
+    assertConditions(rsp.getValues().asMap(2), Utils.makeMap(
+        "/spec[0]/methods[0]", "POST",
+        "/spec[0]/commands", NOT_NULL,
+        "/spec[1]/methods[0]", "GET"));
+
+    rsp = invoke(coreHandlers, "/", "/collections/hello/_introspect", GET, mockCC);
+    assertConditions(rsp.getValues().asMap(2), Utils.makeMap(
+        "/availableSubPaths", NOT_NULL,
+        "availableSubPaths /collections/hello/config/jmx", NOT_NULL,
+        "availableSubPaths /collections/hello/schema", NOT_NULL,
+        "availableSubPaths /collections/hello/shards", NOT_NULL,
+        "availableSubPaths /collections/hello/shards/{shard}", NOT_NULL,
+        "availableSubPaths /collections/hello/shards/{shard}/{replica}", NOT_NULL
+    ));
+
+  }
+  public void testTrailingTemplatePaths(){
+    PathTrie<Api> registry =  new PathTrie<>();
+    Api api = new Api(EMPTY_SPEC) {
+      @Override
+      public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+
+      }
+    };
+    Api intropsect = new ApiBag.IntrospectApi(api,false);
+    ApiBag.registerIntrospect(Collections.emptyMap(),registry,"/c/.system/blob/{name}",intropsect);
+    ApiBag.registerIntrospect(Collections.emptyMap(), registry, "/c/.system/{x}/{name}", intropsect);
+    assertEquals(intropsect, registry.lookup("/c/.system/blob/random_string/_introspect", new HashMap<>()));
+    assertEquals(intropsect, registry.lookup("/c/.system/blob/_introspect", new HashMap<>()));
+    assertEquals(intropsect, registry.lookup("/c/.system/_introspect", new HashMap<>()));
+    assertEquals(intropsect, registry.lookup("/c/.system/v1/_introspect", new HashMap<>()));
+    assertEquals(intropsect, registry.lookup("/c/.system/v1/v2/_introspect", new HashMap<>()));
+  }
+  private SolrQueryResponse invoke(PluginBag<SolrRequestHandler> reqHandlers, String path,
+                                   String fullPath, SolrRequest.METHOD method,
+                                   CoreContainer mockCC) {
+    HashMap<String, String> parts = new HashMap<>();
+    boolean containerHandlerLookup = mockCC.getRequestHandlers() == reqHandlers;
+    path = path == null ? fullPath : path;
+    Api api = null;
+    if (containerHandlerLookup) {
+      api = V2HttpCall.getApiInfo(reqHandlers, path, "GET", fullPath, parts);
+    } else {
+      api = V2HttpCall.getApiInfo(mockCC.getRequestHandlers(), fullPath, "GET", fullPath, parts);
+      if (api == null) api = new CompositeApi(null);
+      if (api instanceof CompositeApi) {
+        CompositeApi compositeApi = (CompositeApi) api;
+        api = V2HttpCall.getApiInfo(reqHandlers, path, "GET", fullPath, parts);
+        compositeApi.add(api);
+        api = compositeApi;
+      }
+    }
+
+    SolrQueryResponse rsp = new SolrQueryResponse();
+    LocalSolrQueryRequest req = new LocalSolrQueryRequest(null, new MapSolrParams(new HashMap<>())){
+      @Override
+      public List<CommandOperation> getCommands(boolean validateInput) {
+        return Collections.emptyList();
+      }
+    };
+
+    api.call(req,rsp);
+    return rsp;
+
+  }
+
+
+  private void assertConditions(Map root, Map conditions) {
+    for (Object o : conditions.entrySet()) {
+      Map.Entry e = (Map.Entry) o;
+      String path = (String) e.getKey();
+      List<String> parts = StrUtils.splitSmart(path, path.charAt(0) == '/' ?  '/':' ');
+      if (parts.get(0).isEmpty()) parts.remove(0);
+      Object val = Utils.getObjectByPath(root, false, parts);
+      if (e.getValue() instanceof ValidatingJsonMap.PredicateWithErrMsg) {
+        ValidatingJsonMap.PredicateWithErrMsg value = (ValidatingJsonMap.PredicateWithErrMsg) e.getValue();
+        String err = value.test(val);
+        if(err != null){
+          assertEquals(err + " for " + e.getKey() + " in :" + Utils.toJSONString(root), e.getValue(), val);
+        }
+
+      } else {
+        assertEquals("incorrect value for path " + e.getKey() + " in :" + Utils.toJSONString(root), e.getValue(), val);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
new file mode 100644
index 0000000..7f072ec
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.io.StringReader;
+import java.lang.invoke.MethodHandles;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.common.params.MultiMapSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.Pair;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.servlet.SolrRequestParsers;
+import org.apache.solr.util.CommandOperation;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
+import static org.apache.solr.common.util.Utils.fromJSONString;
+
+public class TestCollectionAPIs extends SolrTestCaseJ4 {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+
+  public void testCommands() throws Exception {
+    MockCollectionsHandler collectionsHandler = new MockCollectionsHandler();
+    ApiBag apiBag = new ApiBag(false);
+    Collection<Api> apis = collectionsHandler.getApis();
+    for (Api api : apis) apiBag.register(api, Collections.EMPTY_MAP);
+    //test a simple create collection call
+    compareOutput(apiBag, "/collections", POST,
+        "{create:{name:'newcoll', config:'schemaless', numShards:2, replicationFactor:2 }}", null,
+        "{name:newcoll, fromApi:'true', replicationFactor:'2', collection.configName:schemaless, numShards:'2', stateFormat:'2', operation:create}");
+
+    //test a create collection with custom properties
+    compareOutput(apiBag, "/collections", POST,
+        "{create:{name:'newcoll', config:'schemaless', numShards:2, replicationFactor:2, properties:{prop1:'prop1val', prop2: prop2val} }}", null,
+        "{name:newcoll, fromApi:'true', replicationFactor:'2', collection.configName:schemaless, numShards:'2', stateFormat:'2', operation:create, property.prop1:prop1val, property.prop2:prop2val}");
+
+
+    compareOutput(apiBag, "/collections", POST,
+        "{create-alias:{name: aliasName , collections:[c1,c2] }}", null, "{operation : createalias, name: aliasName, collections:[c1,c2] }");
+
+    compareOutput(apiBag, "/collections", POST,
+        "{delete-alias:{ name: aliasName}}", null, "{operation : deletealias, name: aliasName}");
+
+    compareOutput(apiBag, "/collections/collName", POST,
+        "{reload:{}}", null,
+        "{name:collName, operation :reload}");
+
+    compareOutput(apiBag, "/collections/collName", DELETE,
+        null, null,
+        "{name:collName, operation :delete}");
+
+    compareOutput(apiBag, "/collections/collName/shards/shard1", DELETE,
+        null, null,
+        "{collection:collName, shard: shard1 , operation :deleteshard }");
+
+    compareOutput(apiBag, "/collections/collName/shards/shard1/replica1?deleteDataDir=true&onlyIfDown=true", DELETE,
+        null, null,
+        "{collection:collName, shard: shard1, replica :replica1 , deleteDataDir:'true', onlyIfDown: 'true', operation :deletereplica }");
+
+    compareOutput(apiBag, "/collections/collName/shards", POST,
+        "{split:{shard:shard1, ranges: '0-1f4,1f5-3e8,3e9-5dc', coreProperties : {prop1:prop1Val, prop2:prop2Val} }}", null,
+        "{collection: collName , shard : shard1, ranges :'0-1f4,1f5-3e8,3e9-5dc', operation : splitshard, property.prop1:prop1Val, property.prop2: prop2Val}"
+    );
+
+    compareOutput(apiBag, "/collections/collName/shards", POST,
+        "{add-replica:{shard: shard1, node: 'localhost_8978' , coreProperties : {prop1:prop1Val, prop2:prop2Val} }}", null,
+        "{collection: collName , shard : shard1, node :'localhost_8978', operation : addreplica, property.prop1:prop1Val, property.prop2: prop2Val}"
+    );
+
+    compareOutput(apiBag, "/collections/collName/shards", POST,
+        "{split:{ splitKey:id12345, coreProperties : {prop1:prop1Val, prop2:prop2Val} }}", null,
+        "{collection: collName , split.key : id12345 , operation : splitshard, property.prop1:prop1Val, property.prop2: prop2Val}"
+    );
+
+    compareOutput(apiBag, "/collections/collName", POST,
+        "{add-replica-property : {name:propA , value: VALA, shard: shard1, replica:replica1}}", null,
+        "{collection: collName, shard: shard1, replica : replica1 , property : propA , operation : addreplicaprop, property.value : 'VALA'}"
+    );
+
+    compareOutput(apiBag, "/collections/collName", POST,
+        "{delete-replica-property : {property: propA , shard: shard1, replica:replica1} }", null,
+        "{collection: collName, shard: shard1, replica : replica1 , property : propA , operation : deletereplicaprop}"
+    );
+
+    compareOutput(apiBag, "/collections/collName", POST,
+        "{modify : {rule : 'replica:*,cores:<5', autoAddReplicas : false} }", null,
+        "{collection: collName, operation : modifycollection , autoAddReplicas : 'false', rule : [{replica: '*', cores : '<5' }]}"
+    );
+    compareOutput(apiBag, "/cluster", POST,
+        "{add-role : {role : overseer, node : 'localhost_8978'} }", null,
+        "{operation : addrole ,role : overseer, node : 'localhost_8978'}"
+    );
+
+    compareOutput(apiBag, "/cluster", POST,
+        "{remove-role : {role : overseer, node : 'localhost_8978'} }", null,
+        "{operation : removerole ,role : overseer, node : 'localhost_8978'}"
+    );
+
+    compareOutput(apiBag, "/collections/coll1", POST,
+        "{balance-shard-unique : {property: preferredLeader} }", null,
+        "{operation : balanceshardunique ,collection : coll1, property : preferredLeader}"
+    );
+
+    compareOutput(apiBag, "/collections/coll1", POST,
+        "{migrate-docs : {forwardTimeout: 1800, target: coll2, splitKey: 'a123!'} }", null,
+        "{operation : migrate ,collection : coll1, target.collection:coll2, forward.timeout:1800, split.key:'a123!'}"
+    );
+
+  }
+
+  static ZkNodeProps compareOutput(final ApiBag apiBag, final String path, final SolrRequest.METHOD method,
+                            final String payload, final CoreContainer cc, String expectedOutputMapJson) throws Exception {
+    Pair<SolrQueryRequest, SolrQueryResponse> ctx = makeCall(apiBag, path, method, payload, cc);
+    ZkNodeProps output = (ZkNodeProps) ctx.second().getValues().get(ZkNodeProps.class.getName());
+    Map expected = (Map) fromJSONString(expectedOutputMapJson);
+    assertMapEqual(expected, output);
+    return output;
+
+  }
+
+  public static Pair<SolrQueryRequest, SolrQueryResponse> makeCall(final ApiBag apiBag, String path,
+                                                                   final SolrRequest.METHOD method,
+                                                                   final String payload, final CoreContainer cc) throws Exception {
+    SolrParams queryParams = new MultiMapSolrParams(Collections.EMPTY_MAP);
+    if (path.indexOf('?') > 0) {
+      String queryStr = path.substring(path.indexOf('?') + 1);
+      path = path.substring(0, path.indexOf('?'));
+      queryParams = SolrRequestParsers.parseQueryString(queryStr);
+    }
+    final HashMap<String, String> parts = new HashMap<>();
+    Api api = apiBag.lookup(path, method.toString(), parts);
+    if (api == null) throw new RuntimeException("No handler at path :" + path);
+    SolrQueryResponse rsp = new SolrQueryResponse();
+    LocalSolrQueryRequest req = new LocalSolrQueryRequest(null, queryParams) {
+      @Override
+      public List<CommandOperation> getCommands(boolean validateInput) {
+        if (payload == null) return Collections.emptyList();
+        return ApiBag.getCommandOperations(new StringReader(payload), api.getCommandSchema(), true);
+      }
+
+      @Override
+      public Map<String, String> getPathTemplateValues() {
+        return parts;
+      }
+
+      @Override
+      public String getHttpMethod() {
+        return method.toString();
+      }
+    };
+    try {
+      api.call(req, rsp);
+    } catch (ApiBag.ExceptionWithErrObject e) {
+      throw new RuntimeException(e.getMessage() + Utils.toJSONString(e.getErrs()), e);
+
+    }
+    return new Pair<>(req, rsp);
+  }
+
+  private static void assertMapEqual(Map expected, ZkNodeProps actual) {
+    assertEquals(errorMessage(expected, actual), expected.size(), actual.getProperties().size());
+    for (Object o : expected.entrySet()) {
+      Map.Entry e = (Map.Entry) o;
+      Object actualVal = actual.get((String) e.getKey());
+      if (actualVal instanceof String[]) {
+        actualVal = Arrays.asList((String[]) actualVal);
+      }
+      assertEquals(errorMessage(expected, actual), String.valueOf(e.getValue()), String.valueOf(actualVal));
+    }
+  }
+
+  private static String errorMessage(Map expected, ZkNodeProps actual) {
+    return "expected: " + Utils.toJSONString(expected) + "\nactual: " + Utils.toJSONString(actual);
+
+  }
+
+  static class MockCollectionsHandler extends CollectionsHandler {
+    LocalSolrQueryRequest req;
+
+    MockCollectionsHandler() {
+    }
+
+    @Override
+    void invokeAction(SolrQueryRequest req, SolrQueryResponse rsp,
+                      CoreContainer cores,
+                      CollectionParams.CollectionAction action,
+                      CollectionOperation operation) throws Exception {
+      Map<String, Object> result = operation.execute(req, rsp, this);
+      if (result != null) {
+        result.put(QUEUE_OPERATION, operation.action.toLower());
+        rsp.add(ZkNodeProps.class.getName(), new ZkNodeProps(result));
+      }
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java b/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
new file mode 100644
index 0000000..d2c96a6
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+
+import java.util.Map;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.zookeeper.KeeperException;
+
+import static java.util.Collections.EMPTY_MAP;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
+import static org.apache.solr.handler.admin.TestCollectionAPIs.compareOutput;
+
+public class TestConfigsApi extends SolrTestCaseJ4 {
+
+
+  public void testCommands() throws Exception {
+
+    ConfigSetsHandler handler = new ConfigSetsHandler(null) {
+      @Override
+      protected void sendToZk(SolrQueryResponse rsp,
+                              ConfigSetOperation operation,
+                              Map<String, Object> result)
+          throws KeeperException, InterruptedException {
+        result.put(QUEUE_OPERATION, operation.action.toLower());
+        rsp.add(ZkNodeProps.class.getName(), new ZkNodeProps(result));
+      }
+    };
+    ApiBag apiBag = new ApiBag(false);
+    for (Api api : handler.getApis()) apiBag.register(api, EMPTY_MAP);
+    compareOutput(apiBag, "/cluster/configs/sample", DELETE, null, null,
+        "{name :sample, operation:delete}");
+
+    compareOutput(apiBag, "/cluster/configs", POST, "{create:{name : newconf, baseConfigSet: sample }}", null,
+        "{operation:create, name :newconf,  baseConfigSet: sample }");
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/admin/TestCoreAdminApis.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestCoreAdminApis.java b/solr/core/src/test/org/apache/solr/handler/admin/TestCoreAdminApis.java
new file mode 100644
index 0000000..f263f5e
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestCoreAdminApis.java
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.easymock.EasyMock;
+
+import static org.apache.solr.common.util.Utils.fromJSONString;
+import static org.easymock.EasyMock.anyBoolean;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.getCurrentArguments;
+
+public class TestCoreAdminApis extends SolrTestCaseJ4 {
+
+  public void testCalls() throws Exception {
+    Map<String, Object[]> calls = new HashMap<>();
+    CoreContainer mockCC = getCoreContainerMock(calls, new HashMap<>());
+
+    CoreAdminHandler  coreAdminHandler = new CoreAdminHandler(mockCC);
+    ApiBag apiBag = new ApiBag(false);
+    for (Api api : coreAdminHandler.getApis()) {
+      apiBag.register(api, Collections.EMPTY_MAP);
+    }
+    TestCollectionAPIs.makeCall(apiBag, "/cores", SolrRequest.METHOD.POST,
+        "{create:{name: hello, instanceDir : someDir, schema: 'schema.xml'}}", mockCC);
+    Object[] params = calls.get("create");
+    assertEquals("hello" ,params[0]);
+    assertEquals(fromJSONString("{schema : schema.xml}") ,params[2]);
+
+    TestCollectionAPIs.makeCall(apiBag, "/cores/core1", SolrRequest.METHOD.POST,
+        "{swap:{with: core2}}", mockCC);
+    params = calls.get("swap");
+    assertEquals("core1" ,params[0]);
+    assertEquals("core2" ,params[1]);
+
+    TestCollectionAPIs.makeCall(apiBag, "/cores/core1", SolrRequest.METHOD.POST,
+        "{rename:{to: core2}}", mockCC);
+    params = calls.get("swap");
+    assertEquals("core1" ,params[0]);
+    assertEquals("core2" ,params[1]);
+
+    TestCollectionAPIs.makeCall(apiBag, "/cores/core1", SolrRequest.METHOD.POST,
+        "{unload:{deleteIndex : true}}", mockCC);
+    params = calls.get("unload");
+    assertEquals("core1" ,params[0]);
+    assertEquals(Boolean.TRUE ,params[1]);
+  }
+
+  public static CoreContainer getCoreContainerMock(final Map<String, Object[]> in,Map<String,Object> out ) {
+    CoreContainer mockCC = EasyMock.createMock(CoreContainer.class);
+    EasyMock.reset(mockCC);
+    mockCC.create(anyObject(String.class), anyObject(Path.class) , anyObject(Map.class), anyBoolean());
+    EasyMock.expectLastCall().andAnswer(() -> {
+      in.put("create", getCurrentArguments());
+      return null;
+    }).anyTimes();
+    mockCC.swap(anyObject(String.class), anyObject(String.class));
+    EasyMock.expectLastCall().andAnswer(() -> {
+      in.put("swap", getCurrentArguments());
+      return null;
+    }).anyTimes();
+
+    mockCC.rename(anyObject(String.class), anyObject(String.class));
+    EasyMock.expectLastCall().andAnswer(() -> {
+      in.put("rename", getCurrentArguments());
+      return null;
+    }).anyTimes();
+
+    mockCC.unload(anyObject(String.class), anyBoolean(),
+        anyBoolean(), anyBoolean());
+    EasyMock.expectLastCall().andAnswer(() -> {
+      in.put("unload", getCurrentArguments());
+      return null;
+    }).anyTimes();
+
+    mockCC.getCoreRootDirectory();
+    EasyMock.expectLastCall().andAnswer(() -> Paths.get("coreroot")).anyTimes();
+    mockCC.getContainerProperties();
+    EasyMock.expectLastCall().andAnswer(() -> new Properties()).anyTimes();
+
+    mockCC.getRequestHandlers();
+    EasyMock.expectLastCall().andAnswer(() -> out.get("getRequestHandlers")).anyTimes();
+
+    EasyMock.replay(mockCC);
+    return mockCC;
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/component/InfixSuggestersTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/component/InfixSuggestersTest.java b/solr/core/src/test/org/apache/solr/handler/component/InfixSuggestersTest.java
new file mode 100644
index 0000000..a8188bb
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/component/InfixSuggestersTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.component;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.util.ExecutorUtil;
+import org.apache.solr.spelling.suggest.RandomTestDictionaryFactory;
+import org.apache.solr.spelling.suggest.SuggesterParams;
+import org.apache.solr.update.SolrCoreState;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class InfixSuggestersTest extends SolrTestCaseJ4 {
+  private static final String rh_analyzing_short = "/suggest_analyzing_infix_short_dictionary";
+  private static final String rh_analyzing_long = "/suggest_analyzing_infix_long_dictionary";
+  private static final String rh_blended_short = "/suggest_blended_infix_short_dictionary";
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    initCore("solrconfig-infixsuggesters.xml","schema.xml");
+  }
+
+  @Test
+  public void test2xBuildReload() throws Exception {
+    for (int i = 0 ; i < 2 ; ++i) {
+      assertQ(req("qt", rh_analyzing_short,
+          SuggesterParams.SUGGEST_BUILD_ALL, "true"),
+          "//str[@name='command'][.='buildAll']"
+      );
+      h.reload();
+    }
+  }
+
+  @Test
+  public void testTwoSuggestersBuildThenReload() throws Exception {
+    assertQ(req("qt", rh_analyzing_short,
+        SuggesterParams.SUGGEST_BUILD_ALL, "true"),
+        "//str[@name='command'][.='buildAll']"
+    );
+    h.reload();
+
+    assertQ(req("qt", rh_blended_short,
+        SuggesterParams.SUGGEST_BUILD_ALL, "true"),
+        "//str[@name='command'][.='buildAll']"
+    );
+    h.reload();
+  }
+
+  @Test
+  public void testBuildThen2xReload() throws Exception {
+    assertQ(req("qt", rh_analyzing_short,
+        SuggesterParams.SUGGEST_BUILD_ALL, "true"),
+        "//str[@name='command'][.='buildAll']"
+    );
+    h.reload();
+    h.reload();
+  }
+
+  @Test
+  public void testAnalyzingInfixSuggesterBuildThenReload() throws Exception {
+    assertQ(req("qt", rh_analyzing_short,
+        SuggesterParams.SUGGEST_BUILD_ALL, "true"),
+        "//str[@name='command'][.='buildAll']"
+    );
+    h.reload();
+  }
+
+  @Test
+  public void testBlendedInfixSuggesterBuildThenReload() throws Exception {
+    assertQ(req("qt", rh_blended_short,
+        SuggesterParams.SUGGEST_BUILD_ALL, "true"),
+        "//str[@name='command'][.='buildAll']"
+    );
+    h.reload();
+  }
+
+  @Test
+  public void testReloadDuringBuild() throws Exception {
+    ExecutorService executor = ExecutorUtil.newMDCAwareCachedThreadPool("AnalyzingInfixSuggesterTest");
+    try {
+      // Build the suggester in the background with a long dictionary
+      Future job = executor.submit(() ->
+          expectThrows(RuntimeException.class, SolrCoreState.CoreIsClosedException.class,
+              () -> assertQ(req("qt", rh_analyzing_long,
+                  SuggesterParams.SUGGEST_BUILD_ALL, "true"),
+                  "//str[@name='command'][.='buildAll']")));
+      h.reload();
+      // Stop the dictionary's input iterator
+      System.clearProperty(RandomTestDictionaryFactory.RandomTestDictionary
+          .getEnabledSysProp("longRandomAnalyzingInfixSuggester"));
+      job.get();
+    } finally {
+      ExecutorUtil.shutdownAndAwaitTermination(executor);
+    }
+  }
+
+  @Test
+  public void testShutdownDuringBuild() throws Exception {
+    ExecutorService executor = ExecutorUtil.newMDCAwareCachedThreadPool("AnalyzingInfixSuggesterTest");
+    try {
+      // Build the suggester in the background with a long dictionary
+      Future job = executor.submit(() -> 
+          expectThrows(RuntimeException.class, SolrCoreState.CoreIsClosedException.class,
+              () -> assertQ(req("qt", rh_analyzing_long,
+                  SuggesterParams.SUGGEST_BUILD_ALL, "true"),
+                  "//str[@name='command'][.='buildAll']")));
+      Thread.sleep(100); // TODO: is there a better way to ensure that the build has begun?
+      h.close();
+      // Stop the dictionary's input iterator
+      System.clearProperty(RandomTestDictionaryFactory.RandomTestDictionary
+          .getEnabledSysProp("longRandomAnalyzingInfixSuggester"));
+      job.get();
+    } finally {
+      ExecutorUtil.shutdownAndAwaitTermination(executor);
+      initCore("solrconfig-infixsuggesters.xml","schema.xml"); // put the core back for other tests
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/component/SpellCheckComponentTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/component/SpellCheckComponentTest.java b/solr/core/src/test/org/apache/solr/handler/component/SpellCheckComponentTest.java
index 37d02d9..473153a 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/SpellCheckComponentTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/SpellCheckComponentTest.java
@@ -82,77 +82,62 @@ public class SpellCheckComponentTest extends SolrTestCaseJ4 {
   public void testMaximumResultsForSuggest() throws Exception {
    assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", SpellingParams.SPELLCHECK_BUILD, "true", "q","lowerfilt:(this OR brwn)",
         SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"false", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST, "7")
-        ,"/spellcheck/suggestions/[0]=='brwn'"
-        ,"/spellcheck/suggestions/[1]/numFound==1"
+        ,"/spellcheck/suggestions/brwn/numFound==1"
      );
-    try {
-      assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", SpellingParams.SPELLCHECK_BUILD, "true", "q","lowerfilt:(this OR brwn)",
-          SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"false", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST, "6")
-          ,"/spellcheck/suggestions/[1]/numFound==1"
-       );
-      fail("there should have been no suggestions (6<7)");
-    } catch(Exception e) {
-      //correctly threw exception
-    }
+
+   assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", SpellingParams.SPELLCHECK_BUILD, "true", "q","lowerfilt:(this OR brwn)",
+       SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"false", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST, "6")
+       ,"/spellcheck/suggestions=={}");
+   // there should have been no suggestions (6<7)
+
     assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", SpellingParams.SPELLCHECK_BUILD, "true", "q","lowerfilt:(this OR brwn)",
         "fq", "id:[0 TO 9]", /*returns 10, less selective */ "fq", "lowerfilt:th*", /* returns 8, most selective */
         SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"false", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST, ".90")
-        ,"/spellcheck/suggestions/[0]=='brwn'"
-        ,"/spellcheck/suggestions/[1]/numFound==1"
+        ,"/spellcheck/suggestions/brwn/numFound==1"
      );
-    try {
-      assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", SpellingParams.SPELLCHECK_BUILD, "true", "q","lowerfilt:(this OR brwn)",
-          "fq", "id:[0 TO 9]", /*returns 10, less selective */ "fq", "lowerfilt:th*", /* returns 8, most selective */
-          SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"false", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST, ".80")
-          ,"/spellcheck/suggestions/[1]/numFound==1"
-       );
-      fail("there should have been no suggestions ((.8 * 8)<7)");
-    } catch(Exception e) {
-      //correctly threw exception
-    }
+
+    assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", SpellingParams.SPELLCHECK_BUILD, "true", "q","lowerfilt:(this OR brwn)",
+        "fq", "id:[0 TO 9]", /*returns 10, less selective */ "fq", "lowerfilt:th*", /* returns 8, most selective */
+        SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"false", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST, ".80")
+        ,"/spellcheck/suggestions=={}");
+    // there should have been no suggestions ((.8 * 8)<7)
     
     
     assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", SpellingParams.SPELLCHECK_BUILD, "true", "q","lowerfilt:(this OR brwn)",
         "fq", "id:[0 TO 9]", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST_FQ, "id:[0 TO 9]", 
         SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"false", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST, ".70")
-        ,"/spellcheck/suggestions/[0]=='brwn'"
-        ,"/spellcheck/suggestions/[1]/numFound==1"
+        ,"/spellcheck/suggestions/brwn/numFound==1"
      );
-    try {
-      assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", SpellingParams.SPELLCHECK_BUILD, "true", "q","lowerfilt:(this OR brwn)",
-          "fq", "id:[0 TO 9]", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST_FQ, "lowerfilt:th*", 
-          SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"false", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST, ".64")
-          ,"/spellcheck/suggestions/[1]/numFound==1"
-       );
-      fail("there should have been no suggestions ((.64 * 10)<7)");
-    } catch(Exception e) {
-      //correctly threw exception
-    }
+
+    assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", SpellingParams.SPELLCHECK_BUILD, "true", "q","lowerfilt:(this OR brwn)",
+        "fq", "id:[0 TO 9]", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST_FQ, "lowerfilt:th*", 
+        SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"false", SpellingParams.SPELLCHECK_MAX_RESULTS_FOR_SUGGEST, ".64")
+        ,"/spellcheck/suggestions=={}");
+    // there should have been no suggestions ((.64 * 10)<7)
   } 
   
   @Test
   public void testExtendedResultsCount() throws Exception {
     assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", SpellingParams.SPELLCHECK_BUILD, "true", "q","bluo", SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"false")
-       ,"/spellcheck/suggestions/[0]=='bluo'"
-       ,"/spellcheck/suggestions/[1]/numFound==5"
+       ,"/spellcheck/suggestions/bluo/numFound==5"
     );
 
     assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", "q","bluo", SpellingParams.SPELLCHECK_COUNT,"3", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"true")
-       ,"/spellcheck/suggestions/[1]/suggestion==[{'word':'blud','freq':1}, {'word':'blue','freq':1}, {'word':'blee','freq':1}]"
+       ,"/spellcheck/suggestions/bluo/suggestion==[{'word':'blud','freq':1}, {'word':'blue','freq':1}, {'word':'blee','freq':1}]"
     );
   }
 
   @Test
   public void test() throws Exception {
     assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", "q","documemt")
-       ,"/spellcheck=={'suggestions':['documemt',{'numFound':1,'startOffset':0,'endOffset':8,'suggestion':['document']}]}"
+       ,"/spellcheck=={'suggestions':{'documemt':{'numFound':1,'startOffset':0,'endOffset':8,'suggestion':['document']}}}"
     );
   }
   
   @Test
   public void testNumericQuery() throws Exception {
     assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", "q","12346")
-       ,"/spellcheck=={'suggestions':['12346',{'numFound':1,'startOffset':0,'endOffset':5,'suggestion':['12345']}]}"
+       ,"/spellcheck=={'suggestions':{'12346':{'numFound':1,'startOffset':0,'endOffset':5,'suggestion':['12345']}}}"
     );
   }
 
@@ -186,13 +171,21 @@ public class SpellCheckComponentTest extends SolrTestCaseJ4 {
   @Test
   public void testCollateExtendedResultsWithJsonNl() throws Exception {
     final String q = "documemtsss broens";
-    final String jsonNl = "map";
+    final String jsonNl = (random().nextBoolean() ? "map" : "arrntv");
     final boolean collateExtendedResults = random().nextBoolean();
     final List<String> testsList = new ArrayList<String>();
     if (collateExtendedResults) {
       testsList.add("/spellcheck/collations/collation/collationQuery=='document brown'");
       testsList.add("/spellcheck/collations/collation/hits==0");
       switch (jsonNl) {
+        case "arrntv":
+          testsList.add("/spellcheck/collations/collation/misspellingsAndCorrections/[0]/name=='documemtsss'");
+          testsList.add("/spellcheck/collations/collation/misspellingsAndCorrections/[0]/type=='str'");
+          testsList.add("/spellcheck/collations/collation/misspellingsAndCorrections/[0]/value=='document'");
+          testsList.add("/spellcheck/collations/collation/misspellingsAndCorrections/[1]/name=='broens'");
+          testsList.add("/spellcheck/collations/collation/misspellingsAndCorrections/[1]/type=='str'");
+          testsList.add("/spellcheck/collations/collation/misspellingsAndCorrections/[1]/value=='brown'");
+          break;
         case "map":
           testsList.add("/spellcheck/collations/collation/misspellingsAndCorrections/documemtsss=='document'");
           testsList.add("/spellcheck/collations/collation/misspellingsAndCorrections/broens=='brown'");
@@ -311,11 +304,11 @@ public class SpellCheckComponentTest extends SolrTestCaseJ4 {
         //while "document" is present.
 
         assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", "q","documenq", SpellingParams.SPELLCHECK_DICT, "threshold", SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"true")
-            ,"/spellcheck/suggestions/[1]/suggestion==[{'word':'document','freq':2}]"
+            ,"/spellcheck/suggestions/documenq/suggestion==[{'word':'document','freq':2}]"
         );
 
         assertJQ(req("qt",rh, SpellCheckComponent.COMPONENT_NAME, "true", "q","documenq", SpellingParams.SPELLCHECK_DICT, "threshold_direct", SpellingParams.SPELLCHECK_COUNT,"5", SpellingParams.SPELLCHECK_EXTENDED_RESULTS,"true")
-            ,"/spellcheck/suggestions/[1]/suggestion==[{'word':'document','freq':2}]"
+            ,"/spellcheck/suggestions/documenq/suggestion==[{'word':'document','freq':2}]"
         );
 
         //TODO:  how do we make this into a 1-liner using "assertQ()" ???

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
index ecddfba..3813488 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
@@ -51,7 +51,7 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
   }
 
   @Test
-  public void testMoveMetrics() throws Exception {
+  public void testSwapRegistries() throws Exception {
     Random r = random();
 
     SolrMetricManager metricManager = new SolrMetricManager();
@@ -65,13 +65,14 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
       metricManager.register(fromName, entry.getValue(), false, entry.getKey(), "metrics1");
     }
     for (Map.Entry<String, Counter> entry : metrics2.entrySet()) {
-      metricManager.register(fromName, entry.getValue(), false, entry.getKey(), "metrics2");
+      metricManager.register(toName, entry.getValue(), false, entry.getKey(), "metrics2");
     }
-    assertEquals(metrics1.size() + metrics2.size(), metricManager.registry(fromName).getMetrics().size());
+    assertEquals(metrics1.size(), metricManager.registry(fromName).getMetrics().size());
+    assertEquals(metrics2.size(), metricManager.registry(toName).getMetrics().size());
 
-    // move metrics1
-    metricManager.moveMetrics(fromName, toName, new SolrMetricManager.PrefixFilter("metrics1"));
-    // check the remaining metrics
+    // swap
+    metricManager.swapRegistries(fromName, toName);
+    // check metrics
     Map<String, Metric> fromMetrics = metricManager.registry(fromName).getMetrics();
     assertEquals(metrics2.size(), fromMetrics.size());
     for (Map.Entry<String, Counter> entry : metrics2.entrySet()) {
@@ -79,7 +80,6 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
       assertNotNull(value);
       assertEquals(entry.getValue(), value);
     }
-    // check the moved metrics
     Map<String, Metric> toMetrics = metricManager.registry(toName).getMetrics();
     assertEquals(metrics1.size(), toMetrics.size());
     for (Map.Entry<String, Counter> entry : metrics1.entrySet()) {
@@ -87,13 +87,6 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
       assertNotNull(value);
       assertEquals(entry.getValue(), value);
     }
-
-    // move all remaining metrics
-    metricManager.moveMetrics(fromName, toName, null);
-    fromMetrics = metricManager.registry(fromName).getMetrics();
-    assertEquals(0, fromMetrics.size());
-    toMetrics = metricManager.registry(toName).getMetrics();
-    assertEquals(metrics1.size() + metrics2.size(), toMetrics.size());
   }
 
   @Test
@@ -143,13 +136,13 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
 
     assertEquals(metrics.size() * 3, metricManager.registry(registryName).getMetrics().size());
 
-    // clear "foo.bar"
-    Set<String> removed = metricManager.clearMetrics(registryName, "foo", "bar");
+    // clear all metrics with prefix "foo.bar."
+    Set<String> removed = metricManager.clearMetrics(registryName, "foo", "bar.");
     assertEquals(metrics.size(), removed.size());
     for (String s : removed) {
       assertTrue(s.startsWith("foo.bar."));
     }
-    removed = metricManager.clearMetrics(registryName, "foo", "baz");
+    removed = metricManager.clearMetrics(registryName, "foo", "baz.");
     assertEquals(metrics.size(), removed.size());
     for (String s : removed) {
       assertTrue(s.startsWith("foo.baz."));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java b/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
index a21135e..fd8d6ec 100644
--- a/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
+++ b/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
@@ -2081,6 +2081,158 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
     doFacetPrefix("tt_s1", "{!threads=-1}", "", "facet.method","fcs");  // default / unlimited threads
     doFacetPrefix("tt_s1", "{!threads=2}", "", "facet.method","fcs");   // specific number of threads
   }
+
+  @Test
+  public void testFacetExclude() {
+    for (String method : new String[] {"enum", "fcs", "fc", "uif"}) {
+      doFacetExclude("contains_s1", "contains_group_s1", "Astra", "facet.method", method);
+    }
+  }
+
+  private void doFacetExclude(String f, String g, String termSuffix, String... params) {
+    String indent="on";
+    String pre = "//lst[@name='"+f+"']";
+
+    final SolrQueryRequest req = req(params, "q", "id:[* TO *]"
+        ,"indent",indent
+        ,"facet","true"
+        ,"facet.field", f
+        ,"facet.mincount","0"
+        ,"facet.offset","0"
+        ,"facet.limit","100"
+        ,"facet.sort","count"
+        ,"facet.excludeTerms","B,BBB"+termSuffix
+    );
+
+    assertQ("test facet.exclude",
+        req
+        ,"*[count(//lst[@name='facet_fields']/lst/int)=10]"
+        ,pre+"/int[1][@name='BBB'][.='3']"
+        ,pre+"/int[2][@name='CCC'][.='3']"
+        ,pre+"/int[3][@name='CCC"+termSuffix+"'][.='3']"
+        ,pre+"/int[4][@name='BB'][.='2']"
+        ,pre+"/int[5][@name='BB"+termSuffix+"'][.='2']"
+        ,pre+"/int[6][@name='CC'][.='2']"
+        ,pre+"/int[7][@name='CC"+termSuffix+"'][.='2']"
+        ,pre+"/int[8][@name='AAA'][.='1']"
+        ,pre+"/int[9][@name='AAA"+termSuffix+"'][.='1']"
+        ,pre+"/int[10][@name='B"+termSuffix+"'][.='1']"
+    );
+
+    final SolrQueryRequest groupReq = req(params, "q", "id:[* TO *]"
+        ,"indent",indent
+        ,"facet","true"
+        ,"facet.field", f
+        ,"facet.mincount","0"
+        ,"facet.offset","0"
+        ,"facet.limit","100"
+        ,"facet.sort","count"
+        ,"facet.excludeTerms","B,BBB"+termSuffix
+        ,"group","true"
+        ,"group.field",g
+        ,"group.facet","true"
+    );
+
+    assertQ("test facet.exclude for grouped facets",
+        groupReq
+        ,"*[count(//lst[@name='facet_fields']/lst/int)=10]"
+        ,pre+"/int[1][@name='CCC'][.='3']"
+        ,pre+"/int[2][@name='CCC"+termSuffix+"'][.='3']"
+        ,pre+"/int[3][@name='BBB'][.='2']"
+        ,pre+"/int[4][@name='AAA'][.='1']"
+        ,pre+"/int[5][@name='AAA"+termSuffix+"'][.='1']"
+        ,pre+"/int[6][@name='B"+termSuffix+"'][.='1']"
+        ,pre+"/int[7][@name='BB'][.='1']"
+        ,pre+"/int[8][@name='BB"+termSuffix+"'][.='1']"
+        ,pre+"/int[9][@name='CC'][.='1']"
+        ,pre+"/int[10][@name='CC"+termSuffix+"'][.='1']"
+    );
+  }
+
+  @Test
+  public void testFacetContainsAndExclude() {
+    for (String method : new String[] {"enum", "fcs", "fc", "uif"}) {
+      String contains = "BAst";
+      String groupContains = "Ast";
+      final boolean ignoreCase = random().nextBoolean();
+      if (ignoreCase) {
+        contains = randomizeStringCasing(contains);
+        groupContains = randomizeStringCasing(groupContains);
+        doFacetContainsAndExclude("contains_s1", "contains_group_s1", "Astra", contains, groupContains, "facet.method", method, "facet.contains.ignoreCase", "true");
+      } else {
+        doFacetContainsAndExclude("contains_s1", "contains_group_s1", "Astra", contains, groupContains, "facet.method", method);
+      }
+    }
+  }
+
+  private String randomizeStringCasing(String str) {
+    final char[] characters = str.toCharArray();
+
+    for (int i = 0; i != characters.length; ++i) {
+      final boolean switchCase = random().nextBoolean();
+      if (!switchCase) {
+        continue;
+      }
+
+      final char c = str.charAt(i);
+      if (Character.isUpperCase(c)) {
+        characters[i] = Character.toLowerCase(c);
+      } else {
+        characters[i] = Character.toUpperCase(c);
+      }
+    }
+
+    return new String(characters);
+  }
+
+  private void doFacetContainsAndExclude(String f, String g, String termSuffix, String contains, String groupContains, String... params) {
+    String indent="on";
+    String pre = "//lst[@name='"+f+"']";
+
+    final SolrQueryRequest req = req(params, "q", "id:[* TO *]"
+        ,"indent",indent
+        ,"facet","true"
+        ,"facet.field", f
+        ,"facet.mincount","0"
+        ,"facet.offset","0"
+        ,"facet.limit","100"
+        ,"facet.sort","count"
+        ,"facet.contains",contains
+        ,"facet.excludeTerms","BBB"+termSuffix
+    );
+
+    assertQ("test facet.contains with facet.exclude",
+        req
+        ,"*[count(//lst[@name='facet_fields']/lst/int)=2]"
+        ,pre+"/int[1][@name='BB"+termSuffix+"'][.='2']"
+        ,pre+"/int[2][@name='B"+termSuffix+"'][.='1']"
+    );
+
+    final SolrQueryRequest groupReq = req(params, "q", "id:[* TO *]"
+        ,"indent",indent
+        ,"facet","true"
+        ,"facet.field", f
+        ,"facet.mincount","0"
+        ,"facet.offset","0"
+        ,"facet.limit","100"
+        ,"facet.sort","count"
+        ,"facet.contains",groupContains
+        ,"facet.excludeTerms","AAA"+termSuffix
+        ,"group","true"
+        ,"group.field",g
+        ,"group.facet","true"
+    );
+
+    assertQ("test facet.contains with facet.exclude for grouped facets",
+        groupReq
+        ,"*[count(//lst[@name='facet_fields']/lst/int)=5]"
+        ,pre+"/int[1][@name='CCC"+termSuffix+"'][.='3']"
+        ,pre+"/int[2][@name='BBB"+termSuffix+"'][.='2']"
+        ,pre+"/int[3][@name='B"+termSuffix+"'][.='1']"
+        ,pre+"/int[4][@name='BB"+termSuffix+"'][.='1']"
+        ,pre+"/int[5][@name='CC"+termSuffix+"'][.='1']"
+    );
+  }
   
   @Test
   //@Ignore("SOLR-8466 - facet.method=uif ignores facet.contains")
@@ -2684,26 +2836,6 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
                   "facet.range.gap", "0.0003"),
               400);
   }
-
-  public void testContainsAtStart() {
-    assertTrue(SimpleFacets.contains("foobar", "foo", false));
-  }
-
-  public void testContains() {
-    assertTrue(SimpleFacets.contains("foobar", "ooba", false));
-  }
-
-  public void testContainsAtEnd() {
-    assertTrue(SimpleFacets.contains("foobar", "bar", false));
-  }
-
-  public void testContainsWhole() {
-    assertTrue(SimpleFacets.contains("foobar", "foobar", false));
-  }
-
-  public void testContainsIgnoreCase() {
-    assertTrue(SimpleFacets.contains("FooBar", "bar", true));
-  }
   
   public void testRangeQueryHardEndParamFilter() {
     doTestRangeQueryHardEndParam("range_facet_l", FacetRangeMethod.FILTER);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/request/SubstringBytesRefFilterTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/request/SubstringBytesRefFilterTest.java b/solr/core/src/test/org/apache/solr/request/SubstringBytesRefFilterTest.java
new file mode 100644
index 0000000..a5b3048
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/request/SubstringBytesRefFilterTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package org.apache.solr.request;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.BytesRef;
+import org.junit.Test;
+
+public class SubstringBytesRefFilterTest extends LuceneTestCase {
+    
+  @Test
+  public void testSubstringBytesRefFilter() {
+    final List<String> substrings = new ArrayList<>(4);
+    substrings.add("foo");
+    substrings.add("ooba");
+    substrings.add("bar");
+    substrings.add("foobar");
+
+    final String contains = substrings.get(random().nextInt(substrings.size()));
+    final boolean ignoreCase = random().nextBoolean();
+    final SubstringBytesRefFilter filter = new SubstringBytesRefFilter(contains, ignoreCase);
+
+    assertTrue(filter.test(new BytesRef("foobar")));
+
+    if (ignoreCase) {
+      assertTrue(filter.test(new BytesRef("FooBar")));
+    } else {
+      assertFalse(filter.test(new BytesRef("FooBar")));
+    }
+
+    assertFalse(filter.test(new BytesRef("qux")));
+  }
+
+} 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
index d5db82e..ea8fd7b 100644
--- a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
+++ b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
@@ -27,6 +27,7 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.schema.SimilarityFactory;
 import org.apache.solr.search.similarities.SchemaSimilarityFactory;
+import org.apache.solr.util.RESTfulServerProvider;
 import org.apache.solr.util.RestTestBase;
 import org.apache.solr.util.RestTestHarness;
 
@@ -34,9 +35,12 @@ import org.junit.After;
 import org.junit.Before;
 import org.noggit.JSONParser;
 import org.noggit.ObjectBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.StringReader;
+import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -45,6 +49,8 @@ import java.util.Set;
 
 
 public class TestBulkSchemaAPI extends RestTestBase {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
 
   private static File tmpSolrHome;
 
@@ -58,6 +64,15 @@ public class TestBulkSchemaAPI extends RestTestBase {
 
     createJettyAndHarness(tmpSolrHome.getAbsolutePath(), "solrconfig-managed-schema.xml", "schema-rest.xml",
         "/solr", true, null);
+    if (random().nextBoolean()) {
+      log.info("These tests are run with V2 API");
+      restTestHarness.setServerProvider(new RESTfulServerProvider() {
+        @Override
+        public String getBaseURL() {
+          return jetty.getBaseUrl().toString() + "/v2/cores/" + DEFAULT_TEST_CORENAME;
+        }
+      });
+    }
   }
 
   @After

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java b/solr/core/src/test/org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java
index bbc081a..574a736 100644
--- a/solr/core/src/test/org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java
+++ b/solr/core/src/test/org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java
@@ -20,7 +20,7 @@ import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.queryparser.xml.DOMUtils;
 import org.apache.lucene.queryparser.xml.ParserException;
-import org.apache.lucene.queryparser.xml.QueryBuilder;
+import org.apache.lucene.queryparser.xml.builders.SpanQueryBuilder;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.spans.SpanNearQuery;
 import org.apache.lucene.search.spans.SpanQuery;
@@ -28,14 +28,18 @@ import org.apache.lucene.search.spans.SpanTermQuery;
 import org.apache.solr.request.SolrQueryRequest;
 import org.w3c.dom.Element;
 
-public class ApacheLuceneSolrNearQueryBuilder extends SolrQueryBuilder {
+public class ApacheLuceneSolrNearQueryBuilder extends SolrSpanQueryBuilder {
 
   public ApacheLuceneSolrNearQueryBuilder(String defaultField, Analyzer analyzer,
-      SolrQueryRequest req, QueryBuilder queryFactory) {
-    super(defaultField, analyzer, req, queryFactory);
+      SolrQueryRequest req, SpanQueryBuilder spanFactory) {
+    super(defaultField, analyzer, req, spanFactory);
   }
 
   public Query getQuery(Element e) throws ParserException {
+    return getSpanQuery(e);
+  }
+
+  public SpanQuery getSpanQuery(Element e) throws ParserException {
     final String fieldName = DOMUtils.getAttributeWithInheritanceOrFail(e, "fieldName");
     final SpanQuery[] spanQueries = new SpanQuery[]{
         new SpanTermQuery(new Term(fieldName, "Apache")),

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/search/ChooseOneWordQueryBuilder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/ChooseOneWordQueryBuilder.java b/solr/core/src/test/org/apache/solr/search/ChooseOneWordQueryBuilder.java
new file mode 100644
index 0000000..6e2112e
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/ChooseOneWordQueryBuilder.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+package org.apache.solr.search;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.queryparser.xml.DOMUtils;
+import org.apache.lucene.queryparser.xml.ParserException;
+import org.apache.lucene.queryparser.xml.builders.SpanQueryBuilder;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.spans.SpanQuery;
+import org.apache.lucene.search.spans.SpanTermQuery;
+import org.apache.solr.request.SolrQueryRequest;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+public class ChooseOneWordQueryBuilder extends SolrSpanQueryBuilder {
+
+  public ChooseOneWordQueryBuilder(String defaultField, Analyzer analyzer, SolrQueryRequest req,
+      SpanQueryBuilder spanFactory) {
+    super(defaultField, analyzer, req, spanFactory);
+  }
+
+  public Query getQuery(Element e) throws ParserException {
+    return implGetQuery(e, false);
+  }
+
+  public SpanQuery getSpanQuery(Element e) throws ParserException {
+    return (SpanQuery)implGetQuery(e, true);
+  }
+
+  public Query implGetQuery(Element e, boolean span) throws ParserException {
+    Term term = null;
+    final String fieldName = DOMUtils.getAttributeWithInheritanceOrFail(e, "fieldName");
+    for (Node node = e.getFirstChild(); node != null; node = node.getNextSibling()) {
+      if (node.getNodeType() == Node.ELEMENT_NODE &&
+          node.getNodeName().equals("Word")) {
+        final String word = DOMUtils.getNonBlankTextOrFail((Element) node);
+        final Term t = new Term(fieldName, word);
+        if (term == null || term.text().length() < t.text().length()) {
+          term = t;
+        }
+      }
+    }
+    return (span ? new SpanTermQuery(term) : new TermQuery(term));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java b/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java
index c38fb6b..f76015f 100644
--- a/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java
+++ b/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java
@@ -19,20 +19,22 @@ package org.apache.solr.search;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.queryparser.xml.DOMUtils;
 import org.apache.lucene.queryparser.xml.ParserException;
-import org.apache.lucene.queryparser.xml.QueryBuilder;
+import org.apache.lucene.queryparser.xml.builders.SpanQueryBuilder;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.search.spans.SpanOrQuery;
+import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.solr.request.SolrQueryRequest;
 import org.w3c.dom.Element;
 
 // A simple test query builder to demonstrate use of
 // SolrQueryBuilder's queryFactory constructor argument.
-public class HandyQueryBuilder extends SolrQueryBuilder {
+public class HandyQueryBuilder extends SolrSpanQueryBuilder {
 
   public HandyQueryBuilder(String defaultField, Analyzer analyzer,
-      SolrQueryRequest req, QueryBuilder queryFactory) {
-    super(defaultField, analyzer, req, queryFactory);
+      SolrQueryRequest req, SpanQueryBuilder spanFactory) {
+    super(defaultField, analyzer, req, spanFactory);
   }
 
   public Query getQuery(Element e) throws ParserException {
@@ -44,9 +46,24 @@ public class HandyQueryBuilder extends SolrQueryBuilder {
     return bq.build();
   }
 
+  public SpanQuery getSpanQuery(Element e) throws ParserException {
+    SpanQuery subQueries[] = {
+        getSubSpanQuery(e, "Left"),
+        getSubSpanQuery(e, "Right"),
+    };
+
+    return new SpanOrQuery(subQueries);
+  }
+
   private Query getSubQuery(Element e, String name) throws ParserException {
     Element subE = DOMUtils.getChildByTagOrFail(e, name);
     subE = DOMUtils.getFirstChildOrFail(subE);
     return queryFactory.getQuery(subE);
   }
+
+  private SpanQuery getSubSpanQuery(Element e, String name) throws ParserException {
+    Element subE = DOMUtils.getChildByTagOrFail(e, name);
+    subE = DOMUtils.getFirstChildOrFail(subE);
+    return spanFactory.getSpanQuery(subE);
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/search/TestFiltering.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestFiltering.java b/solr/core/src/test/org/apache/solr/search/TestFiltering.java
index 579361b..9f9a51a 100644
--- a/solr/core/src/test/org/apache/solr/search/TestFiltering.java
+++ b/solr/core/src/test/org/apache/solr/search/TestFiltering.java
@@ -18,6 +18,7 @@ package org.apache.solr.search;
 
 
 import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.Query;
 import org.apache.lucene.util.FixedBitSet;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrInputDocument;
@@ -42,8 +43,63 @@ public class TestFiltering extends SolrTestCaseJ4 {
     initCore("solrconfig.xml","schema_latest.xml");
   }
 
+  @Test
+  public void testLiveDocsSharing() throws Exception {
+    clearIndex();
+    for (int i=0; i<20; i++) {
+      for (int repeat=0; repeat < (i%5==0 ? 2 : 1); repeat++) {
+        assertU(adoc("id", Integer.toString(i), "foo_s", "foo", "val_i", Integer.toString(i), "val_s", Character.toString((char)('A' + i))));
+      }
+    }
+    assertU(commit());
+
+    String[] queries = {
+        "foo_s:foo",
+        "foo_s:f*",
+        "*:*",
+        "id:[* TO *]",
+        "id:[0 TO 99]",
+        "val_i:[0 TO 20]",
+        "val_s:[A TO z]"
+    };
+
+    SolrQueryRequest req = req();
+    try {
+      SolrIndexSearcher searcher = req.getSearcher();
+
+      DocSet live = null;
+      for (String qstr :  queries) {
+        Query q = QParser.getParser(qstr, null, req).getQuery();
+        // System.out.println("getting set for " + q);
+        DocSet set = searcher.getDocSet(q);
+        if (live == null) {
+          live = searcher.getLiveDocs();
+        }
+        assertTrue( set == live);
+
+        QueryCommand cmd = new QueryCommand();
+        cmd.setQuery( QParser.getParser(qstr, null, req).getQuery() );
+        cmd.setLen(random().nextInt(30));
+        cmd.setNeedDocSet(true);
+        QueryResult res = new QueryResult();
+        searcher.search(res, cmd);
+        set = res.getDocSet();
+        assertTrue( set == live );
+
+        cmd.setQuery( QParser.getParser(qstr + " OR id:0", null, req).getQuery() );
+        cmd.setFilterList( QParser.getParser(qstr + " OR id:1", null, req).getQuery() );
+        res = new QueryResult();
+        searcher.search(res, cmd);
+        set = res.getDocSet();
+        assertTrue( set == live );
+      }
+
+    } finally {
+      req.close();
+    }
+  }
 
-  public void testCaching() throws Exception {
+    public void testCaching() throws Exception {
     clearIndex();
     assertU(adoc("id","4", "val_i","1"));
     assertU(adoc("id","1", "val_i","2"));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/search/TestSolrCoreParser.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrCoreParser.java b/solr/core/src/test/org/apache/solr/search/TestSolrCoreParser.java
index 3ef96c3..79740e6 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrCoreParser.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrCoreParser.java
@@ -24,13 +24,18 @@ import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.MockAnalyzer;
 import org.apache.lucene.analysis.MockTokenFilter;
 import org.apache.lucene.analysis.MockTokenizer;
+import org.apache.lucene.index.Term;
 import org.apache.lucene.queryparser.xml.CoreParser;
 import org.apache.lucene.queryparser.xml.ParserException;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.spans.SpanBoostQuery;
 import org.apache.lucene.search.spans.SpanNearQuery;
+import org.apache.lucene.search.spans.SpanOrQuery;
+import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.lucene.search.spans.SpanTermQuery;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.solr.common.util.NamedList;
@@ -52,6 +57,7 @@ public class TestSolrCoreParser extends LuceneTestCase {
         args.add("GoodbyeQuery", GoodbyeQueryBuilder.class.getCanonicalName());
         args.add("HandyQuery", HandyQueryBuilder.class.getCanonicalName());
         args.add("ApacheLuceneSolr", ApacheLuceneSolrNearQueryBuilder.class.getCanonicalName());
+        args.add("ChooseOneWord", ChooseOneWordQueryBuilder.class.getCanonicalName());
         solrCoreParser.init(args);
       }
     }
@@ -85,6 +91,10 @@ public class TestSolrCoreParser extends LuceneTestCase {
   public void testApacheLuceneSolr() throws IOException, ParserException {
     final String fieldName = "contents";
     final Query query = parseXmlString("<ApacheLuceneSolr fieldName='"+fieldName+"'/>");
+    checkApacheLuceneSolr(query, fieldName);
+  }
+
+  private static void checkApacheLuceneSolr(Query query, String fieldName) {
     assertTrue(query instanceof SpanNearQuery);
     final SpanNearQuery snq = (SpanNearQuery)query;
     assertEquals(fieldName, snq.getField());
@@ -96,6 +106,7 @@ public class TestSolrCoreParser extends LuceneTestCase {
     assertTrue(snq.getClauses()[2] instanceof SpanTermQuery);
   }
 
+  // test custom query (HandyQueryBuilder) wrapping a Query
   public void testHandyQuery() throws IOException, ParserException {
     final String lhsXml = "<HelloQuery/>";
     final String rhsXml = "<GoodbyeQuery/>";
@@ -107,4 +118,101 @@ public class TestSolrCoreParser extends LuceneTestCase {
     assertTrue(bq.clauses().get(1).getQuery() instanceof MatchNoDocsQuery);
   }
 
+  private static SpanQuery unwrapSpanBoostQuery(Query query) {
+    assertTrue(query instanceof SpanBoostQuery);
+    final SpanBoostQuery spanBoostQuery = (SpanBoostQuery)query;
+    return spanBoostQuery.getQuery();
+  }
+
+  // test custom query (HandyQueryBuilder) wrapping a SpanQuery
+  public void testHandySpanQuery() throws IOException, ParserException {
+    final String lhsXml = "<SpanOr fieldName='contents'>"
+        + "<SpanTerm>rain</SpanTerm>"
+        + "<SpanTerm>spain</SpanTerm>"
+        + "<SpanTerm>plain</SpanTerm>"
+        + "</SpanOr>";
+    final String rhsXml = "<SpanNear fieldName='contents' slop='2' inOrder='true'>"
+        + "<SpanTerm>sunny</SpanTerm>"
+        + "<SpanTerm>sky</SpanTerm>"
+        + "</SpanNear>";
+    final Query query = parseHandyQuery(lhsXml, rhsXml);
+    final BooleanQuery bq = (BooleanQuery)query;
+    assertEquals(2, bq.clauses().size());
+    for (int ii=0; ii<bq.clauses().size(); ++ii) {
+      final Query clauseQuery = bq.clauses().get(ii).getQuery();
+      switch (ii) {
+        case 0:
+          assertTrue(unwrapSpanBoostQuery(clauseQuery) instanceof SpanOrQuery);
+          break;
+        case 1:
+          assertTrue(unwrapSpanBoostQuery(clauseQuery) instanceof SpanNearQuery);
+          break;
+        default:
+          fail("unexpected clause index "+ii);
+      }
+    }
+  }
+
+  private static String composeChooseOneWordQueryXml(String fieldName, String... termTexts) {
+    final StringBuilder sb = new StringBuilder("<ChooseOneWord fieldName='"+fieldName+"'>");
+    for (String termText : termTexts) {
+      sb.append("<Word>").append(termText).append("</Word>");
+    }
+    sb.append("</ChooseOneWord>");
+    return sb.toString();
+  }
+
+  // test custom queries being wrapped in a Query or SpanQuery
+  public void testCustomQueryWrapping() throws IOException, ParserException {
+    final boolean span = random().nextBoolean();
+    // the custom queries
+    final String fieldName = "contents";
+    final String[] randomTerms = new String[] {"bumble", "honey", "solitary"};
+    final String randomQuery = composeChooseOneWordQueryXml(fieldName, randomTerms);
+    final String apacheLuceneSolr = "<ApacheLuceneSolr fieldName='"+fieldName+"'/>";
+    // the wrapping query
+    final String parentQuery = (span ? "SpanOr" : "BooleanQuery");
+    final String subQueryPrefix = (span ? "" : "<Clause occurs='must'>");
+    final String subQuerySuffix = (span ? "" : "</Clause>");
+    final String xml = "<"+parentQuery+">"
+        + subQueryPrefix+randomQuery+subQuerySuffix
+        + subQueryPrefix+apacheLuceneSolr+subQuerySuffix
+        + "</"+parentQuery+">";
+    // the test
+    final Query query = parseXmlString(xml);
+    if (span) {
+      assertTrue(unwrapSpanBoostQuery(query) instanceof SpanOrQuery);
+      final SpanOrQuery soq = (SpanOrQuery)unwrapSpanBoostQuery(query);
+      assertEquals(2, soq.getClauses().length);
+      checkChooseOneWordQuery(span, soq.getClauses()[0], fieldName, randomTerms);
+      checkApacheLuceneSolr(soq.getClauses()[1], fieldName);
+    } else {
+      assertTrue(query instanceof BooleanQuery);
+      final BooleanQuery bq = (BooleanQuery)query;
+      assertEquals(2, bq.clauses().size());
+      checkChooseOneWordQuery(span, bq.clauses().get(0).getQuery(), fieldName, randomTerms);
+      checkApacheLuceneSolr(bq.clauses().get(1).getQuery(), fieldName);
+    }
+  }
+
+  private static void checkChooseOneWordQuery(boolean span, Query query, String fieldName, String ... expectedTermTexts) {
+    final Term term;
+    if (span) {
+      assertTrue(query instanceof SpanTermQuery);
+      final SpanTermQuery stq = (SpanTermQuery)query;
+      term = stq.getTerm();
+    } else {
+      assertTrue(query instanceof TermQuery);
+      final TermQuery tq = (TermQuery)query;
+      term = tq.getTerm();
+    }
+    final String text = term.text();
+    boolean foundExpected = false;
+    for (String expected : expectedTermTexts) {
+      foundExpected |= expected.equals(text);
+    }
+    assertEquals(fieldName, term.field());
+    assertTrue("expected term text ("+text+") not found in ("+expectedTermTexts+")", foundExpected);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
index 137fcdd..397f4e8 100644
--- a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
@@ -85,6 +85,10 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
 
     String authcPrefix = "/admin/authentication";
     String authzPrefix = "/admin/authorization";
+    if(random().nextBoolean()){
+      authcPrefix = "/v2/cluster/security/authentication";
+      authzPrefix = "/v2/cluster/security/authorization";
+    }
 
     NamedList<Object> rsp;
     HttpClient cl = null;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java b/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java
index 0f70d73..03656c5 100644
--- a/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java
+++ b/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java
@@ -312,7 +312,7 @@ public class TestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
     perms.runCmd("{set-permission : {name: config-edit, role: admin } }", true);
     assertEquals("config-edit",  getObjectByPath(perms.conf, false, "permissions[0]/name"));
     assertEquals(1 , perms.getVal("permissions[0]/index"));
-    assertEquals("admin" ,  perms.getVal("permissions[0]/role"));
+    assertEquals("admin", perms.getVal("permissions[0]/role"));
     perms.runCmd("{set-permission : {name: config-edit, role: [admin, dev], index:2 } }", false);
     perms.runCmd("{set-permission : {name: config-edit, role: [admin, dev], index:1}}", true);
     Collection roles = (Collection) perms.getVal("permissions[0]/role");
@@ -324,19 +324,19 @@ public class TestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
     assertEquals("x", perms.getVal("permissions[1]/collection"));
     assertEquals("/a/b", perms.getVal("permissions[1]/path"));
     perms.runCmd("{update-permission : {index : 2, method : POST }}", true);
-    assertEquals("POST" , perms.getVal("permissions[1]/method"));
+    assertEquals("POST", perms.getVal("permissions[1]/method"));
     perms.runCmd("{set-permission : {name : read, collection : y, role:[guest, dev] ,  before :2}}", true);
     assertNotNull(perms.getVal("permissions[2]"));
     assertEquals("y", perms.getVal("permissions[1]/collection"));
     assertEquals("read", perms.getVal("permissions[1]/name"));
     perms.runCmd("{delete-permission : 3}", true);
     assertTrue(captureErrors(perms.parsedCommands).isEmpty());
-    assertEquals("y",perms.getVal("permissions[1]/collection"));
+    assertEquals("y", perms.getVal("permissions[1]/collection"));
   }
 
   static class  Perms {
     Map conf =  new HashMap<>();
-    RuleBasedAuthorizationPlugin plugin = new RuleBasedAuthorizationPlugin();
+    ConfigEditablePlugin plugin = new RuleBasedAuthorizationPlugin();
     List<CommandOperation> parsedCommands;
 
     public void runCmd(String cmds, boolean failOnError) throws IOException {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/security/hadoop/TestDelegationWithHadoopAuth.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/hadoop/TestDelegationWithHadoopAuth.java b/solr/core/src/test/org/apache/solr/security/hadoop/TestDelegationWithHadoopAuth.java
index 37c6b8c..c799296 100644
--- a/solr/core/src/test/org/apache/solr/security/hadoop/TestDelegationWithHadoopAuth.java
+++ b/solr/core/src/test/org/apache/solr/security/hadoop/TestDelegationWithHadoopAuth.java
@@ -268,6 +268,7 @@ public class TestDelegationWithHadoopAuth extends SolrCloudTestCase {
   }
 
   @Test
+  @AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/HADOOP-14044")
   public void testDelegationTokenCancelFail() throws Exception {
     // cancel a bogus token
     cancelDelegationToken("BOGUS", ErrorCode.NOT_FOUND.code, primarySolrClient);


[12/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/api/ApiBag.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/ApiBag.java b/solr/core/src/java/org/apache/solr/api/ApiBag.java
new file mode 100644
index 0000000..82d6a39
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/ApiBag.java
@@ -0,0 +1,354 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.api;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.core.PluginBag;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.handler.RequestHandlerUtils;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.request.SolrRequestHandler;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.security.PermissionNameProvider;
+import org.apache.solr.util.CommandOperation;
+import org.apache.solr.util.JsonSchemaValidator;
+import org.apache.solr.util.PathTrie;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.client.solrj.SolrRequest.SUPPORTED_METHODS;
+import static org.apache.solr.common.params.CommonParams.NAME;
+import static org.apache.solr.common.util.StrUtils.formatString;
+import static org.apache.solr.common.util.ValidatingJsonMap.ENUM_OF;
+import static org.apache.solr.common.util.ValidatingJsonMap.NOT_NULL;
+
+public class ApiBag {
+  private final boolean isCoreSpecific;
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  private final Map<String, PathTrie<Api>> apis = new ConcurrentHashMap<>();
+
+  public ApiBag(boolean isCoreSpecific) {
+    this.isCoreSpecific = isCoreSpecific;
+  }
+
+  public synchronized void register(Api api, Map<String, String> nameSubstitutes) {
+    try {
+      validateAndRegister(api, nameSubstitutes);
+    } catch (Exception e) {
+      log.error("Unable to register plugin:" + api.getClass().getName() + "with spec :" + Utils.toJSONString(api.getSpec()), e);
+      if (e instanceof RuntimeException) {
+        throw (RuntimeException) e;
+      } else {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+      }
+
+    }
+  }
+
+  private void validateAndRegister(Api api, Map<String, String> nameSubstitutes) {
+    ValidatingJsonMap spec = api.getSpec();
+    Api introspect = new IntrospectApi(api, isCoreSpecific);
+    List<String> methods = spec.getList("methods", ENUM_OF, SUPPORTED_METHODS);
+    for (String method : methods) {
+      PathTrie<Api> registry = apis.get(method);
+
+      if (registry == null) apis.put(method, registry = new PathTrie<>(ImmutableSet.of("_introspect")));
+      ValidatingJsonMap url = spec.getMap("url", NOT_NULL);
+      ValidatingJsonMap params = url.getMap("params", null);
+      if (params != null) {
+        for (Object o : params.keySet()) {
+          ValidatingJsonMap param = params.getMap(o.toString(), NOT_NULL);
+          param.get("type", ENUM_OF, KNOWN_TYPES);
+        }
+      }
+      List<String> paths = url.getList("paths", NOT_NULL);
+      ValidatingJsonMap parts = url.getMap("parts", null);
+      if (parts != null) {
+        Set<String> wildCardNames = getWildCardNames(paths);
+        for (Object o : parts.keySet()) {
+          if (!wildCardNames.contains(o.toString()))
+            throw new RuntimeException("" + o + " is not a valid part name");
+          ValidatingJsonMap pathMeta = parts.getMap(o.toString(), NOT_NULL);
+          pathMeta.get("type", ENUM_OF, ImmutableSet.of("enum", "string", "int", "number", "boolean"));
+        }
+      }
+      verifyCommands(api.getSpec());
+      for (String path : paths) {
+        registry.insert(path, nameSubstitutes, api);
+        registerIntrospect(nameSubstitutes, registry, path, introspect);
+      }
+    }
+  }
+
+  public static void registerIntrospect(Map<String, String> nameSubstitutes, PathTrie<Api> registry, String path, Api introspect) {
+    List<String> l = PathTrie.getPathSegments(path);
+    registerIntrospect(l, registry, nameSubstitutes, introspect);
+    int lastIdx = l.size() - 1;
+    for (int i = lastIdx; i >= 0; i--) {
+      String itemAt = l.get(i);
+      if (PathTrie.templateName(itemAt) == null) break;
+      l.remove(i);
+      if (registry.lookup(l, new HashMap<>()) != null) break;
+      registerIntrospect(l, registry, nameSubstitutes, introspect);
+    }
+  }
+
+  static void registerIntrospect(List<String> l, PathTrie<Api> registry, Map<String, String> substitutes, Api introspect) {
+    ArrayList<String> copy = new ArrayList<>(l);
+    copy.add("_introspect");
+    registry.insert(copy, substitutes, introspect);
+  }
+
+  public static class IntrospectApi extends Api {
+    Api baseApi;
+    final boolean isCoreSpecific;
+
+    public IntrospectApi(Api base, boolean isCoreSpecific) {
+      super(EMPTY_SPEC);
+      this.baseApi = base;
+      this.isCoreSpecific = isCoreSpecific;
+    }
+
+    public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+
+      String cmd = req.getParams().get("command");
+      ValidatingJsonMap result = null;
+      if (cmd == null) {
+        result = isCoreSpecific ? ValidatingJsonMap.getDeepCopy(baseApi.getSpec(), 5, true) : baseApi.getSpec();
+      } else {
+        ValidatingJsonMap specCopy = ValidatingJsonMap.getDeepCopy(baseApi.getSpec(), 5, true);
+        ValidatingJsonMap commands = specCopy.getMap("commands", null);
+        if (commands != null) {
+          ValidatingJsonMap m = commands.getMap(cmd, null);
+          specCopy.put("commands", Collections.singletonMap(cmd, m));
+        }
+        result = specCopy;
+      }
+      if (isCoreSpecific) {
+        List<String> pieces = req.getHttpSolrCall() == null ? null : ((V2HttpCall) req.getHttpSolrCall()).pieces;
+        if (pieces != null) {
+          String prefix = "/" + pieces.get(0) + "/" + pieces.get(1);
+          List<String> paths = result.getMap("url", NOT_NULL).getList("paths", NOT_NULL);
+          result.getMap("url", NOT_NULL).put("paths",
+              paths.stream()
+                  .map(s -> prefix + s)
+                  .collect(Collectors.toList()));
+        }
+      }
+      List l = (List) rsp.getValues().get("spec");
+      if (l == null) rsp.getValues().add("spec", l = new ArrayList());
+      l.add(result);
+      RequestHandlerUtils.addExperimentalFormatWarning(rsp);
+    }
+  }
+
+  public static Map<String, JsonSchemaValidator> getParsedSchema(ValidatingJsonMap commands) {
+    Map<String, JsonSchemaValidator> validators = new HashMap<>();
+    for (Object o : commands.entrySet()) {
+      Map.Entry cmd = (Map.Entry) o;
+      try {
+        validators.put((String) cmd.getKey(), new JsonSchemaValidator((Map) cmd.getValue()));
+      } catch (Exception e) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in api spec", e);
+      }
+    }
+    return validators;
+  }
+
+
+  private void verifyCommands(ValidatingJsonMap spec) {
+    ValidatingJsonMap commands = spec.getMap("commands", null);
+    if (commands == null) return;
+    getParsedSchema(commands);
+
+  }
+
+  private Set<String> getWildCardNames(List<String> paths) {
+    Set<String> wildCardNames = new HashSet<>();
+    for (String path : paths) {
+      List<String> p = PathTrie.getPathSegments(path);
+      for (String s : p) {
+        String wildCard = PathTrie.templateName(s);
+        if (wildCard != null) wildCardNames.add(wildCard);
+      }
+    }
+    return wildCardNames;
+  }
+
+
+  public Api lookup(String path, String httpMethod, Map<String, String> parts) {
+    if (httpMethod == null) {
+      for (PathTrie<Api> trie : apis.values()) {
+        Api api = trie.lookup(path, parts);
+        if (api != null) return api;
+      }
+      return null;
+    } else {
+      PathTrie<Api> registry = apis.get(httpMethod);
+      if (registry == null) return null;
+      return registry.lookup(path, parts);
+    }
+  }
+
+  public static SpecProvider getSpec(final String name) {
+    return () -> {
+      return ValidatingJsonMap.parse(APISPEC_LOCATION + name + ".json", APISPEC_LOCATION);
+    };
+  }
+
+  public static class ReqHandlerToApi extends Api implements PermissionNameProvider {
+    SolrRequestHandler rh;
+
+    public ReqHandlerToApi(SolrRequestHandler rh, SpecProvider spec) {
+      super(spec);
+      this.rh = rh;
+    }
+
+    @Override
+    public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+      rh.handleRequest(req, rsp);
+    }
+
+    @Override
+    public Name getPermissionName(AuthorizationContext ctx) {
+      if (rh instanceof PermissionNameProvider) {
+        return ((PermissionNameProvider) rh).getPermissionName(ctx);
+      }
+      return null;
+    }
+  }
+
+  public static List<Api> wrapRequestHandlers(final SolrRequestHandler rh, String... specs) {
+    ImmutableList.Builder<Api> b = ImmutableList.builder();
+    for (String spec : specs) b.add(new ReqHandlerToApi(rh, ApiBag.getSpec(spec)));
+    return b.build();
+  }
+
+  public static final String APISPEC_LOCATION = "apispec/";
+  public static final String INTROSPECT = "/_introspect";
+
+
+  public static final SpecProvider EMPTY_SPEC = () -> ValidatingJsonMap.EMPTY;
+  public static final String HANDLER_NAME = "handlerName";
+  public static final Set<String> KNOWN_TYPES = ImmutableSet.of("string", "boolean", "list", "int", "double", "object");
+
+  public PathTrie<Api> getRegistry(String method) {
+    return apis.get(method);
+  }
+
+  public void registerLazy(PluginBag.PluginHolder<SolrRequestHandler> holder, PluginInfo info) {
+    String specName = info.attributes.get("spec");
+    if (specName == null) specName = "emptySpec";
+    register(new LazyLoadedApi(ApiBag.getSpec(specName), holder), Collections.singletonMap(HANDLER_NAME, info.attributes.get(NAME)));
+  }
+
+  public static SpecProvider constructSpec(PluginInfo info) {
+    Object specObj = info == null ? null : info.attributes.get("spec");
+    if (specObj == null) specObj = "emptySpec";
+    if (specObj instanceof Map) {
+      Map map = (Map) specObj;
+      return () -> ValidatingJsonMap.getDeepCopy(map, 4, false);
+    } else {
+      return ApiBag.getSpec((String) specObj);
+    }
+  }
+
+  public static List<CommandOperation> getCommandOperations(Reader reader, Map<String, JsonSchemaValidator> validators, boolean validate) {
+    List<CommandOperation> parsedCommands = null;
+    try {
+      parsedCommands = CommandOperation.parse(reader);
+    } catch (IOException e) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
+    }
+    if (validators == null || !validate) {    // no validation possible because we do not have a spec
+      return parsedCommands;
+    }
+
+    List<CommandOperation> commandsCopy = CommandOperation.clone(parsedCommands);
+
+    for (CommandOperation cmd : commandsCopy) {
+      JsonSchemaValidator validator = validators.get(cmd.name);
+      if (validator == null) {
+        cmd.addError(formatString("Unknown operation ''{0}'' available ops are ''{1}''", cmd.name,
+            validators.keySet()));
+        continue;
+      } else {
+        List<String> errs = validator.validateJson(cmd.getCommandData());
+        if (errs != null) for (String err : errs) cmd.addError(err);
+      }
+
+    }
+    List<Map> errs = CommandOperation.captureErrors(commandsCopy);
+    if (!errs.isEmpty()) {
+      throw new ExceptionWithErrObject(SolrException.ErrorCode.BAD_REQUEST, "Error in command payload", errs);
+    }
+    return commandsCopy;
+  }
+
+  public static class ExceptionWithErrObject extends SolrException {
+    private List<Map> errs;
+
+    public ExceptionWithErrObject(ErrorCode code, String msg, List<Map> errs) {
+      super(code, msg);
+      this.errs = errs;
+    }
+
+    public List<Map> getErrs() {
+      return errs;
+    }
+  }
+
+  public static class LazyLoadedApi extends Api {
+
+    private final PluginBag.PluginHolder<SolrRequestHandler> holder;
+    private Api delegate;
+
+    protected LazyLoadedApi(SpecProvider specProvider, PluginBag.PluginHolder<SolrRequestHandler> lazyPluginHolder) {
+      super(specProvider);
+      this.holder = lazyPluginHolder;
+    }
+
+    @Override
+    public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+      if (!holder.isLoaded()) {
+        delegate = new ReqHandlerToApi(holder.get(), ApiBag.EMPTY_SPEC);
+      }
+      delegate.call(req, rsp);
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/api/ApiSupport.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/ApiSupport.java b/solr/core/src/java/org/apache/solr/api/ApiSupport.java
new file mode 100644
index 0000000..ca1e866
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/ApiSupport.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.api;
+
+import java.util.Collection;
+
+/**The interface that is implemented by a request handler to support the V2 end point
+ *
+ */
+public interface ApiSupport {
+
+  /**It is possible to support multiple v2 apis by a single requesthandler
+   *
+   * @return the list of v2 api implementations
+   */
+  Collection<Api> getApis();
+
+  /**Whether this should be made available at the regular legacy path
+   */
+  default Boolean registerV1() {
+    return Boolean.TRUE;
+  }
+
+  /**Whether this request handler must be made available at the /v2/ path
+   */
+  default Boolean registerV2() {
+    return Boolean.FALSE;
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/api/SpecProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/SpecProvider.java b/solr/core/src/java/org/apache/solr/api/SpecProvider.java
new file mode 100644
index 0000000..c373c99
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/SpecProvider.java
@@ -0,0 +1,25 @@
+
+/*
+ * 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.
+ */
+package org.apache.solr.api;
+import org.apache.solr.common.util.ValidatingJsonMap;
+
+/**A generic interface for any class that is capable of providing its specification as a json schema
+ */
+public interface SpecProvider {
+  ValidatingJsonMap getSpec();
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
new file mode 100644
index 0000000..4a053dc
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
@@ -0,0 +1,340 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.api;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.cloud.DocCollection;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.PluginBag;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.request.SolrRequestHandler;
+import org.apache.solr.response.QueryResponseWriter;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.servlet.HttpSolrCall;
+import org.apache.solr.servlet.SolrDispatchFilter;
+import org.apache.solr.servlet.SolrRequestParsers;
+import org.apache.solr.util.JsonSchemaValidator;
+import org.apache.solr.util.PathTrie;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.common.params.CommonParams.JSON;
+import static org.apache.solr.common.params.CommonParams.WT;
+import static org.apache.solr.servlet.SolrDispatchFilter.Action.ADMIN;
+import static org.apache.solr.servlet.SolrDispatchFilter.Action.PASSTHROUGH;
+import static org.apache.solr.servlet.SolrDispatchFilter.Action.PROCESS;
+import static org.apache.solr.util.PathTrie.getPathSegments;
+
+// class that handle the '/v2' path
+public class V2HttpCall extends HttpSolrCall {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  private Api api;
+  List<String> pieces;
+  private String prefix;
+  HashMap<String, String> parts = new HashMap<>();
+  static final Set<String> knownPrefixes = ImmutableSet.of("cluster", "node", "collections", "cores", "c");
+
+  public V2HttpCall(SolrDispatchFilter solrDispatchFilter, CoreContainer cc,
+                    HttpServletRequest request, HttpServletResponse response, boolean retry) {
+    super(solrDispatchFilter, cc, request, response, retry);
+  }
+
+  protected void init() throws Exception {
+    String path = this.path;
+    String fullPath = path = path.substring(3);//strip off '/v2'
+    try {
+      pieces = getPathSegments(path);
+      if (pieces.size() == 0) {
+        prefix = "c";
+        path = "/c";
+      } else {
+        prefix = pieces.get(0);
+      }
+
+      boolean isCompositeApi = false;
+      if (knownPrefixes.contains(prefix)) {
+        api = getApiInfo(cores.getRequestHandlers(), path, req.getMethod(), fullPath, parts);
+        if (api != null) {
+          isCompositeApi = api instanceof CompositeApi;
+          if (!isCompositeApi) {
+            initAdminRequest(path);
+            return;
+          }
+        }
+      }
+
+      if ("c".equals(prefix) || "collections".equals(prefix)) {
+        String collectionName = origCorename = corename = pieces.get(1);
+        DocCollection collection = getDocCollection(collectionName);
+        if (collection == null) {
+           if ( ! path.endsWith(ApiBag.INTROSPECT)) {
+            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "no such collection or alias");
+          }
+        } else {
+          boolean isPreferLeader = false;
+          if (path.endsWith("/update") || path.contains("/update/")) {
+            isPreferLeader = true;
+          }
+          core = getCoreByCollection(collection.getName(), isPreferLeader);
+          if (core == null) {
+            //this collection exists , but this node does not have a replica for that collection
+            //todo find a better way to compute remote
+            extractRemotePath(corename, origCorename, 0);
+            return;
+          }
+        }
+      } else if ("cores".equals(prefix)) {
+        origCorename = corename = pieces.get(1);
+        core = cores.getCore(corename);
+      }
+      if (core == null) {
+        log.error(">> path: '" + path + "'");
+        if (path.endsWith(ApiBag.INTROSPECT)) {
+          initAdminRequest(path);
+          return;
+        } else {
+          throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "no core retrieved for " + corename);
+        }
+      }
+
+      this.path = path = path.substring(prefix.length() + pieces.get(1).length() + 2);
+      Api apiInfo = getApiInfo(core.getRequestHandlers(), path, req.getMethod(), fullPath, parts);
+      if (isCompositeApi && apiInfo instanceof CompositeApi) {
+        ((CompositeApi) this.api).add(apiInfo);
+      } else {
+        api = apiInfo;
+      }
+      MDCLoggingContext.setCore(core);
+      parseRequest();
+
+      if (usingAliases) {
+        processAliases(aliases, collectionsList);
+      }
+
+      action = PROCESS;
+      // we are done with a valid handler
+    } catch (RuntimeException rte) {
+      log.error("Error in init()", rte);
+      throw rte;
+    } finally {
+      if (api == null) action = PASSTHROUGH;
+      if (solrReq != null) solrReq.getContext().put(CommonParams.PATH, path);
+    }
+  }
+
+  private void initAdminRequest(String path) throws Exception {
+    solrReq = SolrRequestParsers.DEFAULT.parse(null, path, req);
+    solrReq.getContext().put(CoreContainer.class.getName(), cores);
+    requestType = AuthorizationContext.RequestType.ADMIN;
+    action = ADMIN;
+  }
+
+  protected void parseRequest() throws Exception {
+    config = core.getSolrConfig();
+    // get or create/cache the parser for the core
+    SolrRequestParsers parser = config.getRequestParsers();
+
+    // With a valid handler and a valid core...
+
+    if (solrReq == null) solrReq = parser.parse(core, path, req);
+  }
+
+  protected DocCollection getDocCollection(String collectionName) {
+    if (!cores.isZooKeeperAware()) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Solr not running in cloud mode ");
+    }
+    ZkStateReader zkStateReader = cores.getZkController().getZkStateReader();
+    DocCollection collection = zkStateReader.getClusterState().getCollectionOrNull(collectionName);
+    if (collection == null) {
+      collectionName = corename = lookupAliases(collectionName);
+      collection = zkStateReader.getClusterState().getCollectionOrNull(collectionName);
+    }
+    return collection;
+  }
+
+  public static Api getApiInfo(PluginBag<SolrRequestHandler> requestHandlers,
+                               String path, String method,
+                               String fullPath,
+                               Map<String, String> parts) {
+    fullPath = fullPath == null ? path : fullPath;
+    Api api = requestHandlers.v2lookup(path, method, parts);
+    if (api == null && path.endsWith(ApiBag.INTROSPECT)) {
+      // the particular http method does not have any ,
+      // just try if any other method has this path
+      api = requestHandlers.v2lookup(path, null, parts);
+    }
+
+    if (api == null) {
+      return getSubPathApi(requestHandlers, path, fullPath, new CompositeApi(null));
+    }
+
+    if (api instanceof ApiBag.IntrospectApi) {
+      final Map<String, Api> apis = new LinkedHashMap<>();
+      for (String m : SolrRequest.SUPPORTED_METHODS) {
+        Api x = requestHandlers.v2lookup(path, m, parts);
+        if (x != null) apis.put(m, x);
+      }
+      api = new CompositeApi(new Api(ApiBag.EMPTY_SPEC) {
+        @Override
+        public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+          String method = req.getParams().get("method");
+          Set<Api> added = new HashSet<>();
+          for (Map.Entry<String, Api> e : apis.entrySet()) {
+            if (method == null || e.getKey().equals(method)) {
+              if (!added.contains(e.getValue())) {
+                e.getValue().call(req, rsp);
+                added.add(e.getValue());
+              }
+            }
+          }
+        }
+      });
+      getSubPathApi(requestHandlers,path, fullPath, (CompositeApi) api);
+    }
+
+
+    return api;
+  }
+
+  private static CompositeApi getSubPathApi(PluginBag<SolrRequestHandler> requestHandlers, String path, String fullPath, CompositeApi compositeApi) {
+
+    String newPath = path.endsWith(ApiBag.INTROSPECT) ? path.substring(0, path.length() - ApiBag.INTROSPECT.length()) : path;
+    Map<String, Set<String>> subpaths = new LinkedHashMap<>();
+
+    getSubPaths(newPath, requestHandlers.getApiBag(), subpaths);
+    final Map<String, Set<String>> subPaths = subpaths;
+    if (subPaths.isEmpty()) return null;
+    return compositeApi.add(new Api(() -> ValidatingJsonMap.EMPTY) {
+      @Override
+      public void call(SolrQueryRequest req1, SolrQueryResponse rsp) {
+        String prefix = null;
+        prefix = fullPath.endsWith(ApiBag.INTROSPECT) ?
+            fullPath.substring(0, fullPath.length() - ApiBag.INTROSPECT.length()) :
+            fullPath;
+        LinkedHashMap<String, Set<String>> result = new LinkedHashMap<>(subPaths.size());
+        for (Map.Entry<String, Set<String>> e : subPaths.entrySet()) {
+          if (e.getKey().endsWith(ApiBag.INTROSPECT)) continue;
+          result.put(prefix + e.getKey(), e.getValue());
+        }
+
+        Map m = (Map) rsp.getValues().get("availableSubPaths");
+        if(m != null){
+          m.putAll(result);
+        } else {
+          rsp.add("availableSubPaths", result);
+        }
+      }
+    });
+  }
+
+  private static void getSubPaths(String path, ApiBag bag, Map<String, Set<String>> pathsVsMethod) {
+    for (SolrRequest.METHOD m : SolrRequest.METHOD.values()) {
+      PathTrie<Api> registry = bag.getRegistry(m.toString());
+      if (registry != null) {
+        HashSet<String> subPaths = new HashSet<>();
+        registry.lookup(path, new HashMap<>(), subPaths);
+        for (String subPath : subPaths) {
+          Set<String> supportedMethods = pathsVsMethod.get(subPath);
+          if (supportedMethods == null) pathsVsMethod.put(subPath, supportedMethods = new HashSet<>());
+          supportedMethods.add(m.toString());
+        }
+      }
+    }
+  }
+
+  public static class CompositeApi extends Api {
+    private LinkedList<Api> apis = new LinkedList<>();
+
+    public CompositeApi(Api api) {
+      super(ApiBag.EMPTY_SPEC);
+      if (api != null) apis.add(api);
+    }
+
+    @Override
+    public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+      for (Api api : apis) {
+        api.call(req, rsp);
+      }
+
+    }
+
+    public CompositeApi add(Api api) {
+      apis.add(api);
+      return this;
+    }
+  }
+
+  @Override
+  protected void handleAdmin(SolrQueryResponse solrResp) {
+    api.call(this.solrReq, solrResp);
+  }
+
+  @Override
+  protected void execute(SolrQueryResponse rsp) {
+    try {
+      api.call(solrReq, rsp);
+    } catch (RuntimeException e) {
+      throw e;
+    }
+  }
+
+  @Override
+  protected Object _getHandler() {
+    return api;
+  }
+
+  public Map<String,String> getUrlParts(){
+    return parts;
+  }
+
+  @Override
+  protected QueryResponseWriter getResponseWriter() {
+    String wt = solrReq.getParams().get(WT, JSON);
+    if (core != null) return core.getResponseWriters().get(wt);
+    return SolrCore.DEFAULT_RESPONSE_WRITERS.get(wt);
+  }
+
+  @Override
+  protected ValidatingJsonMap getSpec() {
+    return api == null ? null : api.getSpec();
+  }
+
+  @Override
+  protected Map<String, JsonSchemaValidator> getValidators() {
+    return api == null ? null : api.getCommandSchema();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/api/package-info.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/package-info.java b/solr/core/src/java/org/apache/solr/api/package-info.java
new file mode 100644
index 0000000..c3574c7
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+/**
+ * Commonly used classes for Solr V2 API.
+ */
+package org.apache.solr.api;
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/cloud/Assign.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/Assign.java b/solr/core/src/java/org/apache/solr/cloud/Assign.java
index e6e08f9..ba03ccd 100644
--- a/solr/core/src/java/org/apache/solr/cloud/Assign.java
+++ b/solr/core/src/java/org/apache/solr/cloud/Assign.java
@@ -146,10 +146,16 @@ public class Assign {
   // could be created on live nodes given maxShardsPerNode, Replication factor (if from createShard) etc.
   public static List<ReplicaCount> getNodesForNewReplicas(ClusterState clusterState, String collectionName,
                                                           String shard, int numberOfNodes,
-                                                          String createNodeSetStr, CoreContainer cc) {
+                                                          Object createNodeSet, CoreContainer cc) {
     DocCollection coll = clusterState.getCollection(collectionName);
     Integer maxShardsPerNode = coll.getInt(MAX_SHARDS_PER_NODE, 1);
-    List<String> createNodeList = createNodeSetStr  == null ? null: StrUtils.splitSmart(createNodeSetStr, ",", true);
+    List<String> createNodeList = null;
+
+    if (createNodeSet instanceof List) {
+      createNodeList = (List) createNodeSet;
+    } else {
+      createNodeList = createNodeSet == null ? null : StrUtils.splitSmart((String) createNodeSet, ",", true);
+    }
 
      HashMap<String, ReplicaCount> nodeNameVsShardCount = getNodeNameVsShardCount(collectionName, clusterState, createNodeList);
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/cloud/CreateShardCmd.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/CreateShardCmd.java b/solr/core/src/java/org/apache/solr/cloud/CreateShardCmd.java
index 3d5aa41..52df32b 100644
--- a/solr/core/src/java/org/apache/solr/cloud/CreateShardCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/CreateShardCmd.java
@@ -68,7 +68,7 @@ public class CreateShardCmd implements Cmd {
     ShardHandler shardHandler = ocmh.shardHandlerFactory.getShardHandler();
     DocCollection collection = clusterState.getCollection(collectionName);
     int repFactor = message.getInt(REPLICATION_FACTOR, collection.getInt(REPLICATION_FACTOR, 1));
-    String createNodeSetStr = message.getStr(OverseerCollectionMessageHandler.CREATE_NODE_SET);
+    Object createNodeSetStr = message.get(OverseerCollectionMessageHandler.CREATE_NODE_SET);
     List<Assign.ReplicaCount> sortedNodeList = getNodesForNewReplicas(clusterState, collectionName, sliceName, repFactor,
         createNodeSetStr, ocmh.overseer.getZkController().getCoreContainer());
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/cloud/DeleteCollectionCmd.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/DeleteCollectionCmd.java b/solr/core/src/java/org/apache/solr/cloud/DeleteCollectionCmd.java
index 4c5ae00..b891c92 100644
--- a/solr/core/src/java/org/apache/solr/cloud/DeleteCollectionCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/DeleteCollectionCmd.java
@@ -28,12 +28,14 @@ import java.util.concurrent.TimeUnit;
 import org.apache.solr.common.NonExistentCoreException;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ClusterState;
+import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.snapshots.SolrSnapshotManager;
 import org.apache.solr.util.TimeOut;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
@@ -56,6 +58,11 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd
     ZkStateReader zkStateReader = ocmh.zkStateReader;
     final String collection = message.getStr(NAME);
     try {
+      // Remove the snapshots meta-data for this collection in ZK. Deleting actual index files
+      // should be taken care of as part of collection delete operation.
+      SolrZkClient zkClient = zkStateReader.getZkClient();
+      SolrSnapshotManager.cleanupCollectionLevelSnapshots(zkClient, collection);
+
       if (zkStateReader.getClusterState().getCollectionOrNull(collection) == null) {
         if (zkStateReader.getZkClient().exists(ZkStateReader.COLLECTIONS_ZKNODE + "/" + collection, true)) {
           // if the collection is not in the clusterstate, but is listed in zk, do nothing, it will just

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/core/CoreContainer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 023e7b1..f7a8f33 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -77,6 +77,7 @@ import org.apache.solr.handler.admin.ZookeeperInfoHandler;
 import org.apache.solr.handler.component.ShardHandlerFactory;
 import org.apache.solr.logging.LogWatcher;
 import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.metrics.SolrCoreMetricManager;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.request.SolrRequestHandler;
@@ -678,7 +679,16 @@ public class CoreContainer {
       }
       if (backgroundCloser != null) { // Doesn't seem right, but tests get in here without initializing the core.
         try {
-          backgroundCloser.join();
+          while (true) {
+            backgroundCloser.join(15000);
+            if (backgroundCloser.isAlive()) {
+              synchronized (solrCores.getModifyLock()) {
+                solrCores.getModifyLock().notifyAll(); // there is a race we have to protect against
+              }
+            } else {
+              break;
+            }
+          }
         } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
           if (log.isDebugEnabled()) {
@@ -1183,15 +1193,15 @@ public class CoreContainer {
     SolrCore core = solrCores.remove(name);
     coresLocator.delete(this, cd);
 
-    // delete metrics specific to this core
-    metricManager.removeRegistry(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, name));
-
     if (core == null) {
       // transient core
       SolrCore.deleteUnloadedCore(cd, deleteDataDir, deleteInstanceDir);
       return;
     }
 
+    // delete metrics specific to this core
+    metricManager.removeRegistry(core.getCoreMetricManager().getRegistryName());
+
     if (zkSys.getZkController() != null) {
       // cancel recovery in cloud mode
       core.getSolrCoreState().cancelRecovery();
@@ -1217,6 +1227,9 @@ public class CoreContainer {
     SolrIdentifierValidator.validateCoreName(toName);
     try (SolrCore core = getCore(name)) {
       if (core != null) {
+        String oldRegistryName = core.getCoreMetricManager().getRegistryName();
+        String newRegistryName = SolrCoreMetricManager.createRegistryName(core.getCoreDescriptor().getCollectionName(), toName);
+        metricManager.swapRegistries(oldRegistryName, newRegistryName);
         registerCore(toName, core, true, false);
         SolrCore old = solrCores.remove(name);
         coresLocator.rename(this, old.getCoreDescriptor(), core.getCoreDescriptor());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/core/DirectoryFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/DirectoryFactory.java b/solr/core/src/java/org/apache/solr/core/DirectoryFactory.java
index 9dd0d8a..e4f0c5e 100644
--- a/solr/core/src/java/org/apache/solr/core/DirectoryFactory.java
+++ b/solr/core/src/java/org/apache/solr/core/DirectoryFactory.java
@@ -23,8 +23,10 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.nio.file.NoSuchFileException;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.lucene.store.Directory;
@@ -326,7 +328,7 @@ public abstract class DirectoryFactory implements NamedListInitializedPlugin,
     return Collections.emptySet();
   }
 
-  public void cleanupOldIndexDirectories(final String dataDirPath, final String currentIndexDirPath) {
+  public void cleanupOldIndexDirectories(final String dataDirPath, final String currentIndexDirPath, boolean afterCoreReload) {
     File dataDir = new File(dataDirPath);
     if (!dataDir.isDirectory()) {
       log.debug("{} does not point to a valid data directory; skipping clean-up of old index directories.", dataDirPath);
@@ -347,9 +349,17 @@ public abstract class DirectoryFactory implements NamedListInitializedPlugin,
     if (oldIndexDirs == null || oldIndexDirs.length == 0)
       return; // nothing to do (no log message needed)
 
-    log.info("Found {} old index directories to clean-up under {}", oldIndexDirs.length, dataDirPath);
-    for (File dir : oldIndexDirs) {
-
+    List<File> dirsList = Arrays.asList(oldIndexDirs);
+    Collections.sort(dirsList, Collections.reverseOrder());
+    
+    int i = 0;
+    if (afterCoreReload) {
+      log.info("Will not remove most recent old directory after reload {}", oldIndexDirs[0]);
+      i = 1;
+    }
+    log.info("Found {} old index directories to clean-up under {} afterReload={}", oldIndexDirs.length - i, dataDirPath, afterCoreReload);
+    for (; i < dirsList.size(); i++) {
+      File dir = dirsList.get(i);
       String dirToRmPath = dir.getAbsolutePath();
       try {
         if (deleteOldIndexDirectory(dirToRmPath)) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/core/EphemeralDirectoryFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/EphemeralDirectoryFactory.java b/solr/core/src/java/org/apache/solr/core/EphemeralDirectoryFactory.java
index cee7860..d5bcbb8 100644
--- a/solr/core/src/java/org/apache/solr/core/EphemeralDirectoryFactory.java
+++ b/solr/core/src/java/org/apache/solr/core/EphemeralDirectoryFactory.java
@@ -16,14 +16,18 @@
  */
 package org.apache.solr.core;
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 
 import org.apache.lucene.store.Directory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Directory provider for implementations that do not persist over reboots.
  * 
  */
 public abstract class EphemeralDirectoryFactory extends CachingDirectoryFactory {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   
   @Override
   public boolean exists(String path) throws IOException {
@@ -61,5 +65,9 @@ public abstract class EphemeralDirectoryFactory extends CachingDirectoryFactory
   public void remove(String path) throws IOException {
     // ram dir does not persist its dir anywhere
   }
+  
+  public void cleanupOldIndexDirectories(final String dataDirPath, final String currentIndexDirPath, boolean reload) {
+    // currently a no-op
+  }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/core/HdfsDirectoryFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/HdfsDirectoryFactory.java b/solr/core/src/java/org/apache/solr/core/HdfsDirectoryFactory.java
index e1e3d6e..db953d3 100644
--- a/solr/core/src/java/org/apache/solr/core/HdfsDirectoryFactory.java
+++ b/solr/core/src/java/org/apache/solr/core/HdfsDirectoryFactory.java
@@ -21,8 +21,11 @@ import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.net.URLEncoder;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -505,7 +508,7 @@ public class HdfsDirectoryFactory extends CachingDirectoryFactory implements Sol
   }
 
   @Override
-  public void cleanupOldIndexDirectories(final String dataDir, final String currentIndexDir) {
+  public void cleanupOldIndexDirectories(final String dataDir, final String currentIndexDir, boolean afterReload) {
 
     // Get the FileSystem object
     final Path dataDirPath = new Path(dataDir);
@@ -549,13 +552,27 @@ public class HdfsDirectoryFactory extends CachingDirectoryFactory implements Sol
     } catch (IOException ioExc) {
       LOG.error("Error checking for old index directories to clean-up.", ioExc);
     }
+    
+    List<Path> oldIndexPaths = new ArrayList<>(oldIndexDirs.length);
+    for (FileStatus ofs : oldIndexDirs) {
+      oldIndexPaths.add(ofs.getPath());
+    }
 
     if (oldIndexDirs == null || oldIndexDirs.length == 0)
       return; // nothing to clean-up
 
+    Collections.sort(oldIndexPaths, Collections.reverseOrder());
+    
     Set<String> livePaths = getLivePaths();
-    for (FileStatus oldDir : oldIndexDirs) {
-      Path oldDirPath = oldDir.getPath();
+    
+    int i = 0;
+    if (afterReload) {
+      LOG.info("Will not remove most recent old directory on reload {}", oldIndexDirs[0]);
+      i = 1;
+    }
+    LOG.info("Found {} old index directories to clean-up under {} afterReload={}", oldIndexDirs.length - i, dataDirPath, afterReload);
+    for (; i < oldIndexPaths.size(); i++) {
+      Path oldDirPath = oldIndexPaths.get(i);
       if (livePaths.contains(oldDirPath.toString())) {
         LOG.warn("Cannot delete directory {} because it is still being referenced in the cache.", oldDirPath);
       } else {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/core/MetricsDirectoryFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/MetricsDirectoryFactory.java b/solr/core/src/java/org/apache/solr/core/MetricsDirectoryFactory.java
index f441579..b567434 100644
--- a/solr/core/src/java/org/apache/solr/core/MetricsDirectoryFactory.java
+++ b/solr/core/src/java/org/apache/solr/core/MetricsDirectoryFactory.java
@@ -169,8 +169,8 @@ public class MetricsDirectoryFactory extends DirectoryFactory implements SolrCor
   }
 
   @Override
-  public void cleanupOldIndexDirectories(String dataDirPath, String currentIndexDirPath) {
-    in.cleanupOldIndexDirectories(dataDirPath, currentIndexDirPath);
+  public void cleanupOldIndexDirectories(String dataDirPath, String currentIndexDirPath, boolean reload) {
+    in.cleanupOldIndexDirectories(dataDirPath, currentIndexDirPath, reload);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/core/PluginBag.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/PluginBag.java b/solr/core/src/java/org/apache/solr/core/PluginBag.java
index 77e2379..ad8bdec 100644
--- a/solr/core/src/java/org/apache/solr/core/PluginBag.java
+++ b/solr/core/src/java/org/apache/solr/core/PluginBag.java
@@ -46,10 +46,15 @@ import org.apache.solr.util.SimplePostTool;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 import org.apache.solr.util.plugin.PluginInfoInitialized;
 import org.apache.solr.util.plugin.SolrCoreAware;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.api.ApiSupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static java.util.Collections.singletonMap;
 import static org.apache.solr.common.params.CommonParams.NAME;
+import static org.apache.solr.api.ApiBag.HANDLER_NAME;
 
 /**
  * This manages the lifecycle of a set of plugin of the same type .
@@ -63,11 +68,13 @@ public class PluginBag<T> implements AutoCloseable {
   private final Class klass;
   private SolrCore core;
   private final SolrConfig.SolrPluginInfo meta;
+  private final ApiBag apiBag;
 
   /**
    * Pass needThreadSafety=true if plugins can be added and removed concurrently with lookups.
    */
   public PluginBag(Class<T> klass, SolrCore core, boolean needThreadSafety) {
+    this.apiBag = klass == SolrRequestHandler.class ? new ApiBag(core != null) : null;
     this.core = core;
     this.klass = klass;
     // TODO: since reads will dominate writes, we could also think about creating a new instance of a map each time it changes.
@@ -174,16 +181,52 @@ public class PluginBag<T> implements AutoCloseable {
    */
   public T put(String name, T plugin) {
     if (plugin == null) return null;
-    PluginHolder<T> old = put(name, new PluginHolder<T>(null, plugin));
+    PluginHolder<T> pluginHolder = new PluginHolder<>(null, plugin);
+    pluginHolder.registerAPI = false;
+    PluginHolder<T> old = put(name, pluginHolder);
     return old == null ? null : old.get();
   }
 
-
   PluginHolder<T> put(String name, PluginHolder<T> plugin) {
-    PluginHolder<T> old = registry.put(name, plugin);
-    if (plugin.pluginInfo != null && plugin.pluginInfo.isDefault()) {
-      setDefault(name);
+    Boolean registerApi = null;
+    Boolean disableHandler = null;
+    if (plugin.pluginInfo != null) {
+      String registerAt = plugin.pluginInfo.attributes.get("registerPath");
+      if (registerAt != null) {
+        List<String> strs = StrUtils.splitSmart(registerAt, ',');
+        disableHandler = !strs.contains("/");
+        registerApi = strs.contains("/v2");
+      }
+    }
+
+    if (apiBag != null) {
+      if (plugin.isLoaded()) {
+        T inst = plugin.get();
+        if (inst instanceof ApiSupport) {
+          ApiSupport apiSupport = (ApiSupport) inst;
+          if (registerApi == null) registerApi = apiSupport.registerV2();
+          if (disableHandler == null) disableHandler = !apiSupport.registerV1();
+
+          if(registerApi) {
+            Collection<Api> apis = apiSupport.getApis();
+            if (apis != null) {
+              Map<String, String> nameSubstitutes = singletonMap(HANDLER_NAME, name);
+              for (Api api : apis) {
+                apiBag.register(api, nameSubstitutes);
+              }
+            }
+          }
+
+        }
+      } else {
+        if (registerApi != null && registerApi)
+          apiBag.registerLazy((PluginHolder<SolrRequestHandler>) plugin, plugin.pluginInfo);
+      }
     }
+    if(disableHandler == null) disableHandler = Boolean.FALSE;
+    PluginHolder<T> old = null;
+    if(!disableHandler) old = registry.put(name, plugin);
+    if (plugin.pluginInfo != null && plugin.pluginInfo.isDefault()) setDefault(name);
     if (plugin.isLoaded()) registerMBean(plugin.get(), core, name);
     return old;
   }
@@ -249,7 +292,7 @@ public class PluginBag<T> implements AutoCloseable {
     return result.isLoaded();
   }
 
-  private static void registerMBean(Object inst, SolrCore core, String pluginKey) {
+  private void registerMBean(Object inst, SolrCore core, String pluginKey) {
     if (core == null) return;
     if (inst instanceof SolrInfoMBean) {
       SolrInfoMBean mBean = (SolrInfoMBean) inst;
@@ -280,6 +323,7 @@ public class PluginBag<T> implements AutoCloseable {
   public static class PluginHolder<T> implements AutoCloseable {
     private T inst;
     protected final PluginInfo pluginInfo;
+    boolean registerAPI = false;
 
     public PluginHolder(PluginInfo info) {
       this.pluginInfo = info;
@@ -321,7 +365,7 @@ public class PluginBag<T> implements AutoCloseable {
    * A class that loads plugins Lazily. When the get() method is invoked
    * the Plugin is initialized and returned.
    */
-  public static class LazyPluginHolder<T> extends PluginHolder<T> {
+  public class LazyPluginHolder<T> extends PluginHolder<T> {
     private volatile T lazyInst;
     private final SolrConfig.SolrPluginInfo pluginMeta;
     protected SolrException solrException;
@@ -516,4 +560,17 @@ public class PluginBag<T> implements AutoCloseable {
       }
     }
   }
+
+
+  public Api v2lookup(String path, String method, Map<String, String> parts) {
+    if (apiBag == null) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "this should not happen, looking up for v2 API at the wrong place");
+    }
+    return apiBag.lookup(path, method, parts);
+  }
+
+  public ApiBag getApiBag() {
+    return apiBag;
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/core/SolrCore.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index 74238e7..9e01374 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -617,34 +617,37 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
   }
 
   public SolrCore reload(ConfigSet coreConfig) throws IOException {
-    solrCoreState.increfSolrCoreState();
-    final SolrCore currentCore;
-    if (!getNewIndexDir().equals(getIndexDir())) {
-      // the directory is changing, don't pass on state
-      currentCore = null;
-    } else {
-      currentCore = this;
-    }
+    // only one reload at a time
+    synchronized (getUpdateHandler().getSolrCoreState().getReloadLock()) {
+      solrCoreState.increfSolrCoreState();
+      final SolrCore currentCore;
+      if (!getNewIndexDir().equals(getIndexDir())) {
+        // the directory is changing, don't pass on state
+        currentCore = null;
+      } else {
+        currentCore = this;
+      }
 
-    boolean success = false;
-    SolrCore core = null;
-    try {
-      CoreDescriptor cd = new CoreDescriptor(coreDescriptor.getName(), coreDescriptor);
-      cd.loadExtraProperties(); //Reload the extra properties
-      core = new SolrCore(getName(), getDataDir(), coreConfig.getSolrConfig(),
-          coreConfig.getIndexSchema(), coreConfig.getProperties(),
-          cd, updateHandler, solrDelPolicy, currentCore);
-      
-      // we open a new IndexWriter to pick up the latest config
-      core.getUpdateHandler().getSolrCoreState().newIndexWriter(core, false);
-      
-      core.getSearcher(true, false, null, true);
-      success = true;
-      return core;
-    } finally {
-      // close the new core on any errors that have occurred.
-      if (!success) {
-        IOUtils.closeQuietly(core);
+      boolean success = false;
+      SolrCore core = null;
+      try {
+        CoreDescriptor cd = new CoreDescriptor(coreDescriptor.getName(), coreDescriptor);
+        cd.loadExtraProperties(); //Reload the extra properties
+        core = new SolrCore(getName(), getDataDir(), coreConfig.getSolrConfig(),
+            coreConfig.getIndexSchema(), coreConfig.getProperties(),
+            cd, updateHandler, solrDelPolicy, currentCore, true);
+        
+        // we open a new IndexWriter to pick up the latest config
+        core.getUpdateHandler().getSolrCoreState().newIndexWriter(core, false);
+        
+        core.getSearcher(true, false, null, true);
+        success = true;
+        return core;
+      } finally {
+        // close the new core on any errors that have occurred.
+        if (!success) {
+          IOUtils.closeQuietly(core);
+        }
       }
     }
   }
@@ -686,7 +689,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
     }
   }
 
-  void initIndex(boolean reload) throws IOException {
+  void initIndex(boolean passOnPreviousState, boolean reload) throws IOException {
 
     String indexDir = getNewIndexDir();
     boolean indexExists = getDirectoryFactory().exists(indexDir);
@@ -697,7 +700,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
 
     initIndexReaderFactory();
 
-    if (indexExists && firstTime && !reload) {
+    if (indexExists && firstTime && !passOnPreviousState) {
       final String lockType = getSolrConfig().indexConfig.lockType;
       Directory dir = directoryFactory.get(indexDir, DirContext.DEFAULT, lockType);
       try {
@@ -726,7 +729,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
     }
 
 
-    cleanupOldIndexDirectories();
+    cleanupOldIndexDirectories(reload);
   }
 
 
@@ -823,7 +826,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
 
   public SolrCore(CoreDescriptor cd, ConfigSet coreConfig) {
     this(cd.getName(), null, coreConfig.getSolrConfig(), coreConfig.getIndexSchema(), coreConfig.getProperties(),
-        cd, null, null, null);
+        cd, null, null, null, false);
   }
 
   
@@ -843,7 +846,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
   public SolrCore(String name, String dataDir, SolrConfig config,
       IndexSchema schema, NamedList configSetProperties,
       CoreDescriptor coreDescriptor, UpdateHandler updateHandler,
-      IndexDeletionPolicyWrapper delPolicy, SolrCore prev) {
+      IndexDeletionPolicyWrapper delPolicy, SolrCore prev, boolean reload) {
     
     assert ObjectReleaseTracker.track(searcherExecutor); // ensure that in unclean shutdown tests we still close this
     
@@ -905,7 +908,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
       this.codec = initCodec(solrConfig, this.schema);
 
       memClassLoader = new MemClassLoader(PluginBag.RuntimeLib.getLibObjects(this, solrConfig.getPluginInfos(PluginBag.RuntimeLib.class.getName())), getResourceLoader());
-      initIndex(prev != null);
+      initIndex(prev != null, reload);
 
       initWriters();
       qParserPlugins.init(createInstances(QParserPlugin.standardPlugins), this);
@@ -1533,7 +1536,12 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
     }
 
     if (coreStateClosed) {
-
+      try {
+        cleanupOldIndexDirectories(false);
+      } catch (Exception e) {
+        SolrException.log(log, e);
+      }
+      
       try {
         directoryFactory.close();
       } catch (Throwable e) {
@@ -1542,7 +1550,6 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
           throw (Error) e;
         }
       }
-
     }
 
     if( closeHooks != null ) {
@@ -1557,6 +1564,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
          }
       }
     }
+    
     assert ObjectReleaseTracker.release(this);
   }
 
@@ -2952,16 +2960,16 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
     return false;
   }
 
-  public void cleanupOldIndexDirectories() {
+  public void cleanupOldIndexDirectories(boolean reload) {
     final DirectoryFactory myDirFactory = getDirectoryFactory();
     final String myDataDir = getDataDir();
-    final String myIndexDir = getIndexDir();
+    final String myIndexDir = getNewIndexDir(); // ensure the latest replicated index is protected 
     final String coreName = getName();
     if (myDirFactory != null && myDataDir != null && myIndexDir != null) {
       Thread cleanupThread = new Thread(() -> {
         log.debug("Looking for old index directories to cleanup for core {} in {}", coreName, myDataDir);
         try {
-          myDirFactory.cleanupOldIndexDirectories(myDataDir, myIndexDir);
+          myDirFactory.cleanupOldIndexDirectories(myDataDir, myIndexDir, reload);
         } catch (Exception exc) {
           log.error("Failed to cleanup old index directories for core "+coreName, exc);
         }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/core/SolrCores.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCores.java b/solr/core/src/java/org/apache/solr/core/SolrCores.java
index 2bcea17..b25e9bb 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCores.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCores.java
@@ -239,7 +239,9 @@ class SolrCores {
       }
       cores.put(n0, c1);
       cores.put(n1, c0);
-
+      container.getMetricManager().swapRegistries(
+          c0.getCoreMetricManager().getRegistryName(),
+          c1.getCoreMetricManager().getRegistryName());
       c0.setName(n1);
       c1.setName(n0);
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/core/snapshots/SolrSnapshotsTool.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/snapshots/SolrSnapshotsTool.java b/solr/core/src/java/org/apache/solr/core/snapshots/SolrSnapshotsTool.java
index cb1c52c..935ef63 100644
--- a/solr/core/src/java/org/apache/solr/core/snapshots/SolrSnapshotsTool.java
+++ b/solr/core/src/java/org/apache/solr/core/snapshots/SolrSnapshotsTool.java
@@ -295,6 +295,7 @@ public class SolrSnapshotsTool implements Closeable {
       Optional<String> asyncReqId) {
     try {
       CollectionAdminRequest.Backup backup = new CollectionAdminRequest.Backup(collectionName, snapshotName);
+      backup.setCommitName(snapshotName);
       backup.setIndexBackupStrategy(CollectionAdminParams.COPY_FILES_STRATEGY);
       backup.setLocation(destPath);
       if (backupRepo.isPresent()) {
@@ -350,29 +351,29 @@ public class SolrSnapshotsTool implements Closeable {
 
     if (cmd.hasOption(CREATE) || cmd.hasOption(DELETE) || cmd.hasOption(LIST) || cmd.hasOption(DESCRIBE)
         || cmd.hasOption(PREPARE_FOR_EXPORT) || cmd.hasOption(EXPORT_SNAPSHOT)) {
-      try (SolrSnapshotsTool tool = new SolrSnapshotsTool(cmd.getOptionValue(SOLR_ZK_ENSEMBLE))) {
+      try (SolrSnapshotsTool tool = new SolrSnapshotsTool(requiredArg(options, cmd, SOLR_ZK_ENSEMBLE))) {
         if (cmd.hasOption(CREATE)) {
           String snapshotName = cmd.getOptionValue(CREATE);
-          String collectionName = cmd.getOptionValue(COLLECTION);
+          String collectionName = requiredArg(options, cmd, COLLECTION);
           tool.createSnapshot(collectionName, snapshotName);
 
         } else if (cmd.hasOption(DELETE)) {
           String snapshotName = cmd.getOptionValue(DELETE);
-          String collectionName = cmd.getOptionValue(COLLECTION);
+          String collectionName = requiredArg(options, cmd, COLLECTION);
           tool.deleteSnapshot(collectionName, snapshotName);
 
         } else if (cmd.hasOption(LIST)) {
-          String collectionName = cmd.getOptionValue(COLLECTION);
+          String collectionName = requiredArg(options, cmd, COLLECTION);
           tool.listSnapshots(collectionName);
 
         } else if (cmd.hasOption(DESCRIBE)) {
           String snapshotName = cmd.getOptionValue(DESCRIBE);
-          String collectionName = cmd.getOptionValue(COLLECTION);
+          String collectionName = requiredArg(options, cmd, COLLECTION);
           tool.describeSnapshot(collectionName, snapshotName);
 
         } else if (cmd.hasOption(PREPARE_FOR_EXPORT)) {
           String snapshotName = cmd.getOptionValue(PREPARE_FOR_EXPORT);
-          String collectionName = cmd.getOptionValue(COLLECTION);
+          String collectionName = requiredArg(options, cmd, COLLECTION);
           String localFsDir = requiredArg(options, cmd, TEMP_DIR);
           String hdfsOpDir = requiredArg(options, cmd, DEST_DIR);
           Optional<String> pathPrefix = Optional.ofNullable(cmd.getOptionValue(HDFS_PATH_PREFIX));
@@ -391,7 +392,7 @@ public class SolrSnapshotsTool implements Closeable {
 
         }  else if (cmd.hasOption(EXPORT_SNAPSHOT)) {
           String snapshotName = cmd.getOptionValue(EXPORT_SNAPSHOT);
-          String collectionName = cmd.getOptionValue(COLLECTION);
+          String collectionName = requiredArg(options, cmd, COLLECTION);
           String destDir = requiredArg(options, cmd, DEST_DIR);
           Optional<String> backupRepo = Optional.ofNullable(cmd.getOptionValue(BACKUP_REPO_NAME));
           Optional<String> asyncReqId = Optional.ofNullable(cmd.getOptionValue(ASYNC_REQ_ID));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/BlobHandler.java b/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
index 25b3b14..f5b49ea 100644
--- a/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
@@ -22,6 +22,7 @@ import java.lang.invoke.MethodHandles;
 import java.math.BigInteger;
 import java.nio.ByteBuffer;
 import java.security.MessageDigest;
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -34,6 +35,8 @@ import org.apache.lucene.search.SortField;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.search.TopFieldDocs;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.CommonParams;
@@ -72,7 +75,7 @@ public class BlobHandler extends RequestHandlerBase implements PluginInfoInitial
 
   @Override
   public void handleRequestBody(final SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
-    String httpMethod = (String) req.getContext().get("httpMethod");
+    String httpMethod = req.getHttpMethod();
     String path = (String) req.getContext().get("path");
     SolrConfigHandler.setWt(req, JSON);
 
@@ -277,4 +280,13 @@ public class BlobHandler extends RequestHandlerBase implements PluginInfoInitial
     req.getCore().getRequestHandler(handler).handleRequest(r, rsp);
   }
 
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
+
+  @Override
+  public Collection<Api> getApis() {
+    return ApiBag.wrapRequestHandlers(this, "core.system.blob", "core.system.blob.upload");
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
index ecafb52..d7d5b71 100644
--- a/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
@@ -19,7 +19,9 @@ package org.apache.solr.handler;
 import java.io.IOException;
 import java.io.Reader;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.solr.common.util.ContentStream;
@@ -39,6 +41,15 @@ public class DumpRequestHandler extends RequestHandlerBase
   {
     // Show params
     rsp.add( "params", req.getParams().toNamedList() );
+    String[] parts = req.getParams().getParams("urlTemplateValues");
+    if (parts != null && parts.length > 0) {
+      Map map = new LinkedHashMap<>();
+      rsp.getValues().add("urlTemplateValues", map);
+      for (String part : parts) {
+        map.put(part, req.getPathTemplateValues().get(part));
+      }
+    }
+
     String[] returnParams = req.getParams().getParams("param");
     if(returnParams !=null) {
       NamedList params = (NamedList) rsp.getValues().get("params");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java b/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
index 968af61..8634aee 100644
--- a/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
+++ b/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
@@ -54,7 +54,6 @@ import java.util.zip.Adler32;
 import java.util.zip.Checksum;
 import java.util.zip.InflaterInputStream;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.http.client.HttpClient;
 import org.apache.lucene.codecs.CodecUtil;
 import org.apache.lucene.index.IndexCommit;
@@ -75,6 +74,7 @@ import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.FastInputStream;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SuppressForbidden;
 import org.apache.solr.core.DirectoryFactory;
@@ -182,7 +182,13 @@ public class IndexFetcher {
     useInternalCompression = INTERNAL.equals(compress);
     useExternalCompression = EXTERNAL.equals(compress);
     connTimeout = getParameter(initArgs, HttpClientUtil.PROP_CONNECTION_TIMEOUT, 30000, null);
-    soTimeout = getParameter(initArgs, HttpClientUtil.PROP_SO_TIMEOUT, 120000, null);
+    
+    // allow a master override for tests - you specify this in /replication slave section of solrconfig and some 
+    // test don't want to define this
+    soTimeout = Integer.getInteger("solr.indexfetcher.sotimeout", -1);
+    if (soTimeout == -1) {
+      soTimeout = getParameter(initArgs, HttpClientUtil.PROP_SO_TIMEOUT, 120000, null);
+    }
 
     String httpBasicAuthUser = (String) initArgs.get(HttpClientUtil.PROP_BASIC_AUTH_USER);
     String httpBasicAuthPassword = (String) initArgs.get(HttpClientUtil.PROP_BASIC_AUTH_PASS);
@@ -325,6 +331,7 @@ public class IndexFetcher {
       }
 
       LOG.info("Slave's generation: " + commit.getGeneration());
+      LOG.info("Slave's version: " + IndexDeletionPolicyWrapper.getCommitTimestamp(commit));
 
       if (latestVersion == 0L) {
         if (forceReplication && commit.getGeneration() != 0) {
@@ -459,7 +466,7 @@ public class IndexFetcher {
             downloadConfFiles(confFilesToDownload, latestGeneration);
             if (isFullCopyNeeded) {
               successfulInstall = solrCore.modifyIndexProps(tmpIdxDirName);
-              deleteTmpIdxDir = false;
+              if (successfulInstall) deleteTmpIdxDir = false;
             } else {
               successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
             }
@@ -487,7 +494,7 @@ public class IndexFetcher {
             terminateAndWaitFsyncService();
             if (isFullCopyNeeded) {
               successfulInstall = solrCore.modifyIndexProps(tmpIdxDirName);
-              deleteTmpIdxDir = false;
+              if (successfulInstall) deleteTmpIdxDir = false;
             } else {
               successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
             }
@@ -565,7 +572,8 @@ public class IndexFetcher {
         try {
           logReplicationTimeAndConfFiles(null, successfulInstall);
         } catch (Exception e) {
-          LOG.error("caught", e);
+          // this can happen on shutdown, a fetch may be running in a thread after DirectoryFactory is closed
+          LOG.warn("Could not log failed replication details", e);
         }
       }
 
@@ -583,25 +591,32 @@ public class IndexFetcher {
       stop = false;
       fsyncException = null;
     } finally {
-      if (deleteTmpIdxDir && tmpIndexDir != null) {
-        try {
+      // order below is important
+      try {
+        if (tmpIndexDir != null && deleteTmpIdxDir) {
           core.getDirectoryFactory().doneWithDirectory(tmpIndexDir);
           core.getDirectoryFactory().remove(tmpIndexDir);
-        } catch (IOException e) {
-          SolrException.log(LOG, "Error removing directory " + tmpIndexDir, e);
         }
-      }
-
-      if (tmpIndexDir != null) {
-        core.getDirectoryFactory().release(tmpIndexDir);
-      }
-
-      if (indexDir != null) {
-        core.getDirectoryFactory().release(indexDir);
-      }
-
-      if (tmpTlogDir != null) {
-        delTree(tmpTlogDir);
+      } catch (Exception e) {
+        SolrException.log(LOG, e);
+      } finally {
+        try {
+          if (tmpIndexDir != null) core.getDirectoryFactory().release(tmpIndexDir);
+        } catch (Exception e) {
+          SolrException.log(LOG, e);
+        }
+        try {
+          if (indexDir != null) {
+            core.getDirectoryFactory().release(indexDir);
+          }
+        } catch (Exception e) {
+          SolrException.log(LOG, e);
+        }
+        try {
+          if (tmpTlogDir != null) delTree(tmpTlogDir);
+        } catch (Exception e) {
+          SolrException.log(LOG, e);
+        }
       }
     }
   }
@@ -863,8 +878,9 @@ public class IndexFetcher {
       String filename = (String) file.get(NAME);
       long size = (Long) file.get(SIZE);
       CompareResult compareResult = compareFile(indexDir, filename, size, (Long) file.get(CHECKSUM));
-      if (!compareResult.equal || downloadCompleteIndex
-          || filesToAlwaysDownloadIfNoChecksums(filename, size, compareResult)) {
+      boolean alwaysDownload = filesToAlwaysDownloadIfNoChecksums(filename, size, compareResult);
+      LOG.debug("Downloading file={} size={} checksum={} alwaysDownload={}", filename, size, file.get(CHECKSUM), alwaysDownload);
+      if (!compareResult.equal || downloadCompleteIndex || alwaysDownload) {
         dirFileFetcher = new DirectoryFileFetcher(tmpIndexDir, file,
             (String) file.get(NAME), FILE, latestGeneration);
         currentFile = file;
@@ -915,7 +931,7 @@ public class IndexFetcher {
             compareResult.equal = true;
             return compareResult;
           } else {
-            LOG.warn(
+            LOG.info(
                 "File {} did not match. expected length is {} and actual length is {}", filename, backupIndexFileLen, indexFileLen);
             compareResult.equal = false;
             return compareResult;
@@ -1349,15 +1365,15 @@ public class IndexFetcher {
   private class FileFetcher {
     private final FileInterface file;
     private boolean includeChecksum = true;
-    private String fileName;
-    private String saveAs;
-    private String solrParamOutput;
-    private Long indexGen;
+    private final String fileName;
+    private final String saveAs;
+    private final String solrParamOutput;
+    private final Long indexGen;
 
-    private long size;
+    private final long size;
     private long bytesDownloaded = 0;
     private byte[] buf = new byte[1024 * 1024];
-    private Checksum checksum;
+    private final Checksum checksum;
     private int errorCount = 0;
     private boolean aborted = false;
 
@@ -1369,8 +1385,11 @@ public class IndexFetcher {
       this.solrParamOutput = solrParamOutput;
       this.saveAs = saveAs;
       indexGen = latestGen;
-      if (includeChecksum)
+      if (includeChecksum) {
         checksum = new Adler32();
+      } else {
+        checksum = null;
+      }
     }
 
     public long getBytesDownloaded() {
@@ -1381,6 +1400,21 @@ public class IndexFetcher {
      * The main method which downloads file
      */
     public void fetchFile() throws Exception {
+      bytesDownloaded = 0;
+      try {
+        fetch();
+      } catch(Exception e) {
+        if (!aborted) {
+          SolrException.log(IndexFetcher.LOG, "Error fetching file, doing one retry...", e);
+          // one retry
+          fetch();
+        } else {
+          throw e;
+        }
+      }
+    }
+    
+    private void fetch() throws Exception {
       try {
         while (true) {
           final FastInputStream is = getStream();
@@ -1569,7 +1603,7 @@ public class IndexFetcher {
         return new FastInputStream(is);
       } catch (Exception e) {
         //close stream on error
-        IOUtils.closeQuietly(is);
+        org.apache.commons.io.IOUtils.closeQuietly(is);
         throw new IOException("Could not download file '" + fileName + "'", e);
       }
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
index 04b930a..8230bf5 100644
--- a/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
@@ -330,6 +330,11 @@ public class PingRequestHandler extends RequestHandlerBase implements SolrCoreAw
   }
 
   @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
+
+  @Override
   public Category getCategory() {
     return Category.ADMIN;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/RealTimeGetHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/RealTimeGetHandler.java b/solr/core/src/java/org/apache/solr/handler/RealTimeGetHandler.java
index 6c9b0a9..9049318 100644
--- a/solr/core/src/java/org/apache/solr/handler/RealTimeGetHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/RealTimeGetHandler.java
@@ -16,12 +16,16 @@
  */
 package org.apache.solr.handler;
 
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
 import org.apache.solr.handler.component.*;
 
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
+
 public class RealTimeGetHandler extends SearchHandler {
   @Override
   protected List<String> getDefaultComponents()
@@ -42,6 +46,16 @@ public class RealTimeGetHandler extends SearchHandler {
   public URL[] getDocs() {
     return null;
   }
+
+  @Override
+  public Collection<Api> getApis() {
+    return ApiBag.wrapRequestHandlers(this, "core.RealtimeGet");
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
index b875144..cdbadc4 100644
--- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
@@ -29,6 +29,8 @@ import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -1418,9 +1420,10 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
       params = solrParams;
       delPolicy = core.getDeletionPolicy();
 
-      fileName = params.get(FILE);
-      cfileName = params.get(CONF_FILE_SHORT);
-      tlogFileName = params.get(TLOG_FILE);
+      fileName = validateFilenameOrError(params.get(FILE));
+      cfileName = validateFilenameOrError(params.get(CONF_FILE_SHORT));
+      tlogFileName = validateFilenameOrError(params.get(TLOG_FILE));
+      
       sOffset = params.get(OFFSET);
       sLen = params.get(LEN);
       compress = params.get(COMPRESSION);
@@ -1434,6 +1437,22 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
       rateLimiter = new RateLimiter.SimpleRateLimiter(maxWriteMBPerSec);
     }
 
+    // Throw exception on directory traversal attempts 
+    protected String validateFilenameOrError(String filename) {
+      if (filename != null) {
+        Path filePath = Paths.get(filename);
+        filePath.forEach(subpath -> {
+          if ("..".equals(subpath.toString())) {
+            throw new SolrException(ErrorCode.FORBIDDEN, "File name cannot contain ..");
+          }
+        });
+        if (filePath.isAbsolute()) {
+          throw new SolrException(ErrorCode.FORBIDDEN, "File name must be relative");
+        }
+        return filename;
+      } else return null;
+    }
+
     protected void initWrite() throws IOException {
       if (sOffset != null) offset = Long.parseLong(sOffset);
       if (sLen != null) len = Integer.parseInt(sLen);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index b70c096..3c6f5fa 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -18,7 +18,9 @@ package org.apache.solr.handler;
 
 import java.lang.invoke.MethodHandles;
 import java.net.URL;
+import java.util.Collection;
 
+import com.google.common.collect.ImmutableList;
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Meter;
 import com.codahale.metrics.Timer;
@@ -37,6 +39,9 @@ import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.search.SyntaxError;
 import org.apache.solr.util.SolrPluginUtils;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.api.ApiSupport;
 import org.apache.solr.util.stats.MetricUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,7 +51,7 @@ import static org.apache.solr.core.RequestParams.USEPARAM;
 /**
  *
  */
-public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfoMBean, SolrMetricProducer, NestedRequestHandler {
+public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfoMBean, SolrMetricProducer, NestedRequestHandler,ApiSupport {
 
   protected NamedList initArgs = null;
   protected SolrParams defaults;
@@ -290,6 +295,11 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
     MetricUtils.addMetrics(lst, requestTimes);
     return lst;
   }
+
+  @Override
+  public Collection<Api> getApis() {
+    return ImmutableList.of(new ApiBag.ReqHandlerToApi(this, ApiBag.constructSpec(pluginInfo)));
+  }
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java b/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java
index 9c2d45c..f3e503e 100644
--- a/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java
@@ -19,17 +19,19 @@ package org.apache.solr.handler;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
 import org.apache.solr.cloud.ZkSolrResourceLoader;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
@@ -86,15 +88,12 @@ public class SchemaHandler extends RequestHandlerBase implements SolrCoreAware,
         return;
       }
 
-      for (ContentStream stream : req.getContentStreams()) {
-        try {
-          List errs = new SchemaManager(req).performOperations(stream.getReader());
-          if (!errs.isEmpty()) rsp.add("errors", errs);
-        } catch (IOException e) {
-          rsp.add("errors", Collections.singletonList("Error reading input String " + e.getMessage()));
-          rsp.setException(e);
-        }
-        break;
+      try {
+        List errs = new SchemaManager(req).performOperations();
+        if (!errs.isEmpty()) rsp.add("errors", errs);
+      } catch (IOException e) {
+        rsp.add("errors", Collections.singletonList("Error reading input String " + e.getMessage()));
+        rsp.setException(e);
       }
     } else {
       handleGET(req, rsp);
@@ -260,4 +259,20 @@ public class SchemaHandler extends RequestHandlerBase implements SolrCoreAware,
   public void inform(SolrCore core) {
     isImmutableConfigSet = SolrConfigHandler.getImmutable(core);
   }
+
+  @Override
+  public Collection<Api> getApis() {
+    return ApiBag.wrapRequestHandlers(this, "core.SchemaRead",
+        "core.SchemaRead.fields",
+        "core.SchemaRead.copyFields",
+        "core.SchemaEdit",
+        "core.SchemaRead.dynamicFields_fieldTypes"
+        );
+
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
index 1c584b1..2660cba 100644
--- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
@@ -76,6 +76,8 @@ import org.apache.solr.util.DefaultSolrThreadFactory;
 import org.apache.solr.util.RTimer;
 import org.apache.solr.util.SolrPluginUtils;
 import org.apache.solr.util.plugin.SolrCoreAware;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -895,4 +897,18 @@ public class SolrConfigHandler extends RequestHandlerBase implements SolrCoreAwa
       return null;
     }
   }
+
+  @Override
+  public Collection<Api> getApis() {
+    return ApiBag.wrapRequestHandlers(this,
+        "core.config",
+        "core.config.Commands",
+        "core.config.Params",
+        "core.config.Params.Commands");
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 }


[13/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointTermQueryConstantScoreWrapper.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointTermQueryConstantScoreWrapper.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointTermQueryConstantScoreWrapper.java
deleted file mode 100644
index a5344b7..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointTermQueryConstantScoreWrapper.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.search;
-
-import java.io.IOException;
-
-import org.apache.lucene.index.LeafReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.PostingsEnum;
-import org.apache.lucene.index.SortedNumericDocValues;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.search.ConstantScoreScorer;
-import org.apache.lucene.search.ConstantScoreWeight;
-import org.apache.lucene.search.DocIdSet;
-import org.apache.lucene.search.DocIdSetIterator;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.TwoPhaseIterator;
-import org.apache.lucene.search.Weight;
-import org.apache.lucene.spatial.geopoint.document.GeoPointField;
-import org.apache.lucene.util.BitSet;
-import org.apache.lucene.util.DocIdSetBuilder;
-import org.apache.lucene.util.FixedBitSet;
-import org.apache.lucene.util.SparseFixedBitSet;
-
-/**
- * Custom ConstantScoreWrapper for {@code GeoPointMultiTermQuery} that cuts over to DocValues
- * for post filtering boundary ranges. Multi-valued GeoPoint documents are supported.
- *
- * @lucene.experimental
- */
-final class GeoPointTermQueryConstantScoreWrapper <Q extends GeoPointMultiTermQuery> extends Query {
-  protected final Q query;
-
-  protected GeoPointTermQueryConstantScoreWrapper(Q query) {
-    this.query = query;
-  }
-
-  /**
-   * Returns the encapsulated query.
-   */
-  public Q getQuery() {
-    return query;
-  }
-
-  @Override
-  public String toString(String field) {
-    return query.toString();
-  }
-
-  @Override
-  public final boolean equals(final Object other) {
-    return sameClassAs(other) &&
-           query.equals(((GeoPointTermQueryConstantScoreWrapper<?>) other).query);
-  }
-
-  @Override
-  public final int hashCode() {
-    return 31 * classHash() + query.hashCode();
-  }
-
-  @Override
-  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
-    return new ConstantScoreWeight(this, boost) {
-
-      @Override
-      public Scorer scorer(LeafReaderContext context) throws IOException {
-        final Terms terms = context.reader().terms(query.getField());
-        if (terms == null) {
-          return null;
-        }
-
-        final GeoPointTermsEnum termsEnum = (GeoPointTermsEnum)(query.getTermsEnum(terms, null));
-        assert termsEnum != null;
-
-        LeafReader reader = context.reader();
-        // approximation (postfiltering has not yet been applied)
-        DocIdSetBuilder builder = new DocIdSetBuilder(reader.maxDoc(), terms);
-        // subset of documents that need no postfiltering, this is purely an optimization
-        final BitSet preApproved;
-        // dumb heuristic: if the field is really sparse, use a sparse impl
-        if (terms.getDocCount() * 100L < reader.maxDoc()) {
-          preApproved = new SparseFixedBitSet(reader.maxDoc());
-        } else {
-          preApproved = new FixedBitSet(reader.maxDoc());
-        }
-        PostingsEnum docs = null;
-
-        while (termsEnum.next() != null) {
-          docs = termsEnum.postings(docs, PostingsEnum.NONE);
-          // boundary terms need post filtering
-          if (termsEnum.boundaryTerm()) {
-            builder.add(docs);
-          } else {
-            int numDocs = termsEnum.docFreq();
-            DocIdSetBuilder.BulkAdder adder = builder.grow(numDocs);
-            for (int i = 0; i < numDocs; ++i) {
-              int docId = docs.nextDoc();
-              adder.add(docId);
-              preApproved.set(docId);
-            }
-          }
-        }
-
-        DocIdSet set = builder.build();
-        final DocIdSetIterator disi = set.iterator();
-        if (disi == null) {
-          return null;
-        }
-
-        // return two-phase iterator using docvalues to postfilter candidates
-        SortedNumericDocValues sdv = reader.getSortedNumericDocValues(query.getField());
-        TwoPhaseIterator iterator = new TwoPhaseIterator(disi) {
-          @Override
-          public boolean matches() throws IOException {
-            int docId = disi.docID();
-            if (preApproved.get(docId)) {
-              return true;
-            } else {
-              if (docId > sdv.docID()) {
-                sdv.advance(docId);
-              }
-              if (docId == sdv.docID()) {
-                int count = sdv.docValueCount();
-                for (int i = 0; i < count; i++) {
-                  long hash = sdv.nextValue();
-                  if (termsEnum.postFilter(GeoPointField.decodeLatitude(hash), GeoPointField.decodeLongitude(hash))) {
-                    return true;
-                  }
-                }
-              }
-              return false;
-            }
-          }
-
-          @Override
-          public float matchCost() {
-            return 20; // TODO: make this fancier
-          }
-        };
-        return new ConstantScoreScorer(this, score(), iterator);
-      }
-    };
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointTermsEnum.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointTermsEnum.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointTermsEnum.java
deleted file mode 100644
index 533597d..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointTermsEnum.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.search;
-
-import org.apache.lucene.index.FilteredTermsEnum;
-import org.apache.lucene.index.PointValues.Relation;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.BytesRefBuilder;
-import org.apache.lucene.spatial.geopoint.document.GeoPointField;
-
-import static org.apache.lucene.spatial.geopoint.document.GeoPointField.decodeLatitude;
-import static org.apache.lucene.spatial.geopoint.document.GeoPointField.decodeLongitude;
-import static org.apache.lucene.spatial.geopoint.document.GeoPointField.geoCodedToPrefixCoded;
-import static org.apache.lucene.spatial.geopoint.document.GeoPointField.prefixCodedToGeoCoded;
-import static org.apache.lucene.spatial.geopoint.document.GeoPointField.getPrefixCodedShift;
-
-/**
- * Decomposes a given {@link GeoPointMultiTermQuery} into a set of terms that represent the query criteria. The terms
- * are then enumerated by the {@link GeoPointTermQueryConstantScoreWrapper} and all docs whose GeoPoint fields match
- * the prefix terms or pass the {@link GeoPointMultiTermQuery.CellComparator#postFilter} criteria are returned in the
- * resulting DocIdSet.
- *
- *  @lucene.experimental
- */
-final class GeoPointTermsEnum extends FilteredTermsEnum {
-  private final short maxShift;
-  private final GeoPointMultiTermQuery.CellComparator relationImpl;
-  private final BytesRefBuilder currentCellBRB;
-  private final Range range;
-
-  private short shift;    // shift mask
-  private long start;     // range start as encoded long
-  private long end;       // range end as encoded long
-  private boolean hasNext = false;
-
-  public GeoPointTermsEnum(final TermsEnum tenum, final GeoPointMultiTermQuery query) {
-    super(tenum);
-    this.maxShift = query.maxShift;
-    this.relationImpl = query.cellComparator;
-    // start shift at maxShift value (from computeMaxShift)
-    this.shift = maxShift;
-    final long mask = (1L << shift) - 1;
-    this.start = query.minEncoded & ~mask;
-    this.end = start | mask;
-    this.currentCellBRB = new BytesRefBuilder();
-    this.range = new Range(-1, shift, true);
-  }
-
-  private boolean nextRelation() {
-    Relation relation;
-    do {
-      // within or a boundary
-      if ((shift % GeoPointField.PRECISION_STEP) == 0 &&
-          (relation = relationImpl.relate(decodeLatitude(start), decodeLatitude(end),
-              decodeLongitude(start), decodeLongitude(end))) != Relation.CELL_OUTSIDE_QUERY) {
-        // if at max depth or cell completely within
-        if (shift == maxShift || relation == Relation.CELL_INSIDE_QUERY) {
-          setRange(relation == Relation.CELL_CROSSES_QUERY);
-          advanceVariables();
-          return true;
-        }
-      }
-
-      // within cell but not at a depth factor of PRECISION_STEP
-      if (shift != maxShift && relationImpl.cellIntersectsMBR(start, end) == true) {
-        // descend: start need not change since shift handles end of range
-        end = start | (1L<<--shift) - 1;
-      } else {
-        advanceVariables();
-      }
-    } while(shift < 62);
-    return false;
-  }
-
-  private void setRange(final boolean boundary) {
-    range.start = start;
-    range.shift = shift;
-    range.boundary = boundary;
-    hasNext = true;
-  }
-
-  private void advanceVariables() {
-    /** set next variables */
-    long shiftMask = 1L << shift;
-    // pop-up if shift bit is set
-    while ((start & shiftMask) != 0) {
-      shiftMask = 1L << ++shift;
-    }
-    final long shiftMOne = shiftMask - 1;
-    start = start & ~shiftMOne | shiftMask;
-    end = start | shiftMOne;
-  }
-
-  private void seek(long term, short res) {
-    if (term < start && res < maxShift) {
-      throw new IllegalArgumentException("trying to seek backwards");
-    } else if (term == start && res == shift) {
-      return;
-    }
-    shift = res;
-    start = term;
-    end = start | ((1L<<shift)-1);
-  }
-
-  private final boolean hasNext() {
-    if (hasNext == false) {
-      return nextRelation();
-    }
-    return true;
-  }
-
-  @Override
-  protected final BytesRef nextSeekTerm(BytesRef term) {
-    if (hasNext() == false) {
-      return null;
-    }
-    geoCodedToPrefixCoded(range.start, range.shift, currentCellBRB);
-    hasNext = false;
-    return currentCellBRB.get();
-  }
-
-  /**
-   * The two-phase query approach. {@link #nextSeekTerm} is called to obtain the next term that matches a numeric
-   * range of the bounding box. Those terms that pass the initial range filter are then compared against the
-   * decoded min/max latitude and longitude values of the bounding box only if the range is not a "boundary" range
-   * (e.g., a range that straddles the boundary of the bbox).
-   * @param term term for candidate document
-   * @return match status
-   */
-  @Override
-  protected AcceptStatus accept(BytesRef term) {
-    final long encodedTerm = prefixCodedToGeoCoded(term);
-    final short termShift = (short)(64-getPrefixCodedShift(term));
-    // range < term
-    while (range.compare(encodedTerm, termShift) < 0) {
-      // no more ranges, be gone
-      if (hasNext() == false) {
-        return AcceptStatus.END;
-      }
-
-      // peek next range, if the range > term then seek
-      final int peekCompare = range.compare(encodedTerm, termShift);
-      if (peekCompare > 0) {
-        return AcceptStatus.NO_AND_SEEK;
-      } else if (peekCompare < 0) {
-        seek(encodedTerm, termShift);
-      }
-      hasNext = false;
-    }
-    return AcceptStatus.YES;
-  }
-
-  /** Returns true if the current range term is a boundary of the query shape */
-  protected boolean boundaryTerm() {
-    if (range.start == -1) {
-      throw new IllegalStateException("GeoPointTermsEnum empty or not initialized");
-    }
-    return range.boundary;
-  }
-
-  protected boolean postFilter(final double lat, final double lon) {
-    return relationImpl.postFilter(lat, lon);
-  }
-
-  protected final class Range {
-    private short shift;
-    private long start;
-    private boolean boundary;
-
-    public Range(final long start, final short shift, final boolean boundary) {
-      this.boundary = boundary;
-      this.start = start;
-      this.shift = shift;
-    }
-
-    private int compare(long encoded, short shift) {
-      final int result = Long.compare(this.start, encoded);
-      if (result == 0) {
-        return Short.compare(shift, this.shift);
-      }
-      return result;
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/package-info.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/package-info.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/package-info.java
deleted file mode 100644
index 0d923df..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/package-info.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * Geospatial Query Implementations for Core Lucene
- */
-package org.apache.lucene.spatial.geopoint.search;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
deleted file mode 100644
index a657fd0..0000000
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.search;
-
-import org.apache.lucene.document.Document;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.RandomIndexWriter;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.geo.BaseGeoPointTestCase;
-import org.apache.lucene.geo.Polygon;
-import org.apache.lucene.spatial.geopoint.document.GeoPointField;
-import org.apache.lucene.store.Directory;
-
-/**
- * random testing for GeoPoint query logic
- *
- * @lucene.experimental
- */
-public class TestGeoPointQuery extends BaseGeoPointTestCase {
-  
-  @Override
-  protected double quantizeLat(double lat) {
-    return GeoPointField.decodeLatitude(GeoPointField.encodeLatLon(lat, 0));
-  }
-  
-  @Override
-  protected double quantizeLon(double lon) {
-    return GeoPointField.decodeLongitude(GeoPointField.encodeLatLon(0, lon));
-  }
-
-  @Override
-  protected void addPointToDoc(String field, Document doc, double lat, double lon) {
-    doc.add(new GeoPointField(field, lat, lon, GeoPointField.TYPE_NOT_STORED));
-  }
-
-  @Override
-  protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
-    return new GeoPointInBBoxQuery(field, minLat, maxLat, minLon, maxLon);
-  }
-
-  @Override
-  protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
-    return new GeoPointDistanceQuery(field, centerLat, centerLon, radiusMeters);
-  }
-
-  @Override
-  protected Query newPolygonQuery(String field, Polygon... polygons) {
-    return new GeoPointInPolygonQuery(field, polygons);
-  }
-
-  /** explicit test failure for LUCENE-7325 */
-  public void testInvalidShift() throws Exception {
-    Directory dir = newDirectory();
-    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
-
-    // add a doc with a point
-    Document document = new Document();
-    addPointToDoc("field", document, 80, -65);
-    writer.addDocument(document);
-
-    // search and verify we found our doc
-    IndexReader reader = writer.getReader();
-    IndexSearcher searcher = newSearcher(reader);
-    assertEquals(0, searcher.count(newRectQuery("field", 90, 90, -180, 0)));
-
-    reader.close();
-    writer.close();
-    dir.close();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoPointField.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoPointField.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoPointField.java
deleted file mode 100644
index 1e4f478..0000000
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoPointField.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.lucene.spatial.util;
-
-import org.apache.lucene.spatial.geopoint.document.GeoPointField;
-import org.apache.lucene.util.BytesRefBuilder;
-import org.apache.lucene.util.LuceneTestCase;
-
-import static org.apache.lucene.geo.GeoTestUtil.nextLatitude;
-import static org.apache.lucene.geo.GeoTestUtil.nextLongitude;
-import static org.apache.lucene.spatial.geopoint.document.GeoPointField.encodeLatLon;
-import static org.apache.lucene.spatial.geopoint.document.GeoPointField.geoCodedToPrefixCoded;
-import static org.apache.lucene.spatial.geopoint.document.GeoPointField.prefixCodedToGeoCoded;
-
-/**
- * Tests encoding methods in {@link GeoPointField}
- */
-public class TestGeoPointField extends LuceneTestCase {
-  /**
-   * Tests stability of {@link GeoPointField#geoCodedToPrefixCoded}
-   */
-  public void testGeoPrefixCoding() throws Exception {
-    int numIters = atLeast(1000);
-    long hash;
-    long decodedHash;
-    BytesRefBuilder brb = new BytesRefBuilder();
-    while (numIters-- >= 0) {
-      hash = encodeLatLon(nextLatitude(), nextLongitude());
-      for (int i=32; i<64; ++i) {
-        geoCodedToPrefixCoded(hash, i, brb);
-        decodedHash = prefixCodedToGeoCoded(brb.get());
-        assertEquals((hash >>> i) << i, decodedHash);
-      }
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java
----------------------------------------------------------------------
diff --git a/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java b/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java
index 81880d4..0ca81c7 100644
--- a/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java
+++ b/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java
@@ -249,9 +249,7 @@ public class AnalyzingInfixSuggester extends Lookup implements Closeable {
 
     if (DirectoryReader.indexExists(dir)) {
       // Already built; open it:
-      writer = new IndexWriter(dir,
-                               getIndexWriterConfig(getGramAnalyzer(), IndexWriterConfig.OpenMode.APPEND));
-      searcherMgr = new SearcherManager(writer, null);
+      searcherMgr = new SearcherManager(dir, null);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/suggest/src/test/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggesterTest.java
----------------------------------------------------------------------
diff --git a/lucene/suggest/src/test/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggesterTest.java b/lucene/suggest/src/test/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggesterTest.java
index fc5e2b7..478358b 100644
--- a/lucene/suggest/src/test/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggesterTest.java
+++ b/lucene/suggest/src/test/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggesterTest.java
@@ -1360,7 +1360,8 @@ public class AnalyzingInfixSuggesterTest extends LuceneTestCase {
     // * SearcherManager's IndexWriter reference should be closed 
     //   (as evidenced by maybeRefreshBlocking() throwing AlreadyClosedException)
     Analyzer a = new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false);
-    MyAnalyzingInfixSuggester suggester = new MyAnalyzingInfixSuggester(newDirectory(), a, a, 3, false,
+    Path tempDir = createTempDir("analyzingInfixContext");
+    final MyAnalyzingInfixSuggester suggester = new MyAnalyzingInfixSuggester(newFSDirectory(tempDir), a, a, 3, false,
         AnalyzingInfixSuggester.DEFAULT_ALL_TERMS_REQUIRED, AnalyzingInfixSuggester.DEFAULT_HIGHLIGHT, true);
     suggester.build(new InputArrayIterator(sharedInputs));
     assertNull(suggester.getIndexWriter());
@@ -1368,6 +1369,16 @@ public class AnalyzingInfixSuggesterTest extends LuceneTestCase {
     expectThrows(AlreadyClosedException.class, () -> suggester.getSearcherManager().maybeRefreshBlocking());
     
     suggester.close();
+
+    // After instantiating from an already-built suggester dir:
+    // * The IndexWriter should be null
+    // * The SearcherManager should be non-null
+    final MyAnalyzingInfixSuggester suggester2 = new MyAnalyzingInfixSuggester(newFSDirectory(tempDir), a, a, 3, false,
+        AnalyzingInfixSuggester.DEFAULT_ALL_TERMS_REQUIRED, AnalyzingInfixSuggester.DEFAULT_HIGHLIGHT, true);
+    assertNull(suggester2.getIndexWriter());
+    assertNotNull(suggester2.getSearcherManager());
+
+    suggester2.close();
     a.close();
   }
   

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/test-framework/src/java/org/apache/lucene/analysis/CannedTokenStream.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/analysis/CannedTokenStream.java b/lucene/test-framework/src/java/org/apache/lucene/analysis/CannedTokenStream.java
index 8250799..8323882 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/analysis/CannedTokenStream.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/analysis/CannedTokenStream.java
@@ -18,11 +18,9 @@ package org.apache.lucene.analysis;
 
 import java.io.IOException;
 
-import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
+import org.apache.lucene.analysis.Token;
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
-import org.apache.lucene.analysis.tokenattributes.PayloadAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
-import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
 
 /**
  * TokenStream from a canned list of Tokens.
@@ -30,23 +28,19 @@ import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
 public final class CannedTokenStream extends TokenStream {
   private final Token[] tokens;
   private int upto = 0;
-  private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
-  private final PositionIncrementAttribute posIncrAtt = addAttribute(PositionIncrementAttribute.class);
-  private final PositionLengthAttribute posLengthAtt = addAttribute(PositionLengthAttribute.class);
   private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
-  private final PayloadAttribute payloadAtt = addAttribute(PayloadAttribute.class);
+  private final PositionIncrementAttribute posIncrAtt = addAttribute(PositionIncrementAttribute.class);
   private final int finalOffset;
   private final int finalPosInc;
 
   public CannedTokenStream(Token... tokens) {
-    this.tokens = tokens;
-    finalOffset = 0;
-    finalPosInc = 0;
+    this(0, 0, tokens);
   }
 
   /** If you want trailing holes, pass a non-zero
    *  finalPosInc. */
   public CannedTokenStream(int finalPosInc, int finalOffset, Token... tokens) {
+    super(Token.TOKEN_ATTRIBUTE_FACTORY);
     this.tokens = tokens;
     this.finalOffset = finalOffset;
     this.finalPosInc = finalPosInc;
@@ -62,16 +56,10 @@ public final class CannedTokenStream extends TokenStream {
   @Override
   public boolean incrementToken() {
     if (upto < tokens.length) {
-      final Token token = tokens[upto++];     
-      // TODO: can we just capture/restoreState so
-      // we get all attrs...?
-      clearAttributes();      
-      termAtt.setEmpty();
-      termAtt.append(token.toString());
-      posIncrAtt.setPositionIncrement(token.getPositionIncrement());
-      posLengthAtt.setPositionLength(token.getPositionLength());
-      offsetAtt.setOffset(token.startOffset(), token.endOffset());
-      payloadAtt.setPayload(token.getPayload());
+      clearAttributes();
+      // NOTE: this looks weird, casting offsetAtt to Token, but because we are using the Token class's AttributeFactory, all attributes are
+      // in fact backed by the Token class, so we just copy the current token into our Token:
+      tokens[upto++].copyTo((Token) offsetAtt);
       return true;
     } else {
       return false;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/test-framework/src/java/org/apache/lucene/codecs/compressing/dummy/DummyCompressingCodec.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/codecs/compressing/dummy/DummyCompressingCodec.java b/lucene/test-framework/src/java/org/apache/lucene/codecs/compressing/dummy/DummyCompressingCodec.java
index d15adad..167418e 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/codecs/compressing/dummy/DummyCompressingCodec.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/codecs/compressing/dummy/DummyCompressingCodec.java
@@ -79,6 +79,9 @@ public class DummyCompressingCodec extends CompressingCodec {
       out.writeBytes(bytes, off, len);
     }
 
+    @Override
+    public void close() throws IOException {};
+
   };
 
   /** Constructor that allows to configure the chunk size. */

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
index d4896f8..562db8f 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
@@ -98,7 +98,12 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   protected Polygon nextPolygon() {
     return org.apache.lucene.geo.GeoTestUtil.nextPolygon();
   }
-  
+
+  /** Whether this impl supports polygons. */
+  protected boolean supportsPolygons() {
+    return true;
+  }
+
   /** Valid values that should not cause exception */
   public void testIndexExtremeValues() {
     Document document = new Document();
@@ -284,6 +289,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   
   /** test we can search for a polygon */
   public void testPolygonBasics() throws Exception {
+    assumeTrue("Impl does not support polygons", supportsPolygons());
     Directory dir = newDirectory();
     RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
 
@@ -306,6 +312,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   
   /** test we can search for a polygon with a hole (but still includes the doc) */
   public void testPolygonHole() throws Exception {
+    assumeTrue("Impl does not support polygons", supportsPolygons());
     Directory dir = newDirectory();
     RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
 
@@ -330,6 +337,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   
   /** test we can search for a polygon with a hole (that excludes the doc) */
   public void testPolygonHoleExcludes() throws Exception {
+    assumeTrue("Impl does not support polygons", supportsPolygons());
     Directory dir = newDirectory();
     RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
 
@@ -354,6 +362,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   
   /** test we can search for a multi-polygon */
   public void testMultiPolygonBasics() throws Exception {
+    assumeTrue("Impl does not support polygons", supportsPolygons());
     Directory dir = newDirectory();
     RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
 
@@ -378,6 +387,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   
   /** null field name not allowed */
   public void testPolygonNullField() {
+    assumeTrue("Impl does not support polygons", supportsPolygons());
     IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
       newPolygonQuery(null, new Polygon(
           new double[] { 18, 18, 19, 19, 18 },
@@ -739,7 +749,9 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
     }
     verifyRandomRectangles(lats, lons);
     verifyRandomDistances(lats, lons);
-    verifyRandomPolygons(lats, lons);
+    if (supportsPolygons()) {
+      verifyRandomPolygons(lats, lons);
+    }
   }
 
   protected void verifyRandomRectangles(double[] lats, double[] lons) throws Exception {
@@ -844,6 +856,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
 
         if (hits.get(docID) != expected) {
           StringBuilder b = new StringBuilder();
+          b.append("docID=(" + docID + ")\n");
 
           if (expected) {
             b.append("FAIL: id=" + id + " should match but did not\n");
@@ -1344,10 +1357,12 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
     lons[3] = rect.maxLon;
     lats[4] = rect.minLat;
     lons[4] = rect.minLon;
-    q1 = newPolygonQuery("field", new Polygon(lats, lons));
-    q2 = newPolygonQuery("field", new Polygon(lats, lons));
-    assertEquals(q1, q2);
-    assertFalse(q1.equals(newPolygonQuery("field2", new Polygon(lats, lons))));
+    if (supportsPolygons()) {
+      q1 = newPolygonQuery("field", new Polygon(lats, lons));
+      q2 = newPolygonQuery("field", new Polygon(lats, lons));
+      assertEquals(q1, q2);
+      assertFalse(q1.equals(newPolygonQuery("field2", new Polygon(lats, lons))));
+    }
   }
   
   /** return topdocs over a small set of points in field "point" */
@@ -1436,6 +1451,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   }
   
   public void testSmallSetPoly() throws Exception {
+    assumeTrue("Impl does not support polygons", supportsPolygons());
     TopDocs td = searchSmallSet(newPolygonQuery("point",
         new Polygon(
         new double[]{33.073130, 32.9942669, 32.938386, 33.0374494,
@@ -1447,6 +1463,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   }
 
   public void testSmallSetPolyWholeMap() throws Exception {
+    assumeTrue("Impl does not support polygons", supportsPolygons());
     TopDocs td = searchSmallSet(newPolygonQuery("point",
                       new Polygon(
                       new double[] {GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MIN_LAT_INCL},

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/test-framework/src/java/org/apache/lucene/index/BaseIndexFileFormatTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/index/BaseIndexFileFormatTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/index/BaseIndexFileFormatTestCase.java
index 7a7abc0..3097cdf 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/index/BaseIndexFileFormatTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/index/BaseIndexFileFormatTestCase.java
@@ -544,14 +544,9 @@ abstract class BaseIndexFileFormatTestCase extends LuceneTestCase {
           conf.setMergeScheduler(new SerialMergeScheduler());
           conf.setCodec(getCodec());
           iw = new IndexWriter(dir, conf);            
-        } catch (Exception e) {
-          if (e.getMessage() != null && e.getMessage().startsWith("a random IOException")) {
-            exceptionStream.println("\nTEST: got expected fake exc:" + e.getMessage());
-            e.printStackTrace(exceptionStream);
-            allowAlreadyClosed = true;
-          } else {
-            Rethrow.rethrow(e);
-          }
+        } catch (IOException e) {
+          handleFakeIOException(e, exceptionStream);
+          allowAlreadyClosed = true;
         }
         
         if (random().nextInt(10) == 0) {
@@ -585,14 +580,9 @@ abstract class BaseIndexFileFormatTestCase extends LuceneTestCase {
             conf.setMergeScheduler(new SerialMergeScheduler());
             conf.setCodec(getCodec());
             iw = new IndexWriter(dir, conf);            
-          } catch (Exception e) {
-            if (e.getMessage() != null && e.getMessage().startsWith("a random IOException")) {
-              exceptionStream.println("\nTEST: got expected fake exc:" + e.getMessage());
-              e.printStackTrace(exceptionStream);
-              allowAlreadyClosed = true;
-            } else {
-              Rethrow.rethrow(e);
-            }
+          } catch (IOException e) {
+            handleFakeIOException(e, exceptionStream);
+            allowAlreadyClosed = true;
           }
         }
       }
@@ -601,16 +591,11 @@ abstract class BaseIndexFileFormatTestCase extends LuceneTestCase {
         dir.setRandomIOExceptionRateOnOpen(0.0); // disable exceptions on openInput until next iteration: 
                                                  // or we make slowExists angry and trip a scarier assert!
         iw.close();
-      } catch (Exception e) {
-        if (e.getMessage() != null && e.getMessage().startsWith("a random IOException")) {
-          exceptionStream.println("\nTEST: got expected fake exc:" + e.getMessage());
-          e.printStackTrace(exceptionStream);
-          try {
-            iw.rollback();
-          } catch (Throwable t) {}
-        } else {
-          Rethrow.rethrow(e);
-        }
+      } catch (IOException e) {
+        handleFakeIOException(e, exceptionStream);
+        try {
+          iw.rollback();
+        } catch (Throwable t) {}
       }
       dir.close();
     } catch (Throwable t) {
@@ -626,4 +611,18 @@ abstract class BaseIndexFileFormatTestCase extends LuceneTestCase {
       System.out.println(exceptionLog.toString("UTF-8"));
     }
   }
+  
+  private void handleFakeIOException(IOException e, PrintStream exceptionStream) {
+    Throwable ex = e;
+    while (ex != null) {
+      if (ex.getMessage() != null && ex.getMessage().startsWith("a random IOException")) {
+        exceptionStream.println("\nTEST: got expected fake exc:" + ex.getMessage());
+        ex.printStackTrace(exceptionStream);
+        return;
+      }
+      ex = ex.getCause();
+    }
+    
+    Rethrow.rethrow(e);
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/test-framework/src/java/org/apache/lucene/index/BasePointsFormatTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/index/BasePointsFormatTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/index/BasePointsFormatTestCase.java
index 6014fa5..4cd6534 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/index/BasePointsFormatTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/index/BasePointsFormatTestCase.java
@@ -253,11 +253,16 @@ public abstract class BasePointsFormatTestCase extends BaseIndexFileFormatTestCa
           // This just means we got a too-small maxMB for the maxPointsInLeafNode; just retry w/ more heap
           assertTrue(iae.getMessage().contains("either increase maxMBSortInHeap or decrease maxPointsInLeafNode"));
         } catch (IOException ioe) {
-          String message = ioe.getMessage();
-          if (message.contains("a random IOException") || message.contains("background merge hit exception")) {
-            // BKDWriter should fully clean up after itself:
-            done = true;
-          } else {
+          Throwable ex = ioe;
+          while (ex != null) {
+            String message = ex.getMessage();
+            if (message != null && (message.contains("a random IOException") || message.contains("background merge hit exception"))) {
+              done = true;
+              break;
+            }
+            ex = ex.getCause();            
+          }
+          if (done == false) {
             throw ioe;
           }
         }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
index 50139a0..90f85b9 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
@@ -2703,6 +2703,34 @@ public abstract class LuceneTestCase extends Assert {
     throw new AssertionFailedError("Expected exception " + expectedType.getSimpleName());
   }
 
+  /**
+   * Checks that specific wrapped and outer exception classes are thrown
+   * by the given runnable, and returns the wrapped exception. 
+   */
+  public static <TO extends Throwable, TW extends Throwable> TW expectThrows
+  (Class<TO> expectedOuterType, Class<TW> expectedWrappedType, ThrowingRunnable runnable) {
+    try {
+      runnable.run();
+    } catch (Throwable e) {
+      if (expectedOuterType.isInstance(e)) {
+        Throwable cause = e.getCause();
+        if (expectedWrappedType.isInstance(cause)) {
+          return expectedWrappedType.cast(cause);
+        } else {
+          AssertionFailedError assertion = new AssertionFailedError
+              ("Unexpected wrapped exception type, expected " + expectedWrappedType.getSimpleName());
+          assertion.initCause(e);
+          throw assertion;
+        }
+      }
+      AssertionFailedError assertion = new AssertionFailedError
+          ("Unexpected outer exception type, expected " + expectedOuterType.getSimpleName());
+      assertion.initCause(e);
+      throw assertion;
+    }
+    throw new AssertionFailedError("Expected outer exception " + expectedOuterType.getSimpleName());
+  }
+
   /** Returns true if the file exists (can be opened), false
    *  if it cannot be opened, and (unlike Java's
    *  File.exists) throws IOException if there's some

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 8e2ce61..ba6e574 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -76,6 +76,10 @@ Optimizations
 
 * SOLR-9996: Unstored IntPointField returns Long type (Ishan Chattopadhyaya)
 
+* SOLR-5944: In-place updates of Numeric DocValues. To leverage this, the _version_ field and the updated
+  field must both be stored=false, indexed=false, docValues=true. (Ishan Chattopadhyaya, hossman, noble,
+  shalin, yonik)
+
 Other Changes
 ----------------------
 * SOLR-8396: Add support for PointFields in Solr (Ishan Chattopadhyaya, Tom�s Fern�ndez L�bbe)
@@ -83,7 +87,7 @@ Other Changes
 * SOLR-10011: Refactor PointField & TrieField to now have a common base class, NumericFieldType. The
   TrieField.TrieTypes and PointField.PointTypes are now consolidated to NumericFieldType.NumberType. This
   refactoring also fixes a bug whereby PointFields were not using DocValues for range queries for
-  indexed=false, docValues=true fields.
+  indexed=false, docValues=true fields. (Ishan Chattopadhyaya, Tom�s Fern�ndez L�bbe)
 
 ==================  6.5.0 ==================
 
@@ -111,6 +115,30 @@ New Features
 
 * SOLR-9885: Allow pre-startup Solr log management in Solr bin scripts to be disabled. (Mano Kovacs via Mark Miller)
 
+* SOLR-9481: Authentication and Authorization plugins now work in standalone mode if security.json is placed in
+  SOLR_HOME on every node. Editing config through API is supported but affects only that one node. (janhoy)
+
+* SOLR-8029: Added new style APIs and a framework for creating new APIs and mapping old APIs to new
+  (noble, Steve Rowe, Cassandra Targett, Timothy Potter)
+
+* SOLR-9933: SolrCoreParser now supports configuration of custom SpanQueryBuilder classes.
+  (Daniel Collins, Christine Poerschke)
+
+* SOLR-7955: Auto create .system collection on first request if it does not exist (noble)
+
+* SOLR-10087: StreamHandler now supports registering custom streaming expressions from the blob store (Kevin Risden)
+
+* SOLR-9997: Enable configuring SolrHttpClientBuilder via java system property. (Hrishikesh Gadre via Mark Miller)
+
+* SOLR-9912: Add facet.excludeTerms parameter support. (Jonny Marks, David Smiley, Christine Poerschke)
+
+* SOLR-9916: Adds Stream Evaluators to support evaluating values from tuples. Supports boolean,
+  numeric, and conditional evaluators. BooleanOperations have been removed in preference of
+  BooleanEvaluators. (Dennis Gove)
+
+* SOLR-9903: Stop interrupting the update executor on shutdown, it can cause graceful shutdowns to put replicas into Leader 
+  Initiated Recovery among other undesirable things. (Mark Miller)
+
 Bug Fixes
 ----------------------
 
@@ -123,19 +151,73 @@ Bug Fixes
 
 * SOLR-9114: NPE using TermVectorComponent, MoreLikeThisComponent in combination with ExactStatsCache (Cao Manh Dat, Varun Thacker)
 
+* SOLR-10049: Collection deletion leaves behind the snapshot metadata (Hrishikesh Gadre via yonik)
+
+* SOLR-10083: Fix instanceof check in ConstDoubleSource.equals (Pushkar Raste via Christine Poerschke)
+
+* SOLR-10120: A SolrCore reload can remove the index from the previous SolrCore during replication index rollover. (Mark Miller)
+
+* SOLR-10124: Replication can skip removing a temporary index directory in some cases when it should not. (Mark Miller)
+
+* SOLR-10063: CoreContainer shutdown has race condition that can cause a hang on shutdown. (Mark Miller)
+
 Optimizations
 ----------------------
 
 * SOLR-9941: Clear the deletes lists at UpdateLog before replaying from log. This prevents redundantly pre-applying
   DBQs, during the log replay, to every update in the log as if the DBQs were out of order. (hossman, Ishan Chattopadhyaya)
 
+* SOLR-9764: All filters that which all documents in the index now share the same memory (DocSet).
+  (Michael Sun, yonik)
+
 Other Changes
 ----------------------
 * SOLR-9980: Expose configVersion in core admin status (Jessica Cheng Mallet via Tom�s Fern�ndez L�bbe)
 
+* SOLR-9972: SpellCheckComponent collations and suggestions returned as a JSON object rather than a list
+  (Christine Poerschke in response to bug report from Ricky Oktavianus Lazuardy)
+
 * SOLR-9983: Fixing NullPointerException failure by TestManagedSchemaThreadSafety 
   adding check for Zookeeper session expiration (Steve Rowe, Mikhail Khludnev)
 
+* SOLR-10043: Reduce logging of pre-start log rotation (janhoy)
+
+* SOLR-10018: Increase the default hl.maxAnalyzedChars to 51200 for the Unified & Postings Highlighter so that all
+  highlighters now have this same default. (David Smiley)
+  
+* SOLR-6246: Added tests to check that the changes in LUCENE-7564 and LUCENE-7670 
+  enable AnalyzingInfixSuggester and BlendedInfixSuggester to play nicely with core reload.  
+  SolrSuggester.build() now throws SolrCoreState.CoreIsClosedException when interrupted
+  by a core reload/shutdown. (Steve Rowe)
+
+* SOLR-9800: Factor out FacetComponent.newSimpleFacets method. (Jonny Marks via Christine Poerschke)
+
+* SOLR-9914: SimpleFacets: refactor "contains" check into "SubstringBytesRefFilter" class.
+  (Jonny Marks, David Smiley, Christine Poerschke)
+
+==================  6.4.1 ==================
+
+Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.
+
+Versions of Major Components
+---------------------
+Apache Tika 1.13
+Carrot2 3.15.0
+Velocity 1.7 and Velocity Tools 2.0
+Apache UIMA 2.3.1
+Apache ZooKeeper 3.4.6
+Jetty 9.3.14.v20161028
+
+Bug Fixes
+----------------------
+* SOLR-9969: "Plugin/Stats" section of the UI doesn't display empty metric types (Tom�s Fern�ndez L�bbe)
+
+* SOLR-8491: solr.cmd SOLR_SSL_OPTS is overwritten (Sam Yi, Andy Hind, Marcel Berteler, Kevin Risden)
+
+* SOLR-10031: Validation of filename params in ReplicationHandler (Hrishikesh Gadre, janhoy)
+
+* SOLR-10054: Core swapping doesn't work with new metrics changes in place (ab)
+
 ==================  6.4.0 ==================
 
 Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.
@@ -202,10 +284,6 @@ New Features
 * SOLR-9442, SOLR-9787: Adds Array of Name Type Value (json.nl=arrntv) style to JSONResponseWriter.
   (Jonny Marks, Christine Poerschke, hossman)
 
-* SOLR-9481: Authentication and Authorization plugins now work in standalone mode if security.json is placed in
-  SOLR_HOME on every node. Editing config through API is supported but affects only that one node. 
-  (janhoy)
-
 * SOLR-8542: Adds Solr Learning to Rank (LTR) plugin for reranking results with machine learning models.
   (Michael Nilsson, Diego Ceccarelli, Joshua Pantony, Jon Dorando, Naveen Santhapuri, Alessandro Benedetti, David Grohmann, Christine Poerschke)
 
@@ -1900,6 +1978,23 @@ Other Changes
 * SOLR-8904: DateUtil in SolrJ moved to the extraction contrib as ExtractionDateUtil.  Obsolete methods were removed.
   (David Smiley)
 
+======================= 5.5.4 =======================
+
+Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.
+
+Versions of Major Components
+---------------------
+Apache Tika 1.13
+Carrot2 3.15.0
+Velocity 1.7 and Velocity Tools 2.0
+Apache UIMA 2.3.1
+Apache ZooKeeper 3.4.6
+Jetty 9.3.14.v20161028
+
+
+(No Changes)
+
+
 ======================= 5.5.3 =======================
 
 Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/bin/solr
----------------------------------------------------------------------
diff --git a/solr/bin/solr b/solr/bin/solr
index c2d0feb..cd9db0f 100755
--- a/solr/bin/solr
+++ b/solr/bin/solr
@@ -232,12 +232,39 @@ else
 fi
 
 # Authentication options
+if [ -z "$SOLR_AUTH_TYPE" ] && [ -n "$SOLR_AUTHENTICATION_OPTS" ]; then
+  echo "WARNING: SOLR_AUTHENTICATION_OPTS environment variable configured without associated SOLR_AUTH_TYPE variable"
+  echo "         Please configure SOLR_AUTH_TYPE environment variable with the authentication type to be used."
+  echo "         Currently supported authentication types are [kerberos, basic]"
+fi
+
+if [ -n "$SOLR_AUTH_TYPE" ] && [ -n "$SOLR_AUTHENTICATION_CLIENT_BUILDER" ]; then
+  echo "WARNING: SOLR_AUTHENTICATION_CLIENT_BUILDER and SOLR_AUTH_TYPE environment variables are configured together."
+  echo "         Use SOLR_AUTH_TYPE environment variable to configure authentication type to be used. "
+  echo "         Currently supported authentication types are [kerberos, basic]"
+  echo "         The value of SOLR_AUTHENTICATION_CLIENT_BUILDER environment variable will be ignored"
+fi
+
+if [ -n "$SOLR_AUTH_TYPE" ]; then
+  case "$(echo $SOLR_AUTH_TYPE | awk '{print tolower($0)}')" in
+    basic)
+      SOLR_AUTHENTICATION_CLIENT_BUILDER="org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory"
+      ;;
+    kerberos)
+      SOLR_AUTHENTICATION_CLIENT_BUILDER="org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder"
+      ;;
+    *)
+      echo "ERROR: Value specified for SOLR_AUTH_TYPE environment variable is invalid."
+      exit 1
+   esac
+fi
+
 if [ "$SOLR_AUTHENTICATION_CLIENT_CONFIGURER" != "" ]; then
   echo "WARNING: Found unsupported configuration variable SOLR_AUTHENTICATION_CLIENT_CONFIGURER"
-  echo "         Please start using SOLR_AUTHENTICATION_CLIENT_BUILDER instead"
+  echo "         Please start using SOLR_AUTH_TYPE instead"
 fi
 if [ "$SOLR_AUTHENTICATION_CLIENT_BUILDER" != "" ]; then
-  AUTHC_CLIENT_BUILDER_ARG="-Dsolr.authentication.httpclient.builder=$SOLR_AUTHENTICATION_CLIENT_BUILDER"
+  AUTHC_CLIENT_BUILDER_ARG="-Dsolr.httpclient.builder.factory=$SOLR_AUTHENTICATION_CLIENT_BUILDER"
 fi
 AUTHC_OPTS="$AUTHC_CLIENT_BUILDER_ARG $SOLR_AUTHENTICATION_OPTS"
 
@@ -1480,11 +1507,16 @@ if [ ! -e "$SOLR_HOME" ]; then
   echo -e "\nSolr home directory $SOLR_HOME not found!\n"
   exit 1
 fi
+if $verbose ; then
+  q=""
+else
+  q="-q"
+fi
 if [ "${SOLR_LOG_PRESTART_ROTATION:=true}" == "true" ]; then
-  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -remove_old_solr_logs 7 || echo "Failed removing old solr logs"
-  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -archive_gc_logs        || echo "Failed archiving old GC logs"
-  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -archive_console_logs   || echo "Failed archiving old console logs"
-  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -rotate_solr_logs 9     || echo "Failed rotating old solr logs"
+  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" $q -remove_old_solr_logs 7 || echo "Failed removing old solr logs"
+  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" $q -archive_gc_logs $q     || echo "Failed archiving old GC logs"
+  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" $q -archive_console_logs   || echo "Failed archiving old console logs"
+  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" $q -rotate_solr_logs 9     || echo "Failed rotating old solr logs"
 fi
 
 java_ver_out=`echo "$("$JAVA" -version 2>&1)"`

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/bin/solr.cmd
----------------------------------------------------------------------
diff --git a/solr/bin/solr.cmd b/solr/bin/solr.cmd
index 732c2de..29422f3 100644
--- a/solr/bin/solr.cmd
+++ b/solr/bin/solr.cmd
@@ -45,71 +45,71 @@ set "SOLR_SSL_OPTS= "
 IF DEFINED SOLR_SSL_KEY_STORE (
   set "SOLR_JETTY_CONFIG=--module=https"
   set SOLR_URL_SCHEME=https
-  set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Dsolr.jetty.keystore=%SOLR_SSL_KEY_STORE%"
+  set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Dsolr.jetty.keystore=%SOLR_SSL_KEY_STORE%"
   IF DEFINED SOLR_SSL_KEY_STORE_PASSWORD (
-    set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Dsolr.jetty.keystore.password=%SOLR_SSL_KEY_STORE_PASSWORD%"
+    set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Dsolr.jetty.keystore.password=%SOLR_SSL_KEY_STORE_PASSWORD%"
   )
   IF DEFINED SOLR_SSL_KEY_STORE_TYPE (
-    set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Dsolr.jetty.keystore.type=%SOLR_SSL_KEY_STORE_TYPE%"
+    set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Dsolr.jetty.keystore.type=%SOLR_SSL_KEY_STORE_TYPE%"
   )
 
   IF DEFINED SOLR_SSL_TRUST_STORE (
-    set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Dsolr.jetty.truststore=%SOLR_SSL_TRUST_STORE%"
+    set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Dsolr.jetty.truststore=%SOLR_SSL_TRUST_STORE%"
   )
   IF DEFINED SOLR_SSL_TRUST_STORE_PASSWORD (
-    set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Dsolr.jetty.truststore.password=%SOLR_SSL_TRUST_STORE_PASSWORD%"
+    set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Dsolr.jetty.truststore.password=%SOLR_SSL_TRUST_STORE_PASSWORD%"
   )
   IF DEFINED SOLR_SSL_TRUST_STORE_TYPE (
-    set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Dsolr.jetty.truststore.type=%SOLR_SSL_TRUST_STORE_TYPE%"
+    set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Dsolr.jetty.truststore.type=%SOLR_SSL_TRUST_STORE_TYPE%"
   )
 
   IF DEFINED SOLR_SSL_NEED_CLIENT_AUTH (
-    set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Dsolr.jetty.ssl.needClientAuth=%SOLR_SSL_NEED_CLIENT_AUTH%"
+    set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Dsolr.jetty.ssl.needClientAuth=%SOLR_SSL_NEED_CLIENT_AUTH%"
   )
   IF DEFINED SOLR_SSL_WANT_CLIENT_AUTH (
-    set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Dsolr.jetty.ssl.wantClientAuth=%SOLR_SSL_WANT_CLIENT_AUTH%"
+    set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Dsolr.jetty.ssl.wantClientAuth=%SOLR_SSL_WANT_CLIENT_AUTH%"
   )
 
   IF DEFINED SOLR_SSL_CLIENT_KEY_STORE (
-    set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.keyStore=%SOLR_SSL_CLIENT_KEY_STORE%"
+    set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.keyStore=%SOLR_SSL_CLIENT_KEY_STORE%"
 
     IF DEFINED SOLR_SSL_CLIENT_KEY_STORE_PASSWORD (
-      set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.keyStorePassword=%SOLR_SSL_CLIENT_KEY_STORE_PASSWORD%"
+      set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.keyStorePassword=%SOLR_SSL_CLIENT_KEY_STORE_PASSWORD%"
     )
     IF DEFINED SOLR_SSL_CLIENT_KEY_STORE_TYPE (
-      set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.keyStoreType=%SOLR_SSL_CLIENT_KEY_STORE_TYPE%"
+      set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.keyStoreType=%SOLR_SSL_CLIENT_KEY_STORE_TYPE%"
     )
   ) ELSE (
     IF DEFINED SOLR_SSL_KEY_STORE (
-      set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.keyStore=%SOLR_SSL_KEY_STORE%"
+      set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.keyStore=%SOLR_SSL_KEY_STORE%"
     )
     IF DEFINED SOLR_SSL_KEY_STORE_PASSWORD (
-      set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.keyStorePassword=%SOLR_SSL_KEY_STORE_PASSWORD%"
+      set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.keyStorePassword=%SOLR_SSL_KEY_STORE_PASSWORD%"
     )
     IF DEFINED SOLR_SSL_KEY_STORE_TYPE (
-      set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.keyStoreType=%SOLR_SSL_KEY_STORE_TYPE%"
+      set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.keyStoreType=%SOLR_SSL_KEY_STORE_TYPE%"
     )
   )
 
   IF DEFINED SOLR_SSL_CLIENT_TRUST_STORE (
-    set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.trustStore=%SOLR_SSL_CLIENT_TRUST_STORE%"
+    set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.trustStore=%SOLR_SSL_CLIENT_TRUST_STORE%"
 
     IF DEFINED SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD (
-      set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.trustStorePassword=%SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD%"
+      set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.trustStorePassword=%SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD%"
     )
 
     IF DEFINED SOLR_SSL_CLIENT_TRUST_STORE_TYPE (
-      set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.trustStoreType=%SOLR_SSL_CLIENT_TRUST_STORE_TYPE%"
+      set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.trustStoreType=%SOLR_SSL_CLIENT_TRUST_STORE_TYPE%"
     )
   ) ELSE (
     IF DEFINED SOLR_SSL_TRUST_STORE (
-     set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.trustStore=%SOLR_SSL_TRUST_STORE%"
+     set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.trustStore=%SOLR_SSL_TRUST_STORE%"
     )
     IF DEFINED SOLR_SSL_TRUST_STORE_PASSWORD (
-     set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.trustStorePassword=%SOLR_SSL_TRUST_STORE_PASSWORD%"
+     set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.trustStorePassword=%SOLR_SSL_TRUST_STORE_PASSWORD%"
     )
     IF DEFINED SOLR_SSL_TRUST_STORE_TYPE (
-     set "SOLR_SSL_OPTS=%SOLR_SSL_OPTS% -Djavax.net.ssl.trustStoreType=%SOLR_SSL_TRUST_STORE_TYPE%"
+     set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Djavax.net.ssl.trustStoreType=%SOLR_SSL_TRUST_STORE_TYPE%"
     )
   )
 ) ELSE (
@@ -117,12 +117,43 @@ IF DEFINED SOLR_SSL_KEY_STORE (
 )
 
 REM Authentication options
+
+IF NOT DEFINED SOLR_AUTH_TYPE (
+  IF DEFINED SOLR_AUTHENTICATION_OPTS (
+    echo WARNING: SOLR_AUTHENTICATION_OPTS variable configured without associated SOLR_AUTH_TYPE variable
+    echo          Please configure SOLR_AUTH_TYPE variable with the authentication type to be used.
+    echo          Currently supported authentication types are [kerberos, basic]
+  )
+)
+
+IF DEFINED SOLR_AUTH_TYPE (
+  IF DEFINED SOLR_AUTHENTICATION_CLIENT_BUILDER (
+    echo WARNING: SOLR_AUTHENTICATION_CLIENT_BUILDER and SOLR_AUTH_TYPE variables are configured together
+    echo          Use SOLR_AUTH_TYPE variable to configure authentication type to be used
+    echo          Currently supported authentication types are [kerberos, basic]
+    echo          The value of SOLR_AUTHENTICATION_CLIENT_BUILDER configuration variable will be ignored
+  )
+)
+
+IF DEFINED SOLR_AUTH_TYPE (
+  IF /I "%SOLR_AUTH_TYPE%" == "basic" (
+    set SOLR_AUTHENTICATION_CLIENT_BUILDER="org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory"
+  ) ELSE (
+    IF /I "%SOLR_AUTH_TYPE%" == "kerberos" (
+      set SOLR_AUTHENTICATION_CLIENT_BUILDER="org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory"
+    ) ELSE (
+      echo ERROR: Value specified for SOLR_AUTH_TYPE configuration variable is invalid.
+      goto err
+    )
+  )
+)
+
 IF DEFINED SOLR_AUTHENTICATION_CLIENT_CONFIGURER (
   echo WARNING: Found unsupported configuration variable SOLR_AUTHENTICATION_CLIENT_CONFIGURER
-  echo          Please start using SOLR_AUTHENTICATION_CLIENT_BUILDER instead
+  echo          Please start using SOLR_AUTH_TYPE instead
 )
 IF DEFINED SOLR_AUTHENTICATION_CLIENT_BUILDER (
-  set AUTHC_CLIENT_BUILDER_ARG="-Dsolr.authentication.httpclient.builder=%SOLR_AUTHENTICATION_CLIENT_BUILDER%"
+  set AUTHC_CLIENT_BUILDER_ARG="-Dsolr.httpclient.builder.factory=%SOLR_AUTHENTICATION_CLIENT_BUILDER%"
 )
 set "AUTHC_OPTS=%AUTHC_CLIENT_BUILDER_ARG% %SOLR_AUTHENTICATION_OPTS%"
 
@@ -1154,7 +1185,7 @@ for /f "usebackq" %%i in (`dir /b "%SOLR_TIP%\bin" ^| findstr /i "^solr-.*\.port
           @echo.
           set has_info=1
           echo Found Solr process %%k running on port !SOME_SOLR_PORT!
-          "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^ 
+          "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
             -Dlog4j.configuration="file:%DEFAULT_SERVER_DIR%\scripts\cloud-scripts\log4j.properties" ^
             -classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
             org.apache.solr.util.SolrCLI status -solr !SOLR_URL_SCHEME!://%SOLR_TOOL_HOST%:!SOME_SOLR_PORT!/solr
@@ -1219,10 +1250,12 @@ goto done
 
 :run_utils
 set "TOOL_CMD=%~1"
+set q="-q"
+IF "%verbose%"=="1"  set q=""
 "%JAVA%" %SOLR_SSL_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
   -Dlog4j.configuration="file:%DEFAULT_SERVER_DIR%\scripts\cloud-scripts\log4j.properties" ^
   -classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
-  org.apache.solr.util.SolrCLI utils -s "%DEFAULT_SERVER_DIR%" -l "%SOLR_LOGS_DIR%" %TOOL_CMD%
+  org.apache.solr.util.SolrCLI utils -s "%DEFAULT_SERVER_DIR%" -l "%SOLR_LOGS_DIR%" %q:"=% %TOOL_CMD%
 if errorlevel 1 (
    exit /b 1
 )

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/bin/solr.in.cmd
----------------------------------------------------------------------
diff --git a/solr/bin/solr.in.cmd b/solr/bin/solr.in.cmd
index e565c02..279e03e 100644
--- a/solr/bin/solr.in.cmd
+++ b/solr/bin/solr.in.cmd
@@ -108,7 +108,9 @@ REM set SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD=
 REM set SOLR_SSL_CLIENT_TRUST_STORE_TYPE=
 
 REM Settings for authentication
-REM set SOLR_AUTHENTICATION_CLIENT_BUILDER=
+REM Please configure only one of SOLR_AUTHENTICATION_CLIENT_BUILDER or SOLR_AUTH_TYPE parameters
+REM set SOLR_AUTHENTICATION_CLIENT_BUILDER=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory
+REM set SOLR_AUTH_TYPE=basic
 REM set SOLR_AUTHENTICATION_OPTS="-Dbasicauth=solr:SolrRocks"
 
 REM Settings for ZK ACL

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/bin/solr.in.sh
----------------------------------------------------------------------
diff --git a/solr/bin/solr.in.sh b/solr/bin/solr.in.sh
index 5a9f807..878702f 100644
--- a/solr/bin/solr.in.sh
+++ b/solr/bin/solr.in.sh
@@ -120,7 +120,9 @@
 #SOLR_SSL_CLIENT_TRUST_STORE_TYPE=
 
 # Settings for authentication
-#SOLR_AUTHENTICATION_CLIENT_BUILDER=
+# Please configure only one of SOLR_AUTHENTICATION_CLIENT_BUILDER or SOLR_AUTH_TYPE parameters
+#SOLR_AUTHENTICATION_CLIENT_BUILDER="org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory"
+#SOLR_AUTH_TYPE="basic"
 #SOLR_AUTHENTICATION_OPTS="-Dbasicauth=solr:SolrRocks"
 
 # Settings for ZK ACL

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/build.xml
----------------------------------------------------------------------
diff --git a/solr/build.xml b/solr/build.xml
index 4c52b03..b900aed 100644
--- a/solr/build.xml
+++ b/solr/build.xml
@@ -137,6 +137,9 @@
 
   <target name="test" description="Validate, then run core, solrj, and contrib unit tests."
           depends="-init-totals, test-core, test-contrib, -check-totals"/>
+  <target name="test-nocompile">
+    <fail message="Target 'test-nocompile' will not run recursively.  First change directory to the module you want to test."/>
+  </target>
 
   <target name="jacoco" description="Generates JaCoCo code coverage reports." depends="-jacoco-install">
     <!-- run jacoco for each module -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/contrib/analytics/src/java/org/apache/solr/analytics/accumulator/facet/FieldFacetAccumulator.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/accumulator/facet/FieldFacetAccumulator.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/accumulator/facet/FieldFacetAccumulator.java
index ab36307..fb0884b 100644
--- a/solr/contrib/analytics/src/java/org/apache/solr/analytics/accumulator/facet/FieldFacetAccumulator.java
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/accumulator/facet/FieldFacetAccumulator.java
@@ -58,7 +58,7 @@ public class FieldFacetAccumulator extends ValueAccumulator {
     this.schemaField = schemaField;
     this.name = schemaField.getName();
     this.multiValued = schemaField.multiValued();
-    this.numField = schemaField.getType().getNumericType()!=null;
+    this.numField = schemaField.getType().getNumberType()!=null;
     this.dateField = schemaField.getType() instanceof DateValueFieldType;
     this.parent = parent;  
     this.parser = AnalyticsParsers.getParser(schemaField.getType().getClass());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/contrib/analytics/src/java/org/apache/solr/analytics/util/RangeEndpointCalculator.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/util/RangeEndpointCalculator.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/util/RangeEndpointCalculator.java
index fa9686d..c3c2088 100644
--- a/solr/contrib/analytics/src/java/org/apache/solr/analytics/util/RangeEndpointCalculator.java
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/util/RangeEndpointCalculator.java
@@ -229,8 +229,7 @@ public abstract class RangeEndpointCalculator<T extends Comparable<T>> {
     final FieldType ft = sf.getType();
     final RangeEndpointCalculator<?> calc;
     if (ft instanceof TrieField) {
-      final TrieField trie = (TrieField)ft;
-      switch (trie.getType()) {
+      switch (ft.getNumberType()) {
         case FLOAT:
           calc = new FloatRangeEndpointCalculator(request);
           break;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/contrib/analytics/src/java/org/apache/solr/analytics/util/valuesource/ConstDoubleSource.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/util/valuesource/ConstDoubleSource.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/util/valuesource/ConstDoubleSource.java
index 80e8ed1..e0ebad6 100644
--- a/solr/contrib/analytics/src/java/org/apache/solr/analytics/util/valuesource/ConstDoubleSource.java
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/util/valuesource/ConstDoubleSource.java
@@ -23,7 +23,6 @@ import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.queries.function.FunctionValues;
 import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
 import org.apache.lucene.queries.function.valuesource.ConstNumberSource;
-import org.apache.lucene.queries.function.valuesource.ConstValueSource;
 import org.apache.solr.analytics.util.AnalyticsParams;
 
 /**
@@ -67,7 +66,7 @@ public class ConstDoubleSource extends ConstNumberSource {
 
   @Override
   public boolean equals(Object o) {
-    if (!(o instanceof ConstValueSource)) return false;
+    if (!(o instanceof ConstDoubleSource)) return false;
     ConstDoubleSource other = (ConstDoubleSource)o;
     return  this.constant == other.constant;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/contrib/dataimporthandler-extras/src/test/org/apache/solr/handler/dataimport/TestMailEntityProcessor.java
----------------------------------------------------------------------
diff --git a/solr/contrib/dataimporthandler-extras/src/test/org/apache/solr/handler/dataimport/TestMailEntityProcessor.java b/solr/contrib/dataimporthandler-extras/src/test/org/apache/solr/handler/dataimport/TestMailEntityProcessor.java
index e595c1e..0b00be6 100644
--- a/solr/contrib/dataimporthandler-extras/src/test/org/apache/solr/handler/dataimport/TestMailEntityProcessor.java
+++ b/solr/contrib/dataimporthandler-extras/src/test/org/apache/solr/handler/dataimport/TestMailEntityProcessor.java
@@ -45,6 +45,7 @@ import java.util.Map;
  * @see org.apache.solr.handler.dataimport.MailEntityProcessor
  * @since solr 1.4
  */
+@Ignore("Needs a Mock Mail Server to work")
 public class TestMailEntityProcessor extends AbstractDataImportHandlerTestCase {
 
   // Credentials

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureLtrScoringModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureLtrScoringModel.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureLtrScoringModel.java
deleted file mode 100644
index a50e75e..0000000
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureLtrScoringModel.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.ltr.feature;
-
-import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.store.rest.ManagedFeatureStore;
-import org.apache.solr.ltr.store.rest.TestManagedFeatureStore;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class TestFeatureLtrScoringModel extends TestRerankBase {
-
-  static ManagedFeatureStore store = null;
-
-  @BeforeClass
-  public static void setup() throws Exception {
-    setuptest(true);
-    store = getManagedFeatureStore();
-  }
-
-  @AfterClass
-  public static void after() throws Exception {
-    aftertest();
-  }
-
-  @Test
-  public void getInstanceTest() throws FeatureException
-  {
-    store.addFeature(TestManagedFeatureStore.createMap("test",
-        OriginalScoreFeature.class.getCanonicalName(), null),
-        "testFstore");
-    final Feature feature = store.getFeatureStore("testFstore").get("test");
-    assertNotNull(feature);
-    assertEquals("test", feature.getName());
-    assertEquals(OriginalScoreFeature.class.getCanonicalName(), feature
-        .getClass().getCanonicalName());
-  }
-
-  @Test
-  public void getInvalidInstanceTest()
-  {
-    final String nonExistingClassName = "org.apache.solr.ltr.feature.LOLFeature";
-    final ClassNotFoundException expectedException =
-        new ClassNotFoundException(nonExistingClassName);
-    try {
-      store.addFeature(TestManagedFeatureStore.createMap("test",
-          nonExistingClassName, null),
-          "testFstore2");
-      fail("getInvalidInstanceTest failed to throw exception: "+expectedException);
-    } catch (Exception actualException) {
-      Throwable rootError = getRootCause(actualException);
-      assertEquals(expectedException.toString(), rootError.toString());
-    }
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureStore.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureStore.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureStore.java
deleted file mode 100644
index ca58b7b..0000000
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureStore.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.ltr.feature;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.store.FeatureStore;
-import org.apache.solr.ltr.store.rest.ManagedFeatureStore;
-import org.apache.solr.ltr.store.rest.TestManagedFeatureStore;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class TestFeatureStore extends TestRerankBase {
-
-  static ManagedFeatureStore fstore = null;
-
-  @BeforeClass
-  public static void setup() throws Exception {
-    setuptest(true);
-    fstore = getManagedFeatureStore();
-  }
-
-  @Test
-  public void testDefaultFeatureStoreName()
-  {
-    assertEquals("_DEFAULT_", FeatureStore.DEFAULT_FEATURE_STORE_NAME);
-    final FeatureStore expectedFeatureStore = fstore.getFeatureStore(FeatureStore.DEFAULT_FEATURE_STORE_NAME);
-    final FeatureStore actualFeatureStore = fstore.getFeatureStore(null);
-    assertEquals("getFeatureStore(null) should return the default feature store", expectedFeatureStore, actualFeatureStore);
-  }
-
-  @Test
-  public void testFeatureStoreAdd() throws FeatureException
-  {
-    final FeatureStore fs = fstore.getFeatureStore("fstore-testFeature");
-    for (int i = 0; i < 5; i++) {
-      final String name = "c" + i;
-
-      fstore.addFeature(TestManagedFeatureStore.createMap(name,
-          OriginalScoreFeature.class.getCanonicalName(), null),
-          "fstore-testFeature");
-
-      final Feature f = fs.get(name);
-      assertNotNull(f);
-
-    }
-    assertEquals(5, fs.getFeatures().size());
-
-  }
-
-  @Test
-  public void testFeatureStoreGet() throws FeatureException
-  {
-    final FeatureStore fs = fstore.getFeatureStore("fstore-testFeature2");
-    for (int i = 0; i < 5; i++) {
-      Map<String,Object> params = new HashMap<String,Object>();
-      params.put("value", i);
-      final String name = "c" + i;
-
-      fstore.addFeature(TestManagedFeatureStore.createMap(name,
-          ValueFeature.class.getCanonicalName(), params),
-          "fstore-testFeature2");
-
-    }
-
-    for (int i = 0; i < 5; i++) {
-      final Feature f = fs.get("c" + i);
-      assertEquals("c" + i, f.getName());
-      assertTrue(f instanceof ValueFeature);
-      final ValueFeature vf = (ValueFeature)f;
-      assertEquals(i, vf.getValue());
-    }
-  }
-
-  @Test
-  public void testMissingFeatureReturnsNull() {
-    final FeatureStore fs = fstore.getFeatureStore("fstore-testFeature3");
-    for (int i = 0; i < 5; i++) {
-      Map<String,Object> params = new HashMap<String,Object>();
-      params.put("value", i);
-      final String name = "testc" + (float) i;
-      fstore.addFeature(TestManagedFeatureStore.createMap(name,
-          ValueFeature.class.getCanonicalName(), params),
-          "fstore-testFeature3");
-
-    }
-    assertNull(fs.get("missing_feature_name"));
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestManagedFeatureStore.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestManagedFeatureStore.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestManagedFeatureStore.java
index 14373fb..c229ddd 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestManagedFeatureStore.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestManagedFeatureStore.java
@@ -19,11 +19,27 @@ package org.apache.solr.ltr.store.rest;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.ltr.feature.Feature;
+import org.apache.solr.ltr.feature.FeatureException;
+import org.apache.solr.ltr.feature.OriginalScoreFeature;
+import org.apache.solr.ltr.feature.ValueFeature;
+import org.apache.solr.ltr.store.FeatureStore;
+import org.apache.solr.ltr.store.rest.ManagedFeatureStore;
+import org.junit.BeforeClass;
+import org.junit.Test;
 
-public class TestManagedFeatureStore extends LuceneTestCase {
+public class TestManagedFeatureStore extends SolrTestCaseJ4 {
 
-  public static Map<String,Object> createMap(String name, String className, Map<String,Object> params) {
+  private static ManagedFeatureStore fstore = null;
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    initCore("solrconfig-ltr.xml", "schema.xml");
+    fstore = ManagedFeatureStore.getManagedFeatureStore(h.getCore());
+  }
+
+  private static Map<String,Object> createMap(String name, String className, Map<String,Object> params) {
     final Map<String,Object> map = new HashMap<String,Object>();
     map.put(ManagedFeatureStore.NAME_KEY, name);
     map.put(ManagedFeatureStore.CLASS_KEY, className);
@@ -33,4 +49,101 @@ public class TestManagedFeatureStore extends LuceneTestCase {
     return map;
   }
 
+  @Test
+  public void testDefaultFeatureStoreName()
+  {
+    assertEquals("_DEFAULT_", FeatureStore.DEFAULT_FEATURE_STORE_NAME);
+    final FeatureStore expectedFeatureStore = fstore.getFeatureStore(FeatureStore.DEFAULT_FEATURE_STORE_NAME);
+    final FeatureStore actualFeatureStore = fstore.getFeatureStore(null);
+    assertEquals("getFeatureStore(null) should return the default feature store", expectedFeatureStore, actualFeatureStore);
+  }
+
+  @Test
+  public void testFeatureStoreAdd() throws FeatureException
+  {
+    final FeatureStore fs = fstore.getFeatureStore("fstore-testFeature");
+    for (int i = 0; i < 5; i++) {
+      final String name = "c" + i;
+
+      fstore.addFeature(createMap(name,
+          OriginalScoreFeature.class.getCanonicalName(), null),
+          "fstore-testFeature");
+
+      final Feature f = fs.get(name);
+      assertNotNull(f);
+
+    }
+    assertEquals(5, fs.getFeatures().size());
+
+  }
+
+  @Test
+  public void testFeatureStoreGet() throws FeatureException
+  {
+    final FeatureStore fs = fstore.getFeatureStore("fstore-testFeature2");
+    for (int i = 0; i < 5; i++) {
+      Map<String,Object> params = new HashMap<String,Object>();
+      params.put("value", i);
+      final String name = "c" + i;
+
+      fstore.addFeature(createMap(name,
+          ValueFeature.class.getCanonicalName(), params),
+          "fstore-testFeature2");
+
+    }
+
+    for (int i = 0; i < 5; i++) {
+      final Feature f = fs.get("c" + i);
+      assertEquals("c" + i, f.getName());
+      assertTrue(f instanceof ValueFeature);
+      final ValueFeature vf = (ValueFeature)f;
+      assertEquals(i, vf.getValue());
+    }
+  }
+
+  @Test
+  public void testMissingFeatureReturnsNull() {
+    final FeatureStore fs = fstore.getFeatureStore("fstore-testFeature3");
+    for (int i = 0; i < 5; i++) {
+      Map<String,Object> params = new HashMap<String,Object>();
+      params.put("value", i);
+      final String name = "testc" + (float) i;
+      fstore.addFeature(createMap(name,
+          ValueFeature.class.getCanonicalName(), params),
+          "fstore-testFeature3");
+
+    }
+    assertNull(fs.get("missing_feature_name"));
+  }
+
+  @Test
+  public void getInstanceTest() throws FeatureException
+  {
+    fstore.addFeature(createMap("test",
+        OriginalScoreFeature.class.getCanonicalName(), null),
+        "testFstore");
+    final Feature feature = fstore.getFeatureStore("testFstore").get("test");
+    assertNotNull(feature);
+    assertEquals("test", feature.getName());
+    assertEquals(OriginalScoreFeature.class.getCanonicalName(), feature
+        .getClass().getCanonicalName());
+  }
+
+  @Test
+  public void getInvalidInstanceTest()
+  {
+    final String nonExistingClassName = "org.apache.solr.ltr.feature.LOLFeature";
+    final ClassNotFoundException expectedException =
+        new ClassNotFoundException(nonExistingClassName);
+    try {
+      fstore.addFeature(createMap("test",
+          nonExistingClassName, null),
+          "testFstore2");
+      fail("getInvalidInstanceTest failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/contrib/map-reduce/src/test/org/apache/solr/hadoop/MorphlineMapperTest.java
----------------------------------------------------------------------
diff --git a/solr/contrib/map-reduce/src/test/org/apache/solr/hadoop/MorphlineMapperTest.java b/solr/contrib/map-reduce/src/test/org/apache/solr/hadoop/MorphlineMapperTest.java
index 8b51088..4f93a66 100644
--- a/solr/contrib/map-reduce/src/test/org/apache/solr/hadoop/MorphlineMapperTest.java
+++ b/solr/contrib/map-reduce/src/test/org/apache/solr/hadoop/MorphlineMapperTest.java
@@ -25,6 +25,7 @@ import org.apache.hadoop.io.Text;
 import org.apache.hadoop.mrunit.mapreduce.MapDriver;
 import org.apache.hadoop.mrunit.types.Pair;
 import org.apache.lucene.util.Constants;
+import org.apache.lucene.util.LuceneTestCase.AwaitsFix;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.hadoop.morphline.MorphlineMapper;
 import org.apache.solr.util.BadHdfsThreadsFilter;
@@ -36,6 +37,7 @@ import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
 @ThreadLeakFilters(defaultFilters = true, filters = {
     BadHdfsThreadsFilter.class // hdfs currently leaks thread(s)
 })
+@AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/SOLR-9220")
 public class MorphlineMapperTest extends MRUnitBase {
   
   @BeforeClass
@@ -44,7 +46,6 @@ public class MorphlineMapperTest extends MRUnitBase {
   }
   
   @Test
-  @AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/SOLR-9220")
   public void testMapper() throws Exception {
     MorphlineMapper mapper = new MorphlineMapper();
     MapDriver<LongWritable, Text, Text, SolrInputDocumentWritable> mapDriver = MapDriver.newMapDriver(mapper);;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/contrib/map-reduce/src/test/org/apache/solr/hadoop/MorphlineReducerTest.java
----------------------------------------------------------------------
diff --git a/solr/contrib/map-reduce/src/test/org/apache/solr/hadoop/MorphlineReducerTest.java b/solr/contrib/map-reduce/src/test/org/apache/solr/hadoop/MorphlineReducerTest.java
index 89a5110..31616d8 100644
--- a/solr/contrib/map-reduce/src/test/org/apache/solr/hadoop/MorphlineReducerTest.java
+++ b/solr/contrib/map-reduce/src/test/org/apache/solr/hadoop/MorphlineReducerTest.java
@@ -43,6 +43,7 @@ import org.mockito.stubbing.Answer;
 
 import com.google.common.collect.Lists;
 
+@Ignore("This test cannot currently work because it uses a local filesystem output path for the indexes and Solr requires hdfs output paths")
 public class MorphlineReducerTest extends MRUnitBase {
   
   @BeforeClass
@@ -94,7 +95,6 @@ public class MorphlineReducerTest extends MRUnitBase {
   }
 
   @Test
-  @Ignore("This test cannot currently work because it uses a local filesystem output path for the indexes and Solr requires hdfs output paths")
   public void testReducer() throws Exception {
     MySolrReducer myReducer = new MySolrReducer();
     try {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/api/Api.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/Api.java b/solr/core/src/java/org/apache/solr/api/Api.java
new file mode 100644
index 0000000..8512c89
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/Api.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.api;
+
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.JsonSchemaValidator;
+
+/** Every version 2 API must extend the this class. It's mostly like a request handler
+ * but it has extra methods to provide the json schema of the end point
+ *
+ */
+public abstract class Api implements SpecProvider {
+  protected SpecProvider spec;
+  protected volatile Map<String, JsonSchemaValidator> commandSchema;
+
+  protected Api(SpecProvider spec) {
+    this.spec = spec;
+  }
+
+  /**This method helps to cache the schema validator object
+   */
+  public Map<String, JsonSchemaValidator> getCommandSchema() {
+    if (commandSchema == null) {
+      synchronized (this) {
+        if(commandSchema == null) {
+          ValidatingJsonMap commands = getSpec().getMap("commands", null);
+          commandSchema = commands != null ?
+              ImmutableMap.copyOf(ApiBag.getParsedSchema(commands)) :
+              ImmutableMap.of();
+        }
+      }
+    }
+    return commandSchema;
+  }
+
+  /** The method that gets called for each request
+   */
+  public abstract void call(SolrQueryRequest req , SolrQueryResponse rsp);
+
+  /**Get the specification of the API as a Map
+   */
+  @Override
+  public ValidatingJsonMap getSpec() {
+    return spec.getSpec();
+  }
+
+}


[17/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java b/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
index a8c0a82..b8d8c29 100644
--- a/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
+++ b/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
@@ -19,6 +19,7 @@ package org.apache.lucene.util;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.lucene.analysis.Analyzer;
@@ -56,6 +57,7 @@ import org.apache.lucene.util.graph.GraphTokenStreamFiniteStrings;
 public class QueryBuilder {
   protected Analyzer analyzer;
   protected boolean enablePositionIncrements = true;
+  protected boolean autoGenerateMultiTermSynonymsPhraseQuery = false;
   
   /** Creates a new QueryBuilder using the given analyzer. */
   public QueryBuilder(Analyzer analyzer) {
@@ -131,7 +133,7 @@ public class QueryBuilder {
       throw new IllegalArgumentException("fraction should be >= 0 and <= 1");
     }
     
-    // TODO: wierd that BQ equals/rewrite/scorer doesn't handle this?
+    // TODO: weird that BQ equals/rewrite/scorer doesn't handle this?
     if (fraction == 1) {
       return createBooleanQuery(field, queryText, BooleanClause.Occur.MUST);
     }
@@ -139,21 +141,6 @@ public class QueryBuilder {
     Query query = createFieldQuery(analyzer, BooleanClause.Occur.SHOULD, field, queryText, false, 0);
     if (query instanceof BooleanQuery) {
       query = addMinShouldMatchToBoolean((BooleanQuery) query, fraction);
-    } else if (query instanceof GraphQuery && ((GraphQuery) query).hasBoolean()) {
-      // we have a graph query that has at least one boolean sub-query
-      // re-build and set minimum should match on each boolean found
-      List<Query> oldQueries = ((GraphQuery) query).getQueries();
-      Query[] queries = new Query[oldQueries.size()];
-      for (int i = 0; i < queries.length; i++) {
-        Query oldQuery = oldQueries.get(i);
-        if (oldQuery instanceof BooleanQuery) {
-          queries[i] = addMinShouldMatchToBoolean((BooleanQuery) oldQuery, fraction);
-        } else {
-          queries[i] = oldQuery;
-        }
-      }
-
-      query = new GraphQuery(queries);
     }
     return query;
   }
@@ -208,6 +195,22 @@ public class QueryBuilder {
     this.enablePositionIncrements = enable;
   }
 
+  /**
+   * Returns true if phrase query should be automatically generated for multi terms synonyms.
+   * @see #setAutoGenerateMultiTermSynonymsPhraseQuery(boolean)
+   */
+  public boolean getAutoGenerateMultiTermSynonymsPhraseQuery() {
+    return autoGenerateMultiTermSynonymsPhraseQuery;
+  }
+
+  /**
+   * Set to <code>true</code> if phrase queries should be automatically generated
+   * for multi terms synonyms.
+   * Default: false.
+   */
+  public void setAutoGenerateMultiTermSynonymsPhraseQuery(boolean enable) {
+    this.autoGenerateMultiTermSynonymsPhraseQuery = enable;
+  }
 
   /**
    * Creates a query from the analysis chain.
@@ -294,7 +297,11 @@ public class QueryBuilder {
         return analyzeTerm(field, stream);
       } else if (isGraph) {
         // graph
-        return analyzeGraph(stream, operator, field, quoted, phraseSlop);
+        if (quoted) {
+          return analyzeGraphPhrase(stream, operator, field, phraseSlop);
+        } else {
+          return analyzeGraphBoolean(field, stream, operator);
+        }
       } else if (quoted && positionCount > 1) {
         // phrase
         if (hasSynonyms) {
@@ -444,25 +451,74 @@ public class QueryBuilder {
   }
 
   /**
-   * Creates a query from a graph token stream by extracting all the finite strings from the graph and using them to create the query.
+   * Creates a boolean query from a graph token stream. The articulation points of the graph are visited in order and the queries
+   * created at each point are merged in the returned boolean query.
    */
-  protected Query analyzeGraph(TokenStream source, BooleanClause.Occur operator, String field, boolean quoted, int phraseSlop)
-      throws IOException {
+  protected Query analyzeGraphBoolean(String field, TokenStream source, BooleanClause.Occur operator) throws IOException {
     source.reset();
-    List<TokenStream> tokenStreams = GraphTokenStreamFiniteStrings.getTokenStreams(source);
-
-    if (tokenStreams.isEmpty()) {
-      return null;
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(source);
+    BooleanQuery.Builder builder = new BooleanQuery.Builder();
+    int[] articulationPoints = graph.articulationPoints();
+    int lastState = 0;
+    for (int i = 0; i <= articulationPoints.length; i++) {
+      int start = lastState;
+      int end = -1;
+      if (i < articulationPoints.length) {
+        end = articulationPoints[i];
+      }
+      lastState = end;
+      final Query queryPos;
+      if (graph.hasSidePath(start)) {
+        List<Query> queries = new ArrayList<> ();
+        Iterator<TokenStream> it = graph.getFiniteStrings(start, end);
+        while (it.hasNext()) {
+          TokenStream ts = it.next();
+          // This is a synonym path so all terms are mandatory (MUST).
+          Query q = createFieldQuery(ts, BooleanClause.Occur.MUST, field, getAutoGenerateMultiTermSynonymsPhraseQuery(), 0);
+          if (q != null) {
+            queries.add(q);
+          }
+        }
+        if (queries.size() > 0) {
+          queryPos = newGraphSynonymQuery(queries.toArray(new Query[queries.size()]));
+        } else {
+          queryPos = null;
+        }
+      } else {
+        Term[] terms = graph.getTerms(field, start);
+        assert terms.length > 0;
+        if (terms.length == 1) {
+          queryPos = newTermQuery(terms[0]);
+        } else {
+          queryPos = newSynonymQuery(terms);
+        }
+      }
+      if (queryPos != null) {
+        builder.add(queryPos, operator);
+      }
     }
+    BooleanQuery bq =  builder.build();
+    if (bq.clauses().size() == 1) {
+      return bq.clauses().get(0).getQuery();
+    }
+    return bq;
+  }
 
-    List<Query> queries = new ArrayList<>(tokenStreams.size());
-    for (TokenStream ts : tokenStreams) {
-      Query query = createFieldQuery(ts, operator, field, quoted, phraseSlop);
+  /**
+   * Creates a query from a graph token stream by extracting all the finite strings from the graph and using them to create the query.
+   */
+  protected Query analyzeGraphPhrase(TokenStream source, BooleanClause.Occur operator, String field, int phraseSlop)
+      throws IOException {
+    source.reset();
+    GraphTokenStreamFiniteStrings visitor = new GraphTokenStreamFiniteStrings(source);
+    Iterator<TokenStream> it = visitor.getFiniteStrings();
+    List<Query> queries = new ArrayList<>();
+    while (it.hasNext()) {
+      Query query = createFieldQuery(it.next(), operator, field, true, phraseSlop);
       if (query != null) {
         queries.add(query);
       }
     }
-
     return new GraphQuery(queries.toArray(new Query[0]));
   }
 
@@ -485,6 +541,16 @@ public class QueryBuilder {
   protected Query newSynonymQuery(Term terms[]) {
     return new SynonymQuery(terms);
   }
+
+  /**
+   * Builds a new GraphQuery for multi-terms synonyms.
+   * <p>
+   * This is intended for subclasses that wish to customize the generated queries.
+   * @return new Query instance
+   */
+  protected Query newGraphSynonymQuery(Query queries[]) {
+    return new GraphQuery(queries);
+  }
   
   /**
    * Builds a new TermQuery instance.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/util/Version.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/Version.java b/lucene/core/src/java/org/apache/lucene/util/Version.java
index 00fb329..6477816 100644
--- a/lucene/core/src/java/org/apache/lucene/util/Version.java
+++ b/lucene/core/src/java/org/apache/lucene/util/Version.java
@@ -81,6 +81,13 @@ public final class Version {
   public static final Version LUCENE_6_4_0 = new Version(6, 4, 0);
 
   /**
+   * Match settings and bugs in Lucene's 6.4.1 release.
+   * @deprecated Use latest
+   */
+  @Deprecated
+  public static final Version LUCENE_6_4_1 = new Version(6, 4, 1);
+
+  /**
    * Match settings and bugs in Lucene's 6.5.0 release.
    * @deprecated Use latest
    */

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/util/automaton/FiniteStringsIterator.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/automaton/FiniteStringsIterator.java b/lucene/core/src/java/org/apache/lucene/util/automaton/FiniteStringsIterator.java
index 229cdc9..4472bda 100644
--- a/lucene/core/src/java/org/apache/lucene/util/automaton/FiniteStringsIterator.java
+++ b/lucene/core/src/java/org/apache/lucene/util/automaton/FiniteStringsIterator.java
@@ -17,13 +17,13 @@
 package org.apache.lucene.util.automaton;
 
 
+import java.util.BitSet;
+
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.IntsRef;
 import org.apache.lucene.util.IntsRefBuilder;
 import org.apache.lucene.util.RamUsageEstimator;
 
-import java.util.BitSet;
-
 /**
  * Iterates all accepted strings.
  *
@@ -50,6 +50,11 @@ public class FiniteStringsIterator {
   private final Automaton a;
 
   /**
+   * The state where each path should stop or -1 if only accepted states should be final.
+   */
+  private final int endState;
+
+  /**
    * Tracks which states are in the current path, for cycle detection.
    */
   private final BitSet pathStates;
@@ -75,7 +80,20 @@ public class FiniteStringsIterator {
    * @param a Automaton to create finite string from.
    */
   public FiniteStringsIterator(Automaton a) {
+    this(a, 0, -1);
+  }
+
+
+  /**
+   * Constructor.
+   *
+   * @param a Automaton to create finite string from.
+   * @param startState The starting state for each path.
+   * @param endState The state where each path should stop or -1 if only accepted states should be final.
+   */
+  public FiniteStringsIterator(Automaton a, int startState, int endState) {
     this.a = a;
+    this.endState = endState;
     this.nodes = new PathNode[16];
     for (int i = 0, end = nodes.length; i < end; i++) {
       nodes[i] = new PathNode();
@@ -85,11 +103,11 @@ public class FiniteStringsIterator {
     this.string.setLength(0);
     this.emitEmptyString = a.isAccept(0);
 
-    // Start iteration with node 0.
-    if (a.getNumTransitions(0) > 0) {
-      pathStates.set(0);
-      nodes[0].resetState(a, 0);
-      string.append(0);
+    // Start iteration with node startState.
+    if (a.getNumTransitions(startState) > 0) {
+      pathStates.set(startState);
+      nodes[0].resetState(a, startState);
+      string.append(startState);
     }
   }
 
@@ -115,7 +133,7 @@ public class FiniteStringsIterator {
         string.setIntAt(depth - 1, label);
 
         int to = node.to;
-        if (a.getNumTransitions(to) != 0) {
+        if (a.getNumTransitions(to) != 0 && to != endState) {
           // Now recurse: the destination of this transition has outgoing transitions:
           if (pathStates.get(to)) {
             throw new IllegalArgumentException("automaton has cycles");
@@ -128,7 +146,7 @@ public class FiniteStringsIterator {
           depth++;
           string.setLength(depth);
           string.grow(depth);
-        } else if (a.isAccept(to)) {
+        } else if (endState == to || a.isAccept(to)) {
           // This transition leads to an accept state, so we save the current string:
           return string.get();
         }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
index e120435..ad65990 100644
--- a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
+++ b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
@@ -528,10 +528,19 @@ public final class BKDReader extends PointValues implements Accountable {
   }
 
   /** Fast path: this is called when the query box fully encompasses all cells under this node. */
-  private void addAll(IntersectState state) throws IOException {
+  private void addAll(IntersectState state, boolean grown) throws IOException {
     //System.out.println("R: addAll nodeID=" + nodeID);
 
+    if (grown == false) {
+      final long maxPointCount = (long) maxPointsInLeafNode * state.index.getNumLeaves();
+      if (maxPointCount <= Integer.MAX_VALUE) { // could be >MAX_VALUE if there are more than 2B points in total
+        state.visitor.grow((int) maxPointCount);
+        grown = true;
+      }
+    }
+
     if (state.index.isLeafNode()) {
+      assert grown;
       //System.out.println("ADDALL");
       if (state.index.nodeExists()) {
         visitDocIDs(state.in, state.index.getLeafBlockFP(), state.visitor);
@@ -539,11 +548,11 @@ public final class BKDReader extends PointValues implements Accountable {
       // TODO: we can assert that the first value here in fact matches what the index claimed?
     } else {
       state.index.pushLeft();
-      addAll(state);
+      addAll(state, grown);
       state.index.pop();
 
       state.index.pushRight();
-      addAll(state);
+      addAll(state, grown);
       state.index.pop();
     }
   }
@@ -579,7 +588,7 @@ public final class BKDReader extends PointValues implements Accountable {
 
     // How many points are stored in this leaf cell:
     int count = in.readVInt();
-    visitor.grow(count);
+    // No need to call grow(), it has been called up-front
 
     if (version < BKDWriter.VERSION_COMPRESSED_DOC_IDS) {
       DocIdsWriter.readInts32(in, count, visitor);
@@ -687,7 +696,7 @@ public final class BKDReader extends PointValues implements Accountable {
       // This cell is fully outside of the query shape: stop recursing
     } else if (r == Relation.CELL_INSIDE_QUERY) {
       // This cell is fully inside of the query shape: recursively add all points in this cell without filtering
-      addAll(state);
+      addAll(state, false);
       // The cell crosses the shape boundary, or the cell fully contains the query, so we fall through and do full filtering:
     } else if (state.index.isLeafNode()) {
       

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java b/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java
index cec65fa..fd85836 100644
--- a/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java
+++ b/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java
@@ -19,7 +19,11 @@ package org.apache.lucene.util.graph;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -28,25 +32,27 @@ import org.apache.lucene.analysis.tokenattributes.BytesTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
 import org.apache.lucene.analysis.tokenattributes.TermToBytesRefAttribute;
+import org.apache.lucene.index.Term;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.IntsRef;
 import org.apache.lucene.util.automaton.Automaton;
 import org.apache.lucene.util.automaton.FiniteStringsIterator;
 import org.apache.lucene.util.automaton.Operations;
+import org.apache.lucene.util.automaton.Transition;
 
 import static org.apache.lucene.util.automaton.Operations.DEFAULT_MAX_DETERMINIZED_STATES;
 
 /**
- * Creates a list of {@link TokenStream} where each stream is the tokens that make up a finite string in graph token stream.  To do this,
- * the graph token stream is converted to an {@link Automaton} and from there we use a {@link FiniteStringsIterator} to collect the various
- * token streams for each finite string.
+ * Consumes a TokenStream and creates an {@link Automaton} where the transition labels are terms from
+ * the {@link TermToBytesRefAttribute}.
+ * This class also provides helpers to explore the different paths of the {@link Automaton}.
  */
 public final class GraphTokenStreamFiniteStrings {
-  private final Automaton.Builder builder = new Automaton.Builder();
   private final Map<BytesRef, Integer> termToID = new HashMap<>();
   private final Map<Integer, BytesRef> idToTerm = new HashMap<>();
   private final Map<Integer, Integer> idToInc = new HashMap<>();
-  private Automaton det;
+  private final Automaton det;
+  private final Transition transition = new Transition();
 
   private class FiniteStringsTokenStream extends TokenStream {
     private final BytesTermAttribute termAtt = addAttribute(BytesTermAttribute.class);
@@ -82,37 +88,121 @@ public final class GraphTokenStreamFiniteStrings {
     }
   }
 
-  private GraphTokenStreamFiniteStrings() {
+  public GraphTokenStreamFiniteStrings(TokenStream in) throws IOException {
+    Automaton aut = build(in);
+    this.det = Operations.removeDeadStates(Operations.determinize(aut, DEFAULT_MAX_DETERMINIZED_STATES));
   }
 
   /**
-   * Gets the list of finite string token streams from the given input graph token stream.
+   * Returns whether the provided state is the start of multiple side paths of different length (eg: new york, ny)
    */
-  public static List<TokenStream> getTokenStreams(final TokenStream in) throws IOException {
-    GraphTokenStreamFiniteStrings gfs = new GraphTokenStreamFiniteStrings();
-    return gfs.process(in);
+  public boolean hasSidePath(int state) {
+    int numT = det.initTransition(state, transition);
+    if (numT <= 1) {
+      return false;
+    }
+    det.getNextTransition(transition);
+    int dest = transition.dest;
+    for (int i = 1; i < numT; i++) {
+      det.getNextTransition(transition);
+      if (dest != transition.dest) {
+        return true;
+      }
+    }
+    return false;
   }
 
   /**
-   * Builds automaton and builds the finite string token streams.
+   * Returns the list of terms that start at the provided state
    */
-  private List<TokenStream> process(final TokenStream in) throws IOException {
-    build(in);
-
-    List<TokenStream> tokenStreams = new ArrayList<>();
-    final FiniteStringsIterator finiteStrings = new FiniteStringsIterator(det);
-    for (IntsRef ids; (ids = finiteStrings.next()) != null; ) {
-      tokenStreams.add(new FiniteStringsTokenStream(IntsRef.deepCopyOf(ids)));
+  public Term[] getTerms(String field, int state) {
+    int numT = det.initTransition(state, transition);
+    List<Term> terms = new ArrayList<> ();
+    for (int i = 0; i < numT; i++) {
+      det.getNextTransition(transition);
+      for (int id = transition.min; id <= transition.max; id++) {
+        Term term = new Term(field, idToTerm.get(id));
+        terms.add(term);
+      }
     }
+    return terms.toArray(new Term[terms.size()]);
+  }
+
+  /**
+   * Get all finite strings from the automaton.
+   */
+  public Iterator<TokenStream> getFiniteStrings() throws IOException {
+    return getFiniteStrings(0, -1);
+  }
+
+
+  /**
+   * Get all finite strings that start at {@code startState} and end at {@code endState}.
+   */
+  public Iterator<TokenStream> getFiniteStrings(int startState, int endState) throws IOException {
+    final FiniteStringsIterator it = new FiniteStringsIterator(det, startState, endState);
+    return new Iterator<TokenStream> () {
+      IntsRef current;
+      boolean finished = false;
+
+      @Override
+      public boolean hasNext() {
+        if (finished == false && current == null) {
+          current = it.next();
+          if (current == null) {
+            finished = true;
+          }
+        }
+        return current != null;
+      }
 
-    return tokenStreams;
+      @Override
+      public TokenStream next() {
+        if (current == null) {
+          hasNext();
+        }
+        TokenStream next =  new FiniteStringsTokenStream(current);
+        current = null;
+        return next;
+      }
+    };
   }
 
-  private void build(final TokenStream in) throws IOException {
-    if (det != null) {
-      throw new IllegalStateException("Automation already built");
+  /**
+   * Returns the articulation points (or cut vertices) of the graph:
+   * https://en.wikipedia.org/wiki/Biconnected_component
+   */
+  public int[] articulationPoints() {
+    if (det.getNumStates() == 0) {
+      return new int[0];
+    }
+    //
+    Automaton.Builder undirect = new Automaton.Builder();
+    undirect.copy(det);
+    for (int i = 0; i < det.getNumStates(); i++) {
+      int numT = det.initTransition(i, transition);
+      for (int j = 0; j < numT; j++) {
+        det.getNextTransition(transition);
+        undirect.addTransition(transition.dest, i, transition.min);
+      }
     }
+    int numStates = det.getNumStates();
+    BitSet visited = new BitSet(numStates);
+    int[] depth = new int[det.getNumStates()];
+    int[] low = new int[det.getNumStates()];
+    int[] parent = new int[det.getNumStates()];
+    Arrays.fill(parent, -1);
+    List<Integer> points = new ArrayList<>();
+    articulationPointsRecurse(undirect.finish(), 0, 0, depth, low, parent, visited, points);
+    Collections.reverse(points);
+    return points.stream().mapToInt(p -> p).toArray();
+  }
 
+  /**
+   * Build an automaton from the provided {@link TokenStream}.
+   */
+  private Automaton build(final TokenStream in) throws IOException {
+    Automaton.Builder builder = new Automaton.Builder();
     final TermToBytesRefAttribute termBytesAtt = in.addAttribute(TermToBytesRefAttribute.class);
     final PositionIncrementAttribute posIncAtt = in.addAttribute(PositionIncrementAttribute.class);
     final PositionLengthAttribute posLengthAtt = in.addAttribute(PositionLengthAttribute.class);
@@ -136,12 +226,12 @@ public final class GraphTokenStreamFiniteStrings {
 
       int endPos = pos + posLengthAtt.getPositionLength();
       while (state < endPos) {
-        state = createState();
+        state = builder.createState();
       }
 
       BytesRef term = termBytesAtt.getBytesRef();
       int id = getTermID(currentIncr, prevIncr, term);
-      addTransition(pos, endPos, currentIncr, id);
+      builder.addTransition(pos, endPos, id);
 
       // only save last increment on non-zero increment in case we have multiple stacked tokens
       if (currentIncr > 0) {
@@ -150,47 +240,10 @@ public final class GraphTokenStreamFiniteStrings {
     }
 
     in.end();
-    setAccept(state, true);
-    finish();
-  }
-
-  /**
-   * Returns a new state; state 0 is always the initial state.
-   */
-  private int createState() {
-    return builder.createState();
-  }
-
-  /**
-   * Marks the specified state as accept or not.
-   */
-  private void setAccept(int state, boolean accept) {
-    builder.setAccept(state, accept);
-  }
-
-  /**
-   * Adds a transition to the automaton.
-   */
-  private void addTransition(int source, int dest, int incr, int id) {
-    builder.addTransition(source, dest, id);
-  }
-
-  /**
-   * Call this once you are done adding states/transitions.
-   */
-  private void finish() {
-    finish(DEFAULT_MAX_DETERMINIZED_STATES);
-  }
-
-  /**
-   * Call this once you are done adding states/transitions.
-   *
-   * @param maxDeterminizedStates Maximum number of states created when determinizing the automaton.  Higher numbers allow this operation
-   *                              to consume more memory but allow more complex automatons.
-   */
-  private void finish(int maxDeterminizedStates) {
-    Automaton automaton = builder.finish();
-    det = Operations.removeDeadStates(Operations.determinize(automaton, maxDeterminizedStates));
+    if (state != -1) {
+      builder.setAccept(state, true);
+    }
+    return builder.finish();
   }
 
   /**
@@ -224,7 +277,34 @@ public final class GraphTokenStreamFiniteStrings {
         idToTerm.put(id, term);
       }
     }
-
     return id;
   }
+
+  private static void articulationPointsRecurse(Automaton a, int state, int d, int[] depth, int[] low, int[] parent,
+                                                BitSet visited, List<Integer> points) {
+    visited.set(state);
+    depth[state] = d;
+    low[state] = d;
+    int childCount = 0;
+    boolean isArticulation = false;
+    Transition t = new Transition();
+    int numT = a.initTransition(state, t);
+    for (int i = 0; i < numT; i++) {
+      a.getNextTransition(t);
+      if (visited.get(t.dest) == false) {
+        parent[t.dest] = state;
+        articulationPointsRecurse(a, t.dest, d + 1, depth, low, parent, visited, points);
+        childCount++;
+        if (low[t.dest] >= depth[state]) {
+          isArticulation = true;
+        }
+        low[state] = Math.min(low[state], low[t.dest]);
+      } else if (t.dest != parent[state]) {
+        low[state] = Math.min(low[state], depth[t.dest]);
+      }
+    }
+    if ((parent[state] != -1 && isArticulation) || (parent[state] == -1 && childCount > 1)) {
+      points.add(state);
+    }
+  }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java b/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
index 8f0be3a..b4bfcbe 100644
--- a/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
+++ b/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
@@ -160,8 +160,8 @@ public class TestLucene60PointsFormat extends BasePointsFormatTestCase {
         }));
 
     // If only one point matches, then the point count is (maxPointsInLeafNode + 1) / 2
-    assertEquals((maxPointsInLeafNode + 1) / 2,
-        points.estimatePointCount(new IntersectVisitor() {
+    // in general, or maybe 2x that if the point is a split value
+    final long pointCount = points.estimatePointCount(new IntersectVisitor() {
           @Override
           public void visit(int docID, byte[] packedValue) throws IOException {}
           
@@ -176,7 +176,10 @@ public class TestLucene60PointsFormat extends BasePointsFormatTestCase {
             }
             return Relation.CELL_CROSSES_QUERY;
           }
-        }));
+        });
+    assertTrue(""+pointCount,
+        pointCount == (maxPointsInLeafNode + 1) / 2 || // common case
+        pointCount == 2*((maxPointsInLeafNode + 1) / 2)); // if the point is a split value
 
     r.close();
     dir.close();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/test/org/apache/lucene/geo/TestGeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestGeoUtils.java b/lucene/core/src/test/org/apache/lucene/geo/TestGeoUtils.java
index 2cfb2f8..3d95a6d 100644
--- a/lucene/core/src/test/org/apache/lucene/geo/TestGeoUtils.java
+++ b/lucene/core/src/test/org/apache/lucene/geo/TestGeoUtils.java
@@ -293,4 +293,16 @@ public class TestGeoUtils extends LuceneTestCase {
 
     return false;
   }
+
+  public void testWithin90LonDegrees() {
+    assertTrue(GeoUtils.within90LonDegrees(0, -80, 80));
+    assertFalse(GeoUtils.within90LonDegrees(0, -100, 80));
+    assertFalse(GeoUtils.within90LonDegrees(0, -80, 100));
+
+    assertTrue(GeoUtils.within90LonDegrees(-150, 140, 170));
+    assertFalse(GeoUtils.within90LonDegrees(-150, 120, 150));
+
+    assertTrue(GeoUtils.within90LonDegrees(150, -170, -140));
+    assertFalse(GeoUtils.within90LonDegrees(150, -150, -120));
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/test/org/apache/lucene/index/TestFilterCodecReader.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestFilterCodecReader.java b/lucene/core/src/test/org/apache/lucene/index/TestFilterCodecReader.java
new file mode 100644
index 0000000..feb803f
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/index/TestFilterCodecReader.java
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.index;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestFilterCodecReader extends LuceneTestCase {
+
+  public void testDeclaredMethodsOverridden() throws Exception {
+    final Class<?> subClass = FilterCodecReader.class;
+    implTestDeclaredMethodsOverridden(subClass.getSuperclass(), subClass);
+  }
+
+  private void implTestDeclaredMethodsOverridden(Class<?> superClass, Class<?> subClass) throws Exception {
+    for (final Method superClassMethod : superClass.getDeclaredMethods()) {
+      final int modifiers = superClassMethod.getModifiers();
+      if (Modifier.isPrivate(modifiers)) continue;
+      if (Modifier.isFinal(modifiers)) continue;
+      if (Modifier.isStatic(modifiers)) continue;
+      try {
+        final Method subClassMethod = subClass.getDeclaredMethod(
+            superClassMethod.getName(),
+            superClassMethod.getParameterTypes());
+        assertEquals("getReturnType() difference",
+            superClassMethod.getReturnType(),
+            subClassMethod.getReturnType());
+      } catch (NoSuchMethodException e) {
+        fail(subClass + " needs to override '" + superClassMethod + "'");
+      }
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/test/org/apache/lucene/index/TestIndexSorting.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestIndexSorting.java b/lucene/core/src/test/org/apache/lucene/index/TestIndexSorting.java
index 4885fc2..c2b180a 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestIndexSorting.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestIndexSorting.java
@@ -204,7 +204,7 @@ public class TestIndexSorting extends LuceneTestCase {
     // segment sort is needed
     codec.needsIndexSort = true;
     codec.numCalls = 0;
-    for (int i = 200; i < 300; i++) {
+    for (int i = 201; i < 300; i++) {
       Document doc = new Document();
       doc.add(new StringField("id", Integer.toString(i), Store.YES));
       doc.add(new NumericDocValuesField("id", i));
@@ -1698,7 +1698,6 @@ public class TestIndexSorting extends LuceneTestCase {
     dir.close();
   }
 
-
   // docvalues fields involved in the index sort cannot be updated
   public void testBadDVUpdate() throws Exception {
     Directory dir = newDirectory();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java b/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java
index 9cd8390..e349e98 100644
--- a/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java
+++ b/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java
@@ -164,6 +164,77 @@ public class TestQueryBuilder extends LuceneTestCase {
         queryBuilder.createPhraseQuery("field", "guinea pig"));
   }
 
+  /** forms graph query */
+  public void testMultiWordSynonymsBoolean() throws Exception {
+    for (BooleanClause.Occur occur : new BooleanClause.Occur[] {BooleanClause.Occur.SHOULD, BooleanClause.Occur.MUST}) {
+      Query syn1 = new BooleanQuery.Builder()
+          .add(new TermQuery(new Term("field", "guinea")), BooleanClause.Occur.MUST)
+          .add(new TermQuery(new Term("field", "pig")), BooleanClause.Occur.MUST)
+          .build();
+      Query syn2 = new TermQuery(new Term("field", "cavy"));
+      GraphQuery expectedGraphQuery = new GraphQuery(syn1, syn2);
+      QueryBuilder queryBuilder = new QueryBuilder(new MockSynonymAnalyzer());
+      assertEquals(expectedGraphQuery, queryBuilder.createBooleanQuery("field", "guinea pig", occur));
+
+      BooleanQuery expectedBooleanQuery = new BooleanQuery.Builder()
+          .add(expectedGraphQuery, occur)
+          .add(new TermQuery(new Term("field", "story")), occur)
+          .build();
+      assertEquals(expectedBooleanQuery, queryBuilder.createBooleanQuery("field", "guinea pig story", occur));
+
+      expectedBooleanQuery = new BooleanQuery.Builder()
+          .add(new TermQuery(new Term("field", "the")), occur)
+          .add(expectedGraphQuery, occur)
+          .add(new TermQuery(new Term("field", "story")), occur)
+          .build();
+      assertEquals(expectedBooleanQuery, queryBuilder.createBooleanQuery("field", "the guinea pig story", occur));
+
+      expectedBooleanQuery = new BooleanQuery.Builder()
+          .add(new TermQuery(new Term("field", "the")), occur)
+          .add(expectedGraphQuery, occur)
+          .add(new TermQuery(new Term("field", "story")), occur)
+          .add(expectedGraphQuery, occur)
+          .build();
+      assertEquals(expectedBooleanQuery, queryBuilder.createBooleanQuery("field", "the guinea pig story guinea pig", occur));
+    }
+  }
+
+  /** forms graph query */
+  public void testMultiWordPhraseSynonymsBoolean() throws Exception {
+    for (BooleanClause.Occur occur : new BooleanClause.Occur[] {BooleanClause.Occur.SHOULD, BooleanClause.Occur.MUST}) {
+      Query syn1 = new PhraseQuery.Builder()
+          .add(new Term("field", "guinea"))
+          .add(new Term("field", "pig"))
+          .build();
+      Query syn2 = new TermQuery(new Term("field", "cavy"));
+      GraphQuery expectedGraphQuery = new GraphQuery(syn1, syn2);
+      QueryBuilder queryBuilder = new QueryBuilder(new MockSynonymAnalyzer());
+      queryBuilder.setAutoGenerateMultiTermSynonymsPhraseQuery(true);
+      assertEquals(expectedGraphQuery, queryBuilder.createBooleanQuery("field", "guinea pig", occur));
+
+      BooleanQuery expectedBooleanQuery = new BooleanQuery.Builder()
+          .add(expectedGraphQuery, occur)
+          .add(new TermQuery(new Term("field", "story")), occur)
+          .build();
+      assertEquals(expectedBooleanQuery, queryBuilder.createBooleanQuery("field", "guinea pig story", occur));
+
+      expectedBooleanQuery = new BooleanQuery.Builder()
+          .add(new TermQuery(new Term("field", "the")), occur)
+          .add(expectedGraphQuery, occur)
+          .add(new TermQuery(new Term("field", "story")), occur)
+          .build();
+      assertEquals(expectedBooleanQuery, queryBuilder.createBooleanQuery("field", "the guinea pig story", occur));
+
+      expectedBooleanQuery = new BooleanQuery.Builder()
+          .add(new TermQuery(new Term("field", "the")), occur)
+          .add(expectedGraphQuery, occur)
+          .add(new TermQuery(new Term("field", "story")), occur)
+          .add(expectedGraphQuery, occur)
+          .build();
+      assertEquals(expectedBooleanQuery, queryBuilder.createBooleanQuery("field", "the guinea pig story guinea pig", occur));
+    }
+  }
+
   protected static class SimpleCJKTokenizer extends Tokenizer {
     private CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/test/org/apache/lucene/util/graph/TestGraphTokenStreamFiniteStrings.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/util/graph/TestGraphTokenStreamFiniteStrings.java b/lucene/core/src/test/org/apache/lucene/util/graph/TestGraphTokenStreamFiniteStrings.java
index 4e636e2..8c336cd 100644
--- a/lucene/core/src/test/org/apache/lucene/util/graph/TestGraphTokenStreamFiniteStrings.java
+++ b/lucene/core/src/test/org/apache/lucene/util/graph/TestGraphTokenStreamFiniteStrings.java
@@ -16,13 +16,14 @@
  */
 package org.apache.lucene.util.graph;
 
-import java.util.List;
+import java.util.Iterator;
 
 import org.apache.lucene.analysis.CannedTokenStream;
 import org.apache.lucene.analysis.Token;
 import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.analysis.tokenattributes.BytesTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
+import org.apache.lucene.index.Term;
 import org.apache.lucene.util.LuceneTestCase;
 
 /**
@@ -65,10 +66,19 @@ public class TestGraphTokenStreamFiniteStrings extends LuceneTestCase {
           token("b", 1, 1)
       );
 
-      GraphTokenStreamFiniteStrings.getTokenStreams(ts);
+      new GraphTokenStreamFiniteStrings(ts).getFiniteStrings();
     });
   }
 
+  public void testEmpty() throws Exception {
+    TokenStream ts = new CannedTokenStream(
+    );
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts);
+    Iterator<TokenStream> it = graph.getFiniteStrings();
+    assertFalse(it.hasNext());
+    assertArrayEquals(new int[0], graph.articulationPoints());
+  }
+
   public void testSingleGraph() throws Exception {
     TokenStream ts = new CannedTokenStream(
         token("fast", 1, 1),
@@ -78,11 +88,41 @@ public class TestGraphTokenStreamFiniteStrings extends LuceneTestCase {
         token("network", 1, 1)
     );
 
-    List<TokenStream> finiteTokenStreams = GraphTokenStreamFiniteStrings.getTokenStreams(ts);
-
-    assertEquals(2, finiteTokenStreams.size());
-    assertTokenStream(finiteTokenStreams.get(0), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 1, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(1), new String[]{"fast", "wifi", "network"}, new int[]{1, 1, 1});
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts);
+
+    Iterator<TokenStream> it = graph.getFiniteStrings();
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 1, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wifi", "network"}, new int[]{1, 1, 1});
+    assertFalse(it.hasNext());
+
+    int[] points = graph.articulationPoints();
+    assertArrayEquals(points, new int[] {1, 3});
+
+    assertFalse(graph.hasSidePath(0));
+    it = graph.getFiniteStrings(0, 1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast"}, new int[] {1});
+    assertFalse(it.hasNext());
+    Term[] terms = graph.getTerms("field", 0);
+    assertArrayEquals(terms, new Term[] {new Term("field", "fast")});
+
+    assertTrue(graph.hasSidePath(1));
+    it = graph.getFiniteStrings(1, 3);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wi", "fi"}, new int[]{1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wifi"}, new int[]{1});
+    assertFalse(it.hasNext());
+
+    assertFalse(graph.hasSidePath(3));
+    it = graph.getFiniteStrings(3, -1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"network"}, new int[] {1});
+    assertFalse(it.hasNext());
+    terms = graph.getTerms("field", 3);
+    assertArrayEquals(terms, new Term[] {new Term("field", "network")});
   }
 
   public void testSingleGraphWithGap() throws Exception {
@@ -96,13 +136,51 @@ public class TestGraphTokenStreamFiniteStrings extends LuceneTestCase {
         token("network", 1, 1)
     );
 
-    List<TokenStream> finiteTokenStreams = GraphTokenStreamFiniteStrings.getTokenStreams(ts);
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts);
 
-    assertEquals(2, finiteTokenStreams.size());
-    assertTokenStream(finiteTokenStreams.get(0),
+    Iterator<TokenStream> it = graph.getFiniteStrings();
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(),
         new String[]{"hey", "fast", "wi", "fi", "network"}, new int[]{1, 2, 1, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(1),
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(),
         new String[]{"hey", "fast", "wifi", "network"}, new int[]{1, 2, 1, 1});
+    assertFalse(it.hasNext());
+
+    int[] points = graph.articulationPoints();
+    assertArrayEquals(points, new int[] {1, 2, 4});
+
+    assertFalse(graph.hasSidePath(0));
+    it = graph.getFiniteStrings(0, 1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"hey"}, new int[] {1});
+    assertFalse(it.hasNext());
+    Term[] terms = graph.getTerms("field", 0);
+    assertArrayEquals(terms, new Term[] {new Term("field", "hey")});
+
+    assertFalse(graph.hasSidePath(1));
+    it = graph.getFiniteStrings(1, 2);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast"}, new int[] {2});
+    assertFalse(it.hasNext());
+    terms = graph.getTerms("field", 1);
+    assertArrayEquals(terms, new Term[] {new Term("field", "fast")});
+
+    assertTrue(graph.hasSidePath(2));
+    it = graph.getFiniteStrings(2, 4);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wi", "fi"}, new int[]{1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wifi"}, new int[]{1});
+    assertFalse(it.hasNext());
+
+    assertFalse(graph.hasSidePath(4));
+    it = graph.getFiniteStrings(4, -1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"network"}, new int[] {1});
+    assertFalse(it.hasNext());
+    terms = graph.getTerms("field", 4);
+    assertArrayEquals(terms, new Term[] {new Term("field", "network")});
   }
 
 
@@ -115,11 +193,41 @@ public class TestGraphTokenStreamFiniteStrings extends LuceneTestCase {
         token("network", 1, 1)
     );
 
-    List<TokenStream> finiteTokenStreams = GraphTokenStreamFiniteStrings.getTokenStreams(ts);
-
-    assertEquals(2, finiteTokenStreams.size());
-    assertTokenStream(finiteTokenStreams.get(0), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 2, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(1), new String[]{"fast", "wifi", "network"}, new int[]{1, 2, 1});
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts);
+
+    Iterator<TokenStream> it = graph.getFiniteStrings();
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 2, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wifi", "network"}, new int[]{1, 2, 1});
+    assertFalse(it.hasNext());
+
+    int[] points = graph.articulationPoints();
+    assertArrayEquals(points, new int[] {1, 3});
+
+    assertFalse(graph.hasSidePath(0));
+    it = graph.getFiniteStrings(0, 1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast"}, new int[] {1});
+    assertFalse(it.hasNext());
+    Term[] terms = graph.getTerms("field", 0);
+    assertArrayEquals(terms, new Term[] {new Term("field", "fast")});
+
+    assertTrue(graph.hasSidePath(1));
+    it = graph.getFiniteStrings(1, 3);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wi", "fi"}, new int[]{2, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wifi"}, new int[]{2});
+    assertFalse(it.hasNext());
+
+    assertFalse(graph.hasSidePath(3));
+    it = graph.getFiniteStrings(3, -1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"network"}, new int[] {1});
+    assertFalse(it.hasNext());
+    terms = graph.getTerms("field", 3);
+    assertArrayEquals(terms, new Term[] {new Term("field", "network")});
   }
 
   public void testGraphAndGapSameTokenTerm() throws Exception {
@@ -131,11 +239,41 @@ public class TestGraphTokenStreamFiniteStrings extends LuceneTestCase {
         token("d", 1, 1)
     );
 
-    List<TokenStream> finiteTokenStreams = GraphTokenStreamFiniteStrings.getTokenStreams(ts);
-
-    assertEquals(2, finiteTokenStreams.size());
-    assertTokenStream(finiteTokenStreams.get(0), new String[]{"a", "b", "c", "d"}, new int[]{1, 1, 2, 1});
-    assertTokenStream(finiteTokenStreams.get(1), new String[]{"a", "b", "a"}, new int[]{1, 1, 2});
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts);
+
+    Iterator<TokenStream> it = graph.getFiniteStrings();
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"a", "b", "c", "d"}, new int[]{1, 1, 2, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"a", "b", "a"}, new int[]{1, 1, 2});
+    assertFalse(it.hasNext());
+
+    int[] points = graph.articulationPoints();
+    assertArrayEquals(points, new int[] {1, 2});
+
+    assertFalse(graph.hasSidePath(0));
+    it = graph.getFiniteStrings(0, 1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"a"}, new int[] {1});
+    assertFalse(it.hasNext());
+    Term[] terms = graph.getTerms("field", 0);
+    assertArrayEquals(terms, new Term[] {new Term("field", "a")});
+
+    assertFalse(graph.hasSidePath(1));
+    it = graph.getFiniteStrings(1, 2);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"b"}, new int[] {1});
+    assertFalse(it.hasNext());
+    terms = graph.getTerms("field", 1);
+    assertArrayEquals(terms, new Term[] {new Term("field", "b")});
+
+    assertTrue(graph.hasSidePath(2));
+    it = graph.getFiniteStrings(2, -1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"c", "d"}, new int[] {2, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"a"}, new int[] {2});
+    assertFalse(it.hasNext());
   }
 
   public void testStackedGraph() throws Exception {
@@ -148,12 +286,45 @@ public class TestGraphTokenStreamFiniteStrings extends LuceneTestCase {
         token("network", 1, 1)
     );
 
-    List<TokenStream> finiteTokenStreams = GraphTokenStreamFiniteStrings.getTokenStreams(ts);
-
-    assertEquals(3, finiteTokenStreams.size());
-    assertTokenStream(finiteTokenStreams.get(0), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 1, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(1), new String[]{"fast", "wifi", "network"}, new int[]{1, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(2), new String[]{"fast", "wireless", "network"}, new int[]{1, 1, 1});
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts);
+
+    Iterator<TokenStream> it = graph.getFiniteStrings();
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 1, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wifi", "network"}, new int[]{1, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wireless", "network"}, new int[]{1, 1, 1});
+    assertFalse(it.hasNext());
+
+    int[] points = graph.articulationPoints();
+    assertArrayEquals(points, new int[] {1, 3});
+
+    assertFalse(graph.hasSidePath(0));
+    it = graph.getFiniteStrings(0, 1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast"}, new int[] {1});
+    assertFalse(it.hasNext());
+    Term[] terms = graph.getTerms("field", 0);
+    assertArrayEquals(terms, new Term[] {new Term("field", "fast")});
+
+    assertTrue(graph.hasSidePath(1));
+    it = graph.getFiniteStrings(1, 3);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wi", "fi"}, new int[]{1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wifi"}, new int[]{1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wireless"}, new int[]{1});
+    assertFalse(it.hasNext());
+
+    assertFalse(graph.hasSidePath(3));
+    it = graph.getFiniteStrings(3, -1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"network"}, new int[] {1});
+    assertFalse(it.hasNext());
+    terms = graph.getTerms("field", 3);
+    assertArrayEquals(terms, new Term[] {new Term("field", "network")});
   }
 
   public void testStackedGraphWithGap() throws Exception {
@@ -166,12 +337,45 @@ public class TestGraphTokenStreamFiniteStrings extends LuceneTestCase {
         token("network", 1, 1)
     );
 
-    List<TokenStream> finiteTokenStreams = GraphTokenStreamFiniteStrings.getTokenStreams(ts);
-
-    assertEquals(3, finiteTokenStreams.size());
-    assertTokenStream(finiteTokenStreams.get(0), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 2, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(1), new String[]{"fast", "wifi", "network"}, new int[]{1, 2, 1});
-    assertTokenStream(finiteTokenStreams.get(2), new String[]{"fast", "wireless", "network"}, new int[]{1, 2, 1});
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts);
+
+    Iterator<TokenStream> it = graph.getFiniteStrings();
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 2, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wifi", "network"}, new int[]{1, 2, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wireless", "network"}, new int[]{1, 2, 1});
+    assertFalse(it.hasNext());
+
+    int[] points = graph.articulationPoints();
+    assertArrayEquals(points, new int[] {1, 3});
+
+    assertFalse(graph.hasSidePath(0));
+    it = graph.getFiniteStrings(0, 1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast"}, new int[] {1});
+    assertFalse(it.hasNext());
+    Term[] terms = graph.getTerms("field", 0);
+    assertArrayEquals(terms, new Term[] {new Term("field", "fast")});
+
+    assertTrue(graph.hasSidePath(1));
+    it = graph.getFiniteStrings(1, 3);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wi", "fi"}, new int[]{2, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wifi"}, new int[]{2});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wireless"}, new int[]{2});
+    assertFalse(it.hasNext());
+
+    assertFalse(graph.hasSidePath(3));
+    it = graph.getFiniteStrings(3, -1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"network"}, new int[] {1});
+    assertFalse(it.hasNext());
+    terms = graph.getTerms("field", 3);
+    assertArrayEquals(terms, new Term[] {new Term("field", "network")});
   }
 
   public void testGraphWithRegularSynonym() throws Exception {
@@ -184,13 +388,47 @@ public class TestGraphTokenStreamFiniteStrings extends LuceneTestCase {
         token("network", 1, 1)
     );
 
-    List<TokenStream> finiteTokenStreams = GraphTokenStreamFiniteStrings.getTokenStreams(ts);
-
-    assertEquals(4, finiteTokenStreams.size());
-    assertTokenStream(finiteTokenStreams.get(0), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 1, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(1), new String[]{"fast", "wifi", "network"}, new int[]{1, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(2), new String[]{"speedy", "wi", "fi", "network"}, new int[]{1, 1, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(3), new String[]{"speedy", "wifi", "network"}, new int[]{1, 1, 1});
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts);
+
+    Iterator<TokenStream> it = graph.getFiniteStrings();
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 1, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wifi", "network"}, new int[]{1, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"speedy", "wi", "fi", "network"}, new int[]{1, 1, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"speedy", "wifi", "network"}, new int[]{1, 1, 1});
+    assertFalse(it.hasNext());
+
+    int[] points = graph.articulationPoints();
+    assertArrayEquals(points, new int[] {1, 3});
+
+    assertFalse(graph.hasSidePath(0));
+    it = graph.getFiniteStrings(0, 1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast"}, new int[] {1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"speedy"}, new int[] {1});
+    assertFalse(it.hasNext());
+    Term[] terms = graph.getTerms("field", 0);
+    assertArrayEquals(terms, new Term[] {new Term("field", "fast"), new Term("field", "speedy")});
+
+    assertTrue(graph.hasSidePath(1));
+    it = graph.getFiniteStrings(1, 3);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wi", "fi"}, new int[]{1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wifi"}, new int[]{1});
+    assertFalse(it.hasNext());
+
+    assertFalse(graph.hasSidePath(3));
+    it = graph.getFiniteStrings(3, -1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"network"}, new int[] {1});
+    assertFalse(it.hasNext());
+    terms = graph.getTerms("field", 3);
+    assertArrayEquals(terms, new Term[] {new Term("field", "network")});
   }
 
   public void testMultiGraph() throws Exception {
@@ -204,14 +442,105 @@ public class TestGraphTokenStreamFiniteStrings extends LuceneTestCase {
         token("network", 1, 1)
     );
 
-    List<TokenStream> finiteTokenStreams = GraphTokenStreamFiniteStrings.getTokenStreams(ts);
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts);
 
-    assertEquals(4, finiteTokenStreams.size());
-    assertTokenStream(finiteTokenStreams.get(0),
+    Iterator<TokenStream> it = graph.getFiniteStrings();
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(),
         new String[]{"turbo", "charged", "wi", "fi", "network"}, new int[]{1, 1, 1, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(1),
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(),
         new String[]{"turbo", "charged", "wifi", "network"}, new int[]{1, 1, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(2), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 1, 1, 1});
-    assertTokenStream(finiteTokenStreams.get(3), new String[]{"fast", "wifi", "network"}, new int[]{1, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wi", "fi", "network"}, new int[]{1, 1, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast", "wifi", "network"}, new int[]{1, 1, 1});
+    assertFalse(it.hasNext());
+
+    int[] points = graph.articulationPoints();
+    assertArrayEquals(points, new int[] {2, 4});
+
+    assertTrue(graph.hasSidePath(0));
+    it = graph.getFiniteStrings(0, 2);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"turbo", "charged"}, new int[]{1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"fast"}, new int[]{1});
+    assertFalse(it.hasNext());
+
+    assertTrue(graph.hasSidePath(2));
+    it = graph.getFiniteStrings(2, 4);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wi", "fi"}, new int[]{1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"wifi"}, new int[]{1});
+    assertFalse(it.hasNext());
+
+    assertFalse(graph.hasSidePath(4));
+    it = graph.getFiniteStrings(4, -1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"network"}, new int[] {1});
+    assertFalse(it.hasNext());
+    Term[] terms = graph.getTerms("field", 4);
+    assertArrayEquals(terms, new Term[] {new Term("field", "network")});
+  }
+
+  public void testMultipleSidePaths() throws Exception {
+    TokenStream ts = new CannedTokenStream(
+        token("the", 1, 1),
+        token("ny", 1, 4),
+        token("new", 0, 1),
+        token("york", 1, 1),
+        token("wifi", 1, 4),
+        token("wi", 0, 1),
+        token("fi", 1, 3),
+        token("wifi", 2, 2),
+        token("wi", 0, 1),
+        token("fi", 1, 1),
+        token("network", 1, 1)
+    );
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts);
+
+    Iterator<TokenStream> it = graph.getFiniteStrings();
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"the", "ny", "wifi", "network"}, new int[]{1, 1, 2, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"the", "ny", "wi", "fi", "network"}, new int[]{1, 1, 2, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"the", "new", "york", "wifi", "network"}, new int[]{1, 1, 1, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"the", "new", "york", "wi", "fi", "network"}, new int[]{1, 1, 1, 1, 1, 1});
+    assertFalse(it.hasNext());
+
+    int[] points = graph.articulationPoints();
+    assertArrayEquals(points, new int[] {1, 7});
+
+    assertFalse(graph.hasSidePath(0));
+    it = graph.getFiniteStrings(0, 1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"the"}, new int[]{1});
+    assertFalse(it.hasNext());
+    Term[] terms = graph.getTerms("field", 0);
+    assertArrayEquals(terms, new Term[] {new Term("field", "the")});
+
+    assertTrue(graph.hasSidePath(1));
+    it = graph.getFiniteStrings(1, 7);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"ny", "wifi"}, new int[]{1, 2});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"ny", "wi", "fi"}, new int[]{1, 2, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"new", "york", "wifi"}, new int[]{1, 1, 1});
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"new", "york", "wi", "fi"}, new int[]{1, 1, 1, 1});
+    assertFalse(it.hasNext());
+
+    assertFalse(graph.hasSidePath(7));
+    it = graph.getFiniteStrings(7, -1);
+    assertTrue(it.hasNext());
+    assertTokenStream(it.next(), new String[]{"network"}, new int[]{1});
+    assertFalse(it.hasNext());
+    terms = graph.getTerms("field", 7);
+    assertArrayEquals(terms, new Term[] {new Term("field", "network")});
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/join/build.xml
----------------------------------------------------------------------
diff --git a/lucene/join/build.xml b/lucene/join/build.xml
index b5360c4..c411dbe 100644
--- a/lucene/join/build.xml
+++ b/lucene/join/build.xml
@@ -24,25 +24,4 @@
 
   <import file="../module-build.xml"/>
 
-  <path id="classpath">
-    <pathelement path="${grouping.jar}"/>
-    <path refid="base.classpath"/>
-  </path>
-
-  <path id="run.classpath">
-    <path refid="classpath"/>
-    <pathelement location="${build.dir}/classes/java"/>
-  </path>
-
-  <target name="init" depends="module-build.init,jar-grouping"/>
-
-  <target name="javadocs" depends="javadocs-grouping,compile-core,check-javadocs-uptodate"
-          unless="javadocs-uptodate-${name}">
-    <invoke-module-javadoc>
-      <links>
-        <link href="../grouping"/>
-      </links>
-    </invoke-module-javadoc>
-  </target>
-
 </project>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/join/src/java/org/apache/lucene/search/join/ParentChildrenBlockJoinQuery.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ParentChildrenBlockJoinQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/ParentChildrenBlockJoinQuery.java
new file mode 100644
index 0000000..a739294
--- /dev/null
+++ b/lucene/join/src/java/org/apache/lucene/search/join/ParentChildrenBlockJoinQuery.java
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.search.join;
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.ReaderUtil;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Weight;
+import org.apache.lucene.util.BitSet;
+
+/**
+ * A query that returns all the matching child documents for a specific parent document
+ * indexed together in the same block. The provided child query determines which matching
+ * child doc is being returned.
+ *
+ * @lucene.experimental
+ */
+public class ParentChildrenBlockJoinQuery extends Query {
+
+  private final BitSetProducer parentFilter;
+  private final Query childQuery;
+  private final int parentDocId;
+
+  /**
+   * Creates a <code>ParentChildrenBlockJoinQuery</code> instance
+   *
+   * @param parentFilter  A filter identifying parent documents.
+   * @param childQuery    A child query that determines which child docs are matching
+   * @param parentDocId   The top level doc id of that parent to return children documents for
+   */
+  public ParentChildrenBlockJoinQuery(BitSetProducer parentFilter, Query childQuery, int parentDocId) {
+    this.parentFilter = parentFilter;
+    this.childQuery = childQuery;
+    this.parentDocId = parentDocId;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (sameClassAs(obj) == false) {
+      return false;
+    }
+    ParentChildrenBlockJoinQuery other = (ParentChildrenBlockJoinQuery) obj;
+    return parentFilter.equals(other.parentFilter)
+        && childQuery.equals(other.childQuery)
+        && parentDocId == other.parentDocId;
+  }
+
+  @Override
+  public int hashCode() {
+    int hash = classHash();
+    hash = 31 * hash + parentFilter.hashCode();
+    hash = 31 * hash +  childQuery.hashCode();
+    hash = 31 * hash + parentDocId;
+    return hash;
+  }
+
+  @Override
+  public String toString(String field) {
+    return "ParentChildrenBlockJoinQuery (" + childQuery + ")";
+  }
+
+  @Override
+  public Query rewrite(IndexReader reader) throws IOException {
+    final Query childRewrite = childQuery.rewrite(reader);
+    if (childRewrite != childQuery) {
+      return new ParentChildrenBlockJoinQuery(parentFilter, childRewrite, parentDocId);
+    } else {
+      return super.rewrite(reader);
+    }
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
+    final Weight childWeight = childQuery.createWeight(searcher, needsScores, boost);
+    final int readerIndex = ReaderUtil.subIndex(parentDocId, searcher.getIndexReader().leaves());
+    return new Weight(this) {
+
+      @Override
+      public void extractTerms(Set<Term> terms) {
+        childWeight.extractTerms(terms);
+      }
+
+      @Override
+      public Explanation explain(LeafReaderContext context, int doc) throws IOException {
+        return Explanation.noMatch("Not implemented, use ToParentBlockJoinQuery explain why a document matched");
+      }
+
+      @Override
+      public Scorer scorer(LeafReaderContext context) throws IOException {
+        // Childs docs only reside in a single segment, so no need to evaluate all segments
+        if (context.ord != readerIndex) {
+          return null;
+        }
+
+        final int localParentDocId = parentDocId - context.docBase;
+        // If parentDocId == 0 then a parent doc doesn't have child docs, because child docs are stored
+        // before the parent doc and because parent doc is 0 we can safely assume that there are no child docs.
+        if (localParentDocId == 0) {
+          return null;
+        }
+
+        final BitSet parents = parentFilter.getBitSet(context);
+        final int firstChildDocId = parents.prevSetBit(localParentDocId - 1) + 1;
+        // A parent doc doesn't have child docs, so we can early exit here:
+        if (firstChildDocId == localParentDocId) {
+          return null;
+        }
+
+        final Scorer childrenScorer = childWeight.scorer(context);
+        if (childrenScorer == null) {
+          return null;
+        }
+        DocIdSetIterator childrenIterator = childrenScorer.iterator();
+        final DocIdSetIterator it = new DocIdSetIterator() {
+
+          int doc = -1;
+
+          @Override
+          public int docID() {
+            return doc;
+          }
+
+          @Override
+          public int nextDoc() throws IOException {
+            return advance(doc + 1);
+          }
+
+          @Override
+          public int advance(int target) throws IOException {
+            target = Math.max(firstChildDocId, target);
+            if (target >= localParentDocId) {
+              // We're outside the child nested scope, so it is done
+              return doc = NO_MORE_DOCS;
+            } else {
+              int advanced = childrenIterator.advance(target);
+              if (advanced >= localParentDocId) {
+                // We're outside the child nested scope, so it is done
+                return doc = NO_MORE_DOCS;
+              } else {
+                return doc = advanced;
+              }
+            }
+          }
+
+          @Override
+          public long cost() {
+            return Math.min(childrenIterator.cost(), localParentDocId - firstChildDocId);
+          }
+
+        };
+        return new Scorer(this) {
+          @Override
+          public int docID() {
+            return it.docID();
+          }
+
+          @Override
+          public float score() throws IOException {
+            return childrenScorer.score();
+          }
+
+          @Override
+          public int freq() throws IOException {
+            return childrenScorer.freq();
+          }
+
+          @Override
+          public DocIdSetIterator iterator() {
+            return it;
+          }
+        };
+      }
+    };
+  }
+}


[03/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/HavingStream.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/HavingStream.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/HavingStream.java
index 38c1a6b..35e8952 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/HavingStream.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/HavingStream.java
@@ -23,8 +23,8 @@ import java.util.Locale;
 
 import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.comp.StreamComparator;
-import org.apache.solr.client.solrj.io.ops.BooleanOperation;
-import org.apache.solr.client.solrj.io.ops.StreamOperation;
+import org.apache.solr.client.solrj.io.eval.BooleanEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
 import org.apache.solr.client.solrj.io.stream.expr.Expressible;
@@ -42,19 +42,19 @@ public class HavingStream extends TupleStream implements Expressible {
   private static final long serialVersionUID = 1;
 
   private TupleStream stream;
-  private BooleanOperation op;
+  private BooleanEvaluator evaluator;
 
   private transient Tuple currentGroupHead;
 
-  public HavingStream(TupleStream stream, BooleanOperation op) throws IOException {
-    init(stream, op);
+  public HavingStream(TupleStream stream, BooleanEvaluator evaluator) throws IOException {
+    init(stream, evaluator);
   }
 
 
   public HavingStream(StreamExpression expression, StreamFactory factory) throws IOException{
     // grab all parameters out
     List<StreamExpression> streamExpressions = factory.getExpressionOperandsRepresentingTypes(expression, Expressible.class, TupleStream.class);
-    List<StreamExpression> operationExpressions = factory.getExpressionOperandsRepresentingTypes(expression, BooleanOperation.class);
+    List<StreamExpression> evaluatorExpressions = factory.getExpressionOperandsRepresentingTypes(expression, BooleanEvaluator.class);
 
     // validate expression contains only what we want.
     if(expression.getParameters().size() != streamExpressions.size() + 1){
@@ -66,25 +66,23 @@ public class HavingStream extends TupleStream implements Expressible {
     }
 
 
-    BooleanOperation booleanOperation = null;
-    if(operationExpressions != null && operationExpressions.size() == 1) {
-      StreamExpression ex = operationExpressions.get(0);
-      StreamOperation operation = factory.constructOperation(ex);
-      if(operation instanceof BooleanOperation) {
-        booleanOperation = (BooleanOperation) operation;
-      } else {
-        throw new IOException("The HavingStream requires a BooleanOperation. A StreamOperation was provided.");
+    StreamEvaluator evaluator = null;
+    if(evaluatorExpressions != null && evaluatorExpressions.size() == 1) {
+      StreamExpression ex = evaluatorExpressions.get(0);
+      evaluator = factory.constructEvaluator(ex);
+      if(!(evaluator instanceof BooleanEvaluator)) {
+        throw new IOException("The HavingStream requires a BooleanEvaluator. A StreamEvaluator was provided.");
       }
     } else {
-      throw new IOException("The HavingStream requires a BooleanOperation.");
+      throw new IOException("The HavingStream requires a BooleanEvaluator.");
     }
 
-    init(factory.constructStream(streamExpressions.get(0)), booleanOperation);
+    init(factory.constructStream(streamExpressions.get(0)), (BooleanEvaluator)evaluator);
   }
 
-  private void init(TupleStream stream, BooleanOperation op) throws IOException{
+  private void init(TupleStream stream, BooleanEvaluator evaluator) throws IOException{
     this.stream = stream;
-    this.op = op;
+    this.evaluator = evaluator;
   }
 
   @Override
@@ -104,10 +102,10 @@ public class HavingStream extends TupleStream implements Expressible {
       expression.addParameter("<stream>");
     }
 
-    if(op instanceof Expressible) {
-      expression.addParameter(op.toExpression(factory));
+    if(evaluator instanceof Expressible) {
+      expression.addParameter(evaluator.toExpression(factory));
     } else {
-      throw new IOException("This ReducerStream contains a non-expressible operation - it cannot be converted to an expression");
+      throw new IOException("This HavingStream contains a non-expressible evaluator - it cannot be converted to an expression");
     }
 
     return expression;
@@ -125,7 +123,7 @@ public class HavingStream extends TupleStream implements Expressible {
         .withExpressionType(ExpressionType.STREAM_DECORATOR)
         .withExpression(toExpression(factory, false).toString())
         .withHelpers(new Explanation[]{
-            op.toExplanation(factory)
+            evaluator.toExplanation(factory)
         });
   }
 
@@ -154,9 +152,7 @@ public class HavingStream extends TupleStream implements Expressible {
         return tuple;
       }
 
-      op.operate(tuple);
-
-      if(op.evaluate()) {
+      if(evaluator.evaluate(tuple)){
         return tuple;
       }
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/SelectStream.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/SelectStream.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/SelectStream.java
index b0a1e05..eed8182 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/SelectStream.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/SelectStream.java
@@ -25,6 +25,7 @@ import java.util.Map;
 
 import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.comp.StreamComparator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
 import org.apache.solr.client.solrj.io.ops.StreamOperation;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
@@ -32,6 +33,7 @@ import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExplanation;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParser;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
@@ -47,6 +49,7 @@ public class SelectStream extends TupleStream implements Expressible {
 
   private TupleStream stream;
   private Map<String,String> selectedFields;
+  private Map<StreamEvaluator,String> selectedEvaluators;
   private List<StreamOperation> operations;
 
   public SelectStream(TupleStream stream, List<String> selectedFields) throws IOException {
@@ -56,22 +59,25 @@ public class SelectStream extends TupleStream implements Expressible {
       this.selectedFields.put(selectedField, selectedField);
     }
     operations = new ArrayList<>();
+    selectedEvaluators = new HashMap<>();
   }
   
   public SelectStream(TupleStream stream, Map<String,String> selectedFields) throws IOException {
     this.stream = stream;
     this.selectedFields = selectedFields;
     operations = new ArrayList<>();
+    selectedEvaluators = new HashMap<>();
   }
   
   public SelectStream(StreamExpression expression,StreamFactory factory) throws IOException {
     // grab all parameters out
     List<StreamExpression> streamExpressions = factory.getExpressionOperandsRepresentingTypes(expression, Expressible.class, TupleStream.class);
-    List<StreamExpressionParameter> selectFieldsExpressions = factory.getOperandsOfType(expression, StreamExpressionValue.class);
+    List<StreamExpressionParameter> selectAsFieldsExpressions = factory.getOperandsOfType(expression, StreamExpressionValue.class);
     List<StreamExpression> operationExpressions = factory.getExpressionOperandsRepresentingTypes(expression, StreamOperation.class);
+    List<StreamExpression> evaluatorExpressions = factory.getExpressionOperandsRepresentingTypes(expression, StreamEvaluator.class);
     
     // validate expression contains only what we want.
-    if(expression.getParameters().size() != streamExpressions.size() + selectFieldsExpressions.size() + operationExpressions.size()){
+    if(expression.getParameters().size() != streamExpressions.size() + selectAsFieldsExpressions.size() + operationExpressions.size()){
       throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - unknown operands found", expression));
     }
     
@@ -79,14 +85,19 @@ public class SelectStream extends TupleStream implements Expressible {
       throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting single stream but found %d (must be TupleStream types)",expression, streamExpressions.size()));
     }
 
-    if(0 == selectFieldsExpressions.size()){
+    if(0 == selectAsFieldsExpressions.size()){
       throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one select field but found %d",expression, streamExpressions.size()));
     }
+    
+    if(0 != evaluatorExpressions.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - evaluators must be given a name, like 'add(...) as result' but found %d evaluators without names",expression, evaluatorExpressions.size()));
+    }
 
     stream = factory.constructStream(streamExpressions.get(0));
     
-    selectedFields = new HashMap<String,String>(selectFieldsExpressions.size());
-    for(StreamExpressionParameter parameter : selectFieldsExpressions){
+    selectedFields = new HashMap<String,String>();
+    selectedEvaluators = new HashMap<StreamEvaluator, String>();
+    for(StreamExpressionParameter parameter : selectAsFieldsExpressions){
       StreamExpressionValue selectField = (StreamExpressionValue)parameter;
       String value = selectField.getValue().trim();
       
@@ -99,7 +110,28 @@ public class SelectStream extends TupleStream implements Expressible {
         if(2 != parts.length){
           throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting select field of form 'fieldA' or 'fieldA as alias' but found %s",expression, value));
         }
-        selectedFields.put(parts[0].trim(), parts[1].trim());
+        
+        String asValue = parts[0].trim();
+        String asName = parts[1].trim();
+        
+        boolean handled = false;
+        if(asValue.contains("(")){
+          // possible evaluator
+          try{
+            StreamExpression asValueExpression = StreamExpressionParser.parse(asValue);
+            if(factory.doesRepresentTypes(asValueExpression, StreamEvaluator.class)){
+              selectedEvaluators.put(factory.constructEvaluator(asValueExpression), asName);
+              handled = true;
+            }
+          }
+          catch(Throwable e){
+            // it was not handled, so treat as a non-evaluator
+          }
+        }
+        
+        if(!handled){        
+          selectedFields.put(asValue, asName);
+        }
       }
       else{
         selectedFields.put(value,value);
@@ -134,7 +166,7 @@ public class SelectStream extends TupleStream implements Expressible {
       expression.addParameter("<stream>");
     }
     
-    // selects
+    // selected fields
     for(Map.Entry<String, String> selectField : selectedFields.entrySet()) {
       if(selectField.getKey().equals(selectField.getValue())){
         expression.addParameter(selectField.getKey());
@@ -144,6 +176,11 @@ public class SelectStream extends TupleStream implements Expressible {
       }
     }
     
+    // selected evaluators
+    for(Map.Entry<StreamEvaluator, String> selectedEvaluator : selectedEvaluators.entrySet()) {
+      expression.addParameter(String.format(Locale.ROOT, "%s as %s", selectedEvaluator.getKey().toExpression(factory), selectedEvaluator.getValue()));
+    }
+    
     for(StreamOperation operation : operations){
       expression.addParameter(operation.toExpression(factory));
     }
@@ -163,6 +200,10 @@ public class SelectStream extends TupleStream implements Expressible {
       .withExpressionType(ExpressionType.STREAM_DECORATOR)
       .withExpression(toExpression(factory, false).toString());   
     
+    for(StreamEvaluator evaluator : selectedEvaluators.keySet()){
+      explanation.addHelper(evaluator.toExplanation(factory));
+    }
+    
     for(StreamOperation operation : operations){
       explanation.addHelper(operation.toExplanation(factory));
     }
@@ -196,19 +237,27 @@ public class SelectStream extends TupleStream implements Expressible {
     }
 
     // create a copy with the limited set of fields
-    Tuple working = new Tuple(new HashMap<>());
+    Tuple workingToReturn = new Tuple(new HashMap<>());
+    Tuple workingForEvaluators = new Tuple(new HashMap<>());
     for(Object fieldName : original.fields.keySet()){
+      workingForEvaluators.put(fieldName, original.get(fieldName));
       if(selectedFields.containsKey(fieldName)){
-        working.put(selectedFields.get(fieldName), original.get(fieldName));
+        workingToReturn.put(selectedFields.get(fieldName), original.get(fieldName));
       }
     }
     
     // apply all operations
     for(StreamOperation operation : operations){
-      operation.operate(working);
+      operation.operate(workingToReturn);
+      operation.operate(workingForEvaluators);
+    }
+    
+    // Apply all evaluators
+    for(Map.Entry<StreamEvaluator, String> selectedEvaluator : selectedEvaluators.entrySet()) {
+      workingToReturn.put(selectedEvaluator.getValue(), selectedEvaluator.getKey().evaluate(workingForEvaluators));
     }
     
-    return working;
+    return workingToReturn;
   }
   
   /** Return the stream sort - ie, the order in which records are returned */

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/Explanation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/Explanation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/Explanation.java
index e72d6ed..acaefbf 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/Explanation.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/Explanation.java
@@ -155,6 +155,7 @@ public class Explanation implements MapSerializable {
     public static final String DATASTORE = "datastore";
     public static final String METRIC = "metric";
     public static final String OPERATION = "operation";
+    public static final String EVALUATOR = "evaluator";
     public static final String EQUALITOR = "equalitor";
     public static final String SORTER = "sorter";
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java
index bf20a1e..f57319d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java
@@ -26,12 +26,14 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.stream.Collectors;
 
 import org.apache.solr.client.solrj.io.comp.ComparatorOrder;
 import org.apache.solr.client.solrj.io.comp.MultipleFieldComparator;
 import org.apache.solr.client.solrj.io.comp.StreamComparator;
 import org.apache.solr.client.solrj.io.eq.MultipleFieldEqualitor;
 import org.apache.solr.client.solrj.io.eq.StreamEqualitor;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
 import org.apache.solr.client.solrj.io.ops.StreamOperation;
 import org.apache.solr.client.solrj.io.stream.TupleStream;
 import org.apache.solr.client.solrj.io.stream.metrics.Metric;
@@ -87,6 +89,10 @@ public class StreamFactory implements Serializable {
     return expression.getParameters().get(parameterIndex);
   }
   
+  public List<String> getValueOperands(StreamExpression expression){
+    return getOperandsOfType(expression, StreamExpressionValue.class).stream().map(item -> ((StreamExpressionValue)item).getValue()).collect(Collectors.toList());
+  }
+  
   /** Given an expression, will return the value parameter at the given index, or null if doesn't exist */
   public String getValueOperand(StreamExpression expression, int parameterIndex){
     StreamExpressionParameter parameter = getOperand(expression, parameterIndex);
@@ -176,6 +182,19 @@ public class StreamFactory implements Serializable {
     return matchingStreamExpressions;   
   }
   
+  public boolean doesRepresentTypes(StreamExpression expression, Class ... clazzes){
+    if(functionNames.containsKey(expression.getFunctionName())){
+      for(Class clazz : clazzes){
+        if(!clazz.isAssignableFrom(functionNames.get(expression.getFunctionName()))){
+          return false;
+        }
+      }
+      return true;
+    }
+    
+    return false;    
+  }
+  
   public int getIntOperand(StreamExpression expression, String paramName, Integer defaultValue) throws IOException{
     StreamExpressionNamedParameter param = getNamedOperand(expression, paramName);
     
@@ -343,6 +362,21 @@ public class StreamFactory implements Serializable {
     throw new IOException(String.format(Locale.ROOT,"Invalid operation expression %s - function '%s' is unknown (not mapped to a valid StreamOperation)", expression, expression.getFunctionName()));
   }
 
+  public StreamEvaluator constructEvaluator(String expressionClause) throws IOException {
+    return constructEvaluator(StreamExpressionParser.parse(expressionClause));
+  }
+  public StreamEvaluator constructEvaluator(StreamExpression expression) throws IOException{
+    String function = expression.getFunctionName();
+    if(functionNames.containsKey(function)){
+      Class<? extends Expressible> clazz = functionNames.get(function);
+      if(Expressible.class.isAssignableFrom(clazz) && StreamEvaluator.class.isAssignableFrom(clazz)){
+        return (StreamEvaluator)createInstance(functionNames.get(function), new Class[]{ StreamExpression.class, StreamFactory.class }, new Object[]{ expression, this});
+      }
+    }
+    
+    throw new IOException(String.format(Locale.ROOT,"Invalid evaluator expression %s - function '%s' is unknown (not mapped to a valid StreamEvaluator)", expression, expression.getFunctionName()));
+  }
+
 
   public <T> T createInstance(Class<T> clazz, Class<?>[] paramTypes, Object[] params) throws IOException{
     Constructor<T> ctor;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/request/CoreAdminRequest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CoreAdminRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CoreAdminRequest.java
index 002bbc3..3967d41 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CoreAdminRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CoreAdminRequest.java
@@ -619,6 +619,24 @@ public class CoreAdminRequest extends SolrRequest<CoreAdminResponse> {
     return req.process( client );
   }
 
+  /**
+   * Swap two existing cores.
+   * @param core1 name of the first core
+   * @param core2 name of the other core
+   * @param client SolrClient to use
+   * @return response
+   * @throws SolrServerException if one or both cores don't exist
+   * @throws IOException on IO errors
+   */
+  public static CoreAdminResponse swapCore(String core1, String core2, SolrClient client)
+      throws SolrServerException, IOException {
+    CoreAdminRequest req = new CoreAdminRequest();
+    req.setCoreName(core1);
+    req.setOtherCoreName(core2);
+    req.setAction( CoreAdminAction.SWAP );
+    return req.process( client );
+  }
+
   public static CoreStatus getCoreStatus(String coreName, SolrClient client) throws SolrServerException, IOException {
     return getCoreStatus(coreName, true, client);
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/common/cloud/Replica.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/Replica.java b/solr/solrj/src/java/org/apache/solr/common/cloud/Replica.java
index b47cf00..4968cf2 100644
--- a/solr/solrj/src/java/org/apache/solr/common/cloud/Replica.java
+++ b/solr/solrj/src/java/org/apache/solr/common/cloud/Replica.java
@@ -108,6 +108,9 @@ public class Replica extends ZkNodeProps {
   public String getCoreUrl() {
     return ZkCoreNodeProps.getCoreUrl(getStr(BASE_URL_PROP), getStr(CORE_NAME_PROP));
   }
+  public String getBaseUrl(){
+    return getStr(ZkStateReader.BASE_URL_PROP);
+  }
 
   public String getCoreName() {
     return getStr(CORE_NAME_PROP);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java b/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java
index 038fc6e..e91b9af 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java
@@ -180,6 +180,11 @@ public interface FacetParams {
    */
   public static final String FACET_CONTAINS_IGNORE_CASE = FACET_CONTAINS + ".ignoreCase";
 
+  /**
+   * Only return constraints of a facet field excluding the given string.
+   */
+  public static final String FACET_EXCLUDETERMS = FACET + ".excludeTerms";
+
  /**
    * When faceting by enumerating the terms in a field,
    * only use the filterCache for terms with a df &gt;= to this parameter.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/common/params/HighlightParams.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/HighlightParams.java b/solr/solrj/src/java/org/apache/solr/common/params/HighlightParams.java
index 997fc7e..57f6734 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/HighlightParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/HighlightParams.java
@@ -66,7 +66,7 @@ public interface HighlightParams {
 
   // formatting
   public static final String FORMATTER   = HIGHLIGHT+".formatter"; // OH
-  public static final String ENCODER     = HIGHLIGHT+".encoder"; // OH, (UH, PH limited)
+  public static final String ENCODER     = HIGHLIGHT+".encoder"; // all
   public static final String MERGE_CONTIGUOUS_FRAGMENTS = HIGHLIGHT + ".mergeContiguous"; // OH
   public static final String SIMPLE      = "simple"; // OH
   public static final String SIMPLE_PRE  = HIGHLIGHT+"."+SIMPLE+".pre"; // OH

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/common/util/ObjectReleaseTracker.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/ObjectReleaseTracker.java b/solr/solrj/src/java/org/apache/solr/common/util/ObjectReleaseTracker.java
index 2d7c1a6..2d7c514 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/ObjectReleaseTracker.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/ObjectReleaseTracker.java
@@ -28,7 +28,6 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeUnit;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -57,27 +56,6 @@ public class ObjectReleaseTracker {
   /**
    * @return null if ok else error message
    */
-  public static String clearObjectTrackerAndCheckEmpty(int waitSeconds) {
-    int retries = 0;
-    String result;
-    do {
-      result = checkEmpty();
-      if (result == null)
-        break;
-      try {
-        TimeUnit.SECONDS.sleep(1);
-      } catch (InterruptedException e) { break; }
-    }
-    while (retries++ < waitSeconds);
-    
-    OBJECTS.clear();
-    
-    return result;
-  }
-  
-  /**
-   * @return null if ok else error message
-   */
   public static String checkEmpty() {
     String error = null;
     Set<Entry<Object,String>> entries = OBJECTS.entrySet();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/common/util/StrUtils.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/StrUtils.java b/solr/solrj/src/java/org/apache/solr/common/util/StrUtils.java
index 995e142..7313597 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/StrUtils.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/StrUtils.java
@@ -33,13 +33,18 @@ public class StrUtils {
   public static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6',
       '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
 
+  public static List<String> splitSmart(String s, char separator) {
+    ArrayList<String> lst = new ArrayList<>(4);
+    splitSmart(s, separator, lst);
+    return lst;
+
+  }
   /**
    * Split a string based on a separator, but don't split if it's inside
    * a string.  Assume '\' escapes the next char both inside and
    * outside strings.
    */
-  public static List<String> splitSmart(String s, char separator) {
-    ArrayList<String> lst = new ArrayList<>(4);
+  public static void splitSmart(String s, char separator, List<String> lst) {
     int pos=0, start=0, end=s.length();
     char inString=0;
     char ch=0;
@@ -72,7 +77,6 @@ public class StrUtils {
     }
     ***/
 
-    return lst;
   }
 
   /** Splits a backslash escaped string on the separator.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/Utils.java b/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
index a8de0ac..4cb6b8e 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
@@ -146,7 +146,9 @@ public class Utils {
   }
 
   public static Object getObjectByPath(Map root, boolean onlyPrimitive, String hierarchy) {
-    return getObjectByPath(root, onlyPrimitive, StrUtils.splitSmart(hierarchy, '/'));
+    List<String> parts = StrUtils.splitSmart(hierarchy, '/');
+    if (parts.get(0).isEmpty()) parts.remove(0);
+    return getObjectByPath(root, onlyPrimitive, parts);
   }
 
   public static Object getObjectByPath(Map root, boolean onlyPrimitive, List<String> hierarchy) {
@@ -172,6 +174,7 @@ public class Utils {
         obj = (Map) o;
       } else {
         Object val = obj.get(s);
+        if (val == null) return null;
         if (idx > -1) {
           List l = (List) val;
           val = idx < l.size() ? l.get(idx) : null;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/common/util/ValidatingJsonMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/ValidatingJsonMap.java b/solr/solrj/src/java/org/apache/solr/common/util/ValidatingJsonMap.java
new file mode 100644
index 0000000..eef3823
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/common/util/ValidatingJsonMap.java
@@ -0,0 +1,349 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.common.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.solr.common.SolrException;
+import org.noggit.JSONParser;
+import org.noggit.ObjectBuilder;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.unmodifiableList;
+import static java.util.Collections.unmodifiableSet;
+
+public class ValidatingJsonMap implements Map<String, Object> {
+
+  private static final String INCLUDE = "#include";
+  private static final String RESOURCE_EXTENSION = ".json";
+  public static final PredicateWithErrMsg<Object> NOT_NULL = o -> {
+    if (o == null) return " Must not be NULL";
+    return null;
+  };
+  public static final PredicateWithErrMsg<Pair> ENUM_OF = pair -> {
+    if (pair.second() instanceof Set) {
+      Set set = (Set) pair.second();
+      if (pair.first() instanceof Collection) {
+        for (Object o : (Collection) pair.first()) {
+          if (!set.contains(o)) {
+            return " Must be one of " + pair.second();
+          }
+        }
+      } else {
+        if (!set.contains(pair.first())) return " Must be one of " + pair.second() + ", got " + pair.first();
+      }
+      return null;
+    } else {
+      return " Unknown type";
+    }
+
+  };
+  private final Map<String, Object> delegate;
+
+  public ValidatingJsonMap(Map<String, Object> delegate) {
+    this.delegate = delegate;
+  }
+
+  public ValidatingJsonMap(int i) {
+    delegate = new LinkedHashMap<>(i);
+  }
+
+  public ValidatingJsonMap() {
+    delegate = new LinkedHashMap<>();
+  }
+
+  @Override
+  public int size() {
+    return delegate.size();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return delegate.isEmpty();
+  }
+
+  @Override
+  public boolean containsKey(Object key) {
+    return delegate.containsKey(key);
+  }
+
+  @Override
+  public boolean containsValue(Object value) {
+    return delegate.containsValue(value);
+  }
+
+  @Override
+  public Object get(Object key) {
+    return delegate.get(key);
+  }
+
+  @Override
+  public Object put(String key, Object value) {
+    return delegate.put(key, value);
+  }
+
+  @Override
+  public Object remove(Object key) {
+    return delegate.remove(key);
+  }
+
+  @Override
+  public void putAll(Map<? extends String, ?> m) {
+    delegate.putAll(m);
+  }
+
+  @Override
+  public void clear() {
+    delegate.clear();
+
+  }
+
+  @Override
+  public Set<String> keySet() {
+    return delegate.keySet();
+  }
+
+  @Override
+  public Collection<Object> values() {
+    return delegate.values();
+  }
+
+  @Override
+  public Set<Entry<String, Object>> entrySet() {
+    return delegate.entrySet();
+  }
+
+  public Object get(String key, PredicateWithErrMsg predicate) {
+    Object v = get(key);
+    if (predicate != null) {
+      String msg = predicate.test(v);
+      if (msg != null) {
+        throw new RuntimeException("" + key + msg);
+      }
+    }
+    return v;
+  }
+
+  public Boolean getBool(String key, Boolean def) {
+    Object v = get(key);
+    if (v == null) return def;
+    if (v instanceof Boolean) return (Boolean) v;
+    try {
+      return Boolean.parseBoolean(v.toString());
+    } catch (NumberFormatException e) {
+      throw new RuntimeException("value of " + key + "must be an boolean");
+    }
+  }
+
+  public Integer getInt(String key, Integer def) {
+    Object v = get(key);
+    if (v == null) return def;
+    if (v instanceof Integer) return (Integer) v;
+    try {
+      return Integer.parseInt(v.toString());
+    } catch (NumberFormatException e) {
+      throw new RuntimeException("value of " + key + "must be an integer");
+    }
+  }
+
+  public ValidatingJsonMap getMap(String key) {
+    return getMap(key, null, null);
+  }
+
+  public ValidatingJsonMap getMap(String key, PredicateWithErrMsg predicate) {
+    return getMap(key, predicate, null);
+
+  }
+
+  public ValidatingJsonMap getMap(String key, PredicateWithErrMsg predicate, String message) {
+    Object v = get(key);
+    if (v != null && !(v instanceof Map)) {
+      throw new RuntimeException("" + key + " should be of type map");
+    }
+
+    if (predicate != null) {
+      String msg = predicate.test(v);
+      if (msg != null) {
+        msg = message != null ? message : key + msg;
+        throw new RuntimeException(msg);
+      }
+    }
+    return wrap((Map) v);
+  }
+
+  public List getList(String key, PredicateWithErrMsg predicate) {
+    return getList(key, predicate, null);
+  }
+
+  public List getList(String key, PredicateWithErrMsg predicate, Object test) {
+    Object v = get(key);
+    if (v != null && !(v instanceof List)) {
+      throw new RuntimeException("" + key + " should be of type List");
+    }
+
+    if (predicate != null) {
+      String msg = predicate.test(test == null ? v : new Pair(v, test));
+      if (msg != null) {
+        throw new RuntimeException("" + key + msg);
+      }
+    }
+
+    return (List) v;
+  }
+
+  public Object get(String key, PredicateWithErrMsg<Pair> predicate, Object arg) {
+    Object v = get(key);
+    String test = predicate.test(new Pair(v, arg));
+    if (test != null) {
+      throw new RuntimeException("" + key + test);
+    }
+    return v;
+  }
+
+  public Object get(String k, Object def) {
+    Object v = get(k);
+    if (v == null) return def;
+    return v;
+  }
+
+  static ValidatingJsonMap wrap(Map<String, Object> map) {
+    if (map == null) return null;
+    if (map instanceof ValidatingJsonMap) {
+      return (ValidatingJsonMap) map;
+    } else {
+      return new ValidatingJsonMap(map);
+    }
+
+  }
+
+  public static ValidatingJsonMap fromJSON(InputStream is, String includeLocation) {
+    return fromJSON(new InputStreamReader(is, UTF_8), includeLocation);
+  }
+
+  public static ValidatingJsonMap fromJSON(Reader s, String includeLocation) {
+    try {
+      ValidatingJsonMap map = (ValidatingJsonMap) getObjectBuilder(new JSONParser(s)).getObject();
+      handleIncludes(map, includeLocation, 4);
+      return map;
+    } catch (IOException e) {
+      throw new RuntimeException();
+    }
+  }
+
+  /**
+   * In the given map, recursively replace "#include":"resource-name" with the key/value pairs
+   * parsed from the resource at {location}/{resource-name}.json
+   */
+  private static void handleIncludes(ValidatingJsonMap map, String location, int maxDepth) {
+    final String loc = location == null ? "" // trim trailing slash
+        : (location.endsWith("/") ? location.substring(0, location.length() - 1) : location);
+    String resourceToInclude = (String) map.get(INCLUDE);
+    if (resourceToInclude != null) {
+      ValidatingJsonMap includedMap = parse(loc + "/" + resourceToInclude + RESOURCE_EXTENSION, loc);
+      map.remove(INCLUDE);
+      map.putAll(includedMap);
+    }
+    if (maxDepth > 0) {
+      map.entrySet().stream()
+          .filter(e -> e.getValue() instanceof Map)
+          .map(Map.Entry::getValue)
+          .forEach(m -> handleIncludes((ValidatingJsonMap) m, loc, maxDepth - 1));
+    }
+  }
+
+  public static ValidatingJsonMap getDeepCopy(Map map, int maxDepth, boolean mutable) {
+    if (map == null) return null;
+    if (maxDepth < 1) return ValidatingJsonMap.wrap(map);
+    ValidatingJsonMap copy = mutable ? new ValidatingJsonMap(map.size()) : new ValidatingJsonMap();
+    for (Object o : map.entrySet()) {
+      Map.Entry<String, Object> e = (Entry<String, Object>) o;
+      Object v = e.getValue();
+      if (v instanceof Map) v = getDeepCopy((Map) v, maxDepth - 1, mutable);
+      else if (v instanceof Collection) v = getDeepCopy((Collection) v, maxDepth - 1, mutable);
+      copy.put(e.getKey(), v);
+    }
+    return mutable ? copy : new ValidatingJsonMap(Collections.unmodifiableMap(copy));
+  }
+
+  public static Collection getDeepCopy(Collection c, int maxDepth, boolean mutable) {
+    if (c == null || maxDepth < 1) return c;
+    Collection result = c instanceof Set ? new HashSet() : new ArrayList();
+    for (Object o : c) {
+      if (o instanceof Map) {
+        o = getDeepCopy((Map) o, maxDepth - 1, mutable);
+      }
+      result.add(o);
+    }
+    return mutable ? result : result instanceof Set ? unmodifiableSet((Set) result) : unmodifiableList((List) result);
+  }
+
+  private static ObjectBuilder getObjectBuilder(final JSONParser jp) throws IOException {
+    return new ObjectBuilder(jp) {
+      @Override
+      public Object newObject() throws IOException {
+        return new ValidatingJsonMap();
+      }
+    };
+  }
+
+  public static ValidatingJsonMap parse(String resourceName, String includeLocation) {
+    InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName);
+    if (is == null)
+      throw new RuntimeException("invalid API spec: " + resourceName);
+    ValidatingJsonMap map = null;
+    try {
+      map = fromJSON(is, includeLocation);
+    } catch (Exception e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in JSON : " + resourceName, e);
+    }
+    if (map == null) throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Empty value for " + resourceName);
+
+    return getDeepCopy(map, 5, false);
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    return that instanceof Map && this.delegate.equals(that);
+  }
+
+  public static final ValidatingJsonMap EMPTY = new ValidatingJsonMap(Collections.EMPTY_MAP);
+
+  public interface PredicateWithErrMsg<T> {
+
+    /**
+     * Test the object and return null if the predicate is true
+     * or return a string with a message;
+     *
+     * @param t test value
+     * @return null if test succeeds or an error description if test fails
+     */
+    String test(T t);
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/AbstractEmbeddedSolrServerTestCase.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/AbstractEmbeddedSolrServerTestCase.java b/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/AbstractEmbeddedSolrServerTestCase.java
index 117ec1d..2a5a08c 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/AbstractEmbeddedSolrServerTestCase.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/AbstractEmbeddedSolrServerTestCase.java
@@ -19,26 +19,37 @@ package org.apache.solr.client.solrj.embedded;
 import java.io.File;
 import java.nio.file.Path;
 
+import org.apache.commons.io.FileUtils;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.core.CoreContainer;
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 
 public abstract class AbstractEmbeddedSolrServerTestCase extends SolrTestCaseJ4 {
 
-  protected static final Path SOLR_HOME = getFile("solrj/solr/shared").toPath().toAbsolutePath();
+  protected static Path SOLR_HOME;
+  protected static Path CONFIG_HOME;
 
   protected CoreContainer cores = null;
   protected File tempDir;
 
+  @BeforeClass
+  public static void setUpHome() throws Exception {
+    CONFIG_HOME = getFile("solrj/solr/shared").toPath().toAbsolutePath();
+    SOLR_HOME = createTempDir("solrHome");
+    FileUtils.copyDirectory(CONFIG_HOME.toFile(), SOLR_HOME.toFile());
+  }
+
   @Override
   @Before
   public void setUp() throws Exception {
     super.setUp();
 
     System.setProperty("solr.solr.home", SOLR_HOME.toString());
-    System.setProperty("configSetBase", SOLR_HOME.resolve("../configsets").normalize().toString());
+    System.setProperty("configSetBaseDir", CONFIG_HOME.resolve("../configsets").normalize().toString());
     System.out.println("Solr home: " + SOLR_HOME.toString());
 
     //The index is always stored within a temporary directory
@@ -73,6 +84,13 @@ public abstract class AbstractEmbeddedSolrServerTestCase extends SolrTestCaseJ4
     super.tearDown();
   }
 
+  @AfterClass
+  public static void tearDownHome() throws Exception {
+    if (SOLR_HOME != null) {
+      FileUtils.deleteDirectory(SOLR_HOME.toFile());
+    }
+  }
+
   protected void deleteAdditionalFiles() {
 
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleJettyTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleJettyTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleJettyTest.java
index 2cf8e04..b7ac7de 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleJettyTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleJettyTest.java
@@ -73,7 +73,7 @@ public class SolrExampleJettyTest extends SolrExampleTests {
     // two docs, one with uniqueKey, another without it
     String json = "{\"id\":\"abc1\", \"name\": \"name1\"} {\"name\" : \"name2\"}";
     HttpClient httpClient = client.getHttpClient();
-    HttpPost post = new HttpPost(client.getBaseURL() + "/update/json/docs");
+    HttpPost post = new HttpPost(getUri(client));
     post.setHeader("Content-Type", "application/json");
     post.setEntity(new InputStreamEntity(new ByteArrayInputStream(json.getBytes("UTF-8")), -1));
     HttpResponse response = httpClient.execute(post, HttpClientUtil.createNewHttpClientRequestContext());
@@ -94,4 +94,11 @@ public class SolrExampleJettyTest extends SolrExampleTests {
     assertEquals("name2",m.get("name"));
 
   }
+
+  private String getUri(HttpSolrClient client) {
+    String baseURL = client.getBaseURL();
+    return random().nextBoolean() ?
+        baseURL.replace("/collection1", "/v2/cores/collection1/update") :
+        baseURL + "/update/json/docs";
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/SelectWithEvaluatorsTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/SelectWithEvaluatorsTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/SelectWithEvaluatorsTest.java
new file mode 100644
index 0000000..b91df8d
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/SelectWithEvaluatorsTest.java
@@ -0,0 +1,259 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.LuceneTestCase.Slow;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.AddEvaluator;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.IfThenElseEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.UpdateRequest;
+import org.apache.solr.cloud.AbstractDistribZkTestBase;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ *  All base tests will be done with CloudSolrStream. Under the covers CloudSolrStream uses SolrStream so
+ *  SolrStream will get fully exercised through these tests.
+ *
+ **/
+
+@Slow
+@LuceneTestCase.SuppressCodecs({"Lucene3x", "Lucene40","Lucene41","Lucene42","Lucene45"})
+public class SelectWithEvaluatorsTest extends SolrCloudTestCase {
+
+  private static final String COLLECTIONORALIAS = "collection1";
+  private static final int TIMEOUT = DEFAULT_TIMEOUT;
+  private static final String id = "id";
+
+  private static boolean useAlias;
+
+  @BeforeClass
+  public static void setupCluster() throws Exception {
+    configureCluster(4)
+        .addConfig("conf", getFile("solrj").toPath().resolve("solr").resolve("configsets").resolve("streaming").resolve("conf"))
+        .addConfig("ml", getFile("solrj").toPath().resolve("solr").resolve("configsets").resolve("ml").resolve("conf"))
+        .configure();
+
+    String collection;
+    useAlias = random().nextBoolean();
+    if (useAlias) {
+      collection = COLLECTIONORALIAS + "_collection";
+    } else {
+      collection = COLLECTIONORALIAS;
+    }
+    CollectionAdminRequest.createCollection(collection, "conf", 2, 1).process(cluster.getSolrClient());
+    AbstractDistribZkTestBase.waitForRecoveriesToFinish(collection, cluster.getSolrClient().getZkStateReader(),
+        false, true, TIMEOUT);
+    if (useAlias) {
+      CollectionAdminRequest.createAlias(COLLECTIONORALIAS, collection).process(cluster.getSolrClient());
+    }
+  }
+
+  @Before
+  public void cleanIndex() throws Exception {
+    new UpdateRequest()
+        .deleteByQuery("*:*")
+        .commit(cluster.getSolrClient(), COLLECTIONORALIAS);
+  }
+
+  @Test
+  public void testSelectWithEvaluatorsStream() throws Exception {
+
+    new UpdateRequest()
+        .add(id, "1", "a_s", "foo", "b_i", "1", "c_d", "3.3", "d_b", "true")
+        .commit(cluster.getSolrClient(), COLLECTIONORALIAS);
+
+    String clause;
+    TupleStream stream;
+    List<Tuple> tuples;
+    
+    StreamFactory factory = new StreamFactory()
+      .withCollectionZkHost("collection1", cluster.getZkServer().getZkAddress())
+      .withFunctionName("search", CloudSolrStream.class)
+      .withFunctionName("select", SelectStream.class)
+      .withFunctionName("add", AddEvaluator.class)
+      .withFunctionName("if", IfThenElseEvaluator.class)
+      .withFunctionName("gt", GreaterThanEvaluator.class)
+      ;
+    
+    // Basic test
+    clause = "select("
+            +   "id,"
+            +   "add(b_i,c_d) as result,"
+            +   "search(collection1, q=*:*, fl=\"id,a_s,b_i,c_d,d_b\", sort=\"id asc\")"
+            + ")";
+    stream = factory.constructStream(clause);
+    tuples = getTuples(stream);
+    assertFields(tuples, "id", "result");
+    assertNotFields(tuples, "a_s", "b_i", "c_d", "d_b");
+    assertEquals(1, tuples.size());
+    assertDouble(tuples.get(0), "result", 4.3);
+    assertEquals(4.3, tuples.get(0).get("result"));
+
+  }
+  
+  protected List<Tuple> getTuples(TupleStream tupleStream) throws IOException {
+    tupleStream.open();
+    List<Tuple> tuples = new ArrayList<Tuple>();
+    for(Tuple t = tupleStream.read(); !t.EOF; t = tupleStream.read()) {
+      tuples.add(t);
+    }
+    tupleStream.close();
+    return tuples;
+  }
+  protected boolean assertOrder(List<Tuple> tuples, int... ids) throws Exception {
+    return assertOrderOf(tuples, "id", ids);
+  }
+  protected boolean assertOrderOf(List<Tuple> tuples, String fieldName, int... ids) throws Exception {
+    int i = 0;
+    for(int val : ids) {
+      Tuple t = tuples.get(i);
+      String tip = t.getString(fieldName);
+      if(!tip.equals(Integer.toString(val))) {
+        throw new Exception("Found value:"+tip+" expecting:"+val);
+      }
+      ++i;
+    }
+    return true;
+  }
+
+  protected boolean assertMapOrder(List<Tuple> tuples, int... ids) throws Exception {
+    int i = 0;
+    for(int val : ids) {
+      Tuple t = tuples.get(i);
+      List<Map> tip = t.getMaps("group");
+      int id = (int)tip.get(0).get("id");
+      if(id != val) {
+        throw new Exception("Found value:"+id+" expecting:"+val);
+      }
+      ++i;
+    }
+    return true;
+  }
+
+  protected boolean assertFields(List<Tuple> tuples, String ... fields) throws Exception{
+    for(Tuple tuple : tuples){
+      for(String field : fields){
+        if(!tuple.fields.containsKey(field)){
+          throw new Exception(String.format(Locale.ROOT, "Expected field '%s' not found", field));
+        }
+      }
+    }
+    return true;
+  }
+  protected boolean assertNotFields(List<Tuple> tuples, String ... fields) throws Exception{
+    for(Tuple tuple : tuples){
+      for(String field : fields){
+        if(tuple.fields.containsKey(field)){
+          throw new Exception(String.format(Locale.ROOT, "Unexpected field '%s' found", field));
+        }
+      }
+    }
+    return true;
+  }  
+
+  protected boolean assertGroupOrder(Tuple tuple, int... ids) throws Exception {
+    List<?> group = (List<?>)tuple.get("tuples");
+    int i=0;
+    for(int val : ids) {
+      Map<?,?> t = (Map<?,?>)group.get(i);
+      Long tip = (Long)t.get("id");
+      if(tip.intValue() != val) {
+        throw new Exception("Found value:"+tip.intValue()+" expecting:"+val);
+      }
+      ++i;
+    }
+    return true;
+  }
+
+  public boolean assertLong(Tuple tuple, String fieldName, long l) throws Exception {
+    long lv = (long)tuple.get(fieldName);
+    if(lv != l) {
+      throw new Exception("Longs not equal:"+l+" : "+lv);
+    }
+
+    return true;
+  }
+  
+  public boolean assertDouble(Tuple tuple, String fieldName, double expectedValue) throws Exception {
+    double value = (double)tuple.get(fieldName);
+    if(expectedValue != value) {
+      throw new Exception("Doubles not equal:"+value+" : "+expectedValue);
+    }
+
+    return true;
+  }
+  
+  public boolean assertString(Tuple tuple, String fieldName, String expected) throws Exception {
+    String actual = (String)tuple.get(fieldName);
+    
+    if( (null == expected && null != actual) ||
+        (null != expected && null == actual) ||
+        (null != expected && !expected.equals(actual))){
+      throw new Exception("Longs not equal:"+expected+" : "+actual);
+    }
+
+    return true;
+  }
+  
+  protected boolean assertMaps(List<Map> maps, int... ids) throws Exception {
+    if(maps.size() != ids.length) {
+      throw new Exception("Expected id count != actual map count:"+ids.length+":"+maps.size());
+    }
+
+    int i=0;
+    for(int val : ids) {
+      Map t = maps.get(i);
+      String tip = (String)t.get("id");
+      if(!tip.equals(Integer.toString(val))) {
+        throw new Exception("Found value:"+tip+" expecting:"+val);
+      }
+      ++i;
+    }
+    return true;
+  }
+
+  private boolean assertList(List list, Object... vals) throws Exception {
+
+    if(list.size() != vals.length) {
+      throw new Exception("Lists are not the same size:"+list.size() +" : "+vals.length);
+    }
+
+    for(int i=0; i<list.size(); i++) {
+      Object a = list.get(i);
+      Object b = vals[i];
+      if(!a.equals(b)) {
+        throw new Exception("List items not equals:"+a+" : "+b);
+      }
+    }
+
+    return true;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/StreamExpressionTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/StreamExpressionTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/StreamExpressionTest.java
index 5b806a8..ebc3250 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/StreamExpressionTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/StreamExpressionTest.java
@@ -33,16 +33,19 @@ import org.apache.solr.client.solrj.io.SolrClientCache;
 import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.comp.ComparatorOrder;
 import org.apache.solr.client.solrj.io.comp.FieldComparator;
-import org.apache.solr.client.solrj.io.ops.AndOperation;
+import org.apache.solr.client.solrj.io.eval.AddEvaluator;
+import org.apache.solr.client.solrj.io.eval.AndEvaluator;
+import org.apache.solr.client.solrj.io.eval.EqualsEvaluator;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEqualToEvaluator;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.IfThenElseEvaluator;
+import org.apache.solr.client.solrj.io.eval.LessThanEqualToEvaluator;
+import org.apache.solr.client.solrj.io.eval.LessThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.NotEvaluator;
+import org.apache.solr.client.solrj.io.eval.OrEvaluator;
+import org.apache.solr.client.solrj.io.eval.RawValueEvaluator;
 import org.apache.solr.client.solrj.io.ops.ConcatOperation;
-import org.apache.solr.client.solrj.io.ops.EqualsOperation;
-import org.apache.solr.client.solrj.io.ops.GreaterThanEqualToOperation;
-import org.apache.solr.client.solrj.io.ops.GreaterThanOperation;
 import org.apache.solr.client.solrj.io.ops.GroupOperation;
-import org.apache.solr.client.solrj.io.ops.LessThanEqualToOperation;
-import org.apache.solr.client.solrj.io.ops.LessThanOperation;
-import org.apache.solr.client.solrj.io.ops.NotOperation;
-import org.apache.solr.client.solrj.io.ops.OrOperation;
 import org.apache.solr.client.solrj.io.ops.ReplaceOperation;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParser;
@@ -848,14 +851,14 @@ public class StreamExpressionTest extends SolrCloudTestCase {
         .withFunctionName("having", HavingStream.class)
         .withFunctionName("rollup", RollupStream.class)
         .withFunctionName("sum", SumMetric.class)
-        .withFunctionName("and", AndOperation.class)
-        .withFunctionName("or", OrOperation.class)
-        .withFunctionName("not", NotOperation.class)
-        .withFunctionName("gt", GreaterThanOperation.class)
-        .withFunctionName("lt", LessThanOperation.class)
-        .withFunctionName("eq", EqualsOperation.class)
-        .withFunctionName("lteq", LessThanEqualToOperation.class)
-        .withFunctionName("gteq", GreaterThanEqualToOperation.class);
+        .withFunctionName("and", AndEvaluator.class)
+        .withFunctionName("or", OrEvaluator.class)
+        .withFunctionName("not", NotEvaluator.class)
+        .withFunctionName("gt", GreaterThanEvaluator.class)
+        .withFunctionName("lt", LessThanEvaluator.class)
+        .withFunctionName("eq", EqualsEvaluator.class)
+        .withFunctionName("lteq", LessThanEqualToEvaluator.class)
+        .withFunctionName("gteq", GreaterThanEqualToEvaluator.class);
 
     stream = factory.constructStream("having(search(" + COLLECTIONORALIAS + ", q=*:*, fl=\"id,a_s,a_i,a_f\", sort=\"a_f asc\"), eq(a_i, 9))");
     StreamContext context = new StreamContext();
@@ -956,14 +959,15 @@ public class StreamExpressionTest extends SolrCloudTestCase {
         .withFunctionName("having", HavingStream.class)
         .withFunctionName("rollup", RollupStream.class)
         .withFunctionName("sum", SumMetric.class)
-        .withFunctionName("and", AndOperation.class)
-        .withFunctionName("or", OrOperation.class)
-        .withFunctionName("not", NotOperation.class)
-        .withFunctionName("gt", GreaterThanOperation.class)
-        .withFunctionName("lt", LessThanOperation.class)
-        .withFunctionName("eq", EqualsOperation.class)
-        .withFunctionName("lteq", LessThanEqualToOperation.class)
-        .withFunctionName("gteq", GreaterThanEqualToOperation.class)
+        .withFunctionName("and", AndEvaluator.class)
+        .withFunctionName("or", OrEvaluator.class)
+        .withFunctionName("not", NotEvaluator.class)
+        .withFunctionName("gt", GreaterThanEvaluator.class)
+        .withFunctionName("lt", LessThanEvaluator.class)
+        .withFunctionName("eq", EqualsEvaluator.class)
+        .withFunctionName("lteq", LessThanEqualToEvaluator.class)
+        .withFunctionName("gteq", GreaterThanEqualToEvaluator.class)
+        .withFunctionName("val", RawValueEvaluator.class)
         .withFunctionName("parallel", ParallelStream.class);
 
     stream = factory.constructStream("parallel(" + COLLECTIONORALIAS + ", workers=2, sort=\"a_f asc\", having(search(" + COLLECTIONORALIAS + ", q=*:*, fl=\"id,a_s,a_i,a_f\", sort=\"a_f asc\", partitionKeys=id), eq(a_i, 9)))");
@@ -2192,6 +2196,9 @@ public class StreamExpressionTest extends SolrCloudTestCase {
       .withFunctionName("select", SelectStream.class)
       .withFunctionName("replace", ReplaceOperation.class)
       .withFunctionName("concat", ConcatOperation.class)
+      .withFunctionName("add", AddEvaluator.class)
+      .withFunctionName("if", IfThenElseEvaluator.class)
+      .withFunctionName("gt", GreaterThanEvaluator.class)
       ;
     
     // Basic test

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AbsoluteValueEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AbsoluteValueEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AbsoluteValueEvaluatorTest.java
new file mode 100644
index 0000000..88d3447
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AbsoluteValueEvaluatorTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.AbsoluteValueEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class AbsoluteValueEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public AbsoluteValueEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("abs", AbsoluteValueEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void absoluteValueOneField() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("abs(a)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(1L, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(1.1D, result);
+    
+    values.clear();
+    values.put("a", -1.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(1.1D, result);
+  }
+
+  @Test(expected = IOException.class)
+  public void absNoField() throws Exception{
+    factory.constructEvaluator("abs()");
+  }
+  
+  @Test(expected = IOException.class)
+  public void absTwoFields() throws Exception{
+    factory.constructEvaluator("abs(a,b)");
+  }
+  
+  @Test
+  public void absNoValue() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("abs(a)");
+    
+    values.clear();
+    Object result = evaluator.evaluate(new Tuple(values));
+    assertNull(result);
+  }
+  @Test
+  public void absNullValue() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("abs(a)");
+    
+    values.clear();
+    values.put("a", null);
+    Object result = evaluator.evaluate(new Tuple(values));
+    assertNull(result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AddEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AddEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AddEvaluatorTest.java
new file mode 100644
index 0000000..7115452
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AddEvaluatorTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.AddEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class AddEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public AddEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("add", AddEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void addTwoFieldsWithValues() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("add(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(3L, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(3.1D, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(3.2D, result);
+  }
+
+  @Test(expected = IOException.class)
+  public void addOneField() throws Exception{
+    factory.constructEvaluator("add(a)");
+  }
+  
+  @Test
+  public void addTwoFieldWithNulls() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("add(a,b)");
+    Object result;
+    
+    values.clear();
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+  }
+  
+  @Test
+  public void addTwoFieldsWithNull() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("add(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", null);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", null);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("a", null);
+    values.put("b", 1.1);    
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+  }
+
+  @Test
+  public void addTwoFieldsWithMissingField() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("add(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("b", 1.1);    
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+  }
+
+  @Test
+  public void addManyFieldsWithValues() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("add(a,b,c,d)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(10L, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(10.1D, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    values.put("c", 3.1);
+    values.put("d", 4.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(10.4D, result);
+  }
+  
+  @Test
+  public void addManyFieldsWithSubAdds() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("add(a,b,add(c,d))");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(10L, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(10.1D, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    values.put("c", 3.1);
+    values.put("d", 4.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(10.4D, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    values.put("c", 3.1);
+    values.put("d", 4.123456789123456);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(10.423456789123456, result);
+    
+    values.clear();
+    values.put("a", 123456789123456789L);
+    values.put("b", 123456789123456789L);
+    values.put("c", 123456789123456789L);
+    values.put("d", 123456789123456789L);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(4 * 123456789123456789L, result);
+  }
+  
+  @Test
+  public void addManyFieldsWithManySubAdds() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("add(add(a,b),add(c,d),add(c,a))");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(14L, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(14.2D, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    values.put("c", 3.1);
+    values.put("d", 4.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(14.6D, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    values.put("c", 3.1);
+    values.put("d", 4.123456789123456);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(14.623456789123456, result);
+    
+    values.clear();
+    values.put("a", 123456789123456789L);
+    values.put("b", 123456789123456789L);
+    values.put("c", 123456789123456789L);
+    values.put("d", 123456789123456789L);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(6 * 123456789123456789L, result);
+    
+    values.clear();
+    values.put("a", 4.123456789123456);
+    values.put("b", 4.123456789123456);
+    values.put("c", 4.123456789123456);
+    values.put("d", 4.123456789123456);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(6 * 4.123456789123456, result);
+  }
+  
+  @Test
+  public void addManyFieldsWithManySubAddsWithNegative() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("add(add(a,b),add(c,d),add(c,a))");
+    Object result;
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(10L, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2);
+    values.put("c", -3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(2.2D, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    values.put("c", -3.1);
+    values.put("d", 4.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(2.2D, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    values.put("c", -3.1);
+    values.put("d", 5.223456789123456);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(3.323456789123456, result);
+    
+    values.clear();
+    values.put("a", 123456789123456789L);
+    values.put("b", -123456789123456789L);
+    values.put("c", 123456789123456789L);
+    values.put("d", 123456789123456789L);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(4 * 123456789123456789L, result);
+    
+    values.clear();
+    values.put("a", -4.123456789123456);
+    values.put("b", -4.123456789123456);
+    values.put("c", -4.123456789123456);
+    values.put("d", -4.123456789123456);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(6 * -4.123456789123456, result);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AndEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AndEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AndEvaluatorTest.java
new file mode 100644
index 0000000..9daa928
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AndEvaluatorTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.AndEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class AndEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public AndEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("and", AndEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void andTwoBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("and(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+  
+  @Test
+  public void andWithSubAndsBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("and(a,and(b,c))");
+    Object result;
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", true);
+    values.put("c", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+
+    values.clear();
+    values.put("a", true);
+    values.put("b", true);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", false);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", true);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", false);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CompoundEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CompoundEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CompoundEvaluatorTest.java
new file mode 100644
index 0000000..8ae5657
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CompoundEvaluatorTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.AddEvaluator;
+import org.apache.solr.client.solrj.io.eval.AndEvaluator;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.IfThenElseEvaluator;
+import org.apache.solr.client.solrj.io.eval.LessThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.MultiplyEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.eval.SubtractEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class CompoundEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public CompoundEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("and", AndEvaluator.class)
+      .withFunctionName("gt", GreaterThanEvaluator.class)
+      .withFunctionName("lt", LessThanEvaluator.class)
+      .withFunctionName("add", AddEvaluator.class)
+      .withFunctionName("sub", SubtractEvaluator.class)
+      .withFunctionName("mult", MultiplyEvaluator.class)
+      .withFunctionName("if", IfThenElseEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void compoundTest() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("add(mult(a,b),if(gt(c,d),e,f),g)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 10);
+    values.put("b", -3);
+    values.put("c", "foo");
+    values.put("d", "bar");
+    values.put("e", 9);
+    values.put("f", 2);
+    values.put("g", 5);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(-16L, result);
+    
+    values.clear();
+    values.put("a", .1);
+    values.put("b", -3);
+    values.put("c", "foo");
+    values.put("d", "bar");
+    values.put("e", 9);
+    values.put("f", 2);
+    values.put("g", 5);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(13.7, result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/DivideEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/DivideEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/DivideEvaluatorTest.java
new file mode 100644
index 0000000..680be63
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/DivideEvaluatorTest.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for multitional 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.DivideEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class DivideEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public DivideEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("div", DivideEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void divTwoFieldsWithValues() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("div(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(1.0/2, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(1.1/2, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(1.1/2.1, result);
+  }
+
+  @Test(expected = IOException.class)
+  public void divOneField() throws Exception{
+    factory.constructEvaluator("div(a)");
+  }
+  
+  @Test(expected = IOException.class)
+  public void divTwoFieldWithNulls() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("div(a,b)");
+    
+    values.clear();
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void divTwoFieldsWithNullDenominator() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("div(a,b)");
+    
+    values.clear();
+    values.put("a", 1);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void divTwoFieldsWithNullNumerator() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("div(a,b)");
+    
+    values.clear();
+    values.put("b", 1);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+
+  @Test(expected = IOException.class)
+  public void divTwoFieldsWithMissingDenominator() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("div(a,b)");
+    
+    values.clear();
+    values.put("a", 1);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void divTwoFieldsWithMissingNumerator() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("div(a,b)");
+    
+    values.clear();
+    values.put("b", 1);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  
+  @Test(expected = IOException.class)
+  public void divManyFieldsWithValues() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("div(a,b,c,d)");
+  }
+  
+  @Test
+  public void divManyFieldsWithSubdivs() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("div(a,div(b,c))");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    values.put("c", 3);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals((1.0 / (2.0 / 3)), result);
+  }
+  
+  @Test(expected = IOException.class)
+  public void divByZero() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("div(a,b)");
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 0);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test
+  public void divZeroByValue() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("div(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(0L, result);
+  }
+}


[05/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/servlet/SolrRequestParserTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/servlet/SolrRequestParserTest.java b/solr/core/src/test/org/apache/solr/servlet/SolrRequestParserTest.java
index b9e1e4a..a34febc 100644
--- a/solr/core/src/test/org/apache/solr/servlet/SolrRequestParserTest.java
+++ b/solr/core/src/test/org/apache/solr/servlet/SolrRequestParserTest.java
@@ -55,6 +55,7 @@ import org.apache.solr.servlet.SolrRequestParsers.MultipartRequestParser;
 import org.apache.solr.servlet.SolrRequestParsers.FormDataRequestParser;
 import org.apache.solr.servlet.SolrRequestParsers.RawRequestParser;
 import org.apache.solr.servlet.SolrRequestParsers.StandardRequestParser;
+import org.easymock.EasyMock;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -481,6 +482,7 @@ public class SolrRequestParserTest extends SolrTestCaseJ4 {
     // we dont pass a content-length to let the security mechanism limit it:
     expect(request.getQueryString()).andReturn("foo=1&bar=2").anyTimes();
     expect(request.getInputStream()).andReturn(new ByteServletInputStream(body.getBytes(StandardCharsets.US_ASCII)));
+    expect(request.getAttribute(EasyMock.anyObject(String.class))).andReturn(null).anyTimes();
     replay(request);
 
     SolrRequestParsers parsers = new SolrRequestParsers(h.getCore().getSolrConfig());
@@ -518,7 +520,7 @@ public class SolrRequestParserTest extends SolrTestCaseJ4 {
     expect(request.getRequestURI()).andReturn(uri).anyTimes();
     expect(request.getContentType()).andReturn(contentType).anyTimes();
     expect(request.getContentLength()).andReturn(contentLength).anyTimes();
-    expect(request.getAttribute(SolrRequestParsers.REQUEST_TIMER_SERVLET_ATTRIBUTE)).andReturn(null).anyTimes();
+    expect(request.getAttribute(EasyMock.anyObject(String.class))).andReturn(null).anyTimes();
     return request;
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/spelling/suggest/RandomTestDictionaryFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/spelling/suggest/RandomTestDictionaryFactory.java b/solr/core/src/test/org/apache/solr/spelling/suggest/RandomTestDictionaryFactory.java
new file mode 100644
index 0000000..6e3172d
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/spelling/suggest/RandomTestDictionaryFactory.java
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.spelling.suggest;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+
+import org.apache.lucene.search.spell.Dictionary;
+import org.apache.lucene.search.suggest.InputIterator;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefIterator;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory for a dictionary with an iterator over bounded-length random strings (with fixed
+ * weight of 1 and null payloads) that only operates when RandomDictionary.enabledSysProp
+ * is set; this will be true from the time a RandomDictionary has been constructed until
+ * its enabledSysProp has been cleared.
+ */
+public class RandomTestDictionaryFactory extends DictionaryFactory {
+  public static final String RAND_DICT_MAX_ITEMS = "randDictMaxItems";
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  private static final long DEFAULT_MAX_ITEMS = 100_000_000;
+
+  @Override
+  public RandomTestDictionary create(SolrCore core, SolrIndexSearcher searcher) {
+    if(params == null) {
+      // should not happen; implies setParams was not called
+      throw new IllegalStateException("Value of params not set");
+    }
+    String name = (String)params.get(CommonParams.NAME);
+    if (name == null) { // Shouldn't happen since this is the component name
+      throw new IllegalArgumentException(CommonParams.NAME + " is a mandatory parameter");
+    }
+    long maxItems = DEFAULT_MAX_ITEMS;
+    Object specifiedMaxItems = params.get(RAND_DICT_MAX_ITEMS);
+    if (specifiedMaxItems != null) {
+      maxItems = Long.parseLong(specifiedMaxItems.toString());
+    }
+    return new RandomTestDictionary(name, maxItems);
+  }
+
+  public static class RandomTestDictionary implements Dictionary {
+    private static final String SYS_PROP_PREFIX = RandomTestDictionary.class.getName() + ".enabled.";
+    private final String enabledSysProp; // Clear this property to stop the input iterator
+    private final long maxItems;
+    private long emittedItems = 0L;
+
+    RandomTestDictionary(String name, long maxItems) {
+      enabledSysProp = getEnabledSysProp(name);
+      this.maxItems = maxItems;
+      synchronized (RandomTestDictionary.class) {
+        if (System.getProperty(enabledSysProp) != null) {
+          throw new RuntimeException("System property '" + enabledSysProp + "' is already in use.");
+        }
+        System.setProperty(enabledSysProp, "true");
+      }
+    }
+
+    public static String getEnabledSysProp(String suggesterName) {
+      return SYS_PROP_PREFIX + suggesterName;
+    }
+
+    @Override
+    public InputIterator getEntryIterator() throws IOException {
+      return new InputIterator.InputIteratorWrapper(new RandomByteRefIterator());
+    }
+
+    private class RandomByteRefIterator implements BytesRefIterator {
+      private static final int MAX_LENGTH = 100;
+
+      @Override
+      public BytesRef next() throws IOException {
+        BytesRef next = null;
+        if (System.getProperty(enabledSysProp) != null) {
+          if (emittedItems < maxItems) {
+            ++emittedItems;
+            next = new BytesRef(TestUtil.randomUnicodeString(LuceneTestCase.random(), MAX_LENGTH));
+            if (emittedItems % 1000 == 0) {
+              log.info(enabledSysProp + " emitted " + emittedItems + " items.");
+            }
+          } else {
+            log.info(enabledSysProp + " disabled after emitting " + emittedItems + " items.");
+            System.clearProperty(enabledSysProp); // disable once maxItems has been reached
+            emittedItems = 0L;
+          }
+        } else {
+          log.warn(enabledSysProp + " invoked when disabled");
+          emittedItems = 0L;
+        }
+        return next;
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/store/blockcache/BlockCacheTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/store/blockcache/BlockCacheTest.java b/solr/core/src/test/org/apache/solr/store/blockcache/BlockCacheTest.java
index 8e2edfe..795518d 100644
--- a/solr/core/src/test/org/apache/solr/store/blockcache/BlockCacheTest.java
+++ b/solr/core/src/test/org/apache/solr/store/blockcache/BlockCacheTest.java
@@ -18,6 +18,7 @@ package org.apache.solr.store.blockcache;
 
 import java.util.Arrays;
 import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.lucene.util.LuceneTestCase;
@@ -106,4 +107,137 @@ public class BlockCacheTest extends LuceneTestCase {
     random.nextBytes(buf);
     return buf;
   }
+
+  // given a position, return the appropriate byte.
+  // always returns the same thing so we don't actually have to store the bytes redundantly to check them.
+  private static byte getByte(long pos) {
+    // knuth multiplicative hash method, then take top 8 bits
+    return (byte) ((((int)pos) * (int)(2654435761L)) >> 24);
+
+    // just the lower bits of the block number, to aid in debugging...
+    // return (byte)(pos>>10);
+  }
+
+  @Test
+  @AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/SOLR-10121")
+  public void testBlockCacheConcurrent() throws Exception {
+    Random rnd = random();
+
+    /***
+    final int blocksInTest = 256;
+    final int blockSize = 1024;
+    final int slabSize = blockSize * 128;
+    final long totalMemory = 2 * slabSize;
+    ***/
+
+    final int blocksInTest = 16384;  // pick something that won't fit in memory, but is small enough to cause a medium hit rate.  16MB of blocks is double the total memory size of the cache.
+    final int blockSize = 1024;
+    final int slabSize = blockSize * 4096;
+    final long totalMemory = 2 * slabSize;  // should give us 2 slabs (8MB)
+
+    final int nThreads=2;
+    final int nReads=1000000;
+    final int readsPerThread=nReads/nThreads;
+    final int readLastBlockOdds=10; // odds (1 in N) of the next block operation being on the same block as the previous operation... helps flush concurrency issues
+
+    final BlockCache blockCache = new BlockCache(new Metrics(), true, totalMemory, slabSize, blockSize);
+
+    final AtomicBoolean failed = new AtomicBoolean(false);
+    final AtomicLong hitsInCache = new AtomicLong();
+    final AtomicLong missesInCache = new AtomicLong();
+    final AtomicLong storeFails = new AtomicLong();
+    final AtomicLong lastBlock = new AtomicLong();
+
+    final int file = 0;
+
+
+    Thread[] threads = new Thread[nThreads];
+    for (int i=0; i<threads.length; i++) {
+      final int threadnum = i;
+      final long seed = rnd.nextLong();
+
+      threads[i] = new Thread() {
+        Random r;
+        BlockCacheKey blockCacheKey = new BlockCacheKey();
+        byte[] buffer = new byte[blockSize];
+
+        @Override
+        public void run() {
+          try {
+            r = new Random(seed);
+            blockCacheKey = new BlockCacheKey();
+            blockCacheKey.setFile(file);
+            blockCacheKey.setPath("/foo.txt");
+
+            test(readsPerThread);
+
+          } catch (Throwable e) {
+            failed.set(true);
+            e.printStackTrace();
+          }
+        }
+
+        public void test(int iter) {
+          for (int i=0; i<iter; i++) {
+            test();
+          }
+        }
+
+        public void test() {
+          long block = r.nextInt(blocksInTest);
+          if (r.nextInt(readLastBlockOdds) == 0) block = lastBlock.get();  // some percent of the time, try to read the last block another thread was just reading/writing
+          lastBlock.set(block);
+
+
+          int blockOffset = r.nextInt(blockSize);
+          long globalOffset = block * blockSize + blockOffset;
+          int len = r.nextInt(blockSize - blockOffset) + 1;  // TODO: bias toward smaller reads?
+
+          blockCacheKey.setBlock(block);
+
+          if (blockCache.fetch(blockCacheKey, buffer, blockOffset, 0, len)) {
+            hitsInCache.incrementAndGet();
+            // validate returned bytes
+            for (int i = 0; i < len; i++) {
+              long globalPos = globalOffset + i;
+              if (buffer[i] != getByte(globalPos)) {
+                System.out.println("ERROR: read was " + "block=" + block + " blockOffset=" + blockOffset + " len=" + len + " globalPos=" + globalPos + " localReadOffset=" + i + " got=" + buffer[i] + " expected=" + getByte(globalPos));
+                failed.set(true);
+              }
+            }
+          } else {
+            missesInCache.incrementAndGet();
+
+            // OK, we should "get" the data and then cache the block
+            for (int i = 0; i < blockSize; i++) {
+              buffer[i] = getByte(block * blockSize + i);
+            }
+            boolean cached = blockCache.store(blockCacheKey, 0, buffer, 0, blockSize);
+            if (!cached) {
+              storeFails.incrementAndGet();
+            }
+          }
+
+        }
+
+      };
+    }
+
+
+    for (Thread thread : threads) {
+      thread.start();
+    }
+
+    for (Thread thread : threads) {
+      thread.join();
+    }
+
+    System.out.println("# of Elements = " + blockCache.getSize());
+    System.out.println("Cache Hits = " + hitsInCache.get());
+    System.out.println("Cache Misses = " + missesInCache.get());
+    System.out.println("Cache Store Fails = " + storeFails.get());
+
+    assertFalse( failed.get() );
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/update/SoftAutoCommitTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/update/SoftAutoCommitTest.java b/solr/core/src/test/org/apache/solr/update/SoftAutoCommitTest.java
index c9c9691..261a44b 100644
--- a/solr/core/src/test/org/apache/solr/update/SoftAutoCommitTest.java
+++ b/solr/core/src/test/org/apache/solr/update/SoftAutoCommitTest.java
@@ -17,9 +17,9 @@
 package org.apache.solr.update;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.junit.Assert.assertEquals;
 
+import java.lang.invoke.MethodHandles;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -33,6 +33,8 @@ import org.apache.solr.search.SolrIndexSearcher;
 import org.apache.solr.util.AbstractSolrTestCase;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Test auto commit functionality in a way that doesn't suck.
@@ -55,7 +57,7 @@ import org.junit.BeforeClass;
  */
 @Slow
 public class SoftAutoCommitTest extends AbstractSolrTestCase {
-
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   @BeforeClass
   public static void beforeClass() throws Exception {
@@ -78,28 +80,34 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
     core.registerNewSearcherListener(monitor);
     updater.registerSoftCommitCallback(monitor);
     updater.registerCommitCallback(monitor);
+
+    // isolate searcher getting ready from this test
+    monitor.searcher.poll(5000, MILLISECONDS);
   }
   
   @Override
   public void setUp() throws Exception {
     super.setUp();
-    // reset stats
-    h.getCoreContainer().reload("collection1");
+
   }
 
   public void testSoftAndHardCommitMaxTimeMixedAdds() throws Exception {
-
     final int softCommitWaitMillis = 500;
     final int hardCommitWaitMillis = 1200;
 
     CommitTracker hardTracker = updater.commitTracker;
     CommitTracker softTracker = updater.softCommitTracker;
     
+    int startingHardCommits = hardTracker.getCommitCount();
+    int startingSoftCommits = softTracker.getCommitCount();
+    
     softTracker.setTimeUpperBound(softCommitWaitMillis);
     softTracker.setDocsUpperBound(-1);
     hardTracker.setTimeUpperBound(hardCommitWaitMillis);
     hardTracker.setDocsUpperBound(-1);
-    
+    // simplify whats going on by only having soft auto commits trigger new searchers
+    hardTracker.setOpenSearcher(false);
+
     // Add a single document
     long add529 = System.nanoTime();
     assertU(adoc("id", "529", "subject", "the doc we care about in this test"));
@@ -107,21 +115,24 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
     monitor.assertSaneOffers();
 
     // Wait for the soft commit with some fudge
-    Long soft529 = monitor.soft.poll(softCommitWaitMillis * 3, MILLISECONDS);
+    Long soft529 = monitor.soft.poll(softCommitWaitMillis * 500, MILLISECONDS);
     assertNotNull("soft529 wasn't fast enough", soft529);
     monitor.assertSaneOffers();
 
+    
+    // wait for the hard commit
+    Long hard529 = monitor.hard.poll(hardCommitWaitMillis * 5, MILLISECONDS);
+    assertNotNull("hard529 wasn't fast enough", hard529);
+    
     // check for the searcher, should have happened right after soft commit
-    Long searcher529 = monitor.searcher.poll(softCommitWaitMillis * 3, MILLISECONDS);
+    Long searcher529 = monitor.searcher.poll(5000, MILLISECONDS);
     assertNotNull("searcher529 wasn't fast enough", searcher529);
     monitor.assertSaneOffers();
 
     // toss in another doc, shouldn't affect first hard commit time we poll
     assertU(adoc("id", "530", "subject", "just for noise/activity"));
 
-    // wait for the hard commit
-    Long hard529 = monitor.hard.poll(hardCommitWaitMillis * 5, MILLISECONDS);
-    assertNotNull("hard529 wasn't fast enough", hard529);
+
     monitor.assertSaneOffers();
 
     final long soft529Ms = TimeUnit.MILLISECONDS.convert(soft529 - add529, TimeUnit.NANOSECONDS);
@@ -148,17 +159,17 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
     monitor.assertSaneOffers();
 
     // there may have been (or will be) a second hard commit for 530
-    Long hard530 = monitor.hard.poll(hardCommitWaitMillis, MILLISECONDS);
+    Long hard530 = monitor.hard.poll(hardCommitWaitMillis * 5, MILLISECONDS);
     assertEquals("Tracker reports too many hard commits",
                  (null == hard530 ? 1 : 2),
-                 hardTracker.getCommitCount());
+                 hardTracker.getCommitCount() - startingHardCommits);
 
     // there may have been a second soft commit for 530, 
     // but if so it must have already happend
     Long soft530 = monitor.soft.poll(0, MILLISECONDS);
     if (null != soft530) {
       assertEquals("Tracker reports too many soft commits",
-                   2, softTracker.getCommitCount());
+                   2, softTracker.getCommitCount() - startingSoftCommits);
       if (null != hard530) {
         assertTrue("soft530 after hard530: " +
                    soft530 + " !<= " + hard530,
@@ -170,7 +181,7 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
       }
     } else {
       assertEquals("Tracker reports too many soft commits",
-                   1, softTracker.getCommitCount());
+                   1, softTracker.getCommitCount() - startingSoftCommits);
     }
       
     if (null != soft530 || null != hard530) {
@@ -178,16 +189,19 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
                     monitor.searcher.poll(0, MILLISECONDS));
     }
 
-    monitor.assertSaneOffers();
+    // clear commits
+    monitor.hard.clear();
+    monitor.soft.clear();
 
-    // wait a bit, w/o other action we definitely shouldn't see any 
+    // wait a bit, w/o other action we shouldn't see any 
     // new hard/soft commits 
     assertNull("Got a hard commit we weren't expecting",
-               monitor.hard.poll(2, SECONDS));
+               monitor.hard.poll(1000, MILLISECONDS));
     assertNull("Got a soft commit we weren't expecting",
                monitor.soft.poll(0, MILLISECONDS));
 
     monitor.assertSaneOffers();
+    monitor.searcher.clear();
   }
 
   public void testSoftAndHardCommitMaxTimeDelete() throws Exception {
@@ -198,10 +212,16 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
     CommitTracker hardTracker = updater.commitTracker;
     CommitTracker softTracker = updater.softCommitTracker;
     
+    int startingHardCommits = hardTracker.getCommitCount();
+    int startingSoftCommits = softTracker.getCommitCount();
+    
     softTracker.setTimeUpperBound(softCommitWaitMillis);
     softTracker.setDocsUpperBound(-1);
     hardTracker.setTimeUpperBound(hardCommitWaitMillis);
     hardTracker.setDocsUpperBound(-1);
+    // we don't want to overlap soft and hard opening searchers - this now blocks commits and we
+    // are looking for prompt timings
+    hardTracker.setOpenSearcher(false);
     
     // add a doc and force a commit
     assertU(adoc("id", "529", "subject", "the doc we care about in this test"));
@@ -238,7 +258,7 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
     monitor.assertSaneOffers();
 
     // Wait for the soft commit with some fudge
-    soft529 = monitor.soft.poll(softCommitWaitMillis * 3, MILLISECONDS);
+    soft529 = monitor.soft.poll(softCommitWaitMillis * 5, MILLISECONDS);
     assertNotNull("soft529 wasn't fast enough", soft529);
     monitor.assertSaneOffers();
  
@@ -251,7 +271,7 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
     assertU(adoc("id", "550", "subject", "just for noise/activity"));
 
     // wait for the hard commit
-    hard529 = monitor.hard.poll(hardCommitWaitMillis * 3, MILLISECONDS);
+    hard529 = monitor.hard.poll(hardCommitWaitMillis * 5, MILLISECONDS);
     assertNotNull("hard529 wasn't fast enough", hard529);
     monitor.assertSaneOffers();
 
@@ -276,18 +296,27 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
                searcher529 + " !<= " + hard529,
                searcher529 <= hard529);
 
+    // ensure we wait for the last searcher we triggered with 550
+    monitor.searcher.poll(5000, MILLISECONDS);
+    
+    // ensure we wait for the commits on 550
+    monitor.hard.poll(5000, MILLISECONDS);
+    monitor.soft.poll(5000, MILLISECONDS);
+    
     // clear commits
     monitor.hard.clear();
     monitor.soft.clear();
     
-    // wait a bit, w/o other action we definitely shouldn't see any 
+    // wait a bit, w/o other action we shouldn't see any 
     // new hard/soft commits 
     assertNull("Got a hard commit we weren't expecting",
-               monitor.hard.poll(2, SECONDS));
+        monitor.hard.poll(1000, MILLISECONDS));
     assertNull("Got a soft commit we weren't expecting",
-               monitor.soft.poll(0, MILLISECONDS));
+        monitor.soft.poll(0, MILLISECONDS));
 
     monitor.assertSaneOffers();
+    
+    monitor.searcher.clear();
   }
 
   public void testSoftAndHardCommitMaxTimeRapidAdds() throws Exception {
@@ -302,32 +331,43 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
     softTracker.setDocsUpperBound(-1);
     hardTracker.setTimeUpperBound(hardCommitWaitMillis);
     hardTracker.setDocsUpperBound(-1);
+    // we don't want to overlap soft and hard opening searchers - this now blocks commits and we
+    // are looking for prompt timings
+    hardTracker.setOpenSearcher(false);
     
     // try to add 5 docs really fast
     long fast5start = System.nanoTime();
     for( int i=0;i<5; i++ ) {
       assertU(adoc("id", ""+500 + i, "subject", "five fast docs"));
     }
-    long fast5end = System.nanoTime() - TimeUnit.NANOSECONDS.convert(200, TimeUnit.MILLISECONDS); // minus a tad of slop
+    long fast5end = System.nanoTime() - TimeUnit.NANOSECONDS.convert(300, TimeUnit.MILLISECONDS); // minus a tad of slop
     long fast5time = 1 + TimeUnit.MILLISECONDS.convert(fast5end - fast5start, TimeUnit.NANOSECONDS);
 
     // total time for all 5 adds determines the number of soft to expect
     long expectedSoft = (long)Math.ceil((double) fast5time / softCommitWaitMillis);
     long expectedHard = (long)Math.ceil((double) fast5time / hardCommitWaitMillis);
+    
+    expectedSoft = Math.max(1, expectedSoft);
+    expectedHard = Math.max(1, expectedHard);
 
     // note: counting from 1 for multiplication
     for (int i = 1; i <= expectedSoft; i++) {
-      // Wait for the soft commit with some fudge
+      // Wait for the soft commit with plenty of fudge to survive nasty envs
       Long soft = monitor.soft.poll(softCommitWaitMillis * 2, MILLISECONDS);
-      assertNotNull(i + ": soft wasn't fast enough", soft);
-      monitor.assertSaneOffers();
-
-      // have to assume none of the docs were added until
-      // very end of the add window
-      long softMs = TimeUnit.MILLISECONDS.convert(soft - fast5end, TimeUnit.NANOSECONDS);
-      assertTrue(i + ": soft occurred too fast: " +
-              softMs + " < (" + softCommitWaitMillis + " * " + i + ")",
-          softMs >= (softCommitWaitMillis * i));
+      if (soft != null || i == 1) {
+        assertNotNull(i + ": soft wasn't fast enough", soft);
+        monitor.assertSaneOffers();
+
+        // have to assume none of the docs were added until
+        // very end of the add window
+        long softMs = TimeUnit.MILLISECONDS.convert(soft - fast5end, TimeUnit.NANOSECONDS);
+        assertTrue(i + ": soft occurred too fast: " +
+            softMs + " < (" + softCommitWaitMillis + " * " + i + ")",
+            softMs >= (softCommitWaitMillis * i));
+      } else {
+        // we may have guessed wrong and there were fewer commits
+        assertNull("Got a soft commit we weren't expecting", monitor.soft.poll(2000, MILLISECONDS));
+      }
     }
 
     // note: counting from 1 for multiplication
@@ -345,7 +385,24 @@ public class SoftAutoCommitTest extends AbstractSolrTestCase {
               hardMs + " < (" + hardCommitWaitMillis + " * " + i + ")",
           hardMs >= (hardCommitWaitMillis * i));
     }
+    
+    // we are only guessing how many commits we may see, allow one extra of each
+    monitor.soft.poll(softCommitWaitMillis + 200, MILLISECONDS);
+    monitor.hard.poll(hardCommitWaitMillis + 200, MILLISECONDS);
  
+    // clear commits
+    monitor.hard.clear();
+    monitor.soft.clear();
+
+    // wait a bit, w/o other action we shouldn't see any
+    // new hard/soft commits
+    assertNull("Got a hard commit we weren't expecting",
+        monitor.hard.poll(1000, MILLISECONDS));
+    assertNull("Got a soft commit we weren't expecting",
+        monitor.soft.poll(0, MILLISECONDS));
+
+    monitor.assertSaneOffers();
+    
   }
 }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
index 4f5ea69..850ce03 100644
--- a/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/update/SolrIndexMetricsTest.java
@@ -63,7 +63,7 @@ public class SolrIndexMetricsTest extends SolrTestCaseJ4 {
 
     assertTrue(metrics.entrySet().stream().filter(e -> e.getKey().startsWith("INDEX")).count() >= 12);
     // this is variable, depending on the codec and the number of created files
-    assertTrue(metrics.entrySet().stream().filter(e -> e.getKey().startsWith("DIRECTORY")).count() > 50);
+    assertTrue(metrics.entrySet().stream().filter(e -> e.getKey().startsWith("DIRECTORY")).count() > 20);
 
     // check basic index meters
     Timer timer = (Timer)metrics.get("INDEX.merge.minor");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java b/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java
index 74360e3..5386721 100644
--- a/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java
+++ b/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java
@@ -181,7 +181,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
   // The following should work: full update to doc 0, in-place update for doc 0, delete doc 0
   private void outOfOrderDBQsTest() throws Exception {
     
-    del("*:*");
+    clearIndex();
     commit();
     
     buildRandomIndex(0);
@@ -241,12 +241,12 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     }
 
     log.info("outOfOrderDeleteUpdatesIndividualReplicaTest: This test passed fine...");
-    del("*:*");
+    clearIndex();
     commit();
   }
 
   private void docValuesUpdateTest() throws Exception {
-    del("*:*");
+    clearIndex();
     commit();
 
     // number of docs we're testing (0 <= id), index may contain additional random docs (id < 0)
@@ -397,7 +397,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
   
   
   private void ensureRtgWorksWithPartialUpdatesTest() throws Exception {
-    del("*:*");
+    clearIndex();
     commit();
 
     float inplace_updatable_float = 1;
@@ -496,7 +496,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
 
   private void outOfOrderUpdatesIndividualReplicaTest() throws Exception {
     
-    del("*:*");
+    clearIndex();
     commit();
 
     buildRandomIndex(0);
@@ -561,14 +561,14 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     }
 
     log.info("outOfOrderUpdatesIndividualReplicaTest: This test passed fine...");
-    del("*:*");
+    clearIndex();
     commit();
   }
   
   // The following should work: full update to doc 0, in-place update for doc 0, delete doc 0
   private void outOfOrderDeleteUpdatesIndividualReplicaTest() throws Exception {
     
-    del("*:*");
+    clearIndex();
     commit();
 
     buildRandomIndex(0);
@@ -627,7 +627,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     }
 
     log.info("outOfOrderDeleteUpdatesIndividualReplicaTest: This test passed fine...");
-    del("*:*");
+    clearIndex();
     commit();
   }
 
@@ -641,7 +641,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
         DV(id=x, val=5, ver=3)
    */
   private void reorderedDBQsWithInPlaceUpdatesShouldNotThrowReplicaInLIRTest() throws Exception {
-    del("*:*");
+    clearIndex();
     commit();
 
     buildRandomIndex(0);
@@ -728,12 +728,12 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     }
 
     log.info("reorderedDBQsWithInPlaceUpdatesShouldNotThrowReplicaInLIRTest: This test passed fine...");
-    del("*:*");
+    clearIndex();
     commit();
   }
   
   private void delayedReorderingFetchesMissingUpdateFromLeaderTest() throws Exception {
-    del("*:*");
+    clearIndex();
     commit();
     
     float inplace_updatable_float = 1F;
@@ -795,7 +795,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     // This is to ensure that the fetch missing update from leader doesn't bomb out if the 
     // document has been deleted on the leader later on
     {
-      del("*:*");
+      clearIndex();
       commit();
       shardToJetty.get(SHARD1).get(1).jetty.getDebugFilter().unsetDelay();
       
@@ -1038,7 +1038,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
    * dbq("inp:14",version=4)
    */
   private void testDBQUsingUpdatedFieldFromDroppedUpdate() throws Exception {
-    del("*:*");
+    clearIndex();
     commit();
     
     float inplace_updatable_float = 1F;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/util/JsonValidatorTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/util/JsonValidatorTest.java b/solr/core/src/test/org/apache/solr/util/JsonValidatorTest.java
new file mode 100644
index 0000000..4492586
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/util/JsonValidatorTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+package org.apache.solr.util;
+
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.common.util.Utils;
+
+import static org.apache.solr.common.util.ValidatingJsonMap.NOT_NULL;
+import static org.apache.solr.common.util.Utils.toJSONString;
+
+public class JsonValidatorTest extends SolrTestCaseJ4 {
+
+  public void testSchema() {
+    checkSchema("collections.Commands");
+    checkSchema("collections.collection.Commands");
+    checkSchema("collections.collection.shards.Commands");
+    checkSchema("collections.collection.shards.shard.Commands");
+    checkSchema("cores.Commands");
+    checkSchema("cores.core.Commands");
+    checkSchema("node.Commands");
+    checkSchema("cluster.security.BasicAuth.Commands");
+    checkSchema("cluster.security.RuleBasedAuthorization");
+    checkSchema("core.config.Commands");
+    checkSchema("core.SchemaEdit");
+    checkSchema("cluster.configs.Commands");
+  }
+
+
+  public void testSchemaValidation() {
+    ValidatingJsonMap spec = ApiBag.getSpec("collections.Commands").getSpec();
+    Map createSchema = spec.getMap("commands", NOT_NULL).getMap("create-alias", NOT_NULL);
+    JsonSchemaValidator validator = new JsonSchemaValidator(createSchema);
+    List<String> errs = validator.validateJson(Utils.fromJSONString("{name : x, collections: [ c1 , c2]}"));
+    assertNull(toJSONString(errs), errs);
+    errs = validator.validateJson(Utils.fromJSONString("{name : x, collections: c1 }"));
+    assertNull(toJSONString(errs), errs);
+    errs = validator.validateJson(Utils.fromJSONString("{name : x, x:y, collections: [ c1 , c2]}"));
+    assertNotNull(toJSONString(errs), errs);
+    assertTrue(toJSONString(errs), errs.get(0).contains("Unknown"));
+    errs = validator.validateJson(Utils.fromJSONString("{name : 123, collections: c1 }"));
+    assertNotNull(toJSONString(errs), errs);
+    assertTrue(toJSONString(errs), errs.get(0).contains("Expected type"));
+    errs = validator.validateJson(Utils.fromJSONString("{x:y, collections: [ c1 , c2]}"));
+    assertEquals(toJSONString(errs), 2, errs.size());
+    assertTrue(toJSONString(errs), StrUtils.join(errs, '|').contains("Missing field"));
+    assertTrue(toJSONString(errs), StrUtils.join(errs, '|').contains("Unknown"));
+    errs = validator.validateJson(Utils.fromJSONString("{name : x, collections: [ 1 , 2]}"));
+    assertFalse(toJSONString(errs), errs.isEmpty());
+    assertTrue(toJSONString(errs), errs.get(0).contains("Expected elements of type"));
+    validator = new JsonSchemaValidator("{" +
+        "  type:object," +
+        "  properties: {" +
+        "   age : {type: number}," +
+        "   adult : {type: boolean}," +
+        "   name: {type: string}}}");
+    errs = validator.validateJson(Utils.fromJSONString("{name:x, age:21, adult:true}"));
+    assertNull(errs);
+    errs = validator.validateJson(Utils.fromJSONString("{name:x, age:'21', adult:'true'}"));
+    assertNull(errs);
+
+    errs = validator.validateJson(Utils.fromJSONString("{name:x, age:'x21', adult:'true'}"));
+    assertEquals(1, errs.size());
+    try {
+      validator = new JsonSchemaValidator("{" +
+          "  type:object," +
+          "  properties: {" +
+          "   age : {type: int}," +
+          "   adult : {type: Boolean}," +
+          "   name: {type: string}}}");
+      fail("should have failed");
+    } catch (Exception e) {
+      assertTrue(e.getMessage().contains("Unknown type"));
+    }
+
+    try {
+      new JsonSchemaValidator("{" +
+          "  type:object," +
+          "   x : y," +
+          "  properties: {" +
+          "   age : {type: number}," +
+          "   adult : {type: boolean}," +
+          "   name: {type: string}}}");
+      fail("should have failed");
+    } catch (Exception e) {
+      assertTrue(e.getMessage().contains("Unknown key"));
+    }
+    try {
+      new JsonSchemaValidator("{" +
+          "  type:object," +
+          "  propertes: {" +
+          "   age : {type: number}," +
+          "   adult : {type: boolean}," +
+          "   name: {type: string}}}");
+      fail("should have failed");
+    } catch (Exception e) {
+      assertTrue(e.getMessage().contains("Unknown key : propertes"));
+    }
+
+    validator = new JsonSchemaValidator("{" +
+        "  type:object," +
+        "  properties: {" +
+        "   age : {type: number}," +
+        "   sex: {type: string, enum:[M, F]}," +
+        "   adult : {type: boolean}," +
+        "   name: {type: string}}}");
+    errs = validator.validateJson(Utils.fromJSONString("{name: 'Joe Average' , sex:M}"));
+    assertNull("errs are " + errs, errs);
+    errs = validator.validateJson(Utils.fromJSONString("{name: 'Joe Average' , sex:m}"));
+    assertEquals(1, errs.size());
+    assertTrue(errs.get(0).contains("value of enum"));
+    
+    String schema = "{\n" +
+        "  'type': 'object',\n" +
+        "  'properties': {\n" +
+        "    'links': {\n" +
+        "      'type': 'array',\n" +
+        "      'items':{" +
+        "          'type': 'object',\n" +
+        "          'properties': {\n" +
+        "            'rel': {\n" +
+        "              'type': 'string'\n" +
+        "            },\n" +
+        "            'href': {\n" +
+        "              'type': 'string'\n" +
+        "            }\n" +
+        "          }\n" +
+        "        }\n" +
+        "    }\n" +
+        "\n" +
+        "  }\n" +
+        "}";
+    validator = new JsonSchemaValidator(schema);
+    validator.validateJson(Utils.fromJSONString("{\n" +
+        "  'links': [\n" +
+        "    {\n" +
+        "        'rel': 'x',\n" +
+        "        'href': 'x'\n" +
+        "    },\n" +
+        "    {\n" +
+        "        'rel': 'x',\n" +
+        "        'href': 'x'\n" +
+        "    },\n" +
+        "    {\n" +
+        "        'rel': 'x',\n" +
+        "        'href': 'x'\n" +
+        "    }\n" +
+        "  ]\n" +
+        "}"));
+    
+
+
+
+  }
+
+  private void checkSchema(String name) {
+    ValidatingJsonMap spec = ApiBag.getSpec(name).getSpec();
+    Map commands = (Map) spec.get("commands");
+    for (Object o : commands.entrySet()) {
+      Map.Entry cmd = (Map.Entry) o;
+      try {
+        JsonSchemaValidator validator = new JsonSchemaValidator((Map) cmd.getValue());
+      } catch (Exception e) {
+        throw new RuntimeException("Error in command  " + cmd.getKey() + " in schema " + name, e);
+      }
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/util/TestObjectReleaseTracker.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/util/TestObjectReleaseTracker.java b/solr/core/src/test/org/apache/solr/util/TestObjectReleaseTracker.java
index f7e6943..e7a7637 100644
--- a/solr/core/src/test/org/apache/solr/util/TestObjectReleaseTracker.java
+++ b/solr/core/src/test/org/apache/solr/util/TestObjectReleaseTracker.java
@@ -18,6 +18,7 @@ package org.apache.solr.util;
 
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.TestRuleLimitSysouts.Limit;
+import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.util.ObjectReleaseTracker;
 import org.junit.Test;
 
@@ -29,12 +30,12 @@ public class TestObjectReleaseTracker extends LuceneTestCase {
   public void testObjectReleaseTracker() {
     ObjectReleaseTracker.track(new Object());
     ObjectReleaseTracker.release(new Object());
-    assertNotNull(ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty(1));
-    assertNull(ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty(1));
+    assertNotNull(SolrTestCaseJ4.clearObjectTrackerAndCheckEmpty(1));
+    assertNull(SolrTestCaseJ4.clearObjectTrackerAndCheckEmpty(1));
     Object obj = new Object();
     ObjectReleaseTracker.track(obj);
     ObjectReleaseTracker.release(obj);
-    assertNull(ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty(1));
+    assertNull(SolrTestCaseJ4.clearObjectTrackerAndCheckEmpty(1));
     
     Object obj1 = new Object();
     ObjectReleaseTracker.track(obj1);
@@ -46,7 +47,7 @@ public class TestObjectReleaseTracker extends LuceneTestCase {
     ObjectReleaseTracker.release(obj1);
     ObjectReleaseTracker.release(obj2);
     ObjectReleaseTracker.release(obj3);
-    assertNull(ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty(1));
+    assertNull(SolrTestCaseJ4.clearObjectTrackerAndCheckEmpty(1));
     
     ObjectReleaseTracker.track(obj1);
     ObjectReleaseTracker.track(obj2);
@@ -55,7 +56,7 @@ public class TestObjectReleaseTracker extends LuceneTestCase {
     ObjectReleaseTracker.release(obj1);
     ObjectReleaseTracker.release(obj2);
     // ObjectReleaseTracker.release(obj3);
-    assertNotNull(ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty(1));
-    assertNull(ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty(1));
+    assertNotNull(SolrTestCaseJ4.clearObjectTrackerAndCheckEmpty(1));
+    assertNull(SolrTestCaseJ4.clearObjectTrackerAndCheckEmpty(1));
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/example/example-DIH/build.xml
----------------------------------------------------------------------
diff --git a/solr/example/example-DIH/build.xml b/solr/example/example-DIH/build.xml
index fb82edf..77abc2d 100644
--- a/solr/example/example-DIH/build.xml
+++ b/solr/example/example-DIH/build.xml
@@ -24,6 +24,7 @@
 
   <!-- example tests are currently elsewhere -->
   <target name="test"/>
+  <target name="test-nocompile"/>
 
   <!-- this module has no javadocs -->
   <target name="javadocs"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/server/build.xml
----------------------------------------------------------------------
diff --git a/solr/server/build.xml b/solr/server/build.xml
index ae4e650..d3d7af6 100644
--- a/solr/server/build.xml
+++ b/solr/server/build.xml
@@ -22,6 +22,7 @@
 
   <!-- example tests are currently elsewhere -->
   <target name="test"/>
+  <target name="test-nocompile"/>
 
   <!-- this module has no javadocs -->
   <target name="javadocs"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml
----------------------------------------------------------------------
diff --git a/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml b/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml
index 69a1519..a9ddb25 100644
--- a/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml
+++ b/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml
@@ -850,7 +850,7 @@
     </requestHandler>
 
   <!-- A request handler that returns indented JSON by default -->
-  <requestHandler name="/query" class="solr.SearchHandler">
+  <requestHandler name="/query" class="solr.SearchHandler" registerPath="/,/v2">
      <lst name="defaults">
        <str name="echoParams">explicit</str>
        <str name="wt">json</str>
@@ -958,7 +958,7 @@
   </requestHandler>
 
 
-  <initParams path="/update/**,/query,/select,/tvrh,/elevate,/spell,/browse">
+  <initParams path="/update/**,/query,/select,/tvrh,/elevate,/spell,/browse,update">
     <lst name="defaults">
       <str name="df">text</str>
     </lst>
@@ -966,12 +966,12 @@
 
   <!-- The following are implicitly added
   <requestHandler name="/update/json" class="solr.UpdateRequestHandler">
-        <lst name="defaults">
+        <lst name="invariants">
          <str name="stream.contentType">application/json</str>
        </lst>
   </requestHandler>
   <requestHandler name="/update/csv" class="solr.UpdateRequestHandler">
-        <lst name="defaults">
+        <lst name="invariants">
          <str name="stream.contentType">application/csv</str>
        </lst>
   </requestHandler>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
index 3e31edf..4dbba5b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
@@ -21,10 +21,14 @@ import org.apache.solr.common.util.ContentStream;
 
 import java.io.IOException;
 import java.io.Serializable;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
+import static java.util.Collections.unmodifiableSet;
+
 /**
  * 
  *
@@ -35,9 +39,16 @@ public abstract class SolrRequest<T extends SolrResponse> implements Serializabl
   public enum METHOD {
     GET,
     POST,
-    PUT
+    PUT,
+    DELETE
   };
 
+  public static final Set<String> SUPPORTED_METHODS = unmodifiableSet(new HashSet<>(Arrays.<String>asList(
+      METHOD.GET.toString(),
+      METHOD.POST.toString(),
+      METHOD.PUT.toString(),
+      METHOD.DELETE.toString())));
+
   private METHOD method = METHOD.GET;
   private String path = null;
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
index 3b69484..d0263c8 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
@@ -23,6 +23,7 @@ import java.net.ConnectException;
 import java.net.SocketException;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -83,7 +84,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
 
-import static org.apache.solr.common.params.CommonParams.ADMIN_PATHS;
+import static org.apache.solr.common.params.CommonParams.AUTHC_PATH;
+import static org.apache.solr.common.params.CommonParams.AUTHZ_PATH;
+import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH;
+import static org.apache.solr.common.params.CommonParams.CONFIGSETS_HANDLER_PATH;
+import static org.apache.solr.common.params.CommonParams.CORES_HANDLER_PATH;
 
 /**
  * SolrJ client class to communicate with SolrCloud.
@@ -1036,6 +1041,15 @@ public class CloudSolrClient extends SolrClient {
       collection = (reqParams != null) ? reqParams.get("collection", getDefaultCollection()) : getDefaultCollection();
     return requestWithRetryOnStaleState(request, 0, collection);
   }
+  private static final Set<String> ADMIN_PATHS = new HashSet<>(Arrays.asList(
+      CORES_HANDLER_PATH,
+      COLLECTIONS_HANDLER_PATH,
+      CONFIGSETS_HANDLER_PATH,
+      AUTHC_PATH,
+      AUTHZ_PATH,
+      "/v2/cluster/security/authentication",
+      "/v2/cluster/security/authorization"
+      ));
 
   /**
    * As this class doesn't watch external collections on the client side,

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientUtil.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientUtil.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientUtil.java
index decd5e8..7ee90e1 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientUtil.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClientUtil.java
@@ -22,6 +22,7 @@ import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.zip.GZIPInputStream;
@@ -111,28 +112,46 @@ public class HttpClientUtil {
   // cannot be established within x ms. with a
   // java.net.SocketTimeoutException: Connection timed out
   public static final String PROP_CONNECTION_TIMEOUT = "connTimeout";
-  
+
+  /**
+   * A Java system property to select the {@linkplain HttpClientBuilderFactory} used for
+   * configuring the {@linkplain HttpClientBuilder} instance by default.
+   */
+  public static final String SYS_PROP_HTTP_CLIENT_BUILDER_FACTORY = "solr.httpclient.builder.factory";
+
   static final DefaultHttpRequestRetryHandler NO_RETRY = new DefaultHttpRequestRetryHandler(
       0, false);
 
   private static volatile SolrHttpClientBuilder httpClientBuilder;
-  
+
   private static SolrHttpClientContextBuilder httpClientRequestContextBuilder = new SolrHttpClientContextBuilder();
-  
+
+  private static volatile SchemaRegistryProvider schemaRegistryProvider;
+  private static volatile String cookiePolicy;
+  private static final List<HttpRequestInterceptor> interceptors = Collections.synchronizedList(new ArrayList<HttpRequestInterceptor>());
+
+
   static {
     resetHttpClientBuilder();
+
+    // Configure the HttpClientBuilder if user has specified the factory type.
+    String factoryClassName = System.getProperty(SYS_PROP_HTTP_CLIENT_BUILDER_FACTORY);
+    if (factoryClassName != null) {
+      logger.debug ("Using " + factoryClassName);
+      try {
+        HttpClientBuilderFactory factory = (HttpClientBuilderFactory)Class.forName(factoryClassName).newInstance();
+        httpClientBuilder = factory.getHttpClientBuilder(Optional.of(SolrHttpClientBuilder.create()));
+      } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+        throw new RuntimeException("Unable to instantiate Solr HttpClientBuilderFactory", e);
+      }
+    }
   }
 
   public static abstract class SchemaRegistryProvider {
     /** Must be non-null */
     public abstract Registry<ConnectionSocketFactory> getSchemaRegistry();
   }
-  
-  private static volatile SchemaRegistryProvider schemaRegistryProvider;
-  private static volatile String cookiePolicy;
 
-  private static final List<HttpRequestInterceptor> interceptors = Collections.synchronizedList(new ArrayList<HttpRequestInterceptor>());
-  
   private static class DynamicInterceptor implements HttpRequestInterceptor {
 
     @Override
@@ -151,7 +170,7 @@ public class HttpClientUtil {
 
     }
   }
-  
+
   public static void setHttpClientBuilder(SolrHttpClientBuilder newHttpClientBuilder) {
     httpClientBuilder = newHttpClientBuilder;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
index 7f3cf29..1bcf96b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
@@ -138,8 +138,11 @@ public class Krb5HttpClientBuilder implements HttpClientBuilderFactory {
         });
         HttpClientUtil.addRequestInterceptor(bufferedEntityInterceptor);
       }
+    } else {
+      logger.warn("{} is configured without specifying system property '{}'",
+          getClass().getName(), LOGIN_CONFIG_PROP);
     }
-    
+
     return builder;
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveAuth.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveAuth.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveAuth.java
new file mode 100644
index 0000000..3334d9f
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveAuth.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.impl;
+
+import java.io.IOException;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.AuthState;
+import org.apache.http.auth.Credentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * This HTTP request interceptor adds HTTP authentication credentials to every outgoing
+ * request. This implementation is required since Solr client is not capable of performing
+ * non preemptive authentication. By adding the Http authentication credentials to every request,
+ * this interceptor enables "preemptive" authentication.
+ */
+public class PreemptiveAuth implements HttpRequestInterceptor {
+  private AuthScheme authScheme = null;
+
+  public PreemptiveAuth(AuthScheme authScheme) {
+    this.authScheme = authScheme;
+  }
+
+  @Override
+  public void process(final HttpRequest request, final HttpContext context) throws HttpException,
+      IOException {
+
+    AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
+    // If no auth scheme available yet, try to initialize it preemptively
+    if (authState.getAuthScheme() == null) {
+      CredentialsProvider credsProvider = (CredentialsProvider) context
+          .getAttribute(ClientContext.CREDS_PROVIDER);
+      Credentials creds = credsProvider.getCredentials(AuthScope.ANY);
+      authState.update(authScheme, creds);
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
new file mode 100644
index 0000000..76ce990
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.impl;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Optional;
+import java.util.Properties;
+
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder.CredentialsProviderProvider;
+import org.apache.solr.common.params.MapSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.StrUtils;
+
+/**
+ * HttpClientConfigurer implementation providing support for preemptive Http Basic authentication
+ * scheme.
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class PreemptiveBasicAuthClientBuilderFactory implements HttpClientBuilderFactory {
+  /**
+   * A system property used to specify a properties file containing default parameters used for
+   * creating a HTTP client. This is specifically useful for configuring the HTTP basic auth
+   * credentials (i.e. username/password). The name of the property must match the relevant
+   * Solr config property name.
+   */
+  public static final String SYS_PROP_HTTP_CLIENT_CONFIG = "solr.httpclient.config";
+
+  /**
+   * A system property to configure the Basic auth credentials via a java system property.
+   * Since this will expose the password on the command-line, it is not very secure. But
+   * this mechanism is added for backwards compatibility.
+   */
+  public static final String SYS_PROP_BASIC_AUTH_CREDENTIALS = "basicauth";
+
+  private static SolrParams defaultParams;
+  private static PreemptiveAuth requestInterceptor = new PreemptiveAuth(new BasicScheme());
+
+  static {
+    String credentials = System.getProperty(SYS_PROP_BASIC_AUTH_CREDENTIALS);
+    String configFile = System.getProperty(SYS_PROP_HTTP_CLIENT_CONFIG);
+
+    if (credentials != null && configFile != null) {
+      throw new RuntimeException("Basic authentication credentials passed via a configuration file"
+          + " as well as java system property. Please choose one mechanism!");
+    }
+
+    if (credentials != null) {
+      List<String> ss = StrUtils.splitSmart(credentials, ':');
+      if (ss.size() != 2) {
+        throw new RuntimeException("Please provide 'basicauth' in the 'user:password' format");
+      }
+      Properties defaultProps = new Properties();
+      defaultProps.setProperty(HttpClientUtil.PROP_BASIC_AUTH_USER, ss.get(0));
+      defaultProps.setProperty(HttpClientUtil.PROP_BASIC_AUTH_PASS, ss.get(1));
+      defaultParams = new MapSolrParams(new HashMap(defaultProps));
+    }
+
+    if(configFile != null) {
+      try {
+        Properties defaultProps = new Properties();
+        defaultProps.load(new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8));
+        defaultParams = new MapSolrParams(new HashMap(defaultProps));
+      } catch (IOException e) {
+        throw new IllegalArgumentException("Unable to read the Http client config file", e);
+      }
+    }
+  }
+
+  /**
+   * This method enables configuring system wide defaults (apart from using a config file based approach).
+   */
+  public static void setDefaultSolrParams(SolrParams params) {
+    defaultParams = params;
+  }
+
+  @Override
+  public void close() throws IOException {
+    HttpClientUtil.removeRequestInterceptor(requestInterceptor);
+  }
+
+  @Override
+  public SolrHttpClientBuilder getHttpClientBuilder(Optional<SolrHttpClientBuilder> builder) {
+    return builder.isPresent() ?
+        initHttpClientBuilder(builder.get())
+        : initHttpClientBuilder(SolrHttpClientBuilder.create());
+  }
+
+  private SolrHttpClientBuilder initHttpClientBuilder(SolrHttpClientBuilder builder) {
+    final String basicAuthUser = defaultParams.get(HttpClientUtil.PROP_BASIC_AUTH_USER);
+    final String basicAuthPass = defaultParams.get(HttpClientUtil.PROP_BASIC_AUTH_PASS);
+    if(basicAuthUser == null || basicAuthPass == null) {
+      throw new IllegalArgumentException("username & password must be specified with " + getClass().getName());
+    }
+
+    builder.setDefaultCredentialsProvider(new CredentialsProviderProvider() {
+      @Override
+      public CredentialsProvider getCredentialsProvider() {
+        CredentialsProvider credsProvider = new BasicCredentialsProvider();
+        credsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(basicAuthUser, basicAuthPass));
+        return credsProvider;
+      }
+    });
+
+    HttpClientUtil.addRequestInterceptor(requestInterceptor);
+    return builder;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AbsoluteValueEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AbsoluteValueEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AbsoluteValueEvaluator.java
new file mode 100644
index 0000000..38b3bb5
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AbsoluteValueEvaluator.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class AbsoluteValueEvaluator extends NumberEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public AbsoluteValueEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(1 != subEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Number evaluate(Tuple tuple) throws IOException {
+    
+    List<BigDecimal> results = evaluateAll(tuple);
+    
+    // we're still doing these checks because if we ever add an array-flatten evaluator, 
+    // one found in the constructor could become != 1
+    if(1 != results.size()){
+      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
+    }
+    
+    if(null == results.get(0)){
+      return null;
+    }
+    
+    return normalizeType(results.get(0).abs());
+  }  
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddEvaluator.java
new file mode 100644
index 0000000..317741e
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddEvaluator.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class AddEvaluator extends NumberEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public AddEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Number evaluate(Tuple tuple) throws IOException {
+    
+    List<BigDecimal> results = evaluateAll(tuple);
+    
+    if(results.stream().anyMatch(item -> null == item)){
+      return null;
+    }
+    
+    BigDecimal result = null;
+    if(results.size() > 0){
+      result = results.get(0);
+      for(int idx = 1; idx < results.size(); ++idx){
+        result = result.add(results.get(idx));
+      }
+    }
+    
+    return normalizeType(result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AndEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AndEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AndEvaluator.java
new file mode 100644
index 0000000..290bd98
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AndEvaluator.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class AndEvaluator extends BooleanEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public AndEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Boolean evaluate(Tuple tuple) throws IOException {
+    
+    List<Object> results = evaluateAll(tuple);
+    
+    if(results.size() < 2){
+      String message = null;
+      if(1 == results.size()){
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
+      }
+      else{
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
+      }
+      throw new IOException(message);
+    }
+    
+    Checker checker = constructChecker(results.get(0));
+    if(results.stream().anyMatch(result -> null == result && !checker.isNullAllowed())){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+
+    for(int idx = 1; idx < results.size(); ++idx){
+      if(!checker.test(results.get(0), results.get(idx))){
+        return false;
+      }
+    }
+    
+    return true;
+  }
+  
+  private Checker constructChecker(Object fromValue) throws IOException{
+    if(null == fromValue){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    else if(fromValue instanceof Boolean){
+      return new BooleanChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return (boolean)left && (boolean)right;
+        }
+      };
+    }
+    
+    throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/BooleanEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/BooleanEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/BooleanEvaluator.java
new file mode 100644
index 0000000..bf21f1d
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/BooleanEvaluator.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class BooleanEvaluator extends ComplexEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public BooleanEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+  
+  // restrict result to a Boolean
+  public abstract Boolean evaluate(Tuple tuple) throws IOException;
+  
+  public List<Object> evaluateAll(final Tuple tuple) throws IOException {
+    List<Object> results = new ArrayList<Object>();
+    for(StreamEvaluator subEvaluator : subEvaluators){
+      results.add(subEvaluator.evaluate(tuple));
+    }
+    
+    return results;
+  }
+  
+  public interface Checker {
+    default boolean isNullAllowed(){
+      return false;
+    }
+    boolean isCorrectType(Object value);
+    boolean test(Object left, Object right);
+  }
+  
+  public interface NullChecker extends Checker {
+    default boolean isNullAllowed(){
+      return true;
+    }
+    default boolean isCorrectType(Object value){
+      return true;
+    }
+    default boolean test(Object left, Object right){
+      return null == left && null == right;
+    }
+  }
+  
+  public interface BooleanChecker extends Checker {
+    default boolean isCorrectType(Object value){
+      return value instanceof Boolean;
+    }
+  }
+  
+  public interface NumberChecker extends Checker {
+    default boolean isCorrectType(Object value){
+      return value instanceof Number;
+    }
+  }
+  
+  public interface StringChecker extends Checker {
+    default boolean isCorrectType(Object value){
+      return value instanceof String;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ComplexEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ComplexEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ComplexEvaluator.java
new file mode 100644
index 0000000..1e56d12
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ComplexEvaluator.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+import org.apache.solr.client.solrj.io.stream.expr.Explanation;
+import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class ComplexEvaluator implements StreamEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  protected UUID nodeId = UUID.randomUUID();
+  
+  protected StreamFactory constructingFactory;
+  protected List<StreamEvaluator> subEvaluators = new ArrayList<StreamEvaluator>();
+  
+  public ComplexEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    constructingFactory = factory;
+    
+    // We have to do this because order of the parameters matter
+    List<StreamExpressionParameter> parameters = factory.getOperandsOfType(expression, StreamExpressionParameter.class);
+    
+    for(StreamExpressionParameter parameter : parameters){
+      if(parameter instanceof StreamExpression){
+        // possible evaluator
+        StreamExpression streamExpression = (StreamExpression)parameter;
+        if(factory.doesRepresentTypes(streamExpression, ComplexEvaluator.class)){
+          subEvaluators.add(factory.constructEvaluator(streamExpression));
+        }
+        else if(factory.doesRepresentTypes(streamExpression, SimpleEvaluator.class)){
+          subEvaluators.add(factory.constructEvaluator(streamExpression));
+        }
+        else{
+          // Will be treated as a field name
+          subEvaluators.add(new FieldEvaluator(streamExpression.toString()));
+        }
+      }
+      else if(parameter instanceof StreamExpressionValue){
+        if(0 != ((StreamExpressionValue)parameter).getValue().length()){
+          // special case - if evaluates to a number, boolean, or null then we'll treat it 
+          // as a RawValueEvaluator
+          Object value = factory.constructPrimitiveObject(((StreamExpressionValue)parameter).getValue());
+          if(null == value || value instanceof Boolean || value instanceof Number){
+            subEvaluators.add(new RawValueEvaluator(value));
+          }
+          else if(value instanceof String){
+            subEvaluators.add(new FieldEvaluator((String)value));
+          }
+        }
+      }
+    }
+    
+    if(expression.getParameters().size() != subEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - unknown operands found - expecting only StreamEvaluators or field names", expression));
+    }
+  }
+
+  @Override
+  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
+    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
+    
+    for(StreamEvaluator evaluator : subEvaluators){
+      expression.addParameter(evaluator.toExpression(factory));
+    }
+    return expression;
+  }
+
+  @Override
+  public Explanation toExplanation(StreamFactory factory) throws IOException {
+    return new Explanation(nodeId.toString())
+      .withExpressionType(ExpressionType.EVALUATOR)
+      .withFunctionName(factory.getFunctionName(getClass()))
+      .withImplementingClass(getClass().getName())
+      .withExpression(toExpression(factory).toString());
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConditionalEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConditionalEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConditionalEvaluator.java
new file mode 100644
index 0000000..499e2f8
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConditionalEvaluator.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class ConditionalEvaluator extends ComplexEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public ConditionalEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+  
+  public List<Object> evaluateAll(final Tuple tuple) throws IOException {
+    List<Object> results = new ArrayList<Object>();
+    for(StreamEvaluator subEvaluator : subEvaluators){
+      results.add(subEvaluator.evaluate(tuple));
+    }
+    
+    return results;
+  }
+  
+  public interface Checker {
+    default boolean isNullAllowed(){
+      return false;
+    }
+    boolean isCorrectType(Object value);
+    boolean test(Object left, Object right);
+  }
+    
+  public interface BooleanChecker extends Checker {
+    default boolean isCorrectType(Object value){
+      return value instanceof Boolean;
+    }
+  }  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DivideEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DivideEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DivideEvaluator.java
new file mode 100644
index 0000000..f21a7f3
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DivideEvaluator.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class DivideEvaluator extends NumberEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public DivideEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(2 != subEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Number evaluate(Tuple tuple) throws IOException {
+    
+    List<BigDecimal> results = evaluateAll(tuple);
+    
+    // we're still doing these checks because if we ever add an array-flatten evaluator, 
+    // two found in the constructor could become != 2
+    if(2 != results.size()){
+      String message = null;
+      if(1 == results.size()){
+        message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but 1 was provided", constructingFactory.getFunctionName(getClass())); 
+      }
+      else{
+        message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but %d were provided", constructingFactory.getFunctionName(getClass()), results.size());
+      }
+      throw new IOException(message);
+    }
+    
+    BigDecimal numerator = results.get(0);
+    BigDecimal denominator = results.get(1);
+    
+    if(null == numerator){
+      throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a null numerator", constructingFactory.getFunctionName(getClass())));
+    }
+    
+    if(null == denominator){
+      throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a null denominator", constructingFactory.getFunctionName(getClass())));
+    }
+    
+    if(0 == denominator.compareTo(BigDecimal.ZERO)){
+      throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a 0 denominator", constructingFactory.getFunctionName(getClass())));
+    }
+    
+    return normalizeType(numerator.divide(denominator, MathContext.DECIMAL64));
+  }
+}


[07/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.config.Params.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.Params.Commands.json b/solr/core/src/resources/apispec/core.config.Params.Commands.json
new file mode 100644
index 0000000..474f1a6
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.Params.Commands.json
@@ -0,0 +1,31 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Request+Parameters+API",
+  "description": "Create, update and delete request parameter sets (paramsets) to override or replace parameters defined in solrconfig.xml. Parameter sets are used with request handlers by setting the useParams attribute to the paramset name in the definition of the request handler or with individual requests to Solr. Parameter sets defined with this API are stored in a file params.json in ZooKeeper or on the filesystem when not using SolrCloud. Note this API does not directly update solrconfig.xml. ",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/config/params"
+    ]
+  },
+  "commands": {
+    "set:": {
+      "type":"object",
+      "description":"Add or overwrite one or more paramsets. Each paramset definition includes a paramset name, followed by key-value pairs of the parameter and value to be set.",
+      "additionalProperties": true
+    },
+    "unset": {
+      "type":"array",
+      "description": "Delete one or more paramsets.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "update": {
+      "type":"object",
+      "description": "Update one or more paramsets. This command will attempt to merge an existing paramset with the new values. Each paramset definition includes a paramset name, followed by key-value pairs of the parameters and values to be updated.",
+      "additionalProperties": true
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.config.Params.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.Params.json b/solr/core/src/resources/apispec/core.config.Params.json
new file mode 100644
index 0000000..cff0350
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.Params.json
@@ -0,0 +1,13 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Request+Parameters+API",
+  "description": "List all parameter sets (paramsets). Individual paramsets can be requested by paramset name.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/config/params",
+      "/config/params/{params_set}"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.config.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.json b/solr/core/src/resources/apispec/core.config.json
new file mode 100644
index 0000000..2633fd9
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.json
@@ -0,0 +1,18 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+API",
+  "description": "Gets the Solr configuration for a collection.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/config",
+      "/config/overlay",
+      "/config/query",
+      "/config/jmx",
+      "/config/requestDispatcher",
+      "/config/znodeVersion",
+      "/config/{plugin}"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.system.blob.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.system.blob.json b/solr/core/src/resources/apispec/core.system.blob.json
new file mode 100644
index 0000000..96fedcf
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.system.blob.json
@@ -0,0 +1,20 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Blob+Store+API",
+  "description": "Lists blobs in the blob store (the .system collection). The list can be limited by name or name and version.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/blob",
+      "/blob/{name}",
+      "/blob/{name}/{version}"
+    ],
+    "params": {
+      "wt": {
+        "type":"string",
+        "description": "Use the value 'filestream' to get the file content. Use other response writers (such as xml, or json) to fetch only the metadata."
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.system.blob.upload.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.system.blob.upload.json b/solr/core/src/resources/apispec/core.system.blob.upload.json
new file mode 100644
index 0000000..854e544
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.system.blob.upload.json
@@ -0,0 +1,12 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Blob+Store+API",
+  "description": "Uploads a blob to the blob store. Note that the blob store is a specially named collection (which must be '.system') which must be created before uploading a blob to it.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/blob/{name}"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cores.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cores.Commands.json b/solr/core/src/resources/apispec/cores.Commands.json
new file mode 100644
index 0000000..d6419cf
--- /dev/null
+++ b/solr/core/src/resources/apispec/cores.Commands.json
@@ -0,0 +1,85 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API",
+  "description": "Actions on non-specific cores. See the /cores/{core} endpoint for actions on specific, named, cores.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/cores"
+    ]
+  },
+  "commands": {
+    "create": {
+      "type" : "object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-CREATE",
+      "description": "Creates a new core. If you are using SolrCloud, you should use the Collections API instead. While a core for a SolrCloud cluster can be created with this API, it is considered an expert-level action. The Collections API has commands for creating new shards and replicas that ensure the safety of those actions within your cluster.",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The core name to create. If a core with this name already exists, an error will be returned."
+        },
+        "instanceDir": {
+          "type": "string",
+          "description": "The core instance directory, where files for this core should be stored. While this parameter is not required, if it is not defined it will default to a path relative to Solr Home that includes the name you've given the new core. This location MUST EXIST prior to creating the core, and it must include a conf directory that includes solrconfig.xml and your schema, either as a schema.xml file or using the managed schema feature."
+        },
+        "schema": {
+          "type": "string",
+          "description": "Name of the schema file to use for the core. Please note that if you are using a 'managed schema' (Solr's default behavior) then any value for this property that does not match the effective managedSchemaResourceName will be read once, backed up, and converted for managed schema use. If you are using the default name (schema.xml or the managed schema name), you do not need to define the schema file name."
+        },
+        "dataDir": {
+          "type": "string",
+          "description": "Name of the data directory relative to instanceDir. This is where the index files will be stored."
+        },
+        "config": {
+          "type": "string",
+          "description": "Name of the config file (i.e., solrconfig.xml) relative to instanceDir. If you are using the default name (solrconfig.xml), you do not need to define the config file name."
+        },
+        "configSet": {
+          "type": "string",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+Sets",
+          "description": "The name of a config set to use. The config set must already exist. The solr.xml file defines the location of the configset base directory, and configuration files can be shared between cores by defining sub-directories. The files in the named configSet will be used for the schema and config properties instead of defining them explicitly."
+        },
+        "loadOnStartup": {
+          "type": "boolean",
+          "description": "If true, the core will be loaded on startup. Set to false to enable lazy loading, where the core will only be loaded if it is referenced or called.",
+          "default": "true"
+        },
+        "transient": {
+          "type": "boolean",
+          "description": "Allows Solr to unload the core if resources are required.",
+          "default": "false"
+        },
+        "shard": {
+          "type": "string",
+          "description": "In SolrCloud mode, the shard this core should belong to."
+        },
+        "collection": {
+          "type": "string",
+          "description": "The name of the collection this core belongs to."
+        },
+        "props": {
+          "type": "object",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Defining+core.properties",
+          "description": "Allows adding core.properties for the collection.",
+          "additionalProperties": true
+        },
+        "coreNodeName": {
+          "type": "string",
+          "description": "The replica name."
+        },
+        "numShards": {
+          "type":"number",
+          "description":"The number of shards to create for this core."
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      },
+      "required": [
+        "name"
+      ]
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cores.Status.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cores.Status.json b/solr/core/src/resources/apispec/cores.Status.json
new file mode 100644
index 0000000..155bcf5
--- /dev/null
+++ b/solr/core/src/resources/apispec/cores.Status.json
@@ -0,0 +1,20 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-STATUS",
+  "description": "Provides status and other information about the status of each core. Individual cores can be requested by core name.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/cores",
+      "/cores/{core}"
+    ],
+    "params": {
+      "indexInfo": {
+        "type": "boolean",
+        "description": "If true, index information will be returned, such as information about number of documents, deletions, segments, etc. In a large cluster with more than hundreds of cores, this can take a long time to retrieve. If you have a large cluster, consider setting this to false.",
+        "default": true
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cores.core.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cores.core.Commands.json b/solr/core/src/resources/apispec/cores.core.Commands.json
new file mode 100644
index 0000000..5049a3a
--- /dev/null
+++ b/solr/core/src/resources/apispec/cores.core.Commands.json
@@ -0,0 +1,136 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API",
+  "description": "Actions that are peformed on individual cores, such as reloading, swapping cores, renaming, and others.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/cores/{core}"
+    ]
+  },
+  "commands": {
+    "reload": {
+      "type":"object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-RELOAD",
+      "description": "Reloads a core. This is useful when you have made changes on disk such as editing the schema or solrconfig.xml files. Most APIs reload cores automatically, so this should not be necessary if changes were made with those APIs."
+    },
+    "swap": {
+      "type":"object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-SWAP",
+      "description": "Swaps the names of two existing Solr cores. This can be used to swap new content into production. The former core can be swapped back if necessary. Using this API is not supported in SolrCloud mode.",
+      "properties": {
+        "with": {
+          "type": "string",
+          "description": "The name of the other core to be swapped (the first core name is included in the request)."
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      },
+      "required": [
+        "with"
+      ]
+    },
+    "rename": {
+      "type": "object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-RENAME",
+      "description": "Change the name of a core.",
+      "properties": {
+        "to": {
+          "type": "string",
+          "description": "The new name for the core."
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      },
+      "required": [
+        "to"
+      ]
+    },
+    "unload": {
+      "type": "object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-UNLOAD",
+      "description": "Removes a core. Active requests would continue to be processed, but new requests will not be sent to the new core. If a core is registered under more than one name, only the name given in the request is removed.",
+      "properties": {
+        "deleteIndex": {
+          "type": "boolean",
+          "description": "If true, the index will be removed while unloading the core.",
+          "default": "false"
+        },
+        "deleteDataDir": {
+          "type": "boolean",
+          "description": "If true, the data directory and all sub-directories will be removed while unloading the core.",
+          "default": "false"
+        },
+        "deleteInstanceDir": {
+          "type": "boolean",
+          "description": "If true, everything related to the core, including the index, data, and conf directories, will be removed while unloading the core.",
+          "default": "false"
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      }
+    },
+    "merge-indexes": {
+      "type":"object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-MERGEINDEXES",
+      "description":"Merges one or more indexes to another index. The indexes must have completed commits, and should be locked against writes until the merge is complete to avoid index corruption. The target core (which is the core that should be used as the endpoint for this command) must exist before using this command. A commit should also be performed on this core after the merge is complete.",
+      "properties": {
+        "indexDir": {
+          "type": "array",
+          "description": "A comma-separated list index directories for each source core that will be merged with the target core.",
+          "items": {
+            "type": "string"
+          }
+        },
+        "srcCore": {
+          "type": "array",
+          "description": "A comma-separated list of the names of each source core to be merged with the target core.",
+          "items": {
+            "type": "string"
+          }
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      }
+    },
+    "split":  { "#include": "cores.core.Commands.split"},
+    "request-recovery": {
+      "type":"object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-REQUESTRECOVERY",
+      "description": "Manually asks a core to recover by synching with a leader. It may help SolrCloud clusters where a node refuses to come back up. However, it is considered an expert-level command, and should be used very carefully."
+    },
+    "force-prepare-for-leadership": {
+      "type": "object",
+      "description": "An internal API used by the Collections API to force leader election. This should not be used directly by end-users."
+    },
+    "prep-recovery": {
+      "type": "object",
+      "additionalProperties": true,
+      "description": "An internal API used by the Collections API. This should not be used directly by end-users."
+    },
+    "request-apply-updates": {
+      "type": "object",
+      "additionalProperties": true,
+      "description": "An internal API used by the Collections API. This should not be used directly by end-users."
+    },
+    "request-sync-shard": {
+      "type": "object",
+      "additionalProperties": true,
+      "description": "An internal API used by the Collections API. This should not be used directly by end-users."
+    },
+    "request-buffer-updates": {
+      "type": "object",
+      "additionalProperties": true,
+      "description": "An internal API used by the Collections API. This should not be used directly by end-users."
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cores.core.Commands.split.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cores.core.Commands.split.json b/solr/core/src/resources/apispec/cores.core.Commands.split.json
new file mode 100644
index 0000000..3aa0a91
--- /dev/null
+++ b/solr/core/src/resources/apispec/cores.core.Commands.split.json
@@ -0,0 +1,34 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-SPLIT",
+  "description": "Allows splitting an index into two or more new indexes.",
+  "type": "object",
+  "properties": {
+    "path": {
+      "type": "array",
+      "description": "Directory path(s) in which a piece of the index will be written. This allows splitting the index before creating the cores to contain them. Note if using this approach that the indexes will not be able to receive updates until a new core has been created to handle the incoming updates. If you have already created the new cores, you should define the targetCore property instead.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "targetCore": {
+      "type": "array",
+      "description": "The target Solr core(s) to which a piece of the index will be merged (if the target core already contains data). This requires that the cores have already been created. If the cores have not yet been created, use the path property instead.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "splitKey": {
+      "type":"string",
+      "description": "A route key to use for splitting the index. This parameter is optional, but should not be defined if the ranges parameter is also defined."
+    },
+    "ranges": {
+      "type": "string",
+      "description": "A comma-separated list of hexadecimal hash ranges that will be used to split the core. This parameter is optional, but should not be defined if the splitKey parameter is also defined."
+    },
+    "async": {
+      "type": "string",
+      "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined. This command can be long-running, so running it asynchronously is recommended."
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/emptySpec.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/emptySpec.json b/solr/core/src/resources/apispec/emptySpec.json
new file mode 100644
index 0000000..d95bff9
--- /dev/null
+++ b/solr/core/src/resources/apispec/emptySpec.json
@@ -0,0 +1,11 @@
+{
+  "methods": [
+    "GET",
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "$handlerName"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/node.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/node.Commands.json b/solr/core/src/resources/apispec/node.Commands.json
new file mode 100644
index 0000000..11b3c89
--- /dev/null
+++ b/solr/core/src/resources/apispec/node.Commands.json
@@ -0,0 +1,24 @@
+{
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/node"
+    ]
+  },
+  "commands": {
+    "overseer-op": {
+      "type": "object",
+      "additionalProperties": true
+    },
+    "rejoin-leader-election": {
+      "type": "object",
+      "additionalProperties": true
+    },
+    "invoke":{
+      "type": "object",
+      "additionalProperties": true
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/node.Info.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/node.Info.json b/solr/core/src/resources/apispec/node.Info.json
new file mode 100644
index 0000000..e7752e6
--- /dev/null
+++ b/solr/core/src/resources/apispec/node.Info.json
@@ -0,0 +1,11 @@
+{
+   "description": "Provides information about system properties, threads, logging settings, and system details for a node.",
+  "methods": ["GET"],
+  "url": {
+    "paths": [
+      "/node/properties",
+      "/node/threads",
+      "/node/logging",
+      "/node/system"]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/node.invoke.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/node.invoke.json b/solr/core/src/resources/apispec/node.invoke.json
new file mode 100644
index 0000000..c8a9f69
--- /dev/null
+++ b/solr/core/src/resources/apispec/node.invoke.json
@@ -0,0 +1,16 @@
+{
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/node/invoke"
+    ],
+    "params": {
+      "class": {
+        "type": "string",
+        "description": "Name of the class that must be invoked. "
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test-files/solr/collection1/conf/solrconfig-infixsuggesters.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-infixsuggesters.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-infixsuggesters.xml
new file mode 100644
index 0000000..8fc42ff
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-infixsuggesters.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" ?>
+<!--
+ 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.
+-->
+
+<config>
+  <xi:include href="solrconfig.snippet.randomindexconfig.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
+  <luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
+  <dataDir>${solr.data.dir:}</dataDir>
+  <directoryFactory name="DirectoryFactory" class="solr.NRTCachingDirectoryFactory"/>
+  <schemaFactory class="ClassicIndexSchemaFactory"/>
+
+  <updateHandler class="solr.DirectUpdateHandler2"/>
+
+  <requestHandler name="standard" class="solr.StandardRequestHandler" />
+
+  <searchComponent class="solr.SuggestComponent" name="analyzing_infix_suggester_random_short_dictionary">
+    <lst name="suggester">
+      <str name="name">shortRandomAnalyzingInfixSuggester</str>
+      <str name="lookupImpl">AnalyzingInfixLookupFactory</str>
+      <str name="dictionaryImpl">RandomTestDictionaryFactory</str>
+      <long name="randDictMaxItems">100</long>
+      <str name="indexPath">short_random_analyzing_infix_suggester</str>
+      <str name="suggestAnalyzerFieldType">text</str>
+      <str name="buildOnCommit">false</str>
+      <str name="buildOnStartup">false</str>
+    </lst>
+  </searchComponent>
+  <requestHandler name="/suggest_analyzing_infix_short_dictionary" class="solr.SearchHandler">
+    <lst name="defaults">
+      <str name="suggest">true</str>
+    </lst>
+    <arr name="components">
+      <str>analyzing_infix_suggester_random_short_dictionary</str>
+    </arr>
+  </requestHandler>
+
+  <searchComponent class="solr.SuggestComponent" name="analyzing_infix_suggester_random_long_dictionary">
+    <lst name="suggester">
+      <str name="name">longRandomAnalyzingInfixSuggester</str>
+      <str name="lookupImpl">AnalyzingInfixLookupFactory</str>
+      <str name="dictionaryImpl">RandomTestDictionaryFactory</str>
+      <long name="randDictMaxItems">100000</long>
+      <str name="indexPath">long_random_analyzing_infix_suggester</str>
+      <str name="suggestAnalyzerFieldType">text</str>
+      <str name="buildOnCommit">false</str>
+      <str name="buildOnStartup">false</str>
+    </lst>
+  </searchComponent>
+  <requestHandler name="/suggest_analyzing_infix_long_dictionary" class="solr.SearchHandler">
+    <lst name="defaults">
+      <str name="suggest">true</str>
+    </lst>
+    <arr name="components">
+      <str>analyzing_infix_suggester_random_long_dictionary</str>
+    </arr>
+  </requestHandler>
+
+  <searchComponent class="solr.SuggestComponent" name="blended_infix_suggester_random_short_dictionary">
+    <lst name="suggester">
+      <str name="name">shortRandomBlendedInfixSuggester</str>
+      <str name="lookupImpl">BlendedInfixLookupFactory</str>
+      <str name="dictionaryImpl">RandomTestDictionaryFactory</str>
+      <long name="randDictMaxItems">100</long>
+      <str name="blenderType">position_linear</str>
+      <str name="suggestAnalyzerFieldType">text</str>
+      <str name="indexPath">short_random_blended_infix_suggester</str>
+      <str name="highlight">true</str>
+      <str name="buildOnStartup">false</str>
+      <str name="buildOnCommit">false</str>
+    </lst>
+  </searchComponent>
+  <requestHandler name="/suggest_blended_infix_short_dictionary" class="solr.SearchHandler">
+    <lst name="defaults">
+      <str name="suggest">true</str>
+      <str name="suggest.dictionary">shortRandomBlendedInfixSuggester</str>
+      <str name="suggest.onlyMorePopular">true</str>
+      <str name="suggest.count">10</str>
+      <str name="suggest.collate">true</str>
+    </lst>
+    <arr name="components">
+      <str>blended_infix_suggester_random_short_dictionary</str>
+    </arr>
+  </requestHandler>
+
+  <query><useColdSearcher>false</useColdSearcher></query>
+
+</config>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test-files/solr/collection1/conf/solrconfig-managed-schema.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-managed-schema.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-managed-schema.xml
index 467deca..31bbbb3 100644
--- a/solr/core/src/test-files/solr/collection1/conf/solrconfig-managed-schema.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-managed-schema.xml
@@ -40,7 +40,7 @@
   </requestHandler>
 
 
-  <requestHandler name="/dump" class="DumpRequestHandler" initParams="a">
+  <requestHandler name="/dump" class="DumpRequestHandler" initParams="a" registerPath="/,/v2">
     <lst name="defaults">
       <str name="a">${my.custom.variable.a:A}</str>
       <str name="b">${my.custom.variable.b:B}</str>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test-files/solr/collection1/conf/solrconfig-master-throttled.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-master-throttled.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-master-throttled.xml
index eac571c..5bc5ac7 100644
--- a/solr/core/src/test-files/solr/collection1/conf/solrconfig-master-throttled.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-master-throttled.xml
@@ -34,7 +34,7 @@
 
   <requestHandler name="/replication" class="solr.ReplicationHandler">
     <lst name="defaults">
-      <str name="maxWriteMBPerSec">0.1</str>
+      <str name="maxWriteMBPerSec">0.05</str>
     </lst>
   </requestHandler>
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/TestRandomDVFaceting.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/TestRandomDVFaceting.java b/solr/core/src/test/org/apache/solr/TestRandomDVFaceting.java
index d088924..dc0f5c2 100644
--- a/solr/core/src/test/org/apache/solr/TestRandomDVFaceting.java
+++ b/solr/core/src/test/org/apache/solr/TestRandomDVFaceting.java
@@ -163,7 +163,7 @@ public class TestRandomDVFaceting extends SolrTestCaseJ4 {
       SchemaField sf = req.getSchema().getField(ftype.fname);
       boolean multiValued = sf.getType().multiValuedFieldCache();
       boolean indexed = sf.indexed();
-      boolean numeric = sf.getType().getNumericType() != null;
+      boolean numeric = sf.getType().getNumberType() != null;
 
       int offset = 0;
       if (rand.nextInt(100) < 20) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/api/TestPathTrie.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/api/TestPathTrie.java b/solr/core/src/test/org/apache/solr/api/TestPathTrie.java
new file mode 100644
index 0000000..d4cbf32
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/api/TestPathTrie.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.api;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.util.PathTrie;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonMap;
+import static org.apache.solr.api.ApiBag.HANDLER_NAME;
+
+public class TestPathTrie extends SolrTestCaseJ4 {
+
+  public void testPathTrie() {
+    PathTrie<String> pathTrie = new PathTrie<>(ImmutableSet.of("_introspect"));
+    pathTrie.insert("/", emptyMap(), "R");
+    pathTrie.insert("/aa", emptyMap(), "d");
+    pathTrie.insert("/aa/bb/{cc}/dd", emptyMap(), "a");
+    pathTrie.insert("/$handlerName/{cc}/dd", singletonMap(HANDLER_NAME, "test"), "test");
+    pathTrie.insert("/aa/bb/{cc}/{xx}", emptyMap(), "b");
+    pathTrie.insert("/aa/bb", emptyMap(), "c");
+
+    HashMap templateValues = new HashMap<>();
+    assertEquals("R", pathTrie.lookup("/", templateValues, null));
+    assertEquals("d", pathTrie.lookup("/aa", templateValues, null));
+    assertEquals("a", pathTrie.lookup("/aa/bb/hello/dd", templateValues, null));
+    templateValues.clear();
+    assertEquals("test", pathTrie.lookup("/test/hello/dd", templateValues, null));
+    assertEquals("hello", templateValues.get("cc"));
+    templateValues.clear();
+    assertEquals("b", pathTrie.lookup("/aa/bb/hello/world", templateValues, null));
+    assertEquals("hello", templateValues.get("cc"));
+    assertEquals("world", templateValues.get("xx"));
+    Set<String> subPaths =  new HashSet<>();
+    templateValues.clear();
+    pathTrie.lookup("/aa",templateValues, subPaths);
+    assertEquals(3, subPaths.size());
+
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/CdcrReplicationDistributedZkTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/CdcrReplicationDistributedZkTest.java b/solr/core/src/test/org/apache/solr/cloud/CdcrReplicationDistributedZkTest.java
index 3ba6186..35592ff 100644
--- a/solr/core/src/test/org/apache/solr/cloud/CdcrReplicationDistributedZkTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/CdcrReplicationDistributedZkTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.cloud;
 
+import org.apache.lucene.util.LuceneTestCase.BadApple;
 import org.apache.lucene.util.LuceneTestCase.Nightly;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.util.NamedList;
@@ -31,6 +32,7 @@ import java.util.List;
 import java.util.Map;
 
 @Nightly
+@BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-10107")
 public class CdcrReplicationDistributedZkTest extends BaseCdcrDistributedZkTest {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/CleanupOldIndexTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/CleanupOldIndexTest.java b/solr/core/src/test/org/apache/solr/cloud/CleanupOldIndexTest.java
index 48243bb..006c3c9 100644
--- a/solr/core/src/test/org/apache/solr/cloud/CleanupOldIndexTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/CleanupOldIndexTest.java
@@ -38,6 +38,8 @@ public class CleanupOldIndexTest extends SolrCloudTestCase {
 
   @BeforeClass
   public static void setupCluster() throws Exception {
+    // we restart jetty and expect to find on disk data - need a local fs directory
+    useFactory(null);
     configureCluster(2)
         .addConfig("conf1", TEST_PATH().resolve("configsets").resolve("cloud-dynamic").resolve("conf"))
         .configure();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/CollectionsAPIDistributedZkTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPIDistributedZkTest.java b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPIDistributedZkTest.java
index 7586f2a..c3d1a86 100644
--- a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPIDistributedZkTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPIDistributedZkTest.java
@@ -86,6 +86,8 @@ public class CollectionsAPIDistributedZkTest extends SolrCloudTestCase {
 
   @BeforeClass
   public static void beforeCollectionsAPIDistributedZkTest() {
+    // we don't want this test to have zk timeouts
+    System.setProperty("zkClientTimeout", "240000");
     TestInjection.randomDelayInCoreCreation = "true:20";
     System.setProperty("validateAfterInactivity", "200");
   }
@@ -100,7 +102,11 @@ public class CollectionsAPIDistributedZkTest extends SolrCloudTestCase {
 
   @Before
   public void clearCluster() throws Exception {
-    cluster.deleteAllCollections();
+    try {
+      cluster.deleteAllCollections();
+    } finally {
+      System.clearProperty("zkClientTimeout");
+    }
   }
 
   @Test
@@ -366,7 +372,7 @@ public class CollectionsAPIDistributedZkTest extends SolrCloudTestCase {
       cluster.getSolrClient().request(createCmd);
     });
 
-    TimeUnit.MILLISECONDS.sleep(200);
+    TimeUnit.MILLISECONDS.sleep(1000);
     // in both cases, the collection should have default to the core name
     cloudClient.getZkStateReader().forceUpdateCollection("corewithnocollection3");
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/ConcurrentDeleteAndCreateCollectionTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/ConcurrentDeleteAndCreateCollectionTest.java b/solr/core/src/test/org/apache/solr/cloud/ConcurrentDeleteAndCreateCollectionTest.java
index 3e90759..151230d 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ConcurrentDeleteAndCreateCollectionTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ConcurrentDeleteAndCreateCollectionTest.java
@@ -17,6 +17,7 @@
 package org.apache.solr.cloud;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.nio.file.Path;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -27,14 +28,19 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.response.CollectionAdminResponse;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.util.TimeOut;
 import org.apache.zookeeper.KeeperException;
 import org.junit.After;
 import org.junit.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Nightly
 public class ConcurrentDeleteAndCreateCollectionTest extends SolrTestCaseJ4 {
   
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  
   private MiniSolrCloudCluster solrCluster;
   
   @Override
@@ -54,7 +60,7 @@ public class ConcurrentDeleteAndCreateCollectionTest extends SolrTestCaseJ4 {
   public void testConcurrentCreateAndDeleteDoesNotFail() {
     final AtomicReference<Exception> failure = new AtomicReference<>();
     final int timeToRunSec = 30;
-    final Thread[] threads = new Thread[10];
+    final CreateDeleteCollectionThread[] threads = new CreateDeleteCollectionThread[10];
     for (int i = 0; i < threads.length; i++) {
       final String collectionName = "collection" + i;
       uploadConfig(configset("configset-2"), collectionName);
@@ -74,12 +80,12 @@ public class ConcurrentDeleteAndCreateCollectionTest extends SolrTestCaseJ4 {
     final String configName = "testconfig";
     uploadConfig(configset("configset-2"), configName); // upload config once, to be used by all collections
     final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
-    final SolrClient solrClient = getHttpSolrClient(baseUrl);
     final AtomicReference<Exception> failure = new AtomicReference<>();
     final int timeToRunSec = 30;
-    final Thread[] threads = new Thread[2];
+    final CreateDeleteCollectionThread[] threads = new CreateDeleteCollectionThread[2];
     for (int i = 0; i < threads.length; i++) {
       final String collectionName = "collection" + i;
+      final SolrClient solrClient = getHttpSolrClient(baseUrl);
       threads[i] = new CreateDeleteCollectionThread("create-delete-" + i, collectionName, configName,
                                                     timeToRunSec, solrClient, failure);
     }
@@ -88,12 +94,6 @@ public class ConcurrentDeleteAndCreateCollectionTest extends SolrTestCaseJ4 {
     joinAll(threads);
 
     assertNull("concurrent create and delete collection failed: " + failure.get(), failure.get());
-
-    try {
-      solrClient.close();
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
   }
   
   private void uploadConfig(Path configDir, String configName) {
@@ -104,10 +104,10 @@ public class ConcurrentDeleteAndCreateCollectionTest extends SolrTestCaseJ4 {
     }
   }
   
-  private void joinAll(final Thread[] threads) {
-    for (Thread t : threads) {
+  private void joinAll(final CreateDeleteCollectionThread[] threads) {
+    for (CreateDeleteCollectionThread t : threads) {
       try {
-        t.join();
+        t.joinAndClose();
       } catch (InterruptedException e) {
         Thread.interrupted();
         throw new RuntimeException(e);
@@ -152,6 +152,7 @@ public class ConcurrentDeleteAndCreateCollectionTest extends SolrTestCaseJ4 {
     }
     
     protected void addFailure(Exception e) {
+      log.error("Add Failure", e);
       synchronized (failure) {
         if (failure.get() != null) {
           failure.get().addSuppressed(e);
@@ -190,6 +191,14 @@ public class ConcurrentDeleteAndCreateCollectionTest extends SolrTestCaseJ4 {
         addFailure(e);
       }
     }
+    
+    public void joinAndClose() throws InterruptedException {
+      try {
+        super.join(60000);
+      } finally {
+        IOUtils.closeQuietly(solrClient);
+      }
+    }
   }
   
   private static class CreateDeleteSearchCollectionThread extends CreateDeleteCollectionThread {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/SharedFSAutoReplicaFailoverTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/SharedFSAutoReplicaFailoverTest.java b/solr/core/src/test/org/apache/solr/cloud/SharedFSAutoReplicaFailoverTest.java
index d7a1fb2..d87e82f 100644
--- a/solr/core/src/test/org/apache/solr/cloud/SharedFSAutoReplicaFailoverTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/SharedFSAutoReplicaFailoverTest.java
@@ -306,7 +306,7 @@ public class SharedFSAutoReplicaFailoverTest extends AbstractFullDistribZkTestBa
     request.setPath("/admin/collections");
     cloudClient.request(request);
 
-    assertTrue("Timeout waiting for all live and active", ClusterStateUtil.waitForAllActiveAndLiveReplicas(cloudClient.getZkStateReader(), collection1, 60000));
+    assertTrue("Timeout waiting for all live and active", ClusterStateUtil.waitForAllActiveAndLiveReplicas(cloudClient.getZkStateReader(), collection1, 90000));
     assertSliceAndReplicaCount(collection1);
 
     assertUlogDir(collections);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithDelegationTokens.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithDelegationTokens.java b/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithDelegationTokens.java
index c3fa813..f8f3f7e 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithDelegationTokens.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithDelegationTokens.java
@@ -282,6 +282,7 @@ public class TestSolrCloudWithDelegationTokens extends SolrTestCaseJ4 {
   }
 
   @Test
+  @AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/HADOOP-14044")
   public void testDelegationTokenCancelFail() throws Exception {
     // cancel a bogus token
     cancelDelegationToken("BOGUS", ErrorCode.NOT_FOUND.code, solrClientPrimary);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsBasicDistributedZkTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsBasicDistributedZkTest.java b/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsBasicDistributedZkTest.java
index 70eaf90..97be823 100644
--- a/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsBasicDistributedZkTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsBasicDistributedZkTest.java
@@ -39,12 +39,14 @@ public class HdfsBasicDistributedZkTest extends BasicDistributedZkTest {
   
   @BeforeClass
   public static void setupClass() throws Exception {
+    System.setProperty("tests.hdfs.numdatanodes", "1");
     dfsCluster = HdfsTestUtil.setupClass(createTempDir().toFile().getAbsolutePath());
   }
   
   @AfterClass
   public static void teardownClass() throws Exception {
     HdfsTestUtil.teardownClass(dfsCluster);
+    System.clearProperty("tests.hdfs.numdatanodes");
     dfsCluster = null;
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsCollectionsAPIDistributedZkTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsCollectionsAPIDistributedZkTest.java b/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsCollectionsAPIDistributedZkTest.java
index fc938a1..80aa78d 100644
--- a/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsCollectionsAPIDistributedZkTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsCollectionsAPIDistributedZkTest.java
@@ -38,7 +38,7 @@ public class HdfsCollectionsAPIDistributedZkTest extends CollectionsAPIDistribut
   @BeforeClass
   public static void setupClass() throws Exception {
     dfsCluster = HdfsTestUtil.setupClass(createTempDir().toFile().getAbsolutePath());
-    System.setProperty("solr.hdfs.blockcache.blocksperbank", "2048");
+    System.setProperty("solr.hdfs.blockcache.blocksperbank", "1024");
 
     ZkConfigManager configManager = new ZkConfigManager(zkClient());
     configManager.uploadConfigDir(configset("cloud-hdfs"), "conf");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsTestUtil.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsTestUtil.java b/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsTestUtil.java
index 12d5651..7038328 100644
--- a/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsTestUtil.java
+++ b/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsTestUtil.java
@@ -42,6 +42,8 @@ import org.apache.solr.util.HdfsUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import io.netty.util.internal.ThreadLocalRandom;
+
 public class HdfsTestUtil {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   
@@ -75,7 +77,11 @@ public class HdfsTestUtil {
     
     if (!HA_TESTING_ENABLED) haTesting = false;
     
-    int dataNodes = 2;
+    
+    // keep netty from using secure random on startup: SOLR-10098
+    ThreadLocalRandom.setInitialSeedUniquifier(1L);
+    
+    int dataNodes = Integer.getInteger("tests.hdfs.numdatanodes", 2);
     
     Configuration conf = new Configuration();
     conf.set("dfs.block.access.token.enable", "false");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsWriteToMultipleCollectionsTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsWriteToMultipleCollectionsTest.java b/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsWriteToMultipleCollectionsTest.java
index 2005978..b345342 100644
--- a/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsWriteToMultipleCollectionsTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/hdfs/HdfsWriteToMultipleCollectionsTest.java
@@ -39,7 +39,9 @@ import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.cloud.BasicDistributedZkTest;
 import org.apache.solr.cloud.StoppableIndexingThread;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.HdfsDirectoryFactory;
+import org.apache.solr.core.MetricsDirectoryFactory;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.store.blockcache.BlockCache;
 import org.apache.solr.store.blockcache.BlockDirectory;
@@ -134,10 +136,14 @@ public class HdfsWriteToMultipleCollectionsTest extends BasicDistributedZkTest {
       for (SolrCore core : solrCores) {
         if (core.getCoreDescriptor().getCloudDescriptor().getCollectionName()
             .startsWith(ACOLLECTION)) {
-          assertTrue(core.getDirectoryFactory() instanceof HdfsDirectoryFactory);
-          Directory dir = core.getDirectoryFactory().get(core.getDataDir(), null, null);
+          DirectoryFactory factory = core.getDirectoryFactory();
+          if (factory instanceof MetricsDirectoryFactory) {
+            factory = ((MetricsDirectoryFactory) factory).getDelegate();
+          }
+          assertTrue("Found: " + core.getDirectoryFactory().getClass().getName(), factory instanceof HdfsDirectoryFactory);
+          Directory dir = factory.get(core.getDataDir(), null, null);
           try {
-            long dataDirSize = core.getDirectoryFactory().size(dir);
+            long dataDirSize = factory.size(dir);
             FileSystem fileSystem = null;
             
             fileSystem = FileSystem.newInstance(
@@ -153,8 +159,8 @@ public class HdfsWriteToMultipleCollectionsTest extends BasicDistributedZkTest {
               .getSolrCoreState().getIndexWriter(core);
           try {
             IndexWriter iw = iwRef.get();
-            NRTCachingDirectory directory = (NRTCachingDirectory) iw
-                .getDirectory();
+            NRTCachingDirectory directory = (NRTCachingDirectory) ((MetricsDirectoryFactory.MetricsDirectory)iw
+                .getDirectory()).getDelegate();
             BlockDirectory blockDirectory = (BlockDirectory) directory
                 .getDelegate();
             assertTrue(blockDirectory.isBlockCacheReadEnabled());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/cloud/rule/RulesTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/rule/RulesTest.java b/solr/core/src/test/org/apache/solr/cloud/rule/RulesTest.java
index 6955418..13649e1 100644
--- a/solr/core/src/test/org/apache/solr/cloud/rule/RulesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/rule/RulesTest.java
@@ -22,10 +22,12 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.response.SimpleSolrResponse;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.params.ModifiableSolrParams;
@@ -36,6 +38,7 @@ import org.junit.rules.ExpectedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
 import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
 import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH;
 import static org.junit.matchers.JUnitMatchers.containsString;
@@ -161,6 +164,21 @@ public class RulesTest extends SolrCloudTestCase {
 
   }
 
+  @Test
+  public void testInvokeApi() throws Exception {
+    JettySolrRunner jetty = cluster.getRandomJetty(random());
+    try (SolrClient client = getHttpSolrClient(jetty.getBaseUrl().toString())) {
+      GenericSolrRequest req =  new GenericSolrRequest(GET, "/v2/node/invoke", new ModifiableSolrParams()
+          .add("class", ImplicitSnitch.class.getName())
+          .add("cores", "1")
+          .add("freedisk", "1")
+      );
+      SimpleSolrResponse rsp = req.process(client);
+      assertNotNull(((Map) rsp.getResponse().get(ImplicitSnitch.class.getName())).get("cores"));
+      assertNotNull(((Map) rsp.getResponse().get(ImplicitSnitch.class.getName())).get("freedisk"));
+    }
+  }
+
 
   @Test
   public void testModifyColl() throws Exception {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java b/solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java
index ff5e1cb..dda1eb3 100644
--- a/solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java
+++ b/solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java
@@ -58,4 +58,5 @@ public class BlobStoreTestRequestHandler extends DumpRequestHandler implements R
     run();
 
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/core/HdfsDirectoryFactoryTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/HdfsDirectoryFactoryTest.java b/solr/core/src/test/org/apache/solr/core/HdfsDirectoryFactoryTest.java
index 49315be..75f6c9b 100644
--- a/solr/core/src/test/org/apache/solr/core/HdfsDirectoryFactoryTest.java
+++ b/solr/core/src/test/org/apache/solr/core/HdfsDirectoryFactoryTest.java
@@ -165,7 +165,7 @@ public class HdfsDirectoryFactoryTest extends SolrTestCaseJ4 {
     hdfs.mkdirs(oldIndexDirPath);
     assertTrue(hdfs.isDirectory(oldIndexDirPath));
 
-    hdfsFactory.cleanupOldIndexDirectories(dataHomePath.toString(), currentIndexDirPath.toString());
+    hdfsFactory.cleanupOldIndexDirectories(dataHomePath.toString(), currentIndexDirPath.toString(), false);
 
     assertTrue(hdfs.isDirectory(currentIndexDirPath));
     assertTrue(!hdfs.isDirectory(oldIndexDirPath));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
index 004039c..695e869 100644
--- a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
+++ b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
@@ -112,6 +112,7 @@ public class SolrCoreTest extends SolrTestCaseJ4 {
       ++ihCount; assertEquals(pathToClassMap.get("/analysis/document"), "solr.DocumentAnalysisRequestHandler");
       ++ihCount; assertEquals(pathToClassMap.get("/analysis/field"), "solr.FieldAnalysisRequestHandler");
       ++ihCount; assertEquals(pathToClassMap.get("/debug/dump"), "solr.DumpRequestHandler");
+      ++ihCount; assertEquals(pathToClassMap.get("update"), "solr.UpdateRequestHandlerApi");
     }
     assertEquals("wrong number of implicit handlers", ihCount, implicitHandlers.size());
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/core/TestDynamicLoading.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/TestDynamicLoading.java b/solr/core/src/test/org/apache/solr/core/TestDynamicLoading.java
index bd20b1e..8479ae4 100644
--- a/solr/core/src/test/org/apache/solr/core/TestDynamicLoading.java
+++ b/solr/core/src/test/org/apache/solr/core/TestDynamicLoading.java
@@ -88,7 +88,7 @@ public class TestDynamicLoading extends AbstractFullDistribZkTestBase {
 
 
     payload = "{\n" +
-        "'create-requesthandler' : { 'name' : '/test1', 'class': 'org.apache.solr.core.BlobStoreTestRequestHandler' , 'runtimeLib' : true }\n" +
+        "'create-requesthandler' : { 'name' : '/test1', 'class': 'org.apache.solr.core.BlobStoreTestRequestHandler' ,registerPath: '/,/v2',  'runtimeLib' : true }\n" +
         "}";
 
     client = restTestHarnesses.get(random().nextInt(restTestHarnesses.size()));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/core/TestLazyCores.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/TestLazyCores.java b/solr/core/src/test/org/apache/solr/core/TestLazyCores.java
index 34cd306..0c0845b 100644
--- a/solr/core/src/test/org/apache/solr/core/TestLazyCores.java
+++ b/solr/core/src/test/org/apache/solr/core/TestLazyCores.java
@@ -42,12 +42,20 @@ import org.apache.solr.update.AddUpdateCommand;
 import org.apache.solr.update.CommitUpdateCommand;
 import org.apache.solr.update.UpdateHandler;
 import org.apache.solr.util.ReadOnlyCoresLocator;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class TestLazyCores extends SolrTestCaseJ4 {
 
   private File solrHomeDirectory;
 
+  @BeforeClass
+  public static void setupClass() throws Exception {
+    // Need to use a disk-based directory because there are tests that close a core after adding documents
+    // then expect to be able to re-open that core and execute a search
+    useFactory("solr.StandardDirectoryFactory");
+  }
+
   private static CoreDescriptor makeCoreDescriptor(CoreContainer cc, String coreName, String isTransient, String loadOnStartup) {
     return new CoreDescriptor(cc, coreName, cc.getCoreRootDirectory().resolve(coreName),
         CoreDescriptor.CORE_TRANSIENT, isTransient,
@@ -721,4 +729,72 @@ public class TestLazyCores extends SolrTestCaseJ4 {
     }
   }
 
+  @BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-10101")
+  // Insure that when a core is aged out of the transient cache, any uncommitted docs are preserved.
+  // Note, this needs FS-based indexes to persist!
+  // Cores 2, 3, 6, 7, 8, 9 are transient
+  @Test
+  public void testNoCommit() throws Exception {
+    CoreContainer cc = init();
+    String[] coreList = new String[]{
+        "collection2",
+        "collection3",
+        "collection6",
+        "collection7",
+        "collection8",
+        "collection9"
+    };
+    try {
+      // First, go through all the transient cores and add some docs. DO NOT COMMIT!
+      // The aged-out core should commit the docs when it gets closed.
+      List<SolrCore> openCores = new ArrayList<>();
+      for (String coreName : coreList) {
+        SolrCore core = cc.getCore(coreName);
+        openCores.add(core);
+        add10(core);
+      }
+      
+      // Just proving that some cores have been aged out.
+      checkNotInCores(cc, "collection2", "collection3");
+
+      // Close our get of all cores above.
+      for (SolrCore core : openCores) core.close();
+      openCores.clear();
+      
+      // We still should have 6, 7, 8, 9 loaded, their reference counts have NOT dropped to zero 
+      checkInCores(cc, "collection6", "collection7", "collection8", "collection9");
+
+      for (String coreName : coreList) {
+        // The point of this test is to insure that when cores are aged out and re-opened
+        // that the docs are there, so insure that the core we're testing is gone, gone, gone. 
+        checkNotInCores(cc, coreName);
+        
+        // Load the core up again.
+        SolrCore core = cc.getCore(coreName);
+        openCores.add(core);
+        
+        // Insure docs are still there.
+        check10(core);
+      }
+      for (SolrCore core : openCores) core.close();
+    } finally {
+      cc.shutdown();
+    }
+  }
+
+  private void add10(SolrCore core) throws IOException {
+    for (int idx = 0; idx < 10; ++idx) {
+      addLazy(core, "id", "0" + idx);
+    }
+    SolrQueryRequest req = makeReq(core);
+
+  }
+
+  private void check10(SolrCore core) {
+    // Just get a couple of searches to work!
+    assertQ("test closing core without committing",
+        makeReq(core, "q", "*:*")
+        , "//result[@numFound='10']"
+    );
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
index c182495..3f85a79 100644
--- a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
+++ b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
@@ -36,12 +36,14 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.handler.DumpRequestHandler;
 import org.apache.solr.handler.TestBlobHandler;
 import org.apache.solr.handler.TestSolrConfigHandlerConcurrent;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.search.SolrCache;
+import org.apache.solr.util.RESTfulServerProvider;
 import org.apache.solr.util.RestTestBase;
 import org.apache.solr.util.RestTestHarness;
 import org.eclipse.jetty.servlet.ServletHolder;
@@ -82,6 +84,10 @@ public class TestSolrConfigHandler extends RestTestBase {
 
     createJettyAndHarness(tmpSolrHome.getAbsolutePath(), "solrconfig-managed-schema.xml", "schema-rest.xml",
         "/solr", true, extraServlets);
+    if (random().nextBoolean()) {
+      log.info("These tests are run with V2 API");
+      restTestHarness.setServerProvider(() -> jetty.getBaseUrl().toString() + "/v2/cores/" + DEFAULT_TEST_CORENAME);
+    }
   }
 
   @After
@@ -195,7 +201,8 @@ public class TestSolrConfigHandler extends RestTestBase {
         10);
 
     payload = "{\n" +
-        "'update-requesthandler' : { 'name' : '/x', 'class': 'org.apache.solr.handler.DumpRequestHandler' , 'startup' : 'lazy' , 'a':'b' , 'defaults': {'def_a':'def A val', 'multival':['a','b','c']}}\n" +
+        "'update-requesthandler' : { 'name' : '/x', 'class': 'org.apache.solr.handler.DumpRequestHandler' ,registerPath :'/,/v2', " +
+        " 'startup' : 'lazy' , 'a':'b' , 'defaults': {'def_a':'def A val', 'multival':['a','b','c']}}\n" +
         "}";
     runConfigCommand(writeHarness, "/config?wt=json", payload);
 
@@ -435,7 +442,8 @@ public class TestSolrConfigHandler extends RestTestBase {
     payload = "{\n" +
         "    'add-requesthandler': {\n" +
         "        name : '/dump100',\n" +
-        "        class : 'org.apache.solr.handler.DumpRequestHandler'," +
+        "       registerPath :'/,/v2',"+
+    "        class : 'org.apache.solr.handler.DumpRequestHandler'," +
         "        suggester: [{name: s1,lookupImpl: FuzzyLookupFactory, dictionaryImpl : DocumentDictionaryFactory}," +
         "                    {name: s2,lookupImpl: FuzzyLookupFactory , dictionaryImpl : DocumentExpressionDictionaryFactory}]" +
         "    }\n" +
@@ -451,13 +459,15 @@ public class TestSolrConfigHandler extends RestTestBase {
 
     map = getRespMap("/dump100?wt=json&json.nl=arrmap&initArgs=true", writeHarness);
     List initArgs = (List) map.get("initArgs");
-    assertEquals(2, initArgs.size());
-    assertTrue(((Map)initArgs.get(0)).containsKey("suggester"));
+    assertNotNull(initArgs);
+    assertTrue(initArgs.size() >= 2);
+    assertTrue(((Map)initArgs.get(2)).containsKey("suggester"));
     assertTrue(((Map)initArgs.get(1)).containsKey("suggester"));
 
     payload = "{\n" +
         "'add-requesthandler' : { 'name' : '/dump101', 'class': " +
-        "'" + CacheTest.class.getName() + "' " +
+        "'" + CacheTest.class.getName() + "', " +
+        "    registerPath :'/,/v2'"+
         ", 'startup' : 'lazy'}\n" +
         "}";
     runConfigCommand(writeHarness, "/config?wt=json", payload);
@@ -525,9 +535,20 @@ public class TestSolrConfigHandler extends RestTestBase {
         continue;
 
       }
-      if (Objects.equals(expected, Utils.getObjectByPath(m, false, jsonPath))) {
-        success = true;
-        break;
+      Object actual = Utils.getObjectByPath(m, false, jsonPath);
+
+      if (expected instanceof ValidatingJsonMap.PredicateWithErrMsg) {
+        ValidatingJsonMap.PredicateWithErrMsg predicate = (ValidatingJsonMap.PredicateWithErrMsg) expected;
+        if (predicate.test(actual) == null) {
+          success = true;
+          break;
+        }
+
+      } else {
+        if (Objects.equals(expected, actual)) {
+          success = true;
+          break;
+        }
       }
       Thread.sleep(100);
 
@@ -568,7 +589,7 @@ public class TestSolrConfigHandler extends RestTestBase {
         10);
 
     payload = "{\n" +
-        "'create-requesthandler' : { 'name' : '/d', 'class': 'org.apache.solr.handler.DumpRequestHandler' }\n" +
+        "'create-requesthandler' : { 'name' : '/d', registerPath :'/,/v2' , 'class': 'org.apache.solr.handler.DumpRequestHandler' }\n" +
         "}";
 
     TestSolrConfigHandler.runConfigCommand(harness, "/config?wt=json", payload);
@@ -598,7 +619,7 @@ public class TestSolrConfigHandler extends RestTestBase {
         5);
 
     payload = "{\n" +
-        "'create-requesthandler' : { 'name' : '/dump1', 'class': 'org.apache.solr.handler.DumpRequestHandler', 'useParams':'x' }\n" +
+        "'create-requesthandler' : { 'name' : '/dump1', registerPath :'/,/v2' , 'class': 'org.apache.solr.handler.DumpRequestHandler', 'useParams':'x' }\n" +
         "}";
 
     TestSolrConfigHandler.runConfigCommand(harness, "/config?wt=json", payload);
@@ -643,7 +664,7 @@ public class TestSolrConfigHandler extends RestTestBase {
 
     TestSolrConfigHandler.testForResponseElement(harness,
         null,
-        "/dump?wt=json&useParams=y",
+        "/dump1?wt=json&useParams=y",
         null,
         Arrays.asList("params", "c"),
         "CY val",
@@ -745,6 +766,60 @@ public class TestSolrConfigHandler extends RestTestBase {
         null,
         10);
 
+    payload = "{\n" +
+        "  'create-requesthandler': {\n" +
+        "    'name': 'aRequestHandler',\n" +
+        "    'registerPath': '/v2',\n" +
+        "    'class': 'org.apache.solr.handler.DumpRequestHandler',\n" +
+        "    'spec': {\n" +
+        "      'methods': [\n" +
+        "        'GET',\n" +
+        "        'POST'\n" +
+        "      ],\n" +
+        "      'url': {\n" +
+        "        'paths': [\n" +
+        "          '/something/{part1}/fixed/{part2}'\n" +
+        "        ]\n" +
+        "      }\n" +
+        "    }\n" +
+        "  }\n" +
+        "}";
+
+    TestSolrConfigHandler.runConfigCommand(harness, "/config?wt=json", payload);
+    TestSolrConfigHandler.testForResponseElement(harness,
+        null,
+        "/config/overlay?wt=json",
+        null,
+        Arrays.asList("overlay", "requestHandler", "aRequestHandler", "class"),
+        "org.apache.solr.handler.DumpRequestHandler",
+        10);
+    RESTfulServerProvider oldProvider = restTestHarness.getServerProvider();
+    restTestHarness.setServerProvider(() -> jetty.getBaseUrl().toString() + "/v2/cores/" + DEFAULT_TEST_CORENAME);
+
+    Map rsp = TestSolrConfigHandler.testForResponseElement(
+        harness,
+        null,
+        "/something/part1_Value/fixed/part2_Value?urlTemplateValues=part1&urlTemplateValues=part2",
+        null,
+        Arrays.asList("urlTemplateValues"),
+        new ValidatingJsonMap.PredicateWithErrMsg() {
+          @Override
+          public String test(Object o) {
+            if (o instanceof Map) {
+              Map m = (Map) o;
+              if ("part1_Value".equals(m.get("part1"))  && "part2_Value".equals(m.get("part2"))) return null;
+
+            }
+            return "no match";
+          }
+
+          @Override
+          public String toString() {
+            return "{part1:part1_Value, part2 : part2_Value]";
+          }
+        },
+        10);
+    restTestHarness.setServerProvider(oldProvider);
 
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/core/snapshots/TestSolrCloudSnapshots.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/snapshots/TestSolrCloudSnapshots.java b/solr/core/src/test/org/apache/solr/core/snapshots/TestSolrCloudSnapshots.java
index 65f74ca..bb56a94 100644
--- a/solr/core/src/test/org/apache/solr/core/snapshots/TestSolrCloudSnapshots.java
+++ b/solr/core/src/test/org/apache/solr/core/snapshots/TestSolrCloudSnapshots.java
@@ -249,6 +249,24 @@ public class TestSolrCloudSnapshots extends SolrCloudTestCase {
     // Verify all core-level snapshots are deleted.
     assertTrue("The cores remaining " + snapshotByCoreName, snapshotByCoreName.isEmpty());
     assertTrue(listCollectionSnapshots(solrClient, collectionName).isEmpty());
+
+    // Verify if the collection deletion result in proper cleanup of snapshot metadata.
+    {
+      String commitName_2 = commitName + "_2";
+
+      CollectionAdminRequest.CreateSnapshot createSnap_2 = new CollectionAdminRequest.CreateSnapshot(collectionName, commitName_2);
+      assertEquals(0, createSnap_2.process(solrClient).getStatus());
+
+      Collection<CollectionSnapshotMetaData> collectionSnaps_2 = listCollectionSnapshots(solrClient, collectionName);
+      assertEquals(1, collectionSnaps.size());
+      assertEquals(commitName_2, collectionSnaps_2.iterator().next().getName());
+
+      // Delete collection
+      CollectionAdminRequest.Delete deleteCol = CollectionAdminRequest.deleteCollection(collectionName);
+      assertEquals(0, deleteCol.process(solrClient).getStatus());
+      assertTrue(SolrSnapshotManager.listSnapshots(solrClient.getZkStateReader().getZkClient(), collectionName).isEmpty());
+    }
+
   }
 
   private Collection<CollectionSnapshotMetaData> listCollectionSnapshots(SolrClient adminClient, String collectionName) throws Exception {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/TestBlobHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/TestBlobHandler.java b/solr/core/src/test/org/apache/solr/handler/TestBlobHandler.java
index 4fda926..c395d20 100644
--- a/solr/core/src/test/org/apache/solr/handler/TestBlobHandler.java
+++ b/solr/core/src/test/org/apache/solr/handler/TestBlobHandler.java
@@ -90,27 +90,33 @@ public class TestBlobHandler extends AbstractFullDistribZkTestBase {
           "field",
           "type")));
 
-      byte[] bytarr = new byte[1024];
-      for (int i = 0; i < bytarr.length; i++) bytarr[i] = (byte) (i % 127);
-      byte[] bytarr2 = new byte[2048];
-      for (int i = 0; i < bytarr2.length; i++) bytarr2[i] = (byte) (i % 127);
-      String blobName = "test";
-      postAndCheck(cloudClient, baseUrl, blobName, ByteBuffer.wrap(bytarr), 1);
-      postAndCheck(cloudClient, baseUrl, blobName, ByteBuffer.wrap(bytarr2), 2);
-
-      url = baseUrl + "/.system/blob/test/1";
-      map = TestSolrConfigHandlerConcurrent.getAsMap(url, cloudClient);
-      List l = (List) Utils.getObjectByPath(map, false, Arrays.asList("response", "docs"));
-      assertNotNull("" + map, l);
-      assertTrue("" + map, l.size() > 0);
-      map = (Map) l.get(0);
-      assertEquals("" + bytarr.length, String.valueOf(map.get("size")));
-
-      compareInputAndOutput(baseUrl + "/.system/blob/test?wt=filestream", bytarr2);
-      compareInputAndOutput(baseUrl + "/.system/blob/test/1?wt=filestream", bytarr);
+      checkBlobPost(baseUrl, cloudClient);
     }
   }
 
+  static void checkBlobPost(String baseUrl, CloudSolrClient cloudClient) throws Exception {
+    String url;
+    Map map;
+    byte[] bytarr = new byte[1024];
+    for (int i = 0; i < bytarr.length; i++) bytarr[i] = (byte) (i % 127);
+    byte[] bytarr2 = new byte[2048];
+    for (int i = 0; i < bytarr2.length; i++) bytarr2[i] = (byte) (i % 127);
+    String blobName = "test";
+    postAndCheck(cloudClient, baseUrl, blobName, ByteBuffer.wrap(bytarr), 1);
+    postAndCheck(cloudClient, baseUrl, blobName, ByteBuffer.wrap(bytarr2), 2);
+
+    url = baseUrl + "/.system/blob/test/1";
+    map = TestSolrConfigHandlerConcurrent.getAsMap(url, cloudClient);
+    List l = (List) Utils.getObjectByPath(map, false, Arrays.asList("response", "docs"));
+    assertNotNull("" + map, l);
+    assertTrue("" + map, l.size() > 0);
+    map = (Map) l.get(0);
+    assertEquals("" + bytarr.length, String.valueOf(map.get("size")));
+
+    compareInputAndOutput(baseUrl + "/.system/blob/test?wt=filestream", bytarr2, cloudClient);
+    compareInputAndOutput(baseUrl + "/.system/blob/test/1?wt=filestream", bytarr, cloudClient);
+  }
+
   public static void createSystemCollection(SolrClient client) throws SolrServerException, IOException {
     CollectionAdminResponse response1;
     CollectionAdminRequest.Create createCollectionRequest = new CollectionAdminRequest.Create()
@@ -152,7 +158,7 @@ public class TestBlobHandler extends AbstractFullDistribZkTestBase {
     return new String(Utils.toJSON(map), StandardCharsets.UTF_8);
   }
 
-  private void compareInputAndOutput(String url, byte[] bytarr) throws IOException {
+  static void compareInputAndOutput(String url, byte[] bytarr, CloudSolrClient cloudClient) throws IOException {
 
     HttpClient httpClient = cloudClient.getLbClient().getHttpClient();
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/TestReplicationHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/TestReplicationHandler.java b/solr/core/src/test/org/apache/solr/handler/TestReplicationHandler.java
index 345b86d..9deb51d 100644
--- a/solr/core/src/test/org/apache/solr/handler/TestReplicationHandler.java
+++ b/solr/core/src/test/org/apache/solr/handler/TestReplicationHandler.java
@@ -125,6 +125,8 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
     slave.setUp();
     slaveJetty = createJetty(slave);
     slaveClient = createNewSolrClient(slaveJetty.getLocalPort());
+    
+    System.setProperty("solr.indexfetcher.sotimeout2", "45000");
   }
 
   public void clearIndexWithReplication() throws Exception {
@@ -147,6 +149,7 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
     masterClient.close();
     slaveClient.close();
     masterClient = slaveClient = null;
+    System.clearProperty("solr.indexfetcher.sotimeout");
   }
 
   private static JettySolrRunner createJetty(SolrInstance instance) throws Exception {
@@ -165,7 +168,7 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
       final String baseUrl = buildUrl(port) + "/" + DEFAULT_TEST_CORENAME;
       HttpSolrClient client = getHttpSolrClient(baseUrl);
       client.setConnectionTimeout(15000);
-      client.setSoTimeout(60000);
+      client.setSoTimeout(90000);
       return client;
     }
     catch (Exception ex) {
@@ -292,6 +295,16 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
 
   @Test
   public void doTestDetails() throws Exception {
+    slaveJetty.stop();
+    
+    slave.copyConfigFile(CONF_DIR + "solrconfig-slave.xml", "solrconfig.xml");
+    slaveJetty = createJetty(slave);
+    
+    slaveClient.close();
+    masterClient.close();
+    masterClient = createNewSolrClient(masterJetty.getLocalPort());
+    slaveClient = createNewSolrClient(slaveJetty.getLocalPort());
+    
     clearIndexWithReplication();
     { 
       NamedList<Object> details = getDetails(masterClient);
@@ -307,22 +320,34 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
     // check details on the slave a couple of times before & after fetching
     for (int i = 0; i < 3; i++) {
       NamedList<Object> details = getDetails(slaveClient);
-      List replicatedAtCount = (List) ((NamedList) details.get("slave")).get("indexReplicatedAtList");
+      assertNotNull(i + ": " + details);
+      assertNotNull(i + ": " + details.toString(), details.get("slave"));
+
       if (i > 0) {
-        assertEquals(i, replicatedAtCount.size());
+        rQuery(i, "*:*", slaveClient);
+        List replicatedAtCount = (List) ((NamedList) details.get("slave")).get("indexReplicatedAtList");
+        int tries = 0;
+        while ((replicatedAtCount == null || replicatedAtCount.size() < i) && tries++ < 5) {
+          Thread.currentThread().sleep(1000);
+          details = getDetails(slaveClient);
+          replicatedAtCount = (List) ((NamedList) details.get("slave")).get("indexReplicatedAtList");
+        }
+        
+        assertNotNull("Expected to see that the slave has replicated" + i + ": " + details.toString(), replicatedAtCount);
+        
+        // we can have more replications than we added docs because a replication can legally fail and try 
+        // again (sometimes we cannot merge into a live index and have to try again)
+        assertTrue("i:" + i + " replicationCount:" + replicatedAtCount.size(), replicatedAtCount.size() >= i); 
       }
 
-      assertEquals("slave isMaster?", 
-                   "false", details.get("isMaster"));
-      assertEquals("slave isSlave?", 
-                   "true", details.get("isSlave"));
-      assertNotNull("slave has slave section", 
-                    details.get("slave"));
+      assertEquals(i + ": " + "slave isMaster?", "false", details.get("isMaster"));
+      assertEquals(i + ": " + "slave isSlave?", "true", details.get("isSlave"));
+      assertNotNull(i + ": " + "slave has slave section", details.get("slave"));
       // SOLR-2677: assert not false negatives
       Object timesFailed = ((NamedList)details.get("slave")).get(IndexFetcher.TIMES_FAILED);
       // SOLR-7134: we can have a fail because some mock index files have no checksum, will
       // always be downloaded, and may not be able to be moved into the existing index
-      assertTrue("slave has fetch error count: " + (String)timesFailed, timesFailed == null || ((String) timesFailed).equals("1"));
+      assertTrue(i + ": " + "slave has fetch error count: " + (String)timesFailed, timesFailed == null || ((String) timesFailed).equals("1"));
 
       if (3 != i) {
         // index & fetch
@@ -544,7 +569,7 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
     assertTrue(slaveXsl.exists());
     
     checkForSingleIndex(masterJetty);
-    checkForSingleIndex(slaveJetty);
+    checkForSingleIndex(slaveJetty, true);
     
   }
 
@@ -907,6 +932,10 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
   }
 
   private void checkForSingleIndex(JettySolrRunner jetty) {
+    checkForSingleIndex(jetty, false);
+  }
+  
+  private void checkForSingleIndex(JettySolrRunner jetty, boolean afterReload) {
     CoreContainer cores = jetty.getCoreContainer();
     Collection<SolrCore> theCores = cores.getCores();
     for (SolrCore core : theCores) {
@@ -914,13 +943,27 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
       CachingDirectoryFactory dirFactory = getCachingDirectoryFactory(core);
       synchronized (dirFactory) {
         Set<String> livePaths = dirFactory.getLivePaths();
-        // one for data, one for hte index under data and one for the snapshot metadata.
-        assertEquals(livePaths.toString(), 3, livePaths.size());
+        // one for data, one for the index under data and one for the snapshot metadata.
+        // we also allow one extra index dir - it may not be removed until the core is closed
+        if (afterReload) {
+          assertTrue(livePaths.toString() + ":" + livePaths.size(), 3 == livePaths.size() || 4 == livePaths.size());
+        } else {
+          assertTrue(livePaths.toString() + ":" + livePaths.size(), 3 == livePaths.size());
+        }
+
         // :TODO: assert that one of the paths is a subpath of hte other
       }
       if (dirFactory instanceof StandardDirectoryFactory) {
         System.out.println(Arrays.asList(new File(ddir).list()));
-        assertEquals(Arrays.asList(new File(ddir).list()).toString(), 1, indexDirCount(ddir));
+        // we also allow one extra index dir - it may not be removed until the core is closed
+        int cnt = indexDirCount(ddir);
+        // if after reload, there may be 2 index dirs while the reloaded SolrCore closes.
+        if (afterReload) {
+          assertTrue("found:" + cnt + Arrays.asList(new File(ddir).list()).toString(), 1 == cnt || 2 == cnt);
+        } else {
+          assertTrue("found:" + cnt + Arrays.asList(new File(ddir).list()).toString(), 1 == cnt);
+        }
+
       }
     }
   }
@@ -1337,17 +1380,8 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
     SolrDocumentList slaveQueryResult2 = (SolrDocumentList) slaveQueryRsp2.get("response");
     assertEquals(1, slaveQueryResult2.getNumFound());
     
-    index(slaveClient, "id", "2001", "name", "name = " + 2001, "newname", "n2001");
-    slaveClient.commit();
-
-    slaveQueryRsp = rQuery(1, "id:2001", slaveClient);
-    final SolrDocumentList sdl = (SolrDocumentList) slaveQueryRsp.get("response");
-    assertEquals(1, sdl.getNumFound());
-    final SolrDocument d = sdl.get(0);
-    assertEquals("n2001", (String) d.getFieldValue("newname"));
-    
     checkForSingleIndex(masterJetty);
-    checkForSingleIndex(slaveJetty);
+    checkForSingleIndex(slaveJetty, true);
   }
 
   @Test
@@ -1424,6 +1458,22 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
     assertTrue(timeTakenInSeconds - approximateTimeInSeconds > 0);
   }
 
+  @Test
+  public void doTestIllegalFilePaths() throws Exception {
+    // Loop through the file=, cf=, tlogFile= params and prove that it throws exception for path traversal attempts
+    String absFile = Paths.get("foo").toAbsolutePath().toString();
+    List<String> illegalFilenames = Arrays.asList(absFile, "../dir/traversal", "illegal\rfile\nname\t");
+    List<String> params = Arrays.asList(ReplicationHandler.FILE, ReplicationHandler.CONF_FILE_SHORT, ReplicationHandler.TLOG_FILE);
+    for (String param : params) {
+      for (String filename : illegalFilenames) {
+        try {
+          invokeReplicationCommand(masterJetty.getLocalPort(), "filecontent&" + param + "=" + filename);
+          fail("Should have thrown exception on illegal path for param " + param + " and file name " + filename);
+        } catch (Exception e) {}
+      }
+    }
+  }
+  
   private class AddExtraDocs implements Runnable {
 
     SolrClient masterClient;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/test/org/apache/solr/handler/TestSystemCollAutoCreate.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/TestSystemCollAutoCreate.java b/solr/core/src/test/org/apache/solr/handler/TestSystemCollAutoCreate.java
new file mode 100644
index 0000000..cadda58
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/TestSystemCollAutoCreate.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler;
+
+
+import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
+import org.apache.solr.common.cloud.DocCollection;
+
+public class TestSystemCollAutoCreate extends AbstractFullDistribZkTestBase {
+  public void testAutoCreate() throws Exception {
+    TestBlobHandler.checkBlobPost(cloudJettys.get(0).jetty.getBaseUrl().toExternalForm(), cloudClient);
+    DocCollection sysColl = cloudClient.getZkStateReader().getClusterState().getCollection(".system");
+  }
+}


[16/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java
deleted file mode 100644
index f81b943..0000000
--- a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java
+++ /dev/null
@@ -1,507 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.search.join;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Queue;
-
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.search.Collector;
-import org.apache.lucene.search.FieldComparator;
-import org.apache.lucene.search.FieldValueHitQueue;
-import org.apache.lucene.search.LeafCollector;
-import org.apache.lucene.search.LeafFieldComparator;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.ScoreCachingWrappingScorer;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.Scorer.ChildScorer;
-import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.TopDocs;
-import org.apache.lucene.search.TopDocsCollector;
-import org.apache.lucene.search.TopFieldCollector;
-import org.apache.lucene.search.TopScoreDocCollector;
-import org.apache.lucene.search.grouping.GroupDocs;
-import org.apache.lucene.search.grouping.TopGroups;
-import org.apache.lucene.util.ArrayUtil;
-
-
-/** Collects parent document hits for a Query containing one more more
- *  BlockJoinQuery clauses, sorted by the
- *  specified parent Sort.  Note that this cannot perform
- *  arbitrary joins; rather, it requires that all joined
- *  documents are indexed as a doc block (using {@link
- *  IndexWriter#addDocuments} or {@link
- *  IndexWriter#updateDocuments}).  Ie, the join is computed
- *  at index time.
- *
- *  <p>This collector MUST be used with {@link ToParentBlockJoinIndexSearcher},
- *  in order to work correctly.
- *
- *  <p>The parent Sort must only use
- *  fields from the parent documents; sorting by field in
- *  the child documents is not supported.</p>
- *
- *  <p>You should only use this
- *  collector if one or more of the clauses in the query is
- *  a {@link ToParentBlockJoinQuery}.  This collector will find those query
- *  clauses and record the matching child documents for the
- *  top scoring parent documents.</p>
- *
- *  <p>Multiple joins (star join) and nested joins and a mix
- *  of the two are allowed, as long as in all cases the
- *  documents corresponding to a single row of each joined
- *  parent table were indexed as a doc block.</p>
- *
- *  <p>For the simple star join you can retrieve the
- *  {@link TopGroups} instance containing each {@link ToParentBlockJoinQuery}'s
- *  matching child documents for the top parent groups,
- *  using {@link #getTopGroups}.  Ie,
- *  a single query, which will contain two or more
- *  {@link ToParentBlockJoinQuery}'s as clauses representing the star join,
- *  can then retrieve two or more {@link TopGroups} instances.</p>
- *
- *  <p>For nested joins, the query will run correctly (ie,
- *  match the right parent and child documents), however,
- *  because TopGroups is currently unable to support nesting
- *  (each group is not able to hold another TopGroups), you
- *  are only able to retrieve the TopGroups of the first
- *  join.  The TopGroups of the nested joins will not be
- *  correct.
- *
- *  See {@link org.apache.lucene.search.join} for a code
- *  sample.
- *
- * @lucene.experimental
- */
-public class ToParentBlockJoinCollector implements Collector {
-
-  private final Sort sort;
-
-  // Maps each BlockJoinQuery instance to its "slot" in
-  // joinScorers and in OneGroup's cached doc/scores/count:
-  private final Map<Query,Integer> joinQueryID = new HashMap<>();
-  private final int numParentHits;
-  private final FieldValueHitQueue<OneGroup> queue;
-  private final FieldComparator<?>[] comparators;
-  private final boolean trackMaxScore;
-  private final boolean trackScores;
-
-  private ToParentBlockJoinQuery.BlockJoinScorer[] joinScorers = new ToParentBlockJoinQuery.BlockJoinScorer[0];
-  private boolean queueFull;
-
-  private OneGroup bottom;
-  private int totalHitCount;
-  private float maxScore = Float.NaN;
-
-  /**  Creates a ToParentBlockJoinCollector.  The provided sort must
-   *  not be null.  If you pass true trackScores, all
-   *  ToParentBlockQuery instances must not use
-   *  ScoreMode.None. */
-  public ToParentBlockJoinCollector(Sort sort, int numParentHits, boolean trackScores, boolean trackMaxScore) {
-    // TODO: allow null sort to be specialized to relevance
-    // only collector
-    this.sort = sort;
-    this.trackMaxScore = trackMaxScore;
-    if (trackMaxScore) {
-      maxScore = Float.MIN_VALUE;
-    }
-    //System.out.println("numParentHits=" + numParentHits);
-    this.trackScores = trackScores;
-    this.numParentHits = numParentHits;
-    queue = FieldValueHitQueue.create(sort.getSort(), numParentHits);
-    comparators = queue.getComparators();
-  }
-  
-  private static final class OneGroup extends FieldValueHitQueue.Entry {
-    public OneGroup(int comparatorSlot, int parentDoc, float parentScore, int numJoins, boolean doScores) {
-      super(comparatorSlot, parentDoc, parentScore);
-      //System.out.println("make OneGroup parentDoc=" + parentDoc);
-      docs = new int[numJoins][];
-      for(int joinID=0;joinID<numJoins;joinID++) {
-        docs[joinID] = new int[5];
-      }
-      if (doScores) {
-        scores = new float[numJoins][];
-        for(int joinID=0;joinID<numJoins;joinID++) {
-          scores[joinID] = new float[5];
-        }
-      }
-      counts = new int[numJoins];
-    }
-    LeafReaderContext readerContext;
-    int[][] docs;
-    float[][] scores;
-    int[] counts;
-  }
-
-  @Override
-  public LeafCollector getLeafCollector(final LeafReaderContext context)
-      throws IOException {
-    final LeafFieldComparator[] comparators = queue.getComparators(context);
-    final int[] reverseMul = queue.getReverseMul();
-    final int docBase = context.docBase;
-    return new LeafCollector() {
-
-      private Scorer scorer;
-
-      @Override
-      public void setScorer(Scorer scorer) throws IOException {
-        //System.out.println("C.setScorer scorer=" + scorer);
-        // Since we invoke .score(), and the comparators likely
-        // do as well, cache it so it's only "really" computed
-        // once:
-        if (scorer instanceof ScoreCachingWrappingScorer == false) {
-          scorer = new ScoreCachingWrappingScorer(scorer);
-        }
-        this.scorer = scorer;
-        for (LeafFieldComparator comparator : comparators) {
-          comparator.setScorer(scorer);
-        }
-        Arrays.fill(joinScorers, null);
-
-        Queue<Scorer> queue = new LinkedList<>();
-        //System.out.println("\nqueue: add top scorer=" + scorer);
-        queue.add(scorer);
-        while ((scorer = queue.poll()) != null) {
-          //System.out.println("  poll: " + scorer + "; " + scorer.getWeight().getQuery());
-          if (scorer instanceof ToParentBlockJoinQuery.BlockJoinScorer) {
-            enroll((ToParentBlockJoinQuery) scorer.getWeight().getQuery(), (ToParentBlockJoinQuery.BlockJoinScorer) scorer);
-          }
-
-          for (ChildScorer sub : scorer.getChildren()) {
-            //System.out.println("  add sub: " + sub.child + "; " + sub.child.getWeight().getQuery());
-            queue.add(sub.child);
-          }
-        }
-      }
-      
-      @Override
-      public void collect(int parentDoc) throws IOException {
-      //System.out.println("\nC parentDoc=" + parentDoc);
-        totalHitCount++;
-
-        float score = Float.NaN;
-
-        if (trackMaxScore) {
-          score = scorer.score();
-          maxScore = Math.max(maxScore, score);
-        }
-
-        // TODO: we could sweep all joinScorers here and
-        // aggregate total child hit count, so we can fill this
-        // in getTopGroups (we wire it to 0 now)
-
-        if (queueFull) {
-          //System.out.println("  queueFull");
-          // Fastmatch: return if this hit is not competitive
-          int c = 0;
-          for (int i = 0; i < comparators.length; ++i) {
-            c = reverseMul[i] * comparators[i].compareBottom(parentDoc);
-            if (c != 0) {
-              break;
-            }
-          }
-          if (c <= 0) { // in case of equality, this hit is not competitive as docs are visited in order
-            // Definitely not competitive.
-            //System.out.println("    skip");
-            return;
-          }
-
-          //System.out.println("    competes!  doc=" + (docBase + parentDoc));
-
-          // This hit is competitive - replace bottom element in queue & adjustTop
-          for (LeafFieldComparator comparator : comparators) {
-            comparator.copy(bottom.slot, parentDoc);
-          }
-          if (!trackMaxScore && trackScores) {
-            score = scorer.score();
-          }
-          bottom.doc = docBase + parentDoc;
-          bottom.readerContext = context;
-          bottom.score = score;
-          copyGroups(bottom);
-          bottom = queue.updateTop();
-
-          for (LeafFieldComparator comparator : comparators) {
-            comparator.setBottom(bottom.slot);
-          }
-        } else {
-          // Startup transient: queue is not yet full:
-          final int comparatorSlot = totalHitCount - 1;
-
-          // Copy hit into queue
-          for (LeafFieldComparator comparator : comparators) {
-            comparator.copy(comparatorSlot, parentDoc);
-          }
-          //System.out.println("  startup: new OG doc=" + (docBase+parentDoc));
-          if (!trackMaxScore && trackScores) {
-            score = scorer.score();
-          }
-          final OneGroup og = new OneGroup(comparatorSlot, docBase+parentDoc, score, joinScorers.length, trackScores);
-          og.readerContext = context;
-          copyGroups(og);
-          bottom = queue.add(og);
-          queueFull = totalHitCount == numParentHits;
-          if (queueFull) {
-            // End of startup transient: queue just filled up:
-            for (LeafFieldComparator comparator : comparators) {
-              comparator.setBottom(bottom.slot);
-            }
-          }
-        }
-      }
-      
-      // Pulls out child doc and scores for all join queries:
-      private void copyGroups(OneGroup og) {
-        // While rare, it's possible top arrays could be too
-        // short if join query had null scorer on first
-        // segment(s) but then became non-null on later segments
-        final int numSubScorers = joinScorers.length;
-        if (og.docs.length < numSubScorers) {
-          // While rare, this could happen if join query had
-          // null scorer on first segment(s) but then became
-          // non-null on later segments
-          og.docs = ArrayUtil.grow(og.docs, numSubScorers);
-        }
-        if (og.counts.length < numSubScorers) {
-          og.counts = ArrayUtil.grow(og.counts);
-        }
-        if (trackScores && og.scores.length < numSubScorers) {
-          og.scores = ArrayUtil.grow(og.scores, numSubScorers);
-        }
-
-        //System.out.println("\ncopyGroups parentDoc=" + og.doc);
-        for(int scorerIDX = 0;scorerIDX < numSubScorers;scorerIDX++) {
-          final ToParentBlockJoinQuery.BlockJoinScorer joinScorer = joinScorers[scorerIDX];
-          //System.out.println("  scorer=" + joinScorer);
-          if (joinScorer != null && docBase + joinScorer.getParentDoc() == og.doc) {
-            og.counts[scorerIDX] = joinScorer.getChildCount();
-            //System.out.println("    count=" + og.counts[scorerIDX]);
-            og.docs[scorerIDX] = joinScorer.swapChildDocs(og.docs[scorerIDX]);
-            assert og.docs[scorerIDX].length >= og.counts[scorerIDX]: "length=" + og.docs[scorerIDX].length + " vs count=" + og.counts[scorerIDX];
-            //System.out.println("    len=" + og.docs[scorerIDX].length);
-            /*
-              for(int idx=0;idx<og.counts[scorerIDX];idx++) {
-              System.out.println("    docs[" + idx + "]=" + og.docs[scorerIDX][idx]);
-              }
-            */
-            if (trackScores) {
-              //System.out.println("    copy scores");
-              og.scores[scorerIDX] = joinScorer.swapChildScores(og.scores[scorerIDX]);
-              assert og.scores[scorerIDX].length >= og.counts[scorerIDX]: "length=" + og.scores[scorerIDX].length + " vs count=" + og.counts[scorerIDX];
-            }
-          } else {
-            og.counts[scorerIDX] = 0;
-          }
-        }
-      }
-    };
-  }
-
-  private void enroll(ToParentBlockJoinQuery query, ToParentBlockJoinQuery.BlockJoinScorer scorer) {
-    scorer.trackPendingChildHits();
-    final Integer slot = joinQueryID.get(query);
-    if (slot == null) {
-      joinQueryID.put(query, joinScorers.length);
-      //System.out.println("found JQ: " + query + " slot=" + joinScorers.length);
-      final ToParentBlockJoinQuery.BlockJoinScorer[] newArray = new ToParentBlockJoinQuery.BlockJoinScorer[1+joinScorers.length];
-      System.arraycopy(joinScorers, 0, newArray, 0, joinScorers.length);
-      joinScorers = newArray;
-      joinScorers[joinScorers.length-1] = scorer;
-    } else {
-      joinScorers[slot] = scorer;
-    }
-  }
-
-  private OneGroup[] sortedGroups;
-
-  private void sortQueue() {
-    sortedGroups = new OneGroup[queue.size()];
-    for(int downTo=queue.size()-1;downTo>=0;downTo--) {
-      sortedGroups[downTo] = queue.pop();
-    }
-  }
-
-  /** Returns the TopGroups for the specified
-   *  BlockJoinQuery. The groupValue of each GroupDocs will
-   *  be the parent docID for that group.
-   *  The number of documents within each group is calculated as minimum of <code>maxDocsPerGroup</code>
-   *  and number of matched child documents for that group.
-   *  Returns null if no groups matched.
-   *
-   * @param query Search query
-   * @param withinGroupSort Sort criteria within groups
-   * @param offset Parent docs offset
-   * @param maxDocsPerGroup Upper bound of documents per group number
-   * @param withinGroupOffset Offset within each group of child docs
-   * @param fillSortFields Specifies whether to add sort fields or not
-   * @return TopGroups for specified query
-   * @throws IOException if there is a low-level I/O error
-   */
-  public TopGroups<Integer> getTopGroups(ToParentBlockJoinQuery query, Sort withinGroupSort, int offset,
-                                         int maxDocsPerGroup, int withinGroupOffset, boolean fillSortFields)
-    throws IOException {
-
-    final Integer _slot = joinQueryID.get(query);
-    if (_slot == null && totalHitCount == 0) {
-      return null;
-    }
-
-    if (sortedGroups == null) {
-      if (offset >= queue.size()) {
-        return null;
-      }
-      sortQueue();
-    } else if (offset > sortedGroups.length) {
-      return null;
-    }
-
-    return accumulateGroups(_slot == null ? -1 : _slot.intValue(), offset, maxDocsPerGroup, withinGroupOffset, withinGroupSort, fillSortFields);
-  }
-
-  /**
-   *  Accumulates groups for the BlockJoinQuery specified by its slot.
-   *
-   * @param slot Search query's slot
-   * @param offset Parent docs offset
-   * @param maxDocsPerGroup Upper bound of documents per group number
-   * @param withinGroupOffset Offset within each group of child docs
-   * @param withinGroupSort Sort criteria within groups
-   * @param fillSortFields Specifies whether to add sort fields or not
-   * @return TopGroups for the query specified by slot
-   * @throws IOException if there is a low-level I/O error
-   */
-  @SuppressWarnings({"unchecked","rawtypes"})
-  private TopGroups<Integer> accumulateGroups(int slot, int offset, int maxDocsPerGroup,
-                                              int withinGroupOffset, Sort withinGroupSort, boolean fillSortFields) throws IOException {
-    final GroupDocs<Integer>[] groups = new GroupDocs[sortedGroups.length - offset];
-    final FakeScorer fakeScorer = new FakeScorer();
-
-    int totalGroupedHitCount = 0;
-    //System.out.println("slot=" + slot);
-
-    for(int groupIDX=offset;groupIDX<sortedGroups.length;groupIDX++) {
-      final OneGroup og = sortedGroups[groupIDX];
-      final int numChildDocs;
-      if (slot == -1 || slot >= og.counts.length) {
-        numChildDocs = 0;
-      } else {
-        numChildDocs = og.counts[slot];
-      }
-
-      // Number of documents in group should be bounded to prevent redundant memory allocation
-      final int numDocsInGroup = Math.max(1, Math.min(numChildDocs, maxDocsPerGroup));
-      //System.out.println("parent doc=" + og.doc + " numChildDocs=" + numChildDocs + " maxDocsPG=" + maxDocsPerGroup);
-
-      // At this point we hold all docs w/ in each group,
-      // unsorted; we now sort them:
-      final TopDocsCollector<?> collector;
-      if (withinGroupSort == null) {
-        //System.out.println("sort by score");
-        // Sort by score
-        if (!trackScores) {
-          throw new IllegalArgumentException("cannot sort by relevance within group: trackScores=false");
-        }
-        collector = TopScoreDocCollector.create(numDocsInGroup);
-      } else {
-        // Sort by fields
-        collector = TopFieldCollector.create(withinGroupSort, numDocsInGroup, fillSortFields, trackScores, trackMaxScore);
-      }
-
-      LeafCollector leafCollector = collector.getLeafCollector(og.readerContext);
-      leafCollector.setScorer(fakeScorer);
-      for(int docIDX=0;docIDX<numChildDocs;docIDX++) {
-        //System.out.println("docIDX=" + docIDX + " vs " + og.docs[slot].length);
-        final int doc = og.docs[slot][docIDX];
-        fakeScorer.doc = doc;
-        if (trackScores) {
-          fakeScorer.score = og.scores[slot][docIDX];
-        }
-        leafCollector.collect(doc);
-      }
-      totalGroupedHitCount += numChildDocs;
-
-      final Object[] groupSortValues;
-
-      if (fillSortFields) {
-        groupSortValues = new Object[comparators.length];
-        for(int sortFieldIDX=0;sortFieldIDX<comparators.length;sortFieldIDX++) {
-          groupSortValues[sortFieldIDX] = comparators[sortFieldIDX].value(og.slot);
-        }
-      } else {
-        groupSortValues = null;
-      }
-
-      final TopDocs topDocs = collector.topDocs(withinGroupOffset, numDocsInGroup);
-
-      groups[groupIDX-offset] = new GroupDocs<>(og.score,
-                                                       topDocs.getMaxScore(),
-                                                       numChildDocs,
-                                                       topDocs.scoreDocs,
-                                                       og.doc,
-                                                       groupSortValues);
-    }
-
-    return new TopGroups<>(new TopGroups<>(sort.getSort(),
-                                                       withinGroupSort == null ? null : withinGroupSort.getSort(),
-                                                       0, totalGroupedHitCount, groups, maxScore),
-                                  totalHitCount);
-  }
-
-  /** Returns the TopGroups for the specified BlockJoinQuery.
-   *  The groupValue of each GroupDocs will be the parent docID for that group.
-   *  The number of documents within each group
-   *  equals to the total number of matched child documents for that group.
-   *  Returns null if no groups matched.
-   *
-   * @param query Search query
-   * @param withinGroupSort Sort criteria within groups
-   * @param offset Parent docs offset
-   * @param withinGroupOffset Offset within each group of child docs
-   * @param fillSortFields Specifies whether to add sort fields or not
-   * @return TopGroups for specified query
-   * @throws IOException if there is a low-level I/O error
-   */
-  public TopGroups<Integer> getTopGroupsWithAllChildDocs(ToParentBlockJoinQuery query, Sort withinGroupSort, int offset,
-                                                         int withinGroupOffset, boolean fillSortFields)
-    throws IOException {
-
-    return getTopGroups(query, withinGroupSort, offset, Integer.MAX_VALUE, withinGroupOffset, fillSortFields);
-  }
-  
-  /**
-   * Returns the highest score across all collected parent hits, as long as
-   * <code>trackMaxScores=true</code> was passed
-   * {@link #ToParentBlockJoinCollector(Sort, int, boolean, boolean) on
-   * construction}. Else, this returns <code>Float.NaN</code>
-   */
-  public float getMaxScore() {
-    return maxScore;
-  }
-
-  @Override
-  public boolean needsScores() {
-    // needed so that eg. BooleanQuery does not rewrite its MUST clauses to
-    // FILTER since the filter scorers are hidden in Scorer.getChildren().
-    return true;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinIndexSearcher.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinIndexSearcher.java b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinIndexSearcher.java
deleted file mode 100644
index 84a02a3..0000000
--- a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinIndexSearcher.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.search.join;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.search.Collector;
-import org.apache.lucene.search.DocIdSetIterator;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.LeafCollector;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.Weight;
-import org.apache.lucene.util.Bits;
-
-/**
- * An {@link IndexSearcher} to use in conjunction with
- * {@link ToParentBlockJoinCollector}.
- */
-public class ToParentBlockJoinIndexSearcher extends IndexSearcher {
-
-  /** Creates a searcher searching the provided index. Search on individual
-   *  segments will be run in the provided {@link ExecutorService}.
-   * @see IndexSearcher#IndexSearcher(IndexReader, ExecutorService) */
-  public ToParentBlockJoinIndexSearcher(IndexReader r, ExecutorService executor) {
-    super(r, executor);
-  }
-
-  /** Creates a searcher searching the provided index.
-   * @see IndexSearcher#IndexSearcher(IndexReader) */
-  public ToParentBlockJoinIndexSearcher(IndexReader r) {
-    super(r);
-  }
-
-  @Override
-  protected void search(List<LeafReaderContext> leaves, Weight weight, Collector collector) throws IOException {
-    for (LeafReaderContext ctx : leaves) { // search each subreader
-      // we force the use of Scorer (not BulkScorer) to make sure
-      // that the scorer passed to LeafCollector.setScorer supports
-      // Scorer.getChildren
-      Scorer scorer = weight.scorer(ctx);
-      if (scorer != null) {
-        final LeafCollector leafCollector = collector.getLeafCollector(ctx);
-        leafCollector.setScorer(scorer);
-        final Bits liveDocs = ctx.reader().getLiveDocs();
-        final DocIdSetIterator it = scorer.iterator();
-        for (int doc = it.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = it.nextDoc()) {
-          if (liveDocs == null || liveDocs.get(doc)) {
-            leafCollector.collect(doc);
-          }
-        }
-      }
-    }
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java
index 432ebcc..2837423 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java
@@ -20,18 +20,19 @@ import java.io.IOException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Locale;
+
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.search.FilterWeight;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.FilterWeight;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.ScorerSupplier;
+import org.apache.lucene.search.TwoPhaseIterator;
 import org.apache.lucene.search.Weight;
-import org.apache.lucene.search.grouping.TopGroups;
-import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.BitSet;
 
 /**
@@ -57,20 +58,6 @@ import org.apache.lucene.util.BitSet;
  * documents: the wrapped child query must never
  * return a parent document.</p>
  *
- * If you'd like to retrieve {@link TopGroups} for the
- * resulting query, use the {@link ToParentBlockJoinCollector}.
- * Note that this is not necessary, ie, if you simply want
- * to collect the parent documents and don't need to see
- * which child documents matched under that parent, then
- * you can use any collector.
- *
- * <p><b>NOTE</b>: If the overall query contains parent-only
- * matches, for example you OR a parent-only query with a
- * joined child-only query, then the resulting collected documents
- * will be correct, however the {@link TopGroups} you get
- * from {@link ToParentBlockJoinCollector} will not contain every
- * child for parents that had matched.
- *
  * <p>See {@link org.apache.lucene.search.join} for an
  * overview. </p>
  *
@@ -90,7 +77,7 @@ public class ToParentBlockJoinQuery extends Query {
   private final ScoreMode scoreMode;
 
   /** Create a ToParentBlockJoinQuery.
-   * 
+   *
    * @param childQuery Query matching child documents.
    * @param parentsFilter Filter identifying the parent documents.
    * @param scoreMode How to aggregate multiple child scores
@@ -116,7 +103,7 @@ public class ToParentBlockJoinQuery extends Query {
   public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
     return new BlockJoinWeight(this, childQuery.createWeight(searcher, needsScores, boost), parentsFilter, needsScores ? scoreMode : ScoreMode.None);
   }
-  
+
   /** Return our child query. */
   public Query getChildQuery() {
     return childQuery;
@@ -132,33 +119,44 @@ public class ToParentBlockJoinQuery extends Query {
       this.scoreMode = scoreMode;
     }
 
-    // NOTE: acceptDocs applies (and is checked) only in the
-    // parent document space
     @Override
-    public Scorer scorer(LeafReaderContext readerContext) throws IOException {
-
-      final Scorer childScorer = in.scorer(readerContext);
-      if (childScorer == null) {
-        // No matches
+    public Scorer scorer(LeafReaderContext context) throws IOException {
+      final ScorerSupplier scorerSupplier = scorerSupplier(context);
+      if (scorerSupplier == null) {
         return null;
       }
+      return scorerSupplier.get(false);
+    }
 
-      final int firstChildDoc = childScorer.iterator().nextDoc();
-      if (firstChildDoc == DocIdSetIterator.NO_MORE_DOCS) {
-        // No matches
+    // NOTE: acceptDocs applies (and is checked) only in the
+    // parent document space
+    @Override
+    public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
+      final ScorerSupplier childScorerSupplier = in.scorerSupplier(context);
+      if (childScorerSupplier == null) {
         return null;
       }
 
       // NOTE: this does not take accept docs into account, the responsibility
       // to not match deleted docs is on the scorer
-      final BitSet parents = parentsFilter.getBitSet(readerContext);
-
+      final BitSet parents = parentsFilter.getBitSet(context);
       if (parents == null) {
         // No matches
         return null;
       }
 
-      return new BlockJoinScorer(this, childScorer, parents, firstChildDoc, scoreMode);
+      return new ScorerSupplier() {
+
+        @Override
+        public Scorer get(boolean randomAccess) throws IOException {
+          return new BlockJoinScorer(BlockJoinWeight.this, childScorerSupplier.get(randomAccess), parents, scoreMode);
+        }
+
+        @Override
+        public long cost() {
+          return childScorerSupplier.cost();
+        }
+      };
     }
 
     @Override
@@ -170,259 +168,191 @@ public class ToParentBlockJoinQuery extends Query {
       return Explanation.noMatch("Not a match");
     }
   }
-  
-  /** 
-   * Ascendant for {@link ToParentBlockJoinQuery}'s scorer. 
-   * @lucene.experimental it might be removed at <b>6.0</b>
-   * */
-  public static abstract class ChildrenMatchesScorer extends Scorer{
-    
-    /** inherited constructor */
-    protected ChildrenMatchesScorer(Weight weight) {
-      super(weight);
-    }
-    
-    /** 
-     * enables children matches recording 
-     * */
-    public abstract void trackPendingChildHits() ;
-    
-    /**
-     * reports matched children 
-     * @return number of recorded matched children docs 
-     * */
-    public abstract int getChildCount() ;
-    
-    /**
-     * reports matched children 
-     * @param other array for recording matching children docs of next parent,
-     * it might be null (that's slower) or the same array which was returned 
-     * from the previous call
-     * @return array with {@link #getChildCount()} matched children docnums
-     *  */
-    public abstract int[] swapChildDocs(int[] other);
-  }
-  
-  static class BlockJoinScorer extends ChildrenMatchesScorer {
-    private final Scorer childScorer;
+
+  private static class ParentApproximation extends DocIdSetIterator {
+
+    private final DocIdSetIterator childApproximation;
     private final BitSet parentBits;
-    private final ScoreMode scoreMode;
-    private int parentDoc = -1;
-    private int prevParentDoc;
-    private float parentScore;
-    private int parentFreq;
-    private int nextChildDoc;
-    private int[] pendingChildDocs;
-    private float[] pendingChildScores;
-    private int childDocUpto;
-
-    public BlockJoinScorer(Weight weight, Scorer childScorer, BitSet parentBits, int firstChildDoc, ScoreMode scoreMode) {
-      super(weight);
-      //System.out.println("Q.init firstChildDoc=" + firstChildDoc);
+    private int doc = -1;
+
+    ParentApproximation(DocIdSetIterator childApproximation, BitSet parentBits) {
+      this.childApproximation = childApproximation;
       this.parentBits = parentBits;
-      this.childScorer = childScorer;
-      this.scoreMode = scoreMode;
-      nextChildDoc = firstChildDoc;
     }
 
     @Override
-    public Collection<ChildScorer> getChildren() {
-      return Collections.singleton(new ChildScorer(childScorer, "BLOCK_JOIN"));
+    public int docID() {
+      return doc;
     }
 
     @Override
-    public int getChildCount() {
-      return childDocUpto;
-    }
-
-    int getParentDoc() {
-      return parentDoc;
+    public int nextDoc() throws IOException {
+      return advance(doc + 1);
     }
 
     @Override
-    public int[] swapChildDocs(int[] other) {
-      final int[] ret = pendingChildDocs;
-      if (other == null) {
-        pendingChildDocs = new int[5];
-      } else {
-        pendingChildDocs = other;
+    public int advance(int target) throws IOException {
+      if (target >= parentBits.length()) {
+        return doc = NO_MORE_DOCS;
       }
-      return ret;
-    }
-
-    float[] swapChildScores(float[] other) {
-      if (scoreMode == ScoreMode.None) {
-        throw new IllegalStateException("ScoreMode is None; you must pass trackScores=false to ToParentBlockJoinCollector");
+      final int firstChildTarget = target == 0 ? 0 : parentBits.prevSetBit(target - 1) + 1;
+      int childDoc = childApproximation.docID();
+      if (childDoc < firstChildTarget) {
+        childDoc = childApproximation.advance(firstChildTarget);
       }
-      final float[] ret = pendingChildScores;
-      if (other == null) {
-        pendingChildScores = new float[5];
-      } else {
-        pendingChildScores = other;
+      if (childDoc >= parentBits.length() - 1) {
+        return doc = NO_MORE_DOCS;
       }
-      return ret;
+      return doc = parentBits.nextSetBit(childDoc + 1);
     }
 
     @Override
-    public DocIdSetIterator iterator() {
-      return new DocIdSetIterator() {
-        final DocIdSetIterator childIt = childScorer.iterator();
-
-        @Override
-        public int nextDoc() throws IOException {
-          //System.out.println("Q.nextDoc() nextChildDoc=" + nextChildDoc);
-          if (nextChildDoc == NO_MORE_DOCS) {
-            //System.out.println("  end");
-            return parentDoc = NO_MORE_DOCS;
-          }
-
-          // Gather all children sharing the same parent as
-          // nextChildDoc
-
-          parentDoc = parentBits.nextSetBit(nextChildDoc);
-
-          // Parent & child docs are supposed to be
-          // orthogonal:
-          checkOrthogonal(nextChildDoc, parentDoc);
-
-          //System.out.println("  parentDoc=" + parentDoc);
-          assert parentDoc != DocIdSetIterator.NO_MORE_DOCS;
-
-          float totalScore = 0;
-          float maxScore = Float.NEGATIVE_INFINITY;
-          float minScore = Float.POSITIVE_INFINITY;
-
-          childDocUpto = 0;
-          parentFreq = 0;
-          do {
-
-            //System.out.println("  c=" + nextChildDoc);
-            if (pendingChildDocs != null && pendingChildDocs.length == childDocUpto) {
-              pendingChildDocs = ArrayUtil.grow(pendingChildDocs);
-            }
-            if (pendingChildScores != null && scoreMode != ScoreMode.None && pendingChildScores.length == childDocUpto) {
-              pendingChildScores = ArrayUtil.grow(pendingChildScores);
-            }
-            if (pendingChildDocs != null) {
-              pendingChildDocs[childDocUpto] = nextChildDoc;
-            }
-            if (scoreMode != ScoreMode.None) {
-              // TODO: specialize this into dedicated classes per-scoreMode
-              final float childScore = childScorer.score();
-              final int childFreq = childScorer.freq();
-              if (pendingChildScores != null) {
-                pendingChildScores[childDocUpto] = childScore;
-              }
-              maxScore = Math.max(childScore, maxScore);
-              minScore = Math.min(childScore, minScore);
-              totalScore += childScore;
-              parentFreq += childFreq;
-            }
-            childDocUpto++;
-            nextChildDoc = childIt.nextDoc();
-          } while (nextChildDoc < parentDoc);
-
-          // Parent & child docs are supposed to be
-          // orthogonal:
-          checkOrthogonal(nextChildDoc, parentDoc);
-
-          switch(scoreMode) {
-          case Avg:
-            parentScore = totalScore / childDocUpto;
-            break;
-          case Max:
-            parentScore = maxScore;
-            break;
-          case Min:
-            parentScore = minScore;
-            break;
-          case Total:
-            parentScore = totalScore;
-            break;
-          case None:
-            break;
-          }
-
-          //System.out.println("  return parentDoc=" + parentDoc + " childDocUpto=" + childDocUpto);
-          return parentDoc;
-        }
-
-        @Override
-        public int advance(int parentTarget) throws IOException {
-
-          //System.out.println("Q.advance parentTarget=" + parentTarget);
-          if (parentTarget == NO_MORE_DOCS) {
-            return parentDoc = NO_MORE_DOCS;
-          }
-
-          if (parentTarget == 0) {
-            // Callers should only be passing in a docID from
-            // the parent space, so this means this parent
-            // has no children (it got docID 0), so it cannot
-            // possibly match.  We must handle this case
-            // separately otherwise we pass invalid -1 to
-            // prevSetBit below:
-            return nextDoc();
-          }
+    public long cost() {
+      return childApproximation.cost();
+    }
+  }
 
-          prevParentDoc = parentBits.prevSetBit(parentTarget-1);
+  private static class ParentTwoPhase extends TwoPhaseIterator {
 
-          //System.out.println("  rolled back to prevParentDoc=" + prevParentDoc + " vs parentDoc=" + parentDoc);
-          assert prevParentDoc >= parentDoc;
-          if (prevParentDoc > nextChildDoc) {
-            nextChildDoc = childIt.advance(prevParentDoc);
-            // System.out.println("  childScorer advanced to child docID=" + nextChildDoc);
-          //} else {
-            //System.out.println("  skip childScorer advance");
-          }
+    private final ParentApproximation parentApproximation;
+    private final DocIdSetIterator childApproximation;
+    private final TwoPhaseIterator childTwoPhase;
 
-          // Parent & child docs are supposed to be orthogonal:
-          checkOrthogonal(nextChildDoc, prevParentDoc);
+    ParentTwoPhase(ParentApproximation parentApproximation, TwoPhaseIterator childTwoPhase) {
+      super(parentApproximation);
+      this.parentApproximation = parentApproximation;
+      this.childApproximation = childTwoPhase.approximation();
+      this.childTwoPhase = childTwoPhase;
+    }
 
-          final int nd = nextDoc();
-          //System.out.println("  return nextParentDoc=" + nd);
-          return nd;
+    @Override
+    public boolean matches() throws IOException {
+      assert childApproximation.docID() < parentApproximation.docID();
+      do {
+        if (childTwoPhase.matches()) {
+          return true;
         }
+      } while (childApproximation.nextDoc() < parentApproximation.docID());
+      return false;
+    }
 
-        @Override
-        public int docID() {
-          return parentDoc;
-        }
+    @Override
+    public float matchCost() {
+      // TODO: how could we compute a match cost?
+      return childTwoPhase.matchCost() + 10;
+    }
+  }
 
-        @Override
-        public long cost() {
-          return childIt.cost();
-        }
-      };
+  static class BlockJoinScorer extends Scorer {
+    private final Scorer childScorer;
+    private final BitSet parentBits;
+    private final ScoreMode scoreMode;
+    private final DocIdSetIterator childApproximation;
+    private final TwoPhaseIterator childTwoPhase;
+    private final ParentApproximation parentApproximation;
+    private final ParentTwoPhase parentTwoPhase;
+    private float score;
+    private int freq;
+
+    public BlockJoinScorer(Weight weight, Scorer childScorer, BitSet parentBits, ScoreMode scoreMode) {
+      super(weight);
+      //System.out.println("Q.init firstChildDoc=" + firstChildDoc);
+      this.parentBits = parentBits;
+      this.childScorer = childScorer;
+      this.scoreMode = scoreMode;
+      childTwoPhase = childScorer.twoPhaseIterator();
+      if (childTwoPhase == null) {
+        childApproximation = childScorer.iterator();
+        parentApproximation = new ParentApproximation(childApproximation, parentBits);
+        parentTwoPhase = null;
+      } else {
+        childApproximation = childTwoPhase.approximation();
+        parentApproximation = new ParentApproximation(childTwoPhase.approximation(), parentBits);
+        parentTwoPhase = new ParentTwoPhase(parentApproximation, childTwoPhase);
+      }
     }
 
-    private void checkOrthogonal(int childDoc, int parentDoc) {
-      if (childDoc==parentDoc) {
-        throw new IllegalStateException("Child query must not match same docs with parent filter. "
-             + "Combine them as must clauses (+) to find a problem doc. "
-             + "docId=" + nextChildDoc + ", " + childScorer.getClass());
-        
+    @Override
+    public Collection<ChildScorer> getChildren() {
+      return Collections.singleton(new ChildScorer(childScorer, "BLOCK_JOIN"));
+    }
+
+    @Override
+    public DocIdSetIterator iterator() {
+      if (parentTwoPhase == null) {
+        // the approximation is exact
+        return parentApproximation;
+      } else {
+        return TwoPhaseIterator.asDocIdSetIterator(parentTwoPhase);
       }
     }
 
     @Override
+    public TwoPhaseIterator twoPhaseIterator() {
+      return parentTwoPhase;
+    }
+
+    @Override
     public int docID() {
-      return parentDoc;
+      return parentApproximation.docID();
     }
 
     @Override
     public float score() throws IOException {
-      return parentScore;
+      setScoreAndFreq();
+      return score;
     }
     
     @Override
-    public int freq() {
-      return parentFreq;
+    public int freq() throws IOException {
+      setScoreAndFreq();
+      return freq;
+    }
+
+    private void setScoreAndFreq() throws IOException {
+      if (childApproximation.docID() >= parentApproximation.docID()) {
+        return;
+      }
+      double score = scoreMode == ScoreMode.None ? 0 : childScorer.score();
+      int freq = 1;
+      while (childApproximation.nextDoc() < parentApproximation.docID()) {
+        if (childTwoPhase == null || childTwoPhase.matches()) {
+          final float childScore = childScorer.score();
+          freq += 1;
+          switch (scoreMode) {
+            case Total:
+            case Avg:
+              score += childScore;
+              break;
+            case Min:
+              score = Math.min(score, childScore);
+              break;
+            case Max:
+              score = Math.min(score, childScore);
+              break;
+            case None:
+              break;
+            default:
+              throw new AssertionError();
+          }
+        }
+      }
+      if (childApproximation.docID() == parentApproximation.docID() && (childTwoPhase == null || childTwoPhase.matches())) {
+        throw new IllegalStateException("Child query must not match same docs with parent filter. "
+            + "Combine them as must clauses (+) to find a problem doc. "
+            + "docId=" + parentApproximation.docID() + ", " + childScorer.getClass());
+      }
+      if (scoreMode == ScoreMode.Avg) {
+        score /= freq;
+      }
+      this.score = (float) score;
+      this.freq = freq;
     }
 
     public Explanation explain(LeafReaderContext context, Weight childWeight) throws IOException {
+      int prevParentDoc = parentBits.prevSetBit(parentApproximation.docID() - 1);
       int start = context.docBase + prevParentDoc + 1; // +1 b/c prevParentDoc is previous parent doc
-      int end = context.docBase + parentDoc - 1; // -1 b/c parentDoc is parent doc
+      int end = context.docBase + parentApproximation.docID() - 1; // -1 b/c parentDoc is parent doc
 
       Explanation bestChild = null;
       int matches = 0;
@@ -436,21 +366,11 @@ public class ToParentBlockJoinQuery extends Query {
         }
       }
 
+      assert freq() == matches;
       return Explanation.match(score(), String.format(Locale.ROOT,
           "Score based on %d child docs in range from %d to %d, best match:", matches, start, end), bestChild
       );
     }
-
-    /**
-     * Instructs this scorer to keep track of the child docIds and score ids for retrieval purposes.
-     */
-    @Override
-    public void trackPendingChildHits() {
-      pendingChildDocs = new int[5];
-      if (scoreMode != ScoreMode.None) {
-        pendingChildScores = new float[5];
-      }
-    }
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/join/src/java/org/apache/lucene/search/join/package-info.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/package-info.java b/lucene/join/src/java/org/apache/lucene/search/join/package-info.java
index 6133f99..7c7ff67 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/package-info.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/package-info.java
@@ -41,14 +41,25 @@
  *   resulting query can then be used as a clause in any query that
  *   matches parent.</p>
  * 
- * <p>If you only care about the parent documents matching the query, you
- *   can use any collector to collect the parent hits, but if you'd also
- *   like to see which child documents match for each parent document,
- *   use the {@link org.apache.lucene.search.join.ToParentBlockJoinCollector} to collect the hits. Once the
- *   search is done, you retrieve a {@link
- *   org.apache.lucene.search.grouping.TopGroups} instance from the
- *   {@link org.apache.lucene.search.join.ToParentBlockJoinCollector#getTopGroups ToParentBlockJoinCollector.getTopGroups()} method.</p>
- * 
+ * <p>If you care about what child documents matched for each parent document,
+ *    then use the {@link org.apache.lucene.search.join.ParentChildrenBlockJoinQuery} query to
+ *    per matched parent document retrieve the child documents that caused to match the
+ *    parent document in first place. This query should be used after your main query
+ *    has been executed. For each hit execute the the
+ *    {@link org.apache.lucene.search.join.ParentChildrenBlockJoinQuery} query </p>
+ * <pre class="prettyprint">
+ *   TopDocs results = searcher.search(mainQuery, 10);
+ *   for (int i = 0; i &lt; results.scoreDocs.length; i++) {
+ *     ScoreDoc scoreDoc = results.scoreDocs[i];
+ *
+ *     // Run ParentChildrenBlockJoinQuery to figure out the top matching child docs:
+ *     ParentChildrenBlockJoinQuery parentChildrenBlockJoinQuery =
+ *       new ParentChildrenBlockJoinQuery(parentFilter, childQuery, scoreDoc.doc);
+ *     TopDocs topChildResults = searcher.search(parentChildrenBlockJoinQuery, 3);
+ *     // Process top child hits...
+ *   }
+ * </pre>
+ *
  * <p>To map/join in the opposite direction, use {@link
  *   org.apache.lucene.search.join.ToChildBlockJoinQuery}.  This wraps
  *   any query matching parent documents, creating the joined query
@@ -80,9 +91,9 @@
  * </p>
  * <pre class="prettyprint">
  *   String fromField = "from"; // Name of the from field
- *   boolean multipleValuesPerDocument = false; // Set only yo true in the case when your fromField has multiple values per document in your index
+ *   boolean multipleValuesPerDocument = false; // Set only to true in the case when your fromField has multiple values per document in your index
  *   String toField = "to"; // Name of the to field
- *   ScoreMode scoreMode = ScoreMode.Max // Defines how the scores are translated into the other side of the join.
+ *   ScoreMode scoreMode = ScoreMode.Max; // Defines how the scores are translated into the other side of the join.
  *   Query fromQuery = new TermQuery(new Term("content", searchTerm)); // Query executed to collect from values to join to the to values
  * 
  *   Query joinQuery = JoinUtil.createJoinQuery(fromField, multipleValuesPerDocument, toField, fromQuery, fromSearcher, scoreMode);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java b/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java
index cf21fa4..a13e66f 100644
--- a/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java
+++ b/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java
@@ -22,15 +22,18 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.lucene.analysis.MockAnalyzer;
 import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
 import org.apache.lucene.document.Field.Store;
 import org.apache.lucene.document.IntPoint;
-import org.apache.lucene.document.Field;
 import org.apache.lucene.document.NumericDocValuesField;
 import org.apache.lucene.document.SortedDocValuesField;
 import org.apache.lucene.document.StoredField;
@@ -47,30 +50,8 @@ import org.apache.lucene.index.PostingsEnum;
 import org.apache.lucene.index.RandomIndexWriter;
 import org.apache.lucene.index.ReaderUtil;
 import org.apache.lucene.index.Term;
+import org.apache.lucene.search.*;
 import org.apache.lucene.search.BooleanClause.Occur;
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.BoostQuery;
-import org.apache.lucene.search.CheckHits;
-import org.apache.lucene.search.DocIdSetIterator;
-import org.apache.lucene.search.Explanation;
-import org.apache.lucene.search.FieldDoc;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.search.MatchNoDocsQuery;
-import org.apache.lucene.search.PrefixQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.QueryUtils;
-import org.apache.lucene.search.RandomApproximationQuery;
-import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.SortField;
-import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.search.TopDocs;
-import org.apache.lucene.search.Weight;
-import org.apache.lucene.search.grouping.GroupDocs;
-import org.apache.lucene.search.grouping.TopGroups;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.BitSet;
 import org.apache.lucene.util.Bits;
@@ -157,7 +138,7 @@ public class TestBlockJoin extends LuceneTestCase {
 
     IndexReader r = DirectoryReader.open(w);
     w.close();
-    IndexSearcher s = new IndexSearcher(r);
+    IndexSearcher s = newSearcher(r);
     BitSetProducer parentsFilter = new QueryBitSetProducer(new TermQuery(new Term("docType", "resume")));
     CheckJoinIndex.check(r, parentsFilter);
 
@@ -170,18 +151,21 @@ public class TestBlockJoin extends LuceneTestCase {
     BooleanQuery.Builder fullQuery = new BooleanQuery.Builder();
     fullQuery.add(new BooleanClause(childJoinQuery, Occur.MUST));
     fullQuery.add(new BooleanClause(new MatchAllDocsQuery(), Occur.MUST));
-    ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(Sort.RELEVANCE, 1, true, true);
-    s.search(fullQuery.build(), c);
-    TopGroups<Integer> results = c.getTopGroups(childJoinQuery, null, 0, 10, 0, true);
-    assertFalse(Float.isNaN(results.maxScore));
-    assertEquals(1, results.totalGroupedHitCount);
-    assertEquals(1, results.groups.length);
-    final GroupDocs<Integer> group = results.groups[0];
-    Document childDoc = s.doc(group.scoreDocs[0].doc);
-    assertEquals("java", childDoc.get("skill"));
-    assertNotNull(group.groupValue);
-    Document parentDoc = s.doc(group.groupValue);
-    assertEquals("Lisa", parentDoc.get("name"));
+    TopDocs topDocs = s.search(fullQuery.build(), 2);
+    assertEquals(2, topDocs.totalHits);
+    assertEquals(asSet("Lisa", "Frank"),
+        asSet(s.doc(topDocs.scoreDocs[0].doc).get("name"), s.doc(topDocs.scoreDocs[1].doc).get("name")));
+
+    ParentChildrenBlockJoinQuery childrenQuery =
+        new ParentChildrenBlockJoinQuery(parentsFilter, childQuery.build(), topDocs.scoreDocs[0].doc);
+    TopDocs matchingChildren = s.search(childrenQuery, 1);
+    assertEquals(1, matchingChildren.totalHits);
+    assertEquals("java", s.doc(matchingChildren.scoreDocs[0].doc).get("skill"));
+
+    childrenQuery = new ParentChildrenBlockJoinQuery(parentsFilter, childQuery.build(), topDocs.scoreDocs[1].doc);
+    matchingChildren = s.search(childrenQuery, 1);
+    assertEquals(1, matchingChildren.totalHits);
+    assertEquals("java", s.doc(matchingChildren.scoreDocs[0].doc).get("skill"));
 
     r.close();
     dir.close();
@@ -207,8 +191,7 @@ public class TestBlockJoin extends LuceneTestCase {
 
     IndexReader r = w.getReader();
     w.close();
-    IndexSearcher s = new ToParentBlockJoinIndexSearcher(r);
-    //IndexSearcher s = newSearcher(r, false);
+    IndexSearcher s = newSearcher(r, false);
     //IndexSearcher s = new IndexSearcher(r);
 
     // Create a filter that defines "parent" documents in the index - in this case resumes
@@ -232,23 +215,21 @@ public class TestBlockJoin extends LuceneTestCase {
     fullQuery.add(new BooleanClause(parentQuery, Occur.SHOULD));
     fullQuery.add(new BooleanClause(childJoinQuery, Occur.SHOULD));
 
-    ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(Sort.RELEVANCE, 1, true, true);
-    s.search(fullQuery.build(), c);
-    TopGroups<Integer> results = c.getTopGroups(childJoinQuery, null, 0, 10, 0, true);
-    assertEquals(1, results.totalGroupedHitCount);
-    assertEquals(1, results.groups.length);
-
-    final GroupDocs<Integer> group = results.groups[0];
-    assertEquals(1, group.totalHits);
-    assertFalse(Float.isNaN(group.score));
-
-    Document childDoc = s.doc(group.scoreDocs[0].doc);
-    //System.out.println("  doc=" + group.scoreDocs[0].doc);
-    assertEquals("java", childDoc.get("skill"));
-    assertNotNull(group.groupValue);
-    Document parentDoc = s.doc(group.groupValue);
-    assertEquals("Lisa", parentDoc.get("name"));
-    
+    final TopDocs topDocs = s.search(fullQuery.build(), 2);
+    assertEquals(2, topDocs.totalHits);
+    assertEquals(asSet("Lisa", "Frank"),
+        asSet(s.doc(topDocs.scoreDocs[0].doc).get("name"), s.doc(topDocs.scoreDocs[1].doc).get("name")));
+
+    ParentChildrenBlockJoinQuery childrenQuery =
+        new ParentChildrenBlockJoinQuery(parentsFilter, childQuery.build(), topDocs.scoreDocs[0].doc);
+    TopDocs matchingChildren = s.search(childrenQuery, 1);
+    assertEquals(1, matchingChildren.totalHits);
+    assertEquals("java", s.doc(matchingChildren.scoreDocs[0].doc).get("skill"));
+
+    childrenQuery = new ParentChildrenBlockJoinQuery(parentsFilter, childQuery.build(), topDocs.scoreDocs[1].doc);
+    matchingChildren = s.search(childrenQuery, 1);
+    assertEquals(1, matchingChildren.totalHits);
+    assertEquals("java", s.doc(matchingChildren.scoreDocs[0].doc).get("skill"));
     
     r.close();
     dir.close();
@@ -297,30 +278,21 @@ public class TestBlockJoin extends LuceneTestCase {
     fullQuery.add(new BooleanClause(parentQuery, Occur.MUST));
     fullQuery.add(new BooleanClause(childJoinQuery, Occur.MUST));
 
-    ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(Sort.RELEVANCE, 1, true, true);
-
     CheckHits.checkHitCollector(random(), fullQuery.build(), "country", s, new int[] {2});
 
-    s.search(fullQuery.build(), c);
-
-    TopGroups<Integer> results = c.getTopGroups(childJoinQuery, null, 0, 10, 0, true);
-    assertFalse(Float.isNaN(results.maxScore));
+    TopDocs topDocs = s.search(fullQuery.build(), 1);
 
     //assertEquals(1, results.totalHitCount);
-    assertEquals(1, results.totalGroupedHitCount);
-    assertEquals(1, results.groups.length);
-
-    final GroupDocs<Integer> group = results.groups[0];
-    assertEquals(1, group.totalHits);
-    assertFalse(Float.isNaN(group.score));
-
-    Document childDoc = s.doc(group.scoreDocs[0].doc);
-    //System.out.println("  doc=" + group.scoreDocs[0].doc);
-    assertEquals("java", childDoc.get("skill"));
-    assertNotNull(group.groupValue);
-    Document parentDoc = s.doc(group.groupValue);
+    assertEquals(1, topDocs.totalHits);
+    Document parentDoc = s.doc(topDocs.scoreDocs[0].doc);
     assertEquals("Lisa", parentDoc.get("name"));
 
+    ParentChildrenBlockJoinQuery childrenQuery =
+        new ParentChildrenBlockJoinQuery(parentsFilter, childQuery.build(), topDocs.scoreDocs[0].doc);
+    TopDocs matchingChildren = s.search(childrenQuery, 1);
+    assertEquals(1, matchingChildren.totalHits);
+    assertEquals("java", s.doc(matchingChildren.scoreDocs[0].doc).get("skill"));
+
 
     //System.out.println("TEST: now test up");
 
@@ -333,7 +305,7 @@ public class TestBlockJoin extends LuceneTestCase {
     //System.out.println("FULL: " + fullChildQuery);
     TopDocs hits = s.search(fullChildQuery.build(), 10);
     assertEquals(1, hits.totalHits);
-    childDoc = s.doc(hits.scoreDocs[0].doc);
+    Document childDoc = s.doc(hits.scoreDocs[0].doc);
     //System.out.println("CHILD = " + childDoc + " docID=" + hits.scoreDocs[0].doc);
     assertEquals("java", childDoc.get("skill"));
     assertEquals(2007, childDoc.getField("year").numericValue());
@@ -347,72 +319,6 @@ public class TestBlockJoin extends LuceneTestCase {
     dir.close();
   }
 
-  public void testBugCausedByRewritingTwice() throws IOException {
-    final Directory dir = newDirectory();
-    final RandomIndexWriter w = new RandomIndexWriter(random(), dir);
-
-    final List<Document> docs = new ArrayList<>();
-
-    for (int i=0;i<10;i++) {
-      docs.clear();
-      docs.add(makeJob("ruby", i));
-      docs.add(makeJob("java", 2007));
-      docs.add(makeResume("Frank", "United States"));
-      w.addDocuments(docs);
-    }
-
-    IndexReader r = w.getReader();
-    w.close();
-    IndexSearcher s = newSearcher(r, false);
-
-    // Hacky: this causes the query to need 2 rewrite
-    // iterations:
-    BooleanQuery.Builder builder = new BooleanQuery.Builder();
-    builder.add(IntPoint.newExactQuery("year", 2007), BooleanClause.Occur.MUST);
-    Query qc = new Query() {
-      @Override
-      public Query rewrite(IndexReader reader) throws IOException {
-        return builder.build();
-      }
-
-      @Override
-      public String toString(String field) {
-        return "hack!";
-      }
-
-      @Override
-      public boolean equals(Object o) {
-        return o == this;
-      }
-
-      @Override
-      public int hashCode() {
-        return System.identityHashCode(this);
-      }
-    };
-
-    BitSetProducer parentsFilter = new QueryBitSetProducer(new TermQuery(new Term("docType", "resume")));
-    CheckJoinIndex.check(r, parentsFilter);
-
-    Query qw1 = qc.rewrite(r);
-    Query qw2 = qw1.rewrite(r);
-
-    assertNotSame(qc, qw1);
-    assertNotSame(qw1, qw2);
-
-    ToParentBlockJoinQuery qp = new ToParentBlockJoinQuery(qc, parentsFilter, ScoreMode.Max);
-    ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(Sort.RELEVANCE, 10, true, true);
-
-    s.search(qp, c);
-    TopGroups<Integer> groups = c.getTopGroups(qp, Sort.INDEXORDER, 0, 10, 0, true);
-    for (GroupDocs<Integer> group : groups.groups) {
-      assertEquals(1, group.totalHits);
-    }
-
-    r.close();
-    dir.close();
-  }
-
   protected Query skill(String skill) {
     return new TermQuery(new Term("skill", skill));
   }
@@ -612,6 +518,7 @@ public class TestBlockJoin extends LuceneTestCase {
     final Directory dir = newDirectory();
     final Directory joinDir = newDirectory();
 
+    final int maxNumChildrenPerParent = 20;
     final int numParentDocs = TestUtil.nextInt(random(), 100 * RANDOM_MULTIPLIER, 300 * RANDOM_MULTIPLIER);
     //final int numParentDocs = 30;
 
@@ -669,7 +576,7 @@ public class TestBlockJoin extends LuceneTestCase {
         System.out.println("  " + sb.toString());
       }
 
-      final int numChildDocs = TestUtil.nextInt(random(), 1, 20);
+      final int numChildDocs = TestUtil.nextInt(random(), 1, maxNumChildrenPerParent);
       for(int childDocID=0;childDocID<numChildDocs;childDocID++) {
         // Denormalize: copy all parent fields into child doc:
         Document childDoc = TestUtil.cloneDocument(parentDoc);
@@ -752,7 +659,7 @@ public class TestBlockJoin extends LuceneTestCase {
 
     final IndexSearcher s = newSearcher(r, false);
 
-    final IndexSearcher joinS = new IndexSearcher(joinR);
+    final IndexSearcher joinS = newSearcher(joinR);
 
     final BitSetProducer parentsFilter = new QueryBitSetProducer(new TermQuery(new Term("isParent", "x")));
     CheckJoinIndex.check(joinS.getIndexReader(), parentsFilter);
@@ -764,7 +671,7 @@ public class TestBlockJoin extends LuceneTestCase {
         System.out.println("TEST: iter=" + (1+iter) + " of " + iters);
       }
 
-      final Query childQuery;
+      Query childQuery;
       if (random().nextInt(3) == 2) {
         final int childFieldID = random().nextInt(childFields.length);
         childQuery = new TermQuery(new Term("child" + childFieldID,
@@ -799,6 +706,9 @@ public class TestBlockJoin extends LuceneTestCase {
                random().nextBoolean() ? BooleanClause.Occur.MUST : BooleanClause.Occur.MUST_NOT);
         childQuery = bq.build();
       }
+      if (random().nextBoolean()) {
+        childQuery = new RandomApproximationQuery(childQuery, random());
+      }
 
 
       final ScoreMode agg = ScoreMode.values()[random().nextInt(ScoreMode.values().length)];
@@ -880,55 +790,35 @@ public class TestBlockJoin extends LuceneTestCase {
         }
       }
 
-      final boolean trackScores;
-      final boolean trackMaxScore;
-      if (agg == ScoreMode.None) {
-        trackScores = false;
-        trackMaxScore = false;
-      } else {
-        trackScores = random().nextBoolean();
-        trackMaxScore = random().nextBoolean();
+      TopDocs joinedResults = joinS.search(parentJoinQuery, numParentDocs);
+      SortedMap<Integer, TopDocs> joinResults = new TreeMap<>();
+      for (ScoreDoc parentHit : joinedResults.scoreDocs) {
+        ParentChildrenBlockJoinQuery childrenQuery =
+            new ParentChildrenBlockJoinQuery(parentsFilter, childQuery, parentHit.doc);
+        TopDocs childTopDocs = joinS.search(childrenQuery, maxNumChildrenPerParent, childSort);
+        final Document parentDoc = joinS.doc(parentHit.doc);
+        joinResults.put(Integer.valueOf(parentDoc.get("parentID")), childTopDocs);
       }
-      final ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(parentSort, 10, trackScores, trackMaxScore);
-
-      joinS.search(parentJoinQuery, c);
 
       final int hitsPerGroup = TestUtil.nextInt(random(), 1, 20);
       //final int hitsPerGroup = 100;
-      final TopGroups<Integer> joinResults = c.getTopGroups(childJoinQuery, childSort, 0, hitsPerGroup, 0, true);
 
       if (VERBOSE) {
-        System.out.println("\nTEST: block join index gets " + (joinResults == null ? 0 : joinResults.groups.length) + " groups; hitsPerGroup=" + hitsPerGroup);
+        System.out.println("\nTEST: block join index gets " + (joinResults == null ? 0 : joinResults.size()) + " groups; hitsPerGroup=" + hitsPerGroup);
         if (joinResults != null) {
-          final GroupDocs<Integer>[] groups = joinResults.groups;
-          for(int groupIDX=0;groupIDX<groups.length;groupIDX++) {
-            final GroupDocs<Integer> group = groups[groupIDX];
-            if (group.groupSortValues != null) {
-              System.out.print("  ");
-              for(Object o : group.groupSortValues) {
-                if (o instanceof BytesRef) {
-                  System.out.print(((BytesRef) o).utf8ToString() + " ");
-                } else {
-                  System.out.print(o + " ");
-                }
-              }
-              System.out.println();
-            }
-
-            assertNotNull(group.groupValue);
-            final Document parentDoc = joinS.doc(group.groupValue);
-            System.out.println("  group parentID=" + parentDoc.get("parentID") + " (docID=" + group.groupValue + ")");
-            for(int hitIDX=0;hitIDX<group.scoreDocs.length;hitIDX++) {
-              final Document doc = joinS.doc(group.scoreDocs[hitIDX].doc);
-              //System.out.println("    score=" + group.scoreDocs[hitIDX].score + " childID=" + doc.get("childID") + " (docID=" + group.scoreDocs[hitIDX].doc + ")");
-              System.out.println("    childID=" + doc.get("childID") + " child0=" + doc.get("child0") + " (docID=" + group.scoreDocs[hitIDX].doc + ")");
+          for (Map.Entry<Integer, TopDocs> entry : joinResults.entrySet()) {
+            System.out.println("  group parentID=" + entry.getKey() + " (docID=" + entry.getKey() + ")");
+            for(ScoreDoc childHit : entry.getValue().scoreDocs) {
+              final Document doc = joinS.doc(childHit.doc);
+//              System.out.println("    score=" + childHit.score + " childID=" + doc.get("childID") + " (docID=" + childHit.doc + ")");
+              System.out.println("    childID=" + doc.get("childID") + " child0=" + doc.get("child0") + " (docID=" + childHit.doc + ")");
             }
           }
         }
       }
 
       if (results.totalHits == 0) {
-        assertNull(joinResults);
+        assertEquals(0, joinResults.size());
       } else {
         compareHits(r, joinR, results, joinResults);
         TopDocs b = joinS.search(childJoinQuery, 10);
@@ -1115,43 +1005,24 @@ public class TestBlockJoin extends LuceneTestCase {
     }
   }
 
-  private void compareHits(IndexReader r, IndexReader joinR, TopDocs results, TopGroups<Integer> joinResults) throws Exception {
-    // results is 'complete'; joinResults is a subset
-    int resultUpto = 0;
-    int joinGroupUpto = 0;
-
-    final ScoreDoc[] hits = results.scoreDocs;
-    final GroupDocs<Integer>[] groupDocs = joinResults.groups;
-
-    while(joinGroupUpto < groupDocs.length) {
-      final GroupDocs<Integer> group = groupDocs[joinGroupUpto++];
-      final ScoreDoc[] groupHits = group.scoreDocs;
-      assertNotNull(group.groupValue);
-      final Document parentDoc = joinR.document(group.groupValue);
-      final String parentID = parentDoc.get("parentID");
-      //System.out.println("GROUP groupDoc=" + group.groupDoc + " parent=" + parentDoc);
-      assertNotNull(parentID);
-      assertTrue(groupHits.length > 0);
-      for(int hitIDX=0;hitIDX<groupHits.length;hitIDX++) {
-        final Document nonJoinHit = r.document(hits[resultUpto++].doc);
-        final Document joinHit = joinR.document(groupHits[hitIDX].doc);
-        assertEquals(parentID,
-                     nonJoinHit.get("parentID"));
-        assertEquals(joinHit.get("childID"),
-                     nonJoinHit.get("childID"));
+  private void compareHits(IndexReader r, IndexReader joinR, TopDocs controlHits, Map<Integer, TopDocs> joinResults) throws Exception {
+    int currentParentID = -1;
+    int childHitSlot = 0;
+    TopDocs childHits = new TopDocs(0, new ScoreDoc[0], 0f);
+    for (ScoreDoc controlHit : controlHits.scoreDocs) {
+      Document controlDoc = r.document(controlHit.doc);
+      int parentID = Integer.valueOf(controlDoc.get("parentID"));
+      if (parentID != currentParentID) {
+        assertEquals(childHitSlot, childHits.scoreDocs.length);
+        currentParentID = parentID;
+        childHitSlot = 0;
+        childHits = joinResults.get(parentID);
       }
 
-      if (joinGroupUpto < groupDocs.length) {
-        // Advance non-join hit to the next parentID:
-        //System.out.println("  next joingroupUpto=" + joinGroupUpto + " gd.length=" + groupDocs.length + " parentID=" + parentID);
-        while(true) {
-          assertTrue(resultUpto < hits.length);
-          if (!parentID.equals(r.document(hits[resultUpto].doc).get("parentID"))) {
-            break;
-          }
-          resultUpto++;
-        }
-      }
+      String controlChildID = controlDoc.get("childID");
+      Document childDoc = joinR.document(childHits.scoreDocs[childHitSlot++].doc);
+      String childID = childDoc.get("childID");
+      assertEquals(controlChildID, childID);
     }
   }
 
@@ -1200,43 +1071,21 @@ public class TestBlockJoin extends LuceneTestCase {
     fullQuery.add(new BooleanClause(childJobJoinQuery, Occur.MUST));
     fullQuery.add(new BooleanClause(childQualificationJoinQuery, Occur.MUST));
 
-    // Collects all job and qualification child docs for
-    // each resume hit in the top N (sorted by score):
-    ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(Sort.RELEVANCE, 10, true, false);
-
-    s.search(fullQuery.build(), c);
-
-    // Examine "Job" children
-    TopGroups<Integer> jobResults = c.getTopGroups(childJobJoinQuery, null, 0, 10, 0, true);
-
-    //assertEquals(1, results.totalHitCount);
-    assertEquals(1, jobResults.totalGroupedHitCount);
-    assertEquals(1, jobResults.groups.length);
-
-    final GroupDocs<Integer> group = jobResults.groups[0];
-    assertEquals(1, group.totalHits);
-
-    Document childJobDoc = s.doc(group.scoreDocs[0].doc);
-    //System.out.println("  doc=" + group.scoreDocs[0].doc);
-    assertEquals("java", childJobDoc.get("skill"));
-    assertNotNull(group.groupValue);
-    Document parentDoc = s.doc(group.groupValue);
+    final TopDocs topDocs = s.search(fullQuery.build(), 10);
+    assertEquals(1, topDocs.totalHits);
+    Document parentDoc = s.doc(topDocs.scoreDocs[0].doc);
     assertEquals("Lisa", parentDoc.get("name"));
 
-    // Now Examine qualification children
-    TopGroups<Integer> qualificationResults = c.getTopGroups(childQualificationJoinQuery, null, 0, 10, 0, true);
-
-    assertEquals(1, qualificationResults.totalGroupedHitCount);
-    assertEquals(1, qualificationResults.groups.length);
-
-    final GroupDocs<Integer> qGroup = qualificationResults.groups[0];
-    assertEquals(1, qGroup.totalHits);
+    ParentChildrenBlockJoinQuery childrenQuery =
+        new ParentChildrenBlockJoinQuery(parentsFilter, childJobQuery.build(), topDocs.scoreDocs[0].doc);
+    TopDocs matchingChildren = s.search(childrenQuery, 1);
+    assertEquals(1, matchingChildren.totalHits);
+    assertEquals("java", s.doc(matchingChildren.scoreDocs[0].doc).get("skill"));
 
-    Document childQualificationDoc = s.doc(qGroup.scoreDocs[0].doc);
-    assertEquals("maths", childQualificationDoc.get("qualification"));
-    assertNotNull(qGroup.groupValue);
-    parentDoc = s.doc(qGroup.groupValue);
-    assertEquals("Lisa", parentDoc.get("name"));
+    childrenQuery = new ParentChildrenBlockJoinQuery(parentsFilter, childQualificationQuery.build(), topDocs.scoreDocs[0].doc);
+    matchingChildren = s.search(childrenQuery, 1);
+    assertEquals(1, matchingChildren.totalHits);
+    assertEquals("maths", s.doc(matchingChildren.scoreDocs[0].doc).get("qualification"));
 
     r.close();
     dir.close();
@@ -1300,165 +1149,6 @@ public class TestBlockJoin extends LuceneTestCase {
     dir.close();
   }
 
-  public void testGetTopGroups() throws Exception {
-
-    final Directory dir = newDirectory();
-    final RandomIndexWriter w = new RandomIndexWriter(random(), dir);
-
-    final List<Document> docs = new ArrayList<>();
-    docs.add(makeJob("ruby", 2005));
-    docs.add(makeJob("java", 2006));
-    docs.add(makeJob("java", 2010));
-    docs.add(makeJob("java", 2012));
-    Collections.shuffle(docs, random());
-    docs.add(makeResume("Frank", "United States"));
-
-    addSkillless(w);
-    w.addDocuments(docs);
-    addSkillless(w);
-
-    IndexReader r = w.getReader();
-    w.close();
-    IndexSearcher s = new IndexSearcher(r);
-
-    // Create a filter that defines "parent" documents in the index - in this case resumes
-    BitSetProducer parentsFilter = new QueryBitSetProducer(new TermQuery(new Term("docType", "resume")));
-    CheckJoinIndex.check(s.getIndexReader(), parentsFilter);
-
-    // Define child document criteria (finds an example of relevant work experience)
-    BooleanQuery.Builder childQuery = new BooleanQuery.Builder();
-    childQuery.add(new BooleanClause(new TermQuery(new Term("skill", "java")), Occur.MUST));
-    childQuery.add(new BooleanClause(IntPoint.newRangeQuery("year", 2006, 2011), Occur.MUST));
-
-    // Wrap the child document query to 'join' any matches
-    // up to corresponding parent:
-    ToParentBlockJoinQuery childJoinQuery = new ToParentBlockJoinQuery(childQuery.build(), parentsFilter, ScoreMode.Avg);
-
-    ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(Sort.RELEVANCE, 2, true, true);
-    s.search(childJoinQuery, c);
-
-    //Get all child documents within groups
-    @SuppressWarnings({"unchecked","rawtypes"})
-    TopGroups<Integer>[] getTopGroupsResults = new TopGroups[2];
-    getTopGroupsResults[0] = c.getTopGroups(childJoinQuery, null, 0, 10, 0, true);
-    getTopGroupsResults[1] = c.getTopGroupsWithAllChildDocs(childJoinQuery, null, 0, 0, true);
-
-    for (TopGroups<Integer> results : getTopGroupsResults) {
-      assertFalse(Float.isNaN(results.maxScore));
-      assertEquals(2, results.totalGroupedHitCount);
-      assertEquals(1, results.groups.length);
-
-      final GroupDocs<Integer> group = results.groups[0];
-      assertEquals(2, group.totalHits);
-      assertFalse(Float.isNaN(group.score));
-      assertNotNull(group.groupValue);
-      Document parentDoc = s.doc(group.groupValue);
-      assertEquals("Frank", parentDoc.get("name"));
-
-      assertEquals(2, group.scoreDocs.length); //all matched child documents collected
-
-      for (ScoreDoc scoreDoc : group.scoreDocs) {
-        Document childDoc = s.doc(scoreDoc.doc);
-        assertEquals("java", childDoc.get("skill"));
-        int year = Integer.parseInt(childDoc.get("year"));
-        assertTrue(year >= 2006 && year <= 2011);
-      }
-    }
-
-    //Get part of child documents
-    TopGroups<Integer> boundedResults = c.getTopGroups(childJoinQuery, null, 0, 1, 0, true);
-    assertFalse(Float.isNaN(boundedResults.maxScore));
-    assertEquals(2, boundedResults.totalGroupedHitCount);
-    assertEquals(1, boundedResults.groups.length);
-
-    final GroupDocs<Integer> group = boundedResults.groups[0];
-    assertEquals(2, group.totalHits);
-    assertFalse(Float.isNaN(group.score));
-    assertNotNull(group.groupValue);
-    Document parentDoc = s.doc(group.groupValue);
-    assertEquals("Frank", parentDoc.get("name"));
-
-    assertEquals(1, group.scoreDocs.length); //not all matched child documents collected
-
-    for (ScoreDoc scoreDoc : group.scoreDocs) {
-      Document childDoc = s.doc(scoreDoc.doc);
-      assertEquals("java", childDoc.get("skill"));
-      int year = Integer.parseInt(childDoc.get("year"));
-      assertTrue(year >= 2006 && year <= 2011);
-    }
-
-    r.close();
-    dir.close();
-  }
-
-  // LUCENE-4968
-  public void testSometimesParentOnlyMatches() throws Exception {
-    Directory d = newDirectory();
-    RandomIndexWriter w = new RandomIndexWriter(random(), d);
-    Document parent = new Document();
-    parent.add(new StoredField("parentID", "0"));
-    parent.add(new SortedDocValuesField("parentID", new BytesRef("0")));
-    parent.add(newTextField("parentText", "text", Field.Store.NO));
-    parent.add(newStringField("isParent", "yes", Field.Store.NO));
-
-    List<Document> docs = new ArrayList<>();
-
-    Document child = new Document();
-    docs.add(child);
-    child.add(new StoredField("childID", "0"));
-    child.add(newTextField("childText", "text", Field.Store.NO));
-
-    // parent last:
-    docs.add(parent);
-    w.addDocuments(docs);
-
-    docs.clear();
-
-    parent = new Document();
-    parent.add(newTextField("parentText", "text", Field.Store.NO));
-    parent.add(newStringField("isParent", "yes", Field.Store.NO));
-    parent.add(new StoredField("parentID", "1"));
-    parent.add(new SortedDocValuesField("parentID", new BytesRef("1")));
-
-    // parent last:
-    docs.add(parent);
-    w.addDocuments(docs);
-
-    IndexReader r = w.getReader();
-    w.close();
-
-    IndexSearcher searcher = new ToParentBlockJoinIndexSearcher(r);
-    Query childQuery = new TermQuery(new Term("childText", "text"));
-    BitSetProducer parentsFilter = new QueryBitSetProducer(new TermQuery(new Term("isParent", "yes")));
-    CheckJoinIndex.check(r, parentsFilter);
-    ToParentBlockJoinQuery childJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, ScoreMode.Avg);
-    BooleanQuery.Builder parentQuery = new BooleanQuery.Builder();
-    parentQuery.add(childJoinQuery, Occur.SHOULD);
-    parentQuery.add(new TermQuery(new Term("parentText", "text")), Occur.SHOULD);
-
-    ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(new Sort(new SortField("parentID", SortField.Type.STRING)),
-                                                                  10, true, true);
-    searcher.search(parentQuery.build(), c);
-    TopGroups<Integer> groups = c.getTopGroups(childJoinQuery, null, 0, 10, 0, false);
-
-    // Two parents:
-    assertEquals(2, groups.totalGroupCount.intValue());
-
-    // One child docs:
-    assertEquals(1, groups.totalGroupedHitCount);
-
-    GroupDocs<Integer> group = groups.groups[0];
-    Document doc = r.document(group.groupValue.intValue());
-    assertEquals("0", doc.get("parentID"));
-
-    group = groups.groups[1];
-    doc = r.document(group.groupValue.intValue());
-    assertEquals("1", doc.get("parentID"));
-
-    r.close();
-    d.close();
-  }
-
   // LUCENE-4968
   public void testChildQueryNeverMatches() throws Exception {
     Directory d = newDirectory();
@@ -1496,90 +1186,25 @@ public class TestBlockJoin extends LuceneTestCase {
     IndexReader r = w.getReader();
     w.close();
 
-    IndexSearcher searcher = new ToParentBlockJoinIndexSearcher(r);
+    IndexSearcher searcher = newSearcher(r);
 
     // never matches:
     Query childQuery = new TermQuery(new Term("childText", "bogus"));
     BitSetProducer parentsFilter = new QueryBitSetProducer(new TermQuery(new Term("isParent", "yes")));
     CheckJoinIndex.check(r, parentsFilter);
     ToParentBlockJoinQuery childJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, ScoreMode.Avg);
-    BooleanQuery.Builder parentQuery = new BooleanQuery.Builder();
-    parentQuery.add(childJoinQuery, Occur.SHOULD);
-    parentQuery.add(new TermQuery(new Term("parentText", "text")), Occur.SHOULD);
-
-    ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(new Sort(new SortField("parentID", SortField.Type.STRING)),
-                                                                  10, true, true);
-    searcher.search(parentQuery.build(), c);
-    TopGroups<Integer> groups = c.getTopGroups(childJoinQuery, null, 0, 10, 0, false);
-
-    // Two parents:
-    assertEquals(2, groups.totalGroupCount.intValue());
-
-    // One child docs:
-    assertEquals(0, groups.totalGroupedHitCount);
-
-    GroupDocs<Integer> group = groups.groups[0];
-    Document doc = r.document(group.groupValue.intValue());
-    assertEquals("0", doc.get("parentID"));
-
-    group = groups.groups[1];
-    doc = r.document(group.groupValue.intValue());
-    assertEquals("1", doc.get("parentID"));
-
-    r.close();
-    d.close();
-  }
-
-  // LUCENE-4968
-  public void testChildQueryMatchesParent() throws Exception {
-    Directory d = newDirectory();
-    RandomIndexWriter w = new RandomIndexWriter(random(), d);
-    Document parent = new Document();
-    parent.add(new StoredField("parentID", "0"));
-    parent.add(newTextField("parentText", "text", Field.Store.NO));
-    parent.add(newStringField("isParent", "yes", Field.Store.NO));
-
-    List<Document> docs = new ArrayList<>();
-
-    Document child = new Document();
-    docs.add(child);
-    child.add(new StoredField("childID", "0"));
-    child.add(newTextField("childText", "text", Field.Store.NO));
-
-    // parent last:
-    docs.add(parent);
-    w.addDocuments(docs);
-
-    docs.clear();
-
-    parent = new Document();
-    parent.add(newTextField("parentText", "text", Field.Store.NO));
-    parent.add(newStringField("isParent", "yes", Field.Store.NO));
-    parent.add(new StoredField("parentID", "1"));
-
-    // parent last:
-    docs.add(parent);
-    w.addDocuments(docs);
-
-    IndexReader r = w.getReader();
-    w.close();
-
-    // illegally matches parent:
-    Query childQuery = new TermQuery(new Term("parentText", "text"));
-    BitSetProducer parentsFilter = new QueryBitSetProducer(new TermQuery(new Term("isParent", "yes")));
-    CheckJoinIndex.check(r, parentsFilter);
-    ToParentBlockJoinQuery childJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, ScoreMode.Avg);
-    BooleanQuery.Builder parentQuery = new BooleanQuery.Builder();
-    parentQuery.add(childJoinQuery, Occur.SHOULD);
-    parentQuery.add(new TermQuery(new Term("parentText", "text")), Occur.SHOULD);
 
-    ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(new Sort(new SortField("parentID", SortField.Type.STRING)),
-                                                                  10, true, true);
+    Weight weight = searcher.createNormalizedWeight(childJoinQuery, random().nextBoolean());
+    Scorer scorer = weight.scorer(searcher.getIndexReader().leaves().get(0));
+    assertNull(scorer);
 
-    expectThrows(IllegalStateException.class, () -> {
-      newSearcher(r).search(parentQuery.build(), c);
-    });
+    // never matches and produces a null scorer
+    childQuery = new TermQuery(new Term("bogus", "bogus"));
+    childJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, ScoreMode.Avg);
 
+    weight = searcher.createNormalizedWeight(childJoinQuery, random().nextBoolean());
+    scorer = weight.scorer(searcher.getIndexReader().leaves().get(0));
+    assertNull(scorer);
 
     r.close();
     d.close();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java b/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java
index aa68d09..cb3762c 100644
--- a/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java
+++ b/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java
@@ -87,25 +87,6 @@ public class TestBlockJoinValidation extends LuceneTestCase {
     assertTrue(expected.getMessage() != null && expected.getMessage().contains("Child query must not match same docs with parent filter"));
   }
 
-  public void testAdvanceValidationForToParentBjq() throws Exception {
-    int randomChildNumber = getRandomChildNumber(0);
-    // we need to make advance method meet wrong document, so random child number
-    // in BJQ must be greater than child number in Boolean clause
-    int nextRandomChildNumber = getRandomChildNumber(randomChildNumber);
-    Query parentQueryWithRandomChild = createChildrenQueryWithOneParent(nextRandomChildNumber);
-    ToParentBlockJoinQuery blockJoinQuery = new ToParentBlockJoinQuery(parentQueryWithRandomChild, parentsFilter, ScoreMode.None);
-    // advance() method is used by ConjunctionScorer, so we need to create Boolean conjunction query
-    BooleanQuery.Builder conjunctionQuery = new BooleanQuery.Builder();
-    WildcardQuery childQuery = new WildcardQuery(new Term("child", createFieldValue(randomChildNumber)));
-    conjunctionQuery.add(new BooleanClause(childQuery, BooleanClause.Occur.MUST));
-    conjunctionQuery.add(new BooleanClause(blockJoinQuery, BooleanClause.Occur.MUST));
-    
-    IllegalStateException expected = expectThrows(IllegalStateException.class, () -> {
-      indexSearcher.search(conjunctionQuery.build(), 1);
-    });
-    assertTrue(expected.getMessage() != null && expected.getMessage().contains("Child query must not match same docs with parent filter"));
-  }
-
   public void testNextDocValidationForToChildBjq() throws Exception {
     Query parentQueryWithRandomChild = createParentsQueryWithOneChild(getRandomChildNumber(0));
 


[04/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualsEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualsEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualsEvaluator.java
new file mode 100644
index 0000000..051a2de
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualsEvaluator.java
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class EqualsEvaluator extends BooleanEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public EqualsEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Boolean evaluate(Tuple tuple) throws IOException {
+    
+    List<Object> results = evaluateAll(tuple);
+    
+    if(results.size() < 2){
+      String message = null;
+      if(1 == results.size()){
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
+      }
+      else{
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
+      }
+      throw new IOException(message);
+    }
+    
+    Checker checker = constructChecker(results.get(0));
+    if(results.stream().anyMatch(result -> null == result && !checker.isNullAllowed())){
+      return false;
+    }
+    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+
+    for(int idx = 1; idx < results.size(); ++idx){
+      if(!checker.test(results.get(0), results.get(idx))){
+        return false;
+      }
+    }
+    
+    return true;
+  }
+  
+  private Checker constructChecker(Object fromValue) throws IOException{
+    if(null == fromValue){
+      return new NullChecker() {
+        @Override
+        public boolean test(Object left, Object right) {
+          return null == left && null == right;
+        }
+      };
+    }
+    else if(fromValue instanceof Boolean){
+      return new BooleanChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return (boolean)left.equals((boolean)right);
+        }
+      };
+    }
+    else if(fromValue instanceof Number){
+      return new NumberChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return 0 == (new BigDecimal(left.toString())).compareTo(new BigDecimal(right.toString()));
+        }
+      };
+    }
+    else if(fromValue instanceof String){
+      return new StringChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return left.equals(right);
+        }
+      };
+    }
+    
+    throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ExclusiveOrEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ExclusiveOrEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ExclusiveOrEvaluator.java
new file mode 100644
index 0000000..e63cab0
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ExclusiveOrEvaluator.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class ExclusiveOrEvaluator extends BooleanEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public ExclusiveOrEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Boolean evaluate(Tuple tuple) throws IOException {
+    
+    List<Object> results = evaluateAll(tuple);
+    
+    if(results.size() < 2){
+      String message = null;
+      if(1 == results.size()){
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
+      }
+      else{
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
+      }
+      throw new IOException(message);
+    }
+    
+    if(results.stream().anyMatch(result -> null == result)){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    if(results.stream().anyMatch(result -> !(result instanceof Boolean))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of non-boolean values [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+
+    return 1 == results.stream().filter(result -> (boolean)result).count();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldEvaluator.java
new file mode 100644
index 0000000..0ebe729
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldEvaluator.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.Explanation;
+import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class FieldEvaluator extends SimpleEvaluator {
+  private static final long serialVersionUID = 1L;
+  
+  private String fieldName;
+  
+  public FieldEvaluator(String fieldName) {
+    if(fieldName.startsWith("'") && fieldName.endsWith("'") && fieldName.length() > 1){
+      fieldName = fieldName.substring(1, fieldName.length() - 1);
+    }
+    
+    this.fieldName = fieldName;
+  }
+  
+  @Override
+  public Object evaluate(Tuple tuple) {
+    return tuple.get(fieldName); // returns null if field doesn't exist in tuple
+  }
+  
+  @Override
+  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
+    return new StreamExpressionValue(fieldName);
+  }
+
+  @Override
+  public Explanation toExplanation(StreamFactory factory) throws IOException {
+    return new Explanation(nodeId.toString())
+      .withExpressionType(ExpressionType.EVALUATOR)
+      .withImplementingClass(getClass().getName())
+      .withExpression(toExpression(factory).toString());
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
new file mode 100644
index 0000000..ad79e82
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class GreaterThanEqualToEvaluator extends BooleanEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public GreaterThanEqualToEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Boolean evaluate(Tuple tuple) throws IOException {
+    
+    List<Object> results = evaluateAll(tuple);
+    
+    if(results.size() < 2){
+      String message = null;
+      if(1 == results.size()){
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
+      }
+      else{
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
+      }
+      throw new IOException(message);
+    }
+    
+    Checker checker = constructChecker(results.get(0));
+    if(results.stream().anyMatch(result -> null == result)){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+
+    for(int idx = 1; idx < results.size(); ++idx){
+      if(!checker.test(results.get(idx - 1), results.get(idx))){
+        return false;
+      }
+    }
+    
+    return true;
+  }
+  
+  private Checker constructChecker(Object fromValue) throws IOException{
+    if(null == fromValue){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    else if(fromValue instanceof Number){
+      return new NumberChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return (new BigDecimal(left.toString())).compareTo(new BigDecimal(right.toString())) >= 0;
+        }
+      };
+    }
+    else if(fromValue instanceof String){
+      return new StringChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return ((String)left).compareToIgnoreCase((String)right) >= 0;
+        }
+      };
+    }
+    
+    throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
new file mode 100644
index 0000000..0b0e6e3
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class GreaterThanEvaluator extends BooleanEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public GreaterThanEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Boolean evaluate(Tuple tuple) throws IOException {
+    
+    List<Object> results = evaluateAll(tuple);
+    
+    if(results.size() < 2){
+      String message = null;
+      if(1 == results.size()){
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
+      }
+      else{
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
+      }
+      throw new IOException(message);
+    }
+    
+    Checker checker = constructChecker(results.get(0));
+    if(results.stream().anyMatch(result -> null == result)){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+
+    for(int idx = 1; idx < results.size(); ++idx){
+      if(!checker.test(results.get(idx - 1), results.get(idx))){
+        return false;
+      }
+    }
+    
+    return true;
+  }
+  
+  private Checker constructChecker(Object fromValue) throws IOException{
+    if(null == fromValue){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    else if(fromValue instanceof Number){
+      return new NumberChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return (new BigDecimal(left.toString())).compareTo(new BigDecimal(right.toString())) > 0;
+        }
+      };
+    }
+    else if(fromValue instanceof String){
+      return new StringChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return ((String)left).compareToIgnoreCase((String)right) > 0;
+        }
+      };
+    }
+    
+    throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
new file mode 100644
index 0000000..346b743
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class IfThenElseEvaluator extends ConditionalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public IfThenElseEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(3 != subEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting three values but found %d",expression,subEvaluators.size()));
+    }
+    
+    if(!(subEvaluators.get(0) instanceof BooleanEvaluator)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a boolean as the first parameter but found %s",expression,subEvaluators.get(0).getClass().getSimpleName()));
+    }
+
+  }
+
+  @Override
+  public Object evaluate(Tuple tuple) throws IOException {
+    
+    List<Object> results = evaluateAll(tuple);
+    
+    if(3 != results.size()){
+      String message = String.format(Locale.ROOT,"%s(...) only works with 3 values but %s were provided", constructingFactory.getFunctionName(getClass()), results.size());
+      throw new IOException(message);
+    }
+    
+    if(!(results.get(0) instanceof Boolean)){
+      throw new IOException(String.format(Locale.ROOT,"$s(...) only works with a boolean as the first parameter but found %s",results.get(0).getClass().getSimpleName()));
+    }
+  
+    return (boolean)results.get(0) ? results.get(1) : results.get(2);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
new file mode 100644
index 0000000..cb2fc7a
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class LessThanEqualToEvaluator extends BooleanEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public LessThanEqualToEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Boolean evaluate(Tuple tuple) throws IOException {
+    
+    List<Object> results = evaluateAll(tuple);
+    
+    if(results.size() < 2){
+      String message = null;
+      if(1 == results.size()){
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
+      }
+      else{
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
+      }
+      throw new IOException(message);
+    }
+    
+    Checker checker = constructChecker(results.get(0));
+    if(results.stream().anyMatch(result -> null == result)){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+
+    for(int idx = 1; idx < results.size(); ++idx){
+      if(!checker.test(results.get(idx - 1), results.get(idx))){
+        return false;
+      }
+    }
+    
+    return true;
+  }
+  
+  private Checker constructChecker(Object fromValue) throws IOException{
+    if(null == fromValue){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    else if(fromValue instanceof Number){
+      return new NumberChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return (new BigDecimal(left.toString())).compareTo(new BigDecimal(right.toString())) <= 0;
+        }
+      };
+    }
+    else if(fromValue instanceof String){
+      return new StringChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return ((String)left).compareToIgnoreCase((String)right) <= 0;
+        }
+      };
+    }
+    
+    throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
new file mode 100644
index 0000000..40796b8
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class LessThanEvaluator extends BooleanEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public LessThanEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Boolean evaluate(Tuple tuple) throws IOException {
+    
+    List<Object> results = evaluateAll(tuple);
+    
+    if(results.size() < 2){
+      String message = null;
+      if(1 == results.size()){
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
+      }
+      else{
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
+      }
+      throw new IOException(message);
+    }
+    
+    Checker checker = constructChecker(results.get(0));
+    if(results.stream().anyMatch(result -> null == result)){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+
+    for(int idx = 1; idx < results.size(); ++idx){
+      if(!checker.test(results.get(idx - 1), results.get(idx))){
+        return false;
+      }
+    }
+    
+    return true;
+  }
+  
+  private Checker constructChecker(Object fromValue) throws IOException{
+    if(null == fromValue){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    else if(fromValue instanceof Number){
+      return new NumberChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return (new BigDecimal(left.toString())).compareTo(new BigDecimal(right.toString())) < 0;
+        }
+      };
+    }
+    else if(fromValue instanceof String){
+      return new StringChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return ((String)left).compareToIgnoreCase((String)right) < 0;
+        }
+      };
+    }
+    
+    throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
new file mode 100644
index 0000000..44b0b26
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class MultiplyEvaluator extends NumberEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public MultiplyEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Number evaluate(Tuple tuple) throws IOException {
+    
+    List<BigDecimal> results = evaluateAll(tuple);
+    
+    if(results.stream().anyMatch(item -> null == item)){
+      return null;
+    }
+    
+    BigDecimal result = null;
+    if(results.size() > 0){
+      result = results.get(0);
+      for(int idx = 1; idx < results.size(); ++idx){
+        result = result.multiply(results.get(idx), MathContext.DECIMAL64);
+      }
+    }
+    
+    return normalizeType(result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
new file mode 100644
index 0000000..da2eeff
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class NotEvaluator extends BooleanEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public NotEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(1 != subEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Boolean evaluate(Tuple tuple) throws IOException {
+    
+    List<Object> results = evaluateAll(tuple);
+    
+    if(1 != results.size()){
+      String message = String.format(Locale.ROOT,"%s(...) only works with 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size());
+      throw new IOException(message);
+    }
+
+    Object result = results.get(0);
+    if(null == result){
+      throw new IOException(String.format(Locale.ROOT,"Unable to evaluate %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    if(!(result instanceof Boolean)){
+      throw new IOException(String.format(Locale.ROOT,"Unable to evaluate %s(...) of a non-boolean value [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+    
+    return !((Boolean)result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java
new file mode 100644
index 0000000..f4491fd
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class NumberEvaluator extends ComplexEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public NumberEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+  
+  // restrict result to a Number
+  public abstract Number evaluate(Tuple tuple) throws IOException;
+  
+  public List<BigDecimal> evaluateAll(final Tuple tuple) throws IOException {
+    // evaluate each and confirm they are all either null or numeric
+    List<BigDecimal> results = new ArrayList<BigDecimal>();
+    for(StreamEvaluator subEvaluator : subEvaluators){
+      Object result = subEvaluator.evaluate(tuple);
+      
+      if(null == result){
+        results.add(null);
+      }
+      else if(result instanceof Number){
+        results.add(new BigDecimal(result.toString()));
+      }
+      else{
+        String message = String.format(Locale.ROOT,"Failed to evaluate to a numeric value - evaluator '%s' resulted in type '%s' and value '%s'", 
+                                        subEvaluator.toExpression(constructingFactory),
+                                        result.getClass().getName(),
+                                        result.toString());
+        throw new IOException(message);
+      }
+    }
+    
+    return results;
+  }
+  
+  public Number normalizeType(BigDecimal value){
+    if(null == value){
+      return null;
+    }
+    
+    if(value.signum() == 0 || value.scale() <= 0 || value.stripTrailingZeros().scale() <= 0){
+      return value.longValue();
+    }
+    
+    return value.doubleValue();
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
new file mode 100644
index 0000000..1cd9df8
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class OrEvaluator extends BooleanEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public OrEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Boolean evaluate(Tuple tuple) throws IOException {
+    
+    List<Object> results = evaluateAll(tuple);
+    
+    if(results.size() < 2){
+      String message = null;
+      if(1 == results.size()){
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
+      }
+      else{
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
+      }
+      throw new IOException(message);
+    }
+    
+    Checker checker = constructChecker(results.get(0));
+    if(results.stream().anyMatch(result -> null == result && !checker.isNullAllowed())){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+
+    for(int idx = 1; idx < results.size(); ++idx){
+      if(!checker.test(results.get(0), results.get(idx))){
+        return false;
+      }
+    }
+    
+    return true;
+  }
+  
+  private Checker constructChecker(Object fromValue) throws IOException{
+    if(null == fromValue){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    else if(fromValue instanceof Boolean){
+      return new BooleanChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return (boolean)left || (boolean)right;
+        }
+      };
+    }
+    
+    throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java
new file mode 100644
index 0000000..1751380
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.Explanation;
+import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class RawValueEvaluator extends SimpleEvaluator {
+  private static final long serialVersionUID = 1L;
+  
+  private Object value;
+  
+  public RawValueEvaluator(Object value){
+    init(value);
+  }
+  
+  public RawValueEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    // We have to do this because order of the parameters matter
+    List<StreamExpressionParameter> parameters = factory.getOperandsOfType(expression, StreamExpressionValue.class);
+    
+    if(expression.getParameters().size() != parameters.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - unknown operands found - expecting only raw values", expression));
+    }
+    
+    if(1 != parameters.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - only 1 value can exist in a %s(...) evaluator", expression, factory.getFunctionName(getClass())));
+    }
+    
+    init(factory.constructPrimitiveObject(((StreamExpressionValue)parameters.get(0)).getValue()));
+  }
+  
+  private void init(Object value){
+    if(value instanceof Integer){
+      this.value = (Long)value;
+    }
+    else if(value instanceof Float){
+      this.value = ((Float)value).doubleValue();
+    }
+    else{
+      this.value = value;
+    }
+  }
+  
+  @Override
+  public Object evaluate(Tuple tuple) {
+    return value;
+  }
+  
+  @Override
+  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
+    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
+    expression.addParameter(new StreamExpressionValue(value.toString()));
+    return expression;
+  }
+
+  @Override
+  public Explanation toExplanation(StreamFactory factory) throws IOException {
+    return new Explanation(nodeId.toString())
+      .withExpressionType(ExpressionType.EVALUATOR)
+      .withImplementingClass(getClass().getName())
+      .withExpression(toExpression(factory).toString());
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java
new file mode 100644
index 0000000..79d1799
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.util.UUID;
+
+public abstract class SimpleEvaluator implements StreamEvaluator {
+  private static final long serialVersionUID = 1L;
+  
+  protected UUID nodeId = UUID.randomUUID();
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java
new file mode 100644
index 0000000..6bc4d50
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.Expressible;
+
+public interface StreamEvaluator extends Expressible, Serializable {
+  Object evaluate(final Tuple tuple) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SubtractEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SubtractEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SubtractEvaluator.java
new file mode 100644
index 0000000..3bf62b7
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SubtractEvaluator.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+/**
+ * 
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class SubtractEvaluator extends NumberEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public SubtractEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(subEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    }
+  }
+
+  @Override
+  public Number evaluate(Tuple tuple) throws IOException {
+    
+    List<BigDecimal> results = evaluateAll(tuple);
+    
+    if(results.stream().anyMatch(item -> null == item)){
+      return null;
+    }
+    
+    BigDecimal result = null;
+    if(results.size() > 0){
+      result = results.get(0);
+      for(int idx = 1; idx < results.size(); ++idx){
+        result = result.subtract(results.get(idx));
+      }
+    }
+    
+    return normalizeType(result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/AndOperation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/AndOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/AndOperation.java
deleted file mode 100644
index f095f63..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/AndOperation.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.client.solrj.io.ops;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.UUID;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class AndOperation implements BooleanOperation {
-
-  private static final long serialVersionUID = 1;
-  private UUID operationNodeId = UUID.randomUUID();
-
-  protected BooleanOperation leftOperand;
-  protected BooleanOperation rightOperand;
-
-  public void operate(Tuple tuple) {
-    leftOperand.operate(tuple);
-    rightOperand.operate(tuple);
-  }
-
-  public AndOperation(BooleanOperation leftOperand, BooleanOperation rightOperand) {
-    this.leftOperand = leftOperand;
-    this.rightOperand = rightOperand;
-  }
-
-  public AndOperation(StreamExpression expression, StreamFactory factory) throws IOException {
-      List<StreamExpression> operationExpressions = factory.getExpressionOperandsRepresentingTypes(expression, BooleanOperation.class);
-      if(operationExpressions != null && operationExpressions.size() == 2) {
-        StreamExpression left = operationExpressions.get(0);
-        StreamOperation leftOp = factory.constructOperation(left);
-        if(leftOp instanceof BooleanOperation) {
-          leftOperand = (BooleanOperation) leftOp;
-        } else {
-          throw new IOException("The And/Or Operation requires a BooleanOperation.");
-        }
-
-        StreamExpression right = operationExpressions.get(1);
-        StreamOperation rightOp = factory.constructOperation(right);
-        if(rightOp instanceof BooleanOperation) {
-          rightOperand = (BooleanOperation) rightOp;
-        } else {
-          throw new IOException("The And/Or Operation requires a BooleanOperation.");
-        }
-      } else {
-        throw new IOException("The And/Or Operation requires a BooleanOperations.");
-      }
-  }
-
-  public boolean evaluate() {
-    return leftOperand.evaluate() && rightOperand.evaluate();
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass()));
-    if(leftOperand instanceof Expressible) {
-      expression.addParameter(leftOperand.toExpression(factory));
-    } else {
-      throw new IOException("This left operand of the AndOperation contains a non-expressible operation - it cannot be converted to an expression");
-    }
-
-    if(rightOperand instanceof Expressible) {
-      expression.addParameter(rightOperand.toExpression(factory));
-    } else {
-      throw new IOException("This the right operand of the AndOperation contains a non-expressible operation - it cannot be converted to an expression");
-    }
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(operationNodeId.toString())
-        .withExpressionType(ExpressionType.OPERATION)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/BooleanOperation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/BooleanOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/BooleanOperation.java
deleted file mode 100644
index d455999..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/BooleanOperation.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.client.solrj.io.ops;
-
-/**
- *  A BooleanOperation returns true or false for each tuple that it evaluates. The HavingStream applies a BooleanOperation to
- *  determine which tuples to emit.
- */
-
-public interface BooleanOperation extends StreamOperation {
-  public abstract boolean evaluate();
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/EqualsOperation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/EqualsOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/EqualsOperation.java
deleted file mode 100644
index 1958551..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/EqualsOperation.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.client.solrj.io.ops;
-
-import java.io.IOException;
-import java.util.UUID;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class EqualsOperation extends LeafOperation {
-
-  private static final long serialVersionUID = 1;
-  private UUID operationNodeId = UUID.randomUUID();
-
-  public void operate(Tuple tuple) {
-    this.tuple = tuple;
-  }
-
-  public EqualsOperation(String field, double val) {
-    super(field, val);
-  }
-
-  public EqualsOperation(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  public boolean evaluate() {
-    Double d = tuple.getDouble(field);
-
-    if(d == null) {
-      return false;
-    }
-
-    return d.doubleValue() == val;
-  }
-
-  public StreamExpression toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass()));
-    expression.addParameter(field);
-    expression.addParameter(Double.toString(val));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(operationNodeId.toString())
-        .withExpressionType(ExpressionType.OPERATION)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/GreaterThanEqualToOperation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/GreaterThanEqualToOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/GreaterThanEqualToOperation.java
deleted file mode 100644
index 87c8364..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/GreaterThanEqualToOperation.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.client.solrj.io.ops;
-
-import java.io.IOException;
-import java.util.UUID;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class GreaterThanEqualToOperation extends LeafOperation {
-
-  private static final long serialVersionUID = 1;
-  private UUID operationNodeId = UUID.randomUUID();
-
-  public void operate(Tuple tuple) {
-    this.tuple = tuple;
-  }
-
-  public GreaterThanEqualToOperation(String field, double val) {
-    super(field, val);
-  }
-
-  public GreaterThanEqualToOperation(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  public boolean evaluate() {
-    Double d = tuple.getDouble(field);
-
-    if(d == null) {
-      return false;
-    }
-
-    return d.doubleValue() >= val;
-  }
-
-  public StreamExpression toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass()));
-    expression.addParameter(field);
-    expression.addParameter(Double.toString(val));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(operationNodeId.toString())
-        .withExpressionType(ExpressionType.OPERATION)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/GreaterThanOperation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/GreaterThanOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/GreaterThanOperation.java
deleted file mode 100644
index 664438a..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/GreaterThanOperation.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.client.solrj.io.ops;
-
-import java.io.IOException;
-import java.util.UUID;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class GreaterThanOperation extends LeafOperation {
-
-  private static final long serialVersionUID = 1;
-  private UUID operationNodeId = UUID.randomUUID();
-
-  public void operate(Tuple tuple) {
-    this.tuple = tuple;
-  }
-
-  public GreaterThanOperation(String field, double val) {
-    super(field, val);
-  }
-
-  public GreaterThanOperation(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  public boolean evaluate() {
-    Double d = tuple.getDouble(field);
-
-    if(d == null) {
-      return false;
-    }
-
-    return d.doubleValue() > val;
-  }
-
-  public StreamExpression toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass()));
-    expression.addParameter(field);
-    expression.addParameter(Double.toString(val));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(operationNodeId.toString())
-        .withExpressionType(ExpressionType.OPERATION)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LeafOperation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LeafOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LeafOperation.java
deleted file mode 100644
index 691a328..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LeafOperation.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.client.solrj.io.ops;
-
-import java.io.IOException;
-import java.util.UUID;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public abstract class LeafOperation implements BooleanOperation {
-
-  private static final long serialVersionUID = 1;
-  private UUID operationNodeId = UUID.randomUUID();
-
-  protected String field;
-  protected Double val;
-  protected Tuple tuple;
-
-  public void operate(Tuple tuple) {
-    this.tuple = tuple;
-  }
-
-  public LeafOperation(String field, double val) {
-    this.field = field;
-    this.val = val;
-  }
-
-  public LeafOperation(StreamExpression expression, StreamFactory factory) throws IOException {
-    this.field = factory.getValueOperand(expression, 0);
-    this.val = Double.parseDouble(factory.getValueOperand(expression, 1));
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(operationNodeId.toString())
-        .withExpressionType(ExpressionType.OPERATION)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-
-  protected String quote(String s) {
-    if(s.contains("(")) {
-      return "'"+s+"'";
-    }
-
-    return s;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LessThanEqualToOperation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LessThanEqualToOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LessThanEqualToOperation.java
deleted file mode 100644
index 2da3274..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LessThanEqualToOperation.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.client.solrj.io.ops;
-
-import java.io.IOException;
-import java.util.UUID;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class LessThanEqualToOperation extends LeafOperation {
-
-  private static final long serialVersionUID = 1;
-  private UUID operationNodeId = UUID.randomUUID();
-
-  public void operate(Tuple tuple) {
-    this.tuple = tuple;
-  }
-
-  public LessThanEqualToOperation(String field, double val) {
-    super(field, val);
-  }
-
-  public LessThanEqualToOperation(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  public boolean evaluate() {
-    Double d = tuple.getDouble(field);
-
-    if(d == null) {
-      return true;
-    }
-
-    return d.doubleValue() <= val;
-  }
-
-  public StreamExpression toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass()));
-    expression.addParameter(field);
-    expression.addParameter(Double.toString(val));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(operationNodeId.toString())
-        .withExpressionType(ExpressionType.OPERATION)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LessThanOperation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LessThanOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LessThanOperation.java
deleted file mode 100644
index c1cec95..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/LessThanOperation.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.client.solrj.io.ops;
-
-import java.io.IOException;
-import java.util.UUID;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class LessThanOperation extends LeafOperation {
-
-  private static final long serialVersionUID = 1;
-  private UUID operationNodeId = UUID.randomUUID();
-
-  public void operate(Tuple tuple) {
-    this.tuple = tuple;
-  }
-
-  public LessThanOperation(String field, double val) {
-    super(field, val);
-  }
-
-  public LessThanOperation(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  public boolean evaluate() {
-    Double d = tuple.getDouble(field);
-
-    if(d == null) {
-      return true;
-    }
-    
-    return d.doubleValue() < val;
-  }
-
-  public StreamExpression toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass()));
-    expression.addParameter(field);
-    expression.addParameter(Double.toString(val));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(operationNodeId.toString())
-        .withExpressionType(ExpressionType.OPERATION)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/NotOperation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/NotOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/NotOperation.java
deleted file mode 100644
index 0e40b72..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/NotOperation.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.client.solrj.io.ops;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.UUID;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-
-public class NotOperation implements BooleanOperation {
-
-  private static final long serialVersionUID = 1;
-  private UUID operationNodeId = UUID.randomUUID();
-
-  protected BooleanOperation operand;
-
-  public void operate(Tuple tuple) {
-    operand.operate(tuple);
-  }
-
-  public NotOperation(BooleanOperation operand) {
-    this.operand = operand;
-  }
-
-  public NotOperation(StreamExpression expression, StreamFactory factory) throws IOException {
-    List<StreamExpression> operationExpressions = factory.getExpressionOperandsRepresentingTypes(expression, BooleanOperation.class);
-    if(operationExpressions != null && operationExpressions.size() == 1) {
-      StreamExpression op = operationExpressions.get(0);
-      StreamOperation streamOp = factory.constructOperation(op);
-      if(streamOp instanceof BooleanOperation) {
-        operand = (BooleanOperation) streamOp;
-      } else {
-        throw new IOException("The NotOperation requires a BooleanOperation.");
-      }
-
-    } else {
-      throw new IOException("The NotOperation requires a BooleanOperations.");
-    }
-  }
-
-  public boolean evaluate() {
-    return !operand.evaluate();
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass()));
-    if(operand instanceof Expressible) {
-      expression.addParameter(operand.toExpression(factory));
-    } else {
-      throw new IOException("The operand of the NotOperation contains a non-expressible operation - it cannot be converted to an expression");
-    }
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(operationNodeId.toString())
-        .withExpressionType(ExpressionType.OPERATION)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/OrOperation.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/OrOperation.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/OrOperation.java
deleted file mode 100644
index faac5cd..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/ops/OrOperation.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.client.solrj.io.ops;
-
-import java.io.IOException;
-import java.util.UUID;
-
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class OrOperation extends AndOperation {
-
-  private static final long serialVersionUID = 1;
-  private UUID operationNodeId = UUID.randomUUID();
-
-  public OrOperation(BooleanOperation leftOperand, BooleanOperation rightOperand) {
-    super(leftOperand, rightOperand);
-  }
-
-  public OrOperation(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  public boolean evaluate() {
-    return leftOperand.evaluate() || rightOperand.evaluate();
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass()));
-    if(leftOperand instanceof Expressible) {
-      expression.addParameter(leftOperand.toExpression(factory));
-    } else {
-      throw new IOException("This left operand of the OrOperation contains a non-expressible operation - it cannot be converted to an expression");
-    }
-
-    if(rightOperand instanceof Expressible) {
-      expression.addParameter(rightOperand.toExpression(factory));
-    } else {
-      throw new IOException("This the right operand of the OrOperation contains a non-expressible operation - it cannot be converted to an expression");
-    }
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(operationNodeId.toString())
-        .withExpressionType(ExpressionType.OPERATION)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}


[15/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java b/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
index 6e20f23..39979ac 100644
--- a/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
+++ b/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
@@ -454,59 +454,7 @@ public class TestJoinUtil extends LuceneTestCase {
 
   public void testMinMaxScore() throws Exception {
     String priceField = "price";
-    // FunctionQuery would be helpful, but join module doesn't depend on queries module.
-    Query priceQuery = new Query() {
-
-      private final Query fieldQuery = new FieldValueQuery(priceField);
-
-      @Override
-      public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
-        Weight fieldWeight = fieldQuery.createWeight(searcher, false, boost);
-        return new Weight(this) {
-
-          @Override
-          public void extractTerms(Set<Term> terms) {
-          }
-
-          @Override
-          public Explanation explain(LeafReaderContext context, int doc) throws IOException {
-            return null;
-          }
-
-          @Override
-          public Scorer scorer(LeafReaderContext context) throws IOException {
-            Scorer fieldScorer = fieldWeight.scorer(context);
-            if (fieldScorer == null) {
-              return null;
-            }
-            NumericDocValues price = context.reader().getNumericDocValues(priceField);
-            return new FilterScorer(fieldScorer, this) {
-              @Override
-              public float score() throws IOException {
-                assertEquals(in.docID(), price.nextDoc());
-                return (float) price.longValue();
-              }
-            };
-          }
-        };
-      }
-
-      @Override
-      public String toString(String field) {
-        return fieldQuery.toString(field);
-      }
-
-      @Override
-      public boolean equals(Object o) {
-        return o == this;
-      }
-
-      @Override
-      public int hashCode() {
-        return System.identityHashCode(this);
-      }
-
-    };
+    Query priceQuery = numericDocValuesScoreQuery(priceField);
 
     Directory dir = newDirectory();
     RandomIndexWriter iw = new RandomIndexWriter(
@@ -579,6 +527,62 @@ public class TestJoinUtil extends LuceneTestCase {
     dir.close();
   }
 
+  // FunctionQuery would be helpful, but join module doesn't depend on queries module.
+  static Query numericDocValuesScoreQuery(final String field) {
+    return new Query() {
+
+        private final Query fieldQuery = new FieldValueQuery(field);
+
+        @Override
+        public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
+          Weight fieldWeight = fieldQuery.createWeight(searcher, false, boost);
+          return new Weight(this) {
+
+            @Override
+            public void extractTerms(Set<Term> terms) {
+            }
+
+            @Override
+            public Explanation explain(LeafReaderContext context, int doc) throws IOException {
+              return null;
+            }
+
+            @Override
+            public Scorer scorer(LeafReaderContext context) throws IOException {
+              Scorer fieldScorer = fieldWeight.scorer(context);
+              if (fieldScorer == null) {
+                return null;
+              }
+              NumericDocValues price = context.reader().getNumericDocValues(field);
+              return new FilterScorer(fieldScorer, this) {
+                @Override
+                public float score() throws IOException {
+                  assertEquals(in.docID(), price.advance(in.docID()));
+                  return (float) price.longValue();
+                }
+              };
+            }
+          };
+        }
+
+        @Override
+        public String toString(String field) {
+          return fieldQuery.toString(field);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+          return o == this;
+        }
+
+        @Override
+        public int hashCode() {
+          return System.identityHashCode(this);
+        }
+
+      };
+  }
+
   public void testMinMaxDocs() throws Exception {
     Directory dir = newDirectory();
     RandomIndexWriter iw = new RandomIndexWriter(

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/join/src/test/org/apache/lucene/search/join/TestParentChildrenBlockJoinQuery.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/test/org/apache/lucene/search/join/TestParentChildrenBlockJoinQuery.java b/lucene/join/src/test/org/apache/lucene/search/join/TestParentChildrenBlockJoinQuery.java
new file mode 100644
index 0000000..fe849cb
--- /dev/null
+++ b/lucene/join/src/test/org/apache/lucene/search/join/TestParentChildrenBlockJoinQuery.java
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.search.join;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.NumericDocValuesField;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.ReaderUtil;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestParentChildrenBlockJoinQuery extends LuceneTestCase {
+
+  public void testParentChildrenBlockJoinQuery() throws Exception {
+    int numParentDocs = 8 + random().nextInt(8);
+    int maxChildDocsPerParent = 8 + random().nextInt(8);
+
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    for (int i = 0; i < numParentDocs; i++) {
+      int numChildDocs = random().nextInt(maxChildDocsPerParent);
+      List<Document> docs = new ArrayList<>(numChildDocs + 1);
+      for (int j = 0; j < numChildDocs; j++) {
+        Document childDoc = new Document();
+        childDoc.add(new StringField("type", "child", Field.Store.NO));
+        childDoc.add(new NumericDocValuesField("score", j + 1));
+        docs.add(childDoc);
+      }
+
+      Document parenDoc = new Document();
+      parenDoc.add(new StringField("type", "parent", Field.Store.NO));
+      parenDoc.add(new NumericDocValuesField("num_child_docs", numChildDocs));
+      docs.add(parenDoc);
+      writer.addDocuments(docs);
+    }
+
+    IndexReader reader = writer.getReader();
+    writer.close();
+
+    IndexSearcher searcher = newSearcher(reader);
+    BitSetProducer parentFilter = new QueryBitSetProducer(new TermQuery(new Term("type", "parent")));
+    Query childQuery = new BooleanQuery.Builder()
+        .add(new TermQuery(new Term("type", "child")), BooleanClause.Occur.FILTER)
+        .add(TestJoinUtil.numericDocValuesScoreQuery("score"), BooleanClause.Occur.SHOULD)
+        .build();
+
+    TopDocs parentDocs = searcher.search(new TermQuery(new Term("type", "parent")), numParentDocs);
+    assertEquals(parentDocs.scoreDocs.length, numParentDocs);
+    for (ScoreDoc parentScoreDoc : parentDocs.scoreDocs) {
+      LeafReaderContext leafReader = reader.leaves().get(ReaderUtil.subIndex(parentScoreDoc.doc, reader.leaves()));
+      NumericDocValues numericDocValuesField = leafReader.reader().getNumericDocValues("num_child_docs");
+      numericDocValuesField.advance(parentScoreDoc.doc - leafReader.docBase);
+      long expectedChildDocs = numericDocValuesField.longValue();
+
+      ParentChildrenBlockJoinQuery parentChildrenBlockJoinQuery =
+          new ParentChildrenBlockJoinQuery(parentFilter, childQuery, parentScoreDoc.doc);
+      TopDocs topDocs = searcher.search(parentChildrenBlockJoinQuery, maxChildDocsPerParent);
+      assertEquals(expectedChildDocs, topDocs.totalHits);
+      if (expectedChildDocs > 0) {
+        assertEquals(expectedChildDocs, topDocs.getMaxScore(), 0);
+        for (int i = 0; i < topDocs.scoreDocs.length; i++) {
+          ScoreDoc childScoreDoc = topDocs.scoreDocs[i];
+          assertEquals(expectedChildDocs - i, childScoreDoc.score, 0);
+        }
+      }
+
+    }
+
+    reader.close();
+    dir.close();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java
----------------------------------------------------------------------
diff --git a/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java b/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java
index b1adf60..abf3324 100644
--- a/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java
+++ b/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java
@@ -34,6 +34,7 @@ import org.apache.lucene.analysis.tokenattributes.PayloadAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 import org.apache.lucene.analysis.tokenattributes.TermToBytesRefAttribute;
 import org.apache.lucene.document.Document;
+import org.apache.lucene.document.FieldType;
 import org.apache.lucene.index.*;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
@@ -195,6 +196,8 @@ public class MemoryIndex {
 
   private Similarity normSimilarity = IndexSearcher.getDefaultSimilarity();
 
+  private FieldType defaultFieldType = new FieldType();
+
   /**
    * Constructs an empty instance that will not store offsets or payloads.
    */
@@ -236,6 +239,9 @@ public class MemoryIndex {
   MemoryIndex(boolean storeOffsets, boolean storePayloads, long maxReusedBytes) {
     this.storeOffsets = storeOffsets;
     this.storePayloads = storePayloads;
+    this.defaultFieldType.setIndexOptions(storeOffsets ?
+        IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS : IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
+    this.defaultFieldType.setStoreTermVectors(true);
     this.bytesUsed = Counter.newCounter();
     final int maxBufferedByteBlocks = (int)((maxReusedBytes/2) / ByteBlockPool.BYTE_BLOCK_SIZE );
     final int maxBufferedIntBlocks = (int) ((maxReusedBytes - (maxBufferedByteBlocks*ByteBlockPool.BYTE_BLOCK_SIZE))/(IntBlockPool.INT_BLOCK_SIZE * Integer.BYTES));
@@ -269,8 +275,8 @@ public class MemoryIndex {
       throw new IllegalArgumentException("analyzer must not be null");
     
     TokenStream stream = analyzer.tokenStream(fieldName, text);
-    addField(fieldName, stream, 1.0f, analyzer.getPositionIncrementGap(fieldName), analyzer.getOffsetGap(fieldName),
-        DocValuesType.NONE, null, 0, 0, null);
+    storeTerms(getInfo(fieldName, defaultFieldType), stream, 1.0f,
+        analyzer.getPositionIncrementGap(fieldName), analyzer.getOffsetGap(fieldName));
   }
 
   /**
@@ -385,10 +391,11 @@ public class MemoryIndex {
    * @param field the field to add
    * @param analyzer the analyzer to use for term analysis
    * @param boost a field boost
-   * @throws IllegalArgumentException if the field is a DocValues or Point field, as these
-   *                                  structures are not supported by MemoryIndex
    */
   public void addField(IndexableField field, Analyzer analyzer, float boost) {
+
+    Info info = getInfo(field.name(), field.fieldType());
+
     int offsetGap;
     TokenStream tokenStream;
     int positionIncrementGap;
@@ -401,6 +408,9 @@ public class MemoryIndex {
       tokenStream = field.tokenStream(null, null);
       positionIncrementGap = 0;
     }
+    if (tokenStream != null) {
+      storeTerms(info, tokenStream, boost, positionIncrementGap, offsetGap);
+    }
 
     DocValuesType docValuesType = field.fieldType().docValuesType();
     Object docValuesValue;
@@ -420,12 +430,14 @@ public class MemoryIndex {
       default:
         throw new UnsupportedOperationException("unknown doc values type [" + docValuesType + "]");
     }
-    BytesRef pointValue = null;
+    if (docValuesValue != null) {
+      storeDocValues(info, docValuesType, docValuesValue);
+    }
+
     if (field.fieldType().pointDimensionCount() > 0) {
-      pointValue = field.binaryValue();
+      storePointValues(info, field.binaryValue());
     }
-    addField(field.name(), tokenStream, boost, positionIncrementGap, offsetGap, docValuesType, docValuesValue,
-        field.fieldType().pointDimensionCount(), field.fieldType().pointNumBytes(), pointValue);
+
   }
   
   /**
@@ -494,42 +506,40 @@ public class MemoryIndex {
    * @see org.apache.lucene.document.Field#setBoost(float)
    */
   public void addField(String fieldName, TokenStream tokenStream, float boost, int positionIncrementGap, int offsetGap) {
-    addField(fieldName, tokenStream, boost, positionIncrementGap, offsetGap, DocValuesType.NONE, null, 0, 0, null);
+    Info info = getInfo(fieldName, defaultFieldType);
+    storeTerms(info, tokenStream, boost, positionIncrementGap, offsetGap);
   }
 
-  private void addField(String fieldName, TokenStream tokenStream, float boost, int positionIncrementGap, int offsetGap,
-                        DocValuesType docValuesType, Object docValuesValue, int pointDimensionCount, int pointNumBytes,
-                        BytesRef pointValue) {
-
+  private Info getInfo(String fieldName, IndexableFieldType fieldType) {
     if (frozen) {
       throw new IllegalArgumentException("Cannot call addField() when MemoryIndex is frozen");
     }
     if (fieldName == null) {
       throw new IllegalArgumentException("fieldName must not be null");
     }
-    if (boost <= 0.0f) {
-      throw new IllegalArgumentException("boost factor must be greater than 0.0");
-    }
-
     Info info = fields.get(fieldName);
     if (info == null) {
-      IndexOptions indexOptions = storeOffsets ? IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS : IndexOptions.DOCS_AND_FREQS_AND_POSITIONS;
-      FieldInfo fieldInfo = new FieldInfo(fieldName, fields.size(), true, false, storePayloads, indexOptions, docValuesType, -1, Collections.emptyMap(), 0, 0);
-      fields.put(fieldName, info = new Info(fieldInfo, byteBlockPool));
+      fields.put(fieldName, info = new Info(createFieldInfo(fieldName, fields.size(), fieldType), byteBlockPool));
     }
-    if (pointDimensionCount > 0) {
-      storePointValues(info, pointDimensionCount, pointNumBytes, pointValue);
+    if (fieldType.pointDimensionCount() != info.fieldInfo.getPointDimensionCount()) {
+      if (fieldType.pointDimensionCount() > 0)
+        info.fieldInfo.setPointDimensions(fieldType.pointDimensionCount(), fieldType.pointNumBytes());
     }
-    if (docValuesType != DocValuesType.NONE) {
-      storeDocValues(info, docValuesType, docValuesValue);
-    }
-    if (tokenStream != null) {
-      storeTerms(info, tokenStream, boost, positionIncrementGap, offsetGap);
+    if (fieldType.docValuesType() != info.fieldInfo.getDocValuesType()) {
+      if (fieldType.docValuesType() != DocValuesType.NONE)
+        info.fieldInfo.setDocValuesType(fieldType.docValuesType());
     }
+    return info;
+  }
+
+  private FieldInfo createFieldInfo(String fieldName, int ord, IndexableFieldType fieldType) {
+    IndexOptions indexOptions = storeOffsets ? IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS : IndexOptions.DOCS_AND_FREQS_AND_POSITIONS;
+    return new FieldInfo(fieldName, ord, fieldType.storeTermVectors(), fieldType.omitNorms(), storePayloads,
+        indexOptions, fieldType.docValuesType(), -1, Collections.emptyMap(),
+        fieldType.pointDimensionCount(), fieldType.pointNumBytes());
   }
 
-  private void storePointValues(Info info, int pointDimensionCount, int pointNumBytes, BytesRef pointValue) {
-    info.fieldInfo.setPointDimensions(pointDimensionCount, pointNumBytes);
+  private void storePointValues(Info info, BytesRef pointValue) {
     if (info.pointValues == null) {
       info.pointValues = new BytesRef[4];
     }
@@ -591,6 +601,11 @@ public class MemoryIndex {
   }
 
   private void storeTerms(Info info, TokenStream tokenStream, float boost, int positionIncrementGap, int offsetGap) {
+
+    if (boost <= 0.0f) {
+      throw new IllegalArgumentException("boost factor must be greater than 0.0");
+    }
+
     int pos = -1;
     int offset = 0;
     if (info.numTokens == 0) {
@@ -1598,7 +1613,7 @@ public class MemoryIndex {
     @Override
     public NumericDocValues getNormValues(String field) {
       Info info = fields.get(field);
-      if (info == null) {
+      if (info == null || info.fieldInfo.omitsNorms()) {
         return null;
       }
       return info.getNormDocValues();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java
----------------------------------------------------------------------
diff --git a/lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java b/lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java
index ab4fe21..4e2189c 100644
--- a/lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java
+++ b/lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java
@@ -45,6 +45,7 @@ import org.apache.lucene.document.TextField;
 import org.apache.lucene.index.BinaryDocValues;
 import org.apache.lucene.index.DocValuesType;
 import org.apache.lucene.index.FieldInvertState;
+import org.apache.lucene.index.IndexOptions;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexableField;
 import org.apache.lucene.index.LeafReader;
@@ -200,6 +201,21 @@ public class TestMemoryIndex extends LuceneTestCase {
   }
 
   @Test
+  public void testOmitNorms() throws IOException {
+    MemoryIndex mi = new MemoryIndex();
+    FieldType ft = new FieldType();
+    ft.setTokenized(true);
+    ft.setIndexOptions(IndexOptions.DOCS_AND_FREQS);
+    ft.setOmitNorms(true);
+    mi.addField(new Field("f1", "some text in here", ft), analyzer);
+    mi.freeze();
+
+    LeafReader leader = (LeafReader) mi.createSearcher().getIndexReader();
+    NumericDocValues norms = leader.getNormValues("f1");
+    assertNull(norms);
+  }
+
+  @Test
   public void testBuildFromDocument() {
 
     Document doc = new Document();
@@ -276,7 +292,7 @@ public class TestMemoryIndex extends LuceneTestCase {
     try {
       MemoryIndex.fromDocument(doc, analyzer);
     } catch (IllegalArgumentException e) {
-      assertEquals("Can't add [BINARY] doc values field [field], because [NUMERIC] doc values field already exists", e.getMessage());
+      assertEquals("cannot change DocValues type from NUMERIC to BINARY for field \"field\"", e.getMessage());
     }
 
     doc = new Document();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/DoubleFieldSource.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/DoubleFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/DoubleFieldSource.java
index d525dab..3f5f454 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/DoubleFieldSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/DoubleFieldSource.java
@@ -52,7 +52,7 @@ public class DoubleFieldSource extends FieldCacheSource {
   @Override
   public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
 
-    final NumericDocValues values = DocValues.getNumeric(readerContext.reader(), field);
+    final NumericDocValues values = getNumericDocValues(context, readerContext);
 
     return new DoubleDocValues(this) {
       int lastDocID;
@@ -104,6 +104,10 @@ public class DoubleFieldSource extends FieldCacheSource {
     };
   }
 
+  protected NumericDocValues getNumericDocValues(Map context, LeafReaderContext readerContext) throws IOException {
+    return DocValues.getNumeric(readerContext.reader(), field);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (o.getClass() != DoubleFieldSource.class) return false;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/FloatFieldSource.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/FloatFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/FloatFieldSource.java
index c4ab4b9..87285f4 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/FloatFieldSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/FloatFieldSource.java
@@ -52,7 +52,7 @@ public class FloatFieldSource extends FieldCacheSource {
   @Override
   public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
 
-    final NumericDocValues arr = DocValues.getNumeric(readerContext.reader(), field);
+    final NumericDocValues arr = getNumericDocValues(context, readerContext);
     
     return new FloatDocValues(this) {
       int lastDocID;
@@ -105,6 +105,10 @@ public class FloatFieldSource extends FieldCacheSource {
     };
   }
 
+  protected NumericDocValues getNumericDocValues(Map context, LeafReaderContext readerContext) throws IOException {
+    return DocValues.getNumeric(readerContext.reader(), field);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (o.getClass() !=  FloatFieldSource.class) return false;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/IntFieldSource.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/IntFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/IntFieldSource.java
index 2887e03..8d2ab7f 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/IntFieldSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/IntFieldSource.java
@@ -52,11 +52,9 @@ public class IntFieldSource extends FieldCacheSource {
   @Override
   public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
     
-    final NumericDocValues arr = DocValues.getNumeric(readerContext.reader(), field);
+    final NumericDocValues arr = getNumericDocValues(context, readerContext);
 
     return new IntDocValues(this) {
-      final MutableValueInt val = new MutableValueInt();
-
       int lastDocID;
 
       private int getValueForDoc(int doc) throws IOException {
@@ -111,6 +109,10 @@ public class IntFieldSource extends FieldCacheSource {
     };
   }
 
+  protected NumericDocValues getNumericDocValues(Map context, LeafReaderContext readerContext) throws IOException {
+    return DocValues.getNumeric(readerContext.reader(), field);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (o.getClass() !=  IntFieldSource.class) return false;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/LongFieldSource.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/LongFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/LongFieldSource.java
index 2a43f60..8474362 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/LongFieldSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/LongFieldSource.java
@@ -64,7 +64,7 @@ public class LongFieldSource extends FieldCacheSource {
   @Override
   public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
     
-    final NumericDocValues arr = DocValues.getNumeric(readerContext.reader(), field);
+    final NumericDocValues arr = getNumericDocValues(context, readerContext);
 
     return new LongDocValues(this) {
       int lastDocID;
@@ -142,6 +142,10 @@ public class LongFieldSource extends FieldCacheSource {
     };
   }
 
+  protected NumericDocValues getNumericDocValues(Map context, LeafReaderContext readerContext) throws IOException {
+    return DocValues.getNumeric(readerContext.reader(), field);
+  }
+
   protected MutableValueLong newMutableValueLong() {
     return new MutableValueLong();  
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedDoubleFieldSource.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedDoubleFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedDoubleFieldSource.java
new file mode 100644
index 0000000..b0728cf
--- /dev/null
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedDoubleFieldSource.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.queries.function.valuesource;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.SortedNumericSelector;
+import org.apache.lucene.search.SortedNumericSelector.Type;
+import org.apache.lucene.search.SortedNumericSortField;
+
+/**
+ * Obtains double field values from {@link org.apache.lucene.index.LeafReader#getSortedNumericDocValues} and using a 
+ * {@link org.apache.lucene.search.SortedNumericSelector} it gives a single-valued ValueSource view of a field.
+ */
+public class MultiValuedDoubleFieldSource extends DoubleFieldSource {
+
+  protected final SortedNumericSelector.Type selector;
+
+  public MultiValuedDoubleFieldSource(String field, Type selector) {
+    super(field);
+    this.selector = selector;
+    Objects.requireNonNull(field, "Field is required to create a MultiValuedDoubleFieldSource");
+    Objects.requireNonNull(selector, "SortedNumericSelector is required to create a MultiValuedDoubleFieldSource");
+  }
+  
+  @Override
+  public SortField getSortField(boolean reverse) {
+    return new SortedNumericSortField(field, SortField.Type.DOUBLE, reverse, selector);
+  }
+  
+  @Override
+  public String description() {
+    return "double(" + field + ',' + selector.name() + ')';
+  }
+  
+  @Override
+  protected NumericDocValues getNumericDocValues(Map context, LeafReaderContext readerContext) throws IOException {
+    SortedNumericDocValues sortedDv = DocValues.getSortedNumeric(readerContext.reader(), field);
+    return SortedNumericSelector.wrap(sortedDv, selector, SortField.Type.DOUBLE);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o.getClass() !=  MultiValuedDoubleFieldSource.class) return false;
+    MultiValuedDoubleFieldSource other = (MultiValuedDoubleFieldSource)o;
+    if (this.selector != other.selector) return false;
+    return this.field.equals(other.field);
+  }
+
+  @Override
+  public int hashCode() {
+    int h = super.hashCode();
+    h += selector.hashCode();
+    return h;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedFloatFieldSource.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedFloatFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedFloatFieldSource.java
new file mode 100644
index 0000000..af8eacf
--- /dev/null
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedFloatFieldSource.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.queries.function.valuesource;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.SortedNumericSelector;
+import org.apache.lucene.search.SortedNumericSelector.Type;
+import org.apache.lucene.search.SortedNumericSortField;
+
+/**
+ * Obtains float field values from {@link org.apache.lucene.index.LeafReader#getSortedNumericDocValues} and using a 
+ * {@link org.apache.lucene.search.SortedNumericSelector} it gives a single-valued ValueSource view of a field.
+ */
+public class MultiValuedFloatFieldSource extends FloatFieldSource {
+
+  protected final SortedNumericSelector.Type selector;
+
+  public MultiValuedFloatFieldSource(String field, Type selector) {
+    super(field);
+    this.selector = selector;
+    Objects.requireNonNull(field, "Field is required to create a MultiValuedFloatFieldSource");
+    Objects.requireNonNull(selector, "SortedNumericSelector is required to create a MultiValuedFloatFieldSource");
+  }
+  
+  @Override
+  public SortField getSortField(boolean reverse) {
+    return new SortedNumericSortField(field, SortField.Type.FLOAT, reverse, selector);
+  }
+  
+  @Override
+  public String description() {
+    return "float(" + field + ',' + selector.name() + ')';
+  }
+  
+  @Override
+  protected NumericDocValues getNumericDocValues(Map context, LeafReaderContext readerContext) throws IOException {
+    SortedNumericDocValues sortedDv = DocValues.getSortedNumeric(readerContext.reader(), field);
+    return SortedNumericSelector.wrap(sortedDv, selector, SortField.Type.FLOAT);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o.getClass() !=  MultiValuedFloatFieldSource.class) return false;
+    MultiValuedFloatFieldSource other = (MultiValuedFloatFieldSource)o;
+    if (this.selector != other.selector) return false;
+    return this.field.equals(other.field);
+  }
+
+  @Override
+  public int hashCode() {
+    int h = super.hashCode();
+    h += selector.hashCode();
+    return h;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedIntFieldSource.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedIntFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedIntFieldSource.java
new file mode 100644
index 0000000..3110f8d
--- /dev/null
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedIntFieldSource.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.queries.function.valuesource;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.SortedNumericSelector;
+import org.apache.lucene.search.SortedNumericSelector.Type;
+import org.apache.lucene.search.SortedNumericSortField;
+
+/**
+ * Obtains int field values from {@link org.apache.lucene.index.LeafReader#getSortedNumericDocValues} and using a 
+ * {@link org.apache.lucene.search.SortedNumericSelector} it gives a single-valued ValueSource view of a field.
+ */
+public class MultiValuedIntFieldSource extends IntFieldSource {
+
+  protected final SortedNumericSelector.Type selector;
+
+  public MultiValuedIntFieldSource(String field, Type selector) {
+    super(field);
+    this.selector = selector;
+    Objects.requireNonNull(field, "Field is required to create a MultiValuedIntFieldSource");
+    Objects.requireNonNull(selector, "SortedNumericSelector is required to create a MultiValuedIntFieldSource");
+  }
+  
+  @Override
+  public SortField getSortField(boolean reverse) {
+    return new SortedNumericSortField(field, SortField.Type.INT, reverse, selector);
+  }
+  
+  @Override
+  public String description() {
+    return "int(" + field + ',' + selector.name() + ')';
+  }
+  
+  @Override
+  protected NumericDocValues getNumericDocValues(Map context, LeafReaderContext readerContext) throws IOException {
+    SortedNumericDocValues sortedDv = DocValues.getSortedNumeric(readerContext.reader(), field);
+    return SortedNumericSelector.wrap(sortedDv, selector, SortField.Type.INT);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o.getClass() !=  MultiValuedIntFieldSource.class) return false;
+    MultiValuedIntFieldSource other = (MultiValuedIntFieldSource)o;
+    if (this.selector != other.selector) return false;
+    return this.field.equals(other.field);
+  }
+
+  @Override
+  public int hashCode() {
+    int h = super.hashCode();
+    h += selector.hashCode();
+    return h;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedLongFieldSource.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedLongFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedLongFieldSource.java
new file mode 100644
index 0000000..5e31e13
--- /dev/null
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedLongFieldSource.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.queries.function.valuesource;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.SortedNumericSelector;
+import org.apache.lucene.search.SortedNumericSelector.Type;
+import org.apache.lucene.search.SortedNumericSortField;
+
+/**
+ * Obtains long field values from {@link org.apache.lucene.index.LeafReader#getSortedNumericDocValues} and using a 
+ * {@link org.apache.lucene.search.SortedNumericSelector} it gives a single-valued ValueSource view of a field.
+ */
+public class MultiValuedLongFieldSource extends LongFieldSource {
+
+  protected final SortedNumericSelector.Type selector;
+
+  public MultiValuedLongFieldSource(String field, Type selector) {
+    super(field);
+    this.selector = selector;
+    Objects.requireNonNull(field, "Field is required to create a MultiValuedLongFieldSource");
+    Objects.requireNonNull(selector, "SortedNumericSelector is required to create a MultiValuedLongFieldSource");
+  }
+  
+  @Override
+  public SortField getSortField(boolean reverse) {
+    return new SortedNumericSortField(field, SortField.Type.LONG, reverse, selector);
+  }
+  
+  @Override
+  public String description() {
+    return "long(" + field + ',' + selector.name() + ')';
+  }
+  
+  @Override
+  protected NumericDocValues getNumericDocValues(Map context, LeafReaderContext readerContext) throws IOException {
+    SortedNumericDocValues sortedDv = DocValues.getSortedNumeric(readerContext.reader(), field);
+    return SortedNumericSelector.wrap(sortedDv, selector, SortField.Type.LONG);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o.getClass() !=  MultiValuedLongFieldSource.class) return false;
+    MultiValuedLongFieldSource other = (MultiValuedLongFieldSource)o;
+    if (this.selector != other.selector) return false;
+    return this.field.equals(other.field);
+  }
+
+  @Override
+  public int hashCode() {
+    int h = super.hashCode();
+    h += selector.hashCode();
+    return h;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/test/org/apache/lucene/queries/function/FunctionTestSetup.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/FunctionTestSetup.java b/lucene/queries/src/test/org/apache/lucene/queries/function/FunctionTestSetup.java
index 2764a8f..4b4c2a9 100644
--- a/lucene/queries/src/test/org/apache/lucene/queries/function/FunctionTestSetup.java
+++ b/lucene/queries/src/test/org/apache/lucene/queries/function/FunctionTestSetup.java
@@ -23,15 +23,20 @@ import org.apache.lucene.document.Field;
 import org.apache.lucene.document.FieldType;
 import org.apache.lucene.document.NumericDocValuesField;
 import org.apache.lucene.document.SortedDocValuesField;
+import org.apache.lucene.document.SortedNumericDocValuesField;
 import org.apache.lucene.document.StoredField;
 import org.apache.lucene.document.TextField;
 import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.RandomIndexWriter;
 import org.apache.lucene.queries.function.valuesource.FloatFieldSource;
 import org.apache.lucene.queries.function.valuesource.IntFieldSource;
+import org.apache.lucene.queries.function.valuesource.MultiValuedFloatFieldSource;
+import org.apache.lucene.queries.function.valuesource.MultiValuedIntFieldSource;
+import org.apache.lucene.search.SortedNumericSelector;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.NumericUtils;
 import org.apache.lucene.util.TestUtil;
 import org.junit.AfterClass;
 import org.junit.Ignore;
@@ -53,10 +58,35 @@ public abstract class FunctionTestSetup extends LuceneTestCase {
   protected static final String ID_FIELD = "id";
   protected static final String TEXT_FIELD = "text";
   protected static final String INT_FIELD = "iii";
+  /**
+   * This field is multiValued and should give the exact same results as
+   * {@link #INT_FIELD} when used with MIN selector
+   */
+  protected static final String INT_FIELD_MV_MIN = "iii_min";
+  /**
+   * This field is multiValued and should give the exact same results as
+   * {@link #INT_FIELD} when used with MAX selector
+   */
+  protected static final String INT_FIELD_MV_MAX = "iii_max";
+
   protected static final String FLOAT_FIELD = "fff";
+  /**
+   * This field is multiValued and should give the exact same results as
+   * {@link #FLOAT_FIELD} when used with MIN selector
+   */
+  protected static final String FLOAT_FIELD_MV_MIN = "fff_min";
+  /**
+   * This field is multiValued and should give the exact same results as
+   * {@link #FLOAT_FIELD} when used with MAX selector
+   */
+  protected static final String FLOAT_FIELD_MV_MAX = "fff_max";
 
   protected ValueSource INT_VALUESOURCE = new IntFieldSource(INT_FIELD);
+  protected ValueSource INT_MV_MIN_VALUESOURCE = new MultiValuedIntFieldSource(INT_FIELD_MV_MIN, SortedNumericSelector.Type.MIN);
+  protected ValueSource INT_MV_MAX_VALUESOURCE = new MultiValuedIntFieldSource(INT_FIELD_MV_MAX, SortedNumericSelector.Type.MAX);
   protected ValueSource FLOAT_VALUESOURCE = new FloatFieldSource(FLOAT_FIELD);
+  protected ValueSource FLOAT_MV_MIN_VALUESOURCE = new MultiValuedFloatFieldSource(FLOAT_FIELD_MV_MIN, SortedNumericSelector.Type.MIN);
+  protected ValueSource FLOAT_MV_MAX_VALUESOURCE = new MultiValuedFloatFieldSource(FLOAT_FIELD_MV_MAX, SortedNumericSelector.Type.MAX);
 
   private static final String DOC_TEXT_LINES[] = {
           "Well, this is just some plain text we use for creating the ",
@@ -148,6 +178,34 @@ public abstract class FunctionTestSetup extends LuceneTestCase {
     f = new StoredField(FLOAT_FIELD, scoreAndID); // for function scoring
     d.add(f);
     d.add(new NumericDocValuesField(FLOAT_FIELD, Float.floatToRawIntBits(scoreAndID)));
+    
+    f = new StoredField(INT_FIELD_MV_MIN, scoreAndID);
+    d.add(f);
+    f = new StoredField(INT_FIELD_MV_MIN, scoreAndID + 1);
+    d.add(f);
+    d.add(new SortedNumericDocValuesField(INT_FIELD_MV_MIN, scoreAndID));
+    d.add(new SortedNumericDocValuesField(INT_FIELD_MV_MIN, scoreAndID + 1));
+    
+    f = new StoredField(INT_FIELD_MV_MAX, scoreAndID);
+    d.add(f);
+    f = new StoredField(INT_FIELD_MV_MAX, scoreAndID - 1);
+    d.add(f);
+    d.add(new SortedNumericDocValuesField(INT_FIELD_MV_MAX, scoreAndID));
+    d.add(new SortedNumericDocValuesField(INT_FIELD_MV_MAX, scoreAndID - 1));
+    
+    f = new StoredField(FLOAT_FIELD_MV_MIN, scoreAndID);
+    d.add(f);
+    f = new StoredField(FLOAT_FIELD_MV_MIN, scoreAndID + 1);
+    d.add(f);
+    d.add(new SortedNumericDocValuesField(FLOAT_FIELD_MV_MIN, NumericUtils.floatToSortableInt(scoreAndID)));
+    d.add(new SortedNumericDocValuesField(FLOAT_FIELD_MV_MIN, NumericUtils.floatToSortableInt(scoreAndID + 1)));
+    
+    f = new StoredField(FLOAT_FIELD_MV_MAX, scoreAndID);
+    d.add(f);
+    f = new StoredField(FLOAT_FIELD_MV_MAX, scoreAndID - 1);
+    d.add(f);
+    d.add(new SortedNumericDocValuesField(FLOAT_FIELD_MV_MAX, NumericUtils.floatToSortableInt(scoreAndID)));
+    d.add(new SortedNumericDocValuesField(FLOAT_FIELD_MV_MAX, NumericUtils.floatToSortableInt(scoreAndID - 1)));
 
     iw.addDocument(d);
     log("added: " + d);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/test/org/apache/lucene/queries/function/TestDocValuesFieldSources.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestDocValuesFieldSources.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestDocValuesFieldSources.java
index 7af2d49..479ac3c 100644
--- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestDocValuesFieldSources.java
+++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestDocValuesFieldSources.java
@@ -24,6 +24,7 @@ import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.NumericDocValuesField;
 import org.apache.lucene.document.SortedDocValuesField;
+import org.apache.lucene.document.SortedNumericDocValuesField;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.DocValuesType;
@@ -31,6 +32,8 @@ import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.RandomIndexWriter;
 import org.apache.lucene.queries.function.valuesource.BytesRefFieldSource;
 import org.apache.lucene.queries.function.valuesource.LongFieldSource;
+import org.apache.lucene.queries.function.valuesource.MultiValuedLongFieldSource;
+import org.apache.lucene.search.SortedNumericSelector.Type;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.BytesRefBuilder;
@@ -60,6 +63,9 @@ public class TestDocValuesFieldSources extends LuceneTestCase {
       case NUMERIC:
         f = new NumericDocValuesField("dv", 0);
         break;
+      case SORTED_NUMERIC:
+        f = new SortedNumericDocValuesField("dv", 0);
+        break;
       default:
         throw new AssertionError();
     }
@@ -81,6 +87,7 @@ public class TestDocValuesFieldSources extends LuceneTestCase {
           f.setBytesValue(new BytesRef((String) vals[i]));
           break;
         case NUMERIC:
+        case SORTED_NUMERIC:
           final int bitsPerValue = RandomNumbers.randomIntBetween(random(), 1, 31); // keep it an int
           vals[i] = (long) random().nextInt((int) PackedInts.maxValue(bitsPerValue));
           f.setLongValue((Long) vals[i]);
@@ -105,6 +112,10 @@ public class TestDocValuesFieldSources extends LuceneTestCase {
         case NUMERIC:
           vs = new LongFieldSource("dv");
           break;
+        case SORTED_NUMERIC:
+          // Since we are not indexing multiple values, MIN and MAX should work the same way
+          vs = random().nextBoolean()? new MultiValuedLongFieldSource("dv", Type.MIN): new MultiValuedLongFieldSource("dv", Type.MAX);
+          break;
         default:
           throw new AssertionError();
       }
@@ -136,6 +147,7 @@ public class TestDocValuesFieldSources extends LuceneTestCase {
             assertEquals(new BytesRef((String) expected), bytes.get());
             break;
           case NUMERIC:
+          case SORTED_NUMERIC:
             assertEquals(((Number) expected).longValue(), values.longVal(i));
             break;
         }
@@ -147,7 +159,7 @@ public class TestDocValuesFieldSources extends LuceneTestCase {
 
   public void test() throws IOException {
     for (DocValuesType type : DocValuesType.values()) {
-      if (type != DocValuesType.SORTED_SET && type != DocValuesType.SORTED_NUMERIC && type != DocValuesType.NONE) {
+      if (type != DocValuesType.SORTED_SET && type != DocValuesType.NONE) {
         test(type);
       }
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/test/org/apache/lucene/queries/function/TestFieldScoreQuery.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFieldScoreQuery.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFieldScoreQuery.java
index 843f452..977d801 100644
--- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFieldScoreQuery.java
+++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFieldScoreQuery.java
@@ -49,6 +49,12 @@ public class TestFieldScoreQuery extends FunctionTestSetup {
   public void testRankInt () throws Exception {
     doTestRank(INT_VALUESOURCE);
   }
+  
+  @Test
+  public void testRankIntMultiValued() throws Exception {
+    doTestRank(INT_MV_MAX_VALUESOURCE);
+    doTestRank(INT_MV_MIN_VALUESOURCE);
+  }
 
   /** Test that FieldScoreQuery of Type.FLOAT returns docs in expected order. */
   @Test
@@ -56,6 +62,13 @@ public class TestFieldScoreQuery extends FunctionTestSetup {
     // same values, but in flot format
     doTestRank(FLOAT_VALUESOURCE);
   }
+  
+  @Test
+  public void testRankFloatMultiValued() throws Exception {
+    // same values, but in flot format
+    doTestRank(FLOAT_MV_MAX_VALUESOURCE);
+    doTestRank(FLOAT_MV_MIN_VALUESOURCE);
+  }
 
   // Test that FieldScoreQuery returns docs in expected order.
   private void doTestRank (ValueSource valueSource) throws Exception {
@@ -82,6 +95,12 @@ public class TestFieldScoreQuery extends FunctionTestSetup {
   public void testExactScoreInt () throws  Exception {
     doTestExactScore(INT_VALUESOURCE);
   }
+  
+  @Test
+  public void testExactScoreIntMultiValued() throws  Exception {
+    doTestExactScore(INT_MV_MAX_VALUESOURCE);
+    doTestExactScore(INT_MV_MIN_VALUESOURCE);
+  }
 
   /** Test that FieldScoreQuery of Type.FLOAT returns the expected scores. */
   @Test
@@ -89,6 +108,13 @@ public class TestFieldScoreQuery extends FunctionTestSetup {
     // same values, but in flot format
     doTestExactScore(FLOAT_VALUESOURCE);
   }
+  
+  @Test
+  public void testExactScoreFloatMultiValued() throws  Exception {
+    // same values, but in flot format
+    doTestExactScore(FLOAT_MV_MAX_VALUESOURCE);
+    doTestExactScore(FLOAT_MV_MIN_VALUESOURCE);
+  }
 
   // Test that FieldScoreQuery returns docs with expected score.
   private void doTestExactScore (ValueSource valueSource) throws Exception {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionRangeQuery.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionRangeQuery.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionRangeQuery.java
index 5f3a412..22b3ed5 100644
--- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionRangeQuery.java
+++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionRangeQuery.java
@@ -57,11 +57,23 @@ public class TestFunctionRangeQuery extends FunctionTestSetup {
   public void testRangeInt() throws IOException {
     doTestRange(INT_VALUESOURCE);
   }
+  
+  @Test
+  public void testRangeIntMultiValued() throws IOException {
+    doTestRange(INT_MV_MAX_VALUESOURCE);
+    doTestRange(INT_MV_MIN_VALUESOURCE);
+  }
 
   @Test
   public void testRangeFloat() throws IOException {
     doTestRange(FLOAT_VALUESOURCE);
   }
+  
+  @Test
+  public void testRangeFloatMultiValued() throws IOException {
+    doTestRange(FLOAT_MV_MAX_VALUESOURCE);
+    doTestRange(FLOAT_MV_MIN_VALUESOURCE);
+  }
 
   private void doTestRange(ValueSource valueSource) throws IOException {
     Query rangeQuery = new FunctionRangeQuery(valueSource, 2, 4, true, false);
@@ -75,8 +87,17 @@ public class TestFunctionRangeQuery extends FunctionTestSetup {
 
   @Test
   public void testDeleted() throws IOException {
-    // We delete doc with #3. Note we don't commit it to disk; we search using a near eal-time reader.
-    final ValueSource valueSource = INT_VALUESOURCE;
+    doTestDeleted(INT_VALUESOURCE);
+  }
+  
+  @Test
+  public void testDeletedMultiValued() throws IOException {
+    doTestDeleted(INT_MV_MAX_VALUESOURCE);
+    doTestDeleted(INT_MV_MIN_VALUESOURCE);
+  }
+  
+  private void doTestDeleted(ValueSource valueSource) throws IOException {
+    // We delete doc with #3. Note we don't commit it to disk; we search using a near real-time reader.
     IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(null));
     try {
       writer.deleteDocuments(new FunctionRangeQuery(valueSource, 3, 3, true, true));//delete the one with #3
@@ -103,6 +124,18 @@ public class TestFunctionRangeQuery extends FunctionTestSetup {
             "  2.0 = int(" + INT_FIELD + ")=2\n",
         explain.toString());
   }
+  
+  @Test
+  public void testExplainMultiValued() throws IOException {
+    Query rangeQuery = new FunctionRangeQuery(INT_MV_MIN_VALUESOURCE, 2, 2, true, true);
+    ScoreDoc[] scoreDocs = indexSearcher.search(rangeQuery, N_DOCS).scoreDocs;
+    Explanation explain = indexSearcher.explain(rangeQuery, scoreDocs[0].doc);
+    // Just validate it looks reasonable
+    assertEquals(
+            "2.0 = frange(int(" + INT_FIELD_MV_MIN + ",MIN)):[2 TO 2]\n" +
+            "  2.0 = int(" + INT_FIELD_MV_MIN + ",MIN)=2\n",
+        explain.toString());
+  }
 
   private void expectScores(ScoreDoc[] scoreDocs, int... docScores) {
     assertEquals(docScores.length, scoreDocs.length);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java
index 57caac6..e008293 100644
--- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java
+++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java
@@ -16,10 +16,10 @@
  */
 package org.apache.lucene.queries.function;
 
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.io.IOException;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.MockAnalyzer;
@@ -27,13 +27,14 @@ import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.NumericDocValuesField;
 import org.apache.lucene.document.SortedDocValuesField;
+import org.apache.lucene.document.SortedNumericDocValuesField;
 import org.apache.lucene.document.StringField;
 import org.apache.lucene.document.TextField;
-import org.apache.lucene.index.Term;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriterConfig;
-import org.apache.lucene.index.RandomIndexWriter;
 import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.Term;
 import org.apache.lucene.queries.function.docvalues.FloatDocValues;
 import org.apache.lucene.queries.function.valuesource.BytesRefFieldSource;
 import org.apache.lucene.queries.function.valuesource.ConstValueSource;
@@ -54,6 +55,10 @@ import org.apache.lucene.queries.function.valuesource.MaxFloatFunction;
 import org.apache.lucene.queries.function.valuesource.MinFloatFunction;
 import org.apache.lucene.queries.function.valuesource.MultiFloatFunction;
 import org.apache.lucene.queries.function.valuesource.MultiFunction;
+import org.apache.lucene.queries.function.valuesource.MultiValuedDoubleFieldSource;
+import org.apache.lucene.queries.function.valuesource.MultiValuedFloatFieldSource;
+import org.apache.lucene.queries.function.valuesource.MultiValuedIntFieldSource;
+import org.apache.lucene.queries.function.valuesource.MultiValuedLongFieldSource;
 import org.apache.lucene.queries.function.valuesource.NormValueSource;
 import org.apache.lucene.queries.function.valuesource.NumDocsValueSource;
 import org.apache.lucene.queries.function.valuesource.PowFloatFunction;
@@ -73,13 +78,15 @@ import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
-import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.SortedNumericSelector.Type;
 import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.search.similarities.ClassicSimilarity;
 import org.apache.lucene.search.similarities.Similarity;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.NumericUtils;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
@@ -99,9 +106,9 @@ public class TestValueSources extends LuceneTestCase {
   static final ValueSource BOGUS_LONG_VS = new LongFieldSource("bogus_field");
 
   static final List<String[]> documents = Arrays.asList(new String[][] {
-      /*             id,  double, float, int,  long,   string, text */ 
-      new String[] { "0", "3.63", "5.2", "35", "4343", "test", "this is a test test test" },
-      new String[] { "1", "5.65", "9.3", "54", "1954", "bar",  "second test" },
+      /*             id,  double, float, int,  long,   string, text,                       double MV (x3),             int MV (x3)*/ 
+      new String[] { "0", "3.63", "5.2", "35", "4343", "test", "this is a test test test", "2.13", "3.69",  "-0.11",   "1", "7", "5"},
+      new String[] { "1", "5.65", "9.3", "54", "1954", "bar",  "second test",              "12.79", "123.456", "0.01", "12", "900", "-1" },
   });
   
   @BeforeClass
@@ -111,36 +118,29 @@ public class TestValueSources extends LuceneTestCase {
     IndexWriterConfig iwConfig = newIndexWriterConfig(analyzer);
     iwConfig.setMergePolicy(newLogMergePolicy());
     RandomIndexWriter iw = new RandomIndexWriter(random(), dir, iwConfig);
-    Document document = new Document();
-    Field idField = new StringField("id", "", Field.Store.NO);
-    document.add(idField);
-    Field idDVField = new SortedDocValuesField("id", new BytesRef());
-    document.add(idDVField);
-    Field doubleDVField = new NumericDocValuesField("double", 0);
-    document.add(doubleDVField);
-    Field floatDVField = new NumericDocValuesField("float", 0);
-    document.add(floatDVField);
-    Field intDVField = new NumericDocValuesField("int", 0);
-    document.add(intDVField);
-    Field longDVField = new NumericDocValuesField("long", 0);
-    document.add(longDVField);
-    Field stringField = new StringField("string", "", Field.Store.NO);
-    document.add(stringField);
-    Field stringDVField = new SortedDocValuesField("string", new BytesRef());
-    document.add(stringDVField);
-    Field textField = new TextField("text", "", Field.Store.NO);
-    document.add(textField);
-    
     for (String [] doc : documents) {
-      idField.setStringValue(doc[0]);
-      idDVField.setBytesValue(new BytesRef(doc[0]));
-      doubleDVField.setLongValue(Double.doubleToRawLongBits(Double.valueOf(doc[1])));
-      floatDVField.setLongValue(Float.floatToRawIntBits(Float.valueOf(doc[2])));
-      intDVField.setLongValue(Integer.valueOf(doc[3]));
-      longDVField.setLongValue(Long.valueOf(doc[4]));
-      stringField.setStringValue(doc[5]);
-      stringDVField.setBytesValue(new BytesRef(doc[5]));
-      textField.setStringValue(doc[6]);
+      Document document = new Document();
+      document.add(new StringField("id", doc[0], Field.Store.NO));
+      document.add(new SortedDocValuesField("id", new BytesRef(doc[0])));
+      document.add(new NumericDocValuesField("double", Double.doubleToRawLongBits(Double.parseDouble(doc[1]))));
+      document.add(new NumericDocValuesField("float", Float.floatToRawIntBits(Float.valueOf(doc[2]))));
+      document.add(new NumericDocValuesField("int", Integer.valueOf(doc[3])));
+      document.add(new NumericDocValuesField("long", Long.valueOf(doc[4])));
+      document.add(new StringField("string", doc[5], Field.Store.NO));
+      document.add(new SortedDocValuesField("string", new BytesRef(doc[5])));
+      document.add(new TextField("text", doc[6], Field.Store.NO));
+      document.add(new SortedNumericDocValuesField("floatMv", NumericUtils.floatToSortableInt(Float.parseFloat(doc[7]))));
+      document.add(new SortedNumericDocValuesField("floatMv", NumericUtils.floatToSortableInt(Float.parseFloat(doc[8]))));
+      document.add(new SortedNumericDocValuesField("floatMv", NumericUtils.floatToSortableInt(Float.parseFloat(doc[9]))));
+      document.add(new SortedNumericDocValuesField("doubleMv", NumericUtils.doubleToSortableLong(Double.parseDouble(doc[7]))));
+      document.add(new SortedNumericDocValuesField("doubleMv", NumericUtils.doubleToSortableLong(Double.parseDouble(doc[8]))));
+      document.add(new SortedNumericDocValuesField("doubleMv", NumericUtils.doubleToSortableLong(Double.parseDouble(doc[9]))));
+      document.add(new SortedNumericDocValuesField("intMv", Long.parseLong(doc[10])));
+      document.add(new SortedNumericDocValuesField("intMv", Long.parseLong(doc[11])));
+      document.add(new SortedNumericDocValuesField("intMv", Long.parseLong(doc[12])));
+      document.add(new SortedNumericDocValuesField("longMv", Long.parseLong(doc[10])));
+      document.add(new SortedNumericDocValuesField("longMv", Long.parseLong(doc[11])));
+      document.add(new SortedNumericDocValuesField("longMv", Long.parseLong(doc[12])));
       iw.addDocument(document);
     }
     
@@ -197,6 +197,16 @@ public class TestValueSources extends LuceneTestCase {
     assertNoneExist(BOGUS_DOUBLE_VS);
   }
   
+  public void testDoubleMultiValued() throws Exception {
+    ValueSource vs = new MultiValuedDoubleFieldSource("doubleMv", Type.MAX);
+    assertHits(new FunctionQuery(vs), new float[] { 3.69f, 123.456f });
+    assertAllExist(vs);
+    
+    vs = new MultiValuedDoubleFieldSource("doubleMv", Type.MIN);
+    assertHits(new FunctionQuery(vs), new float[] { -0.11f, 0.01f });
+    assertAllExist(vs);
+  }
+  
   public void testFloat() throws Exception {
     ValueSource vs = new FloatFieldSource("float");
     assertHits(new FunctionQuery(vs), new float[] { 5.2f, 9.3f });
@@ -204,6 +214,16 @@ public class TestValueSources extends LuceneTestCase {
     assertNoneExist(BOGUS_FLOAT_VS);
   }
   
+  public void testFloatMultiValued() throws Exception {
+    ValueSource vs = new MultiValuedFloatFieldSource("floatMv", Type.MAX);
+    assertHits(new FunctionQuery(vs), new float[] { 3.69f, 123.456f });
+    assertAllExist(vs);
+    
+    vs = new MultiValuedFloatFieldSource("floatMv", Type.MIN);
+    assertHits(new FunctionQuery(vs), new float[] { -0.11f, 0.01f });
+    assertAllExist(vs);
+  }
+  
   public void testIDF() throws Exception {
     Similarity saved = searcher.getSimilarity(true);
     try {
@@ -249,7 +269,16 @@ public class TestValueSources extends LuceneTestCase {
     assertHits(new FunctionQuery(vs), new float[] { 35f, 54f });
     assertAllExist(vs);
     assertNoneExist(BOGUS_INT_VS);
-                                 
+  }
+  
+  public void testIntMultiValued() throws Exception {
+    ValueSource vs = new MultiValuedIntFieldSource("intMv", Type.MAX);
+    assertHits(new FunctionQuery(vs), new float[] { 7f, 900f });
+    assertAllExist(vs);
+    
+    vs = new MultiValuedIntFieldSource("intMv", Type.MIN);
+    assertHits(new FunctionQuery(vs), new float[] { 1f, -1f });
+    assertAllExist(vs);
   }
   
   public void testJoinDocFreq() throws Exception {
@@ -274,6 +303,16 @@ public class TestValueSources extends LuceneTestCase {
     assertNoneExist(BOGUS_LONG_VS);
   }
   
+  public void testLongMultiValued() throws Exception {
+    ValueSource vs = new MultiValuedLongFieldSource("longMv", Type.MAX);
+    assertHits(new FunctionQuery(vs), new float[] { 7f, 900f });
+    assertAllExist(vs);
+    
+    vs = new MultiValuedLongFieldSource("longMv", Type.MIN);
+    assertHits(new FunctionQuery(vs), new float[] { 1f, -1f });
+    assertAllExist(vs);
+  }
+  
   public void testMaxDoc() throws Exception {
     ValueSource vs = new MaxDocValueSource();
     assertHits(new FunctionQuery(vs), new float[] { 2f, 2f });

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CoreParser.java
----------------------------------------------------------------------
diff --git a/lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CoreParser.java b/lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CoreParser.java
index d8aa8ef..8637c4a 100644
--- a/lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CoreParser.java
+++ b/lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CoreParser.java
@@ -20,6 +20,7 @@ import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.queryparser.classic.QueryParser;
 import org.apache.lucene.queryparser.xml.builders.*;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.search.spans.SpanQuery;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
@@ -31,7 +32,7 @@ import java.io.InputStream;
 /**
  * Assembles a QueryBuilder which uses only core Lucene Query objects
  */
-public class CoreParser implements QueryBuilder {
+public class CoreParser implements QueryBuilder, SpanQueryBuilder {
 
   protected String defaultField;
   protected Analyzer analyzer;
@@ -114,6 +115,11 @@ public class CoreParser implements QueryBuilder {
     return getQuery(parseXML(xmlStream).getDocumentElement());
   }
 
+  // for test use
+  SpanQuery parseAsSpanQuery(InputStream xmlStream) throws ParserException {
+    return getSpanQuery(parseXML(xmlStream).getDocumentElement());
+  }
+
   public void addQueryBuilder(String nodeName, QueryBuilder builder) {
     queryFactory.addBuilder(nodeName, builder);
   }
@@ -122,6 +128,11 @@ public class CoreParser implements QueryBuilder {
     spanFactory.addBuilder(nodeName, builder);
   }
 
+  public void addSpanQueryBuilder(String nodeName, SpanQueryBuilder builder) {
+    queryFactory.addBuilder(nodeName, builder);
+    spanFactory.addBuilder(nodeName, builder);
+  }
+
   static Document parseXML(InputStream pXmlFile) throws ParserException {
     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
     DocumentBuilder db = null;
@@ -144,4 +155,9 @@ public class CoreParser implements QueryBuilder {
   public Query getQuery(Element e) throws ParserException {
     return queryFactory.getQuery(e);
   }
+
+  @Override
+  public SpanQuery getSpanQuery(Element e) throws ParserException {
+    return spanFactory.getSpanQuery(e);
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestMultiFieldQueryParser.java
----------------------------------------------------------------------
diff --git a/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestMultiFieldQueryParser.java b/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestMultiFieldQueryParser.java
index ae15284..9e1fb55 100644
--- a/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestMultiFieldQueryParser.java
+++ b/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestMultiFieldQueryParser.java
@@ -351,8 +351,8 @@ public class TestMultiFieldQueryParser extends LuceneTestCase {
     assertEquals("Synonym(b:dog b:dogs) Synonym(t:dog t:dogs)", q.toString());
     q = parser.parse("guinea pig");
     assertFalse(parser.getSplitOnWhitespace());
-    assertEquals("Graph(b:guinea b:pig, b:cavy, hasBoolean=true, hasPhrase=false) "
-        + "Graph(t:guinea t:pig, t:cavy, hasBoolean=true, hasPhrase=false)", q.toString());
+    assertEquals("Graph(+b:guinea +b:pig, b:cavy, hasBoolean=true, hasPhrase=false) "
+        + "Graph(+t:guinea +t:pig, t:cavy, hasBoolean=true, hasPhrase=false)", q.toString());
     parser.setSplitOnWhitespace(true);
     q = parser.parse("guinea pig");
     assertEquals("(b:guinea t:guinea) (b:pig t:pig)", q.toString());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java
----------------------------------------------------------------------
diff --git a/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java b/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java
index 87bc89f..1d7a0f6 100644
--- a/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java
+++ b/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java
@@ -509,8 +509,8 @@ public class TestQueryParser extends QueryParserTestBase {
 
     // A multi-word synonym source will form a graph query for synonyms that formed the graph token stream
     BooleanQuery.Builder synonym = new BooleanQuery.Builder();
-    synonym.add(guinea, BooleanClause.Occur.SHOULD);
-    synonym.add(pig, BooleanClause.Occur.SHOULD);
+    synonym.add(guinea, BooleanClause.Occur.MUST);
+    synonym.add(pig, BooleanClause.Occur.MUST);
     BooleanQuery guineaPig = synonym.build();
 
     GraphQuery graphQuery = new GraphQuery(guineaPig, cavy);
@@ -583,30 +583,30 @@ public class TestQueryParser extends QueryParserTestBase {
     assertQueryEquals("guinea /pig/", a, "guinea /pig/");
 
     // Operators should not interrupt multiword analysis if not don't associate
-    assertQueryEquals("(guinea pig)", a, "Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("+(guinea pig)", a, "+Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("-(guinea pig)", a, "-Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("!(guinea pig)", a, "-Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("NOT (guinea pig)", a, "-Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("(guinea pig)^2", a, "(Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false))^2.0");
-
-    assertQueryEquals("field:(guinea pig)", a, "Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-
-    assertQueryEquals("+small guinea pig", a, "+small Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("-small guinea pig", a, "-small Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("!small guinea pig", a, "-small Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("NOT small guinea pig", a, "-small Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("small* guinea pig", a, "small* Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("small? guinea pig", a, "small? Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-    assertQueryEquals("\"small\" guinea pig", a, "small Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
-
-    assertQueryEquals("guinea pig +running", a, "Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false) +running");
-    assertQueryEquals("guinea pig -running", a, "Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false) -running");
-    assertQueryEquals("guinea pig !running", a, "Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false) -running");
-    assertQueryEquals("guinea pig NOT running", a, "Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false) -running");
-    assertQueryEquals("guinea pig running*", a, "Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false) running*");
-    assertQueryEquals("guinea pig running?", a, "Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false) running?");
-    assertQueryEquals("guinea pig \"running\"", a, "Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false) running");
+    assertQueryEquals("(guinea pig)", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("+(guinea pig)", a, "+Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("-(guinea pig)", a, "-Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("!(guinea pig)", a, "-Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("NOT (guinea pig)", a, "-Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("(guinea pig)^2", a, "(Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false))^2.0");
+
+    assertQueryEquals("field:(guinea pig)", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+
+    assertQueryEquals("+small guinea pig", a, "+small Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("-small guinea pig", a, "-small Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("!small guinea pig", a, "-small Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("NOT small guinea pig", a, "-small Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("small* guinea pig", a, "small* Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("small? guinea pig", a, "small? Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("\"small\" guinea pig", a, "small Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+
+    assertQueryEquals("guinea pig +running", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false) +running");
+    assertQueryEquals("guinea pig -running", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false) -running");
+    assertQueryEquals("guinea pig !running", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false) -running");
+    assertQueryEquals("guinea pig NOT running", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false) -running");
+    assertQueryEquals("guinea pig running*", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false) running*");
+    assertQueryEquals("guinea pig running?", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false) running?");
+    assertQueryEquals("guinea pig \"running\"", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false) running");
 
     assertQueryEquals("\"guinea pig\"~2", a, "Graph(field:\"guinea pig\"~2, field:cavy, hasBoolean=false, hasPhrase=true)");
 
@@ -706,8 +706,8 @@ public class TestQueryParser extends QueryParserTestBase {
 
     // A multi-word synonym source will form a graph query for synonyms that formed the graph token stream
     BooleanQuery.Builder synonym = new BooleanQuery.Builder();
-    synonym.add(guinea, BooleanClause.Occur.SHOULD);
-    synonym.add(pig, BooleanClause.Occur.SHOULD);
+    synonym.add(guinea, BooleanClause.Occur.MUST);
+    synonym.add(pig, BooleanClause.Occur.MUST);
     BooleanQuery guineaPig = synonym.build();
 
     GraphQuery graphQuery = new GraphQuery(guineaPig, cavy);
@@ -715,7 +715,7 @@ public class TestQueryParser extends QueryParserTestBase {
 
     boolean oldSplitOnWhitespace = splitOnWhitespace;
     splitOnWhitespace = QueryParser.DEFAULT_SPLIT_ON_WHITESPACE;
-    assertQueryEquals("guinea pig", new MockSynonymAnalyzer(), "Graph(field:guinea field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
+    assertQueryEquals("guinea pig", new MockSynonymAnalyzer(), "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false)");
     splitOnWhitespace = oldSplitOnWhitespace;
   }
    

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/queryparser/src/test/org/apache/lucene/queryparser/xml/TestCoreParser.java
----------------------------------------------------------------------
diff --git a/lucene/queryparser/src/test/org/apache/lucene/queryparser/xml/TestCoreParser.java b/lucene/queryparser/src/test/org/apache/lucene/queryparser/xml/TestCoreParser.java
index 1906aef..8f07c4a 100644
--- a/lucene/queryparser/src/test/org/apache/lucene/queryparser/xml/TestCoreParser.java
+++ b/lucene/queryparser/src/test/org/apache/lucene/queryparser/xml/TestCoreParser.java
@@ -27,6 +27,7 @@ import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.lucene.util.LuceneTestCase;
 import org.junit.AfterClass;
 
@@ -116,6 +117,9 @@ public class TestCoreParser extends LuceneTestCase {
   public void testSpanTermXML() throws Exception {
     Query q = parse("SpanQuery.xml");
     dumpResults("Span Query", q, 5);
+    SpanQuery sq = parseAsSpan("SpanQuery.xml");
+    dumpResults("Span Query", sq, 5);
+    assertEquals(q, sq);
   }
 
   public void testConstantScoreQueryXML() throws Exception {
@@ -207,10 +211,21 @@ public class TestCoreParser extends LuceneTestCase {
   }
 
   protected Query parse(String xmlFileName) throws ParserException, IOException {
+    return implParse(xmlFileName, false);
+  }
+
+  protected SpanQuery parseAsSpan(String xmlFileName) throws ParserException, IOException {
+    return (SpanQuery)implParse(xmlFileName, true);
+  }
+
+  private Query implParse(String xmlFileName, boolean span) throws ParserException, IOException {
     try (InputStream xmlStream = TestCoreParser.class.getResourceAsStream(xmlFileName)) {
       assertNotNull("Test XML file " + xmlFileName + " cannot be found", xmlStream);
-      Query result = coreParser().parse(xmlStream);
-      return result;
+      if (span) {
+        return coreParser().parseAsSpanQuery(xmlStream);
+      } else {
+        return coreParser().parse(xmlStream);
+      }
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java
new file mode 100644
index 0000000..50ddf1a
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.document;
+
+import java.io.IOException;
+
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.GeoUtils;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.TwoPhaseIterator;
+import org.apache.lucene.search.Weight;
+
+/** Distance query for {@link LatLonDocValuesField}. */
+final class LatLonDocValuesBoxQuery extends Query {
+
+  private final String field;
+  private final int minLatitude, maxLatitude, minLongitude, maxLongitude;
+  private final boolean crossesDateline;
+
+  LatLonDocValuesBoxQuery(String field, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
+    GeoUtils.checkLatitude(minLatitude);
+    GeoUtils.checkLatitude(maxLatitude);
+    GeoUtils.checkLongitude(minLongitude);
+    GeoUtils.checkLongitude(maxLongitude);
+    if (field == null) {
+      throw new IllegalArgumentException("field must not be null");
+    }
+    this.field = field;
+    this.crossesDateline = minLongitude > maxLongitude; // make sure to compute this before rounding
+    this.minLatitude = GeoEncodingUtils.encodeLatitudeCeil(minLatitude);
+    this.maxLatitude = GeoEncodingUtils.encodeLatitude(maxLatitude);
+    this.minLongitude = GeoEncodingUtils.encodeLongitudeCeil(minLongitude);
+    this.maxLongitude = GeoEncodingUtils.encodeLongitude(maxLongitude);
+  }
+
+  @Override
+  public String toString(String field) {
+    StringBuilder sb = new StringBuilder();
+    if (!this.field.equals(field)) {
+      sb.append(this.field);
+      sb.append(':');
+    }
+    sb.append("box(minLat=").append(GeoEncodingUtils.decodeLatitude(minLatitude));
+    sb.append(", maxLat=").append(GeoEncodingUtils.decodeLatitude(maxLatitude));
+    sb.append(", minLon=").append(GeoEncodingUtils.decodeLongitude(minLongitude));
+    sb.append(", maxLon=").append(GeoEncodingUtils.decodeLongitude(maxLongitude));
+    return sb.append(")").toString();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (sameClassAs(obj) == false) {
+      return false;
+    }
+    LatLonDocValuesBoxQuery other = (LatLonDocValuesBoxQuery) obj;
+    return field.equals(other.field) &&
+        crossesDateline == other.crossesDateline &&
+        minLatitude == other.minLatitude &&
+        maxLatitude == other.maxLatitude &&
+        minLongitude == other.minLongitude &&
+        maxLongitude == other.maxLongitude;
+  }
+
+  @Override
+  public int hashCode() {
+    int h = classHash();
+    h = 31 * h + field.hashCode();
+    h = 31 * h + Boolean.hashCode(crossesDateline);
+    h = 31 * h + Integer.hashCode(minLatitude);
+    h = 31 * h + Integer.hashCode(maxLatitude);
+    h = 31 * h + Integer.hashCode(minLongitude);
+    h = 31 * h + Integer.hashCode(maxLongitude);
+    return h;
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
+    return new ConstantScoreWeight(this, boost) {
+      @Override
+      public Scorer scorer(LeafReaderContext context) throws IOException {
+        final SortedNumericDocValues values = context.reader().getSortedNumericDocValues(field);
+        if (values == null) {
+          return null;
+        }
+
+        final TwoPhaseIterator iterator = new TwoPhaseIterator(values) {
+          @Override
+          public boolean matches() throws IOException {
+            for (int i = 0, count = values.docValueCount(); i < count; ++i) {
+              final long value = values.nextValue();
+              final int lat = (int) (value >>> 32);
+              if (lat < minLatitude || lat > maxLatitude) {
+                // not within latitude range
+                continue;
+              }
+
+              final int lon = (int) (value & 0xFFFFFFFF);
+              if (crossesDateline) {
+                if (lon > maxLongitude && lon < minLongitude) {
+                  // not within longitude range
+                  continue;
+                }
+              } else {
+                if (lon < minLongitude || lon > maxLongitude) {
+                  // not within longitude range
+                  continue;
+                }
+              }
+
+              return true;
+            }
+            return false;
+          }
+
+          @Override
+          public float matchCost() {
+            return 5; // 5 comparisons
+          }
+        };
+        return new ConstantScoreScorer(this, boost, iterator);
+      }
+    };
+  }
+
+}


[11/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/StreamHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/StreamHandler.java b/solr/core/src/java/org/apache/solr/handler/StreamHandler.java
index 98486b8..bcb2faa 100644
--- a/solr/core/src/java/org/apache/solr/handler/StreamHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/StreamHandler.java
@@ -29,21 +29,63 @@ import org.apache.solr.client.solrj.io.ModelCache;
 import org.apache.solr.client.solrj.io.SolrClientCache;
 import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.comp.StreamComparator;
+import org.apache.solr.client.solrj.io.eval.AbsoluteValueEvaluator;
+import org.apache.solr.client.solrj.io.eval.AddEvaluator;
+import org.apache.solr.client.solrj.io.eval.AndEvaluator;
+import org.apache.solr.client.solrj.io.eval.DivideEvaluator;
+import org.apache.solr.client.solrj.io.eval.EqualsEvaluator;
+import org.apache.solr.client.solrj.io.eval.ExclusiveOrEvaluator;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEqualToEvaluator;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.IfThenElseEvaluator;
+import org.apache.solr.client.solrj.io.eval.LessThanEqualToEvaluator;
+import org.apache.solr.client.solrj.io.eval.LessThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.MultiplyEvaluator;
+import org.apache.solr.client.solrj.io.eval.NotEvaluator;
+import org.apache.solr.client.solrj.io.eval.OrEvaluator;
+import org.apache.solr.client.solrj.io.eval.RawValueEvaluator;
+import org.apache.solr.client.solrj.io.eval.SubtractEvaluator;
 import org.apache.solr.client.solrj.io.graph.GatherNodesStream;
 import org.apache.solr.client.solrj.io.graph.ShortestPathStream;
-import org.apache.solr.client.solrj.io.ops.AndOperation;
 import org.apache.solr.client.solrj.io.ops.ConcatOperation;
 import org.apache.solr.client.solrj.io.ops.DistinctOperation;
-import org.apache.solr.client.solrj.io.ops.EqualsOperation;
-import org.apache.solr.client.solrj.io.ops.GreaterThanEqualToOperation;
-import org.apache.solr.client.solrj.io.ops.GreaterThanOperation;
 import org.apache.solr.client.solrj.io.ops.GroupOperation;
-import org.apache.solr.client.solrj.io.ops.LessThanEqualToOperation;
-import org.apache.solr.client.solrj.io.ops.LessThanOperation;
-import org.apache.solr.client.solrj.io.ops.NotOperation;
-import org.apache.solr.client.solrj.io.ops.OrOperation;
 import org.apache.solr.client.solrj.io.ops.ReplaceOperation;
-import org.apache.solr.client.solrj.io.stream.*;
+import org.apache.solr.client.solrj.io.stream.CloudSolrStream;
+import org.apache.solr.client.solrj.io.stream.CommitStream;
+import org.apache.solr.client.solrj.io.stream.ComplementStream;
+import org.apache.solr.client.solrj.io.stream.DaemonStream;
+import org.apache.solr.client.solrj.io.stream.ExceptionStream;
+import org.apache.solr.client.solrj.io.stream.ExecutorStream;
+import org.apache.solr.client.solrj.io.stream.FacetStream;
+import org.apache.solr.client.solrj.io.stream.FeaturesSelectionStream;
+import org.apache.solr.client.solrj.io.stream.FetchStream;
+import org.apache.solr.client.solrj.io.stream.HashJoinStream;
+import org.apache.solr.client.solrj.io.stream.HavingStream;
+import org.apache.solr.client.solrj.io.stream.InnerJoinStream;
+import org.apache.solr.client.solrj.io.stream.IntersectStream;
+import org.apache.solr.client.solrj.io.stream.JDBCStream;
+import org.apache.solr.client.solrj.io.stream.LeftOuterJoinStream;
+import org.apache.solr.client.solrj.io.stream.MergeStream;
+import org.apache.solr.client.solrj.io.stream.ModelStream;
+import org.apache.solr.client.solrj.io.stream.NullStream;
+import org.apache.solr.client.solrj.io.stream.OuterHashJoinStream;
+import org.apache.solr.client.solrj.io.stream.ParallelStream;
+import org.apache.solr.client.solrj.io.stream.PriorityStream;
+import org.apache.solr.client.solrj.io.stream.RandomStream;
+import org.apache.solr.client.solrj.io.stream.RankStream;
+import org.apache.solr.client.solrj.io.stream.ReducerStream;
+import org.apache.solr.client.solrj.io.stream.RollupStream;
+import org.apache.solr.client.solrj.io.stream.ScoreNodesStream;
+import org.apache.solr.client.solrj.io.stream.SelectStream;
+import org.apache.solr.client.solrj.io.stream.SortStream;
+import org.apache.solr.client.solrj.io.stream.StatsStream;
+import org.apache.solr.client.solrj.io.stream.StreamContext;
+import org.apache.solr.client.solrj.io.stream.TextLogitStream;
+import org.apache.solr.client.solrj.io.stream.TopicStream;
+import org.apache.solr.client.solrj.io.stream.TupleStream;
+import org.apache.solr.client.solrj.io.stream.UniqueStream;
+import org.apache.solr.client.solrj.io.stream.UpdateStream;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
 import org.apache.solr.client.solrj.io.stream.expr.Expressible;
@@ -151,6 +193,7 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware,
       .withFunctionName("executor", ExecutorStream.class)
       .withFunctionName("null", NullStream.class)
       .withFunctionName("priority", PriorityStream.class)
+      
       // metrics
       .withFunctionName("min", MinMetric.class)
       .withFunctionName("max", MaxMetric.class)
@@ -166,19 +209,36 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware,
       .withFunctionName("group", GroupOperation.class)
       .withFunctionName("distinct", DistinctOperation.class)
       .withFunctionName("having", HavingStream.class)
-      .withFunctionName("and", AndOperation.class)
-      .withFunctionName("or", OrOperation.class)
-      .withFunctionName("not", NotOperation.class)
-      .withFunctionName("gt", GreaterThanOperation.class)
-      .withFunctionName("lt", LessThanOperation.class)
-      .withFunctionName("eq", EqualsOperation.class)
-      .withFunctionName("lteq", LessThanEqualToOperation.class)
-      .withFunctionName("gteq", GreaterThanEqualToOperation.class);
+      
+      // Stream Evaluators
+      .withFunctionName("val", RawValueEvaluator.class)
+      
+      // Boolean Stream Evaluators
+      .withFunctionName("and", AndEvaluator.class)
+      .withFunctionName("eor", ExclusiveOrEvaluator.class)
+      .withFunctionName("eq", EqualsEvaluator.class)
+      .withFunctionName("gt", GreaterThanEvaluator.class)
+      .withFunctionName("gteq", GreaterThanEqualToEvaluator.class)
+      .withFunctionName("lt", LessThanEvaluator.class)
+      .withFunctionName("lteq", LessThanEqualToEvaluator.class)
+      .withFunctionName("not", NotEvaluator.class)
+      .withFunctionName("or", OrEvaluator.class)
+      
+      // Number Stream Evaluators
+      .withFunctionName("abs", AbsoluteValueEvaluator.class)
+      .withFunctionName("add", AddEvaluator.class)
+      .withFunctionName("div", DivideEvaluator.class)
+      .withFunctionName("mult", MultiplyEvaluator.class)
+      .withFunctionName("sub", SubtractEvaluator.class)
+      
+      // Conditional Stream Evaluators
+      .withFunctionName("if", IfThenElseEvaluator.class)
+      ;
 
      // This pulls all the overrides and additions from the config
      List<PluginInfo> pluginInfos = core.getSolrConfig().getPluginInfos(Expressible.class.getName());
      for (PluginInfo pluginInfo : pluginInfos) {
-       Class<? extends Expressible> clazz = core.getResourceLoader().findClass(pluginInfo.className, Expressible.class);
+       Class<? extends Expressible> clazz = core.getMemClassLoader().findClass(pluginInfo.className, Expressible.class);
        streamFactory.withFunctionName(pluginInfo.name, clazz);
      }
         

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandler.java
index 6628368..fd7a754 100644
--- a/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandler.java
@@ -150,6 +150,7 @@ public class UpdateRequestHandler extends ContentStreamHandlerBase implements Pe
     pathVsLoaders.put(JSON_PATH,registry.get("application/json"));
     pathVsLoaders.put(DOC_PATH,registry.get("application/json"));
     pathVsLoaders.put(CSV_PATH,registry.get("application/csv"));
+    pathVsLoaders.put(BIN_PATH,registry.get("application/javabin"));
     return registry;
   }
 
@@ -178,6 +179,7 @@ public class UpdateRequestHandler extends ContentStreamHandlerBase implements Pe
   public static final String DOC_PATH = "/update/json/docs";
   public static final String JSON_PATH = "/update/json";
   public static final String CSV_PATH = "/update/csv";
+  public static final String BIN_PATH = "/update/bin";
 
 }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandlerApi.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandlerApi.java
new file mode 100644
index 0000000..6ba3229
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandlerApi.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+
+public class UpdateRequestHandlerApi extends UpdateRequestHandler  {
+
+
+  @Override
+  public Collection<Api> getApis() {
+    return Collections.singleton(getApiImpl());
+  }
+
+  private Api getApiImpl() {
+    return new Api(ApiBag.getSpec("core.Update")) {
+      @Override
+      public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+        String path = req.getPath();
+        String target =  mapping.get(path);
+        if(target != null) req.getContext().put("path", target);
+        try {
+          handleRequest(req, rsp);
+        } catch (RuntimeException e) {
+          throw e;
+        } catch (Exception e){
+          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,e );
+        }
+      }
+    };
+  }
+
+  @Override
+  public Boolean registerV1() {
+    return Boolean.FALSE;
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
+
+  private static final Map<String, String> mapping = ImmutableMap.<String,String>builder()
+      .put("/update", DOC_PATH)
+      .put(JSON_PATH, DOC_PATH)
+      .put("/update/json/commands", JSON_PATH)
+      .build();
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java b/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
new file mode 100644
index 0000000..0e58ccc
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.CommandOperation;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.api.ApiSupport;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
+import static org.apache.solr.common.util.StrUtils.splitSmart;
+
+/**
+ * This is a utility class to provide an easy mapping of request handlers which support multiple commands
+ * to the V2 API format (core admin api, collections api). This helps in automatically mapping paths
+ * to actions and old parameter names to new parameter names
+ */
+public abstract class BaseHandlerApiSupport implements ApiSupport {
+  protected final Map<SolrRequest.METHOD, Map<V2EndPoint, List<ApiCommand>>> commandsMapping;
+
+  protected BaseHandlerApiSupport() {
+    commandsMapping = new HashMap<>();
+    for (ApiCommand cmd : getCommands()) {
+      Map<V2EndPoint, List<ApiCommand>> m = commandsMapping.get(cmd.getHttpMethod());
+      if (m == null) commandsMapping.put(cmd.getHttpMethod(), m = new HashMap<>());
+      List<ApiCommand> list = m.get(cmd.getEndPoint());
+      if (list == null) m.put(cmd.getEndPoint(), list = new ArrayList<>());
+      list.add(cmd);
+    }
+  }
+
+  @Override
+  public synchronized Collection<Api> getApis() {
+    ImmutableList.Builder<Api> l = ImmutableList.builder();
+    for (V2EndPoint op : getEndPoints()) l.add(getApi(op));
+    return l.build();
+  }
+
+
+  private Api getApi(final V2EndPoint op) {
+    final BaseHandlerApiSupport apiHandler = this;
+    return new Api(ApiBag.getSpec(op.getSpecName())) {
+      @Override
+      public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+        SolrParams params = req.getParams();
+        SolrRequest.METHOD method = SolrRequest.METHOD.valueOf(req.getHttpMethod());
+        List<ApiCommand> commands = commandsMapping.get(method).get(op);
+        try {
+          if (method == POST) {
+            List<CommandOperation> cmds = req.getCommands(true);
+            if (cmds.size() > 1)
+              throw new SolrException(BAD_REQUEST, "Only one command is allowed");
+            CommandOperation c = cmds.size() == 0 ? null : cmds.get(0);
+            ApiCommand command = null;
+            String commandName = c == null ? null : c.name;
+            for (ApiCommand cmd : commands) {
+              if (Objects.equals(cmd.getName(), commandName)) {
+                command = cmd;
+                break;
+              }
+            }
+
+            if (command == null) {
+              throw new SolrException(BAD_REQUEST, " no such command " + c);
+            }
+            wrapParams(req, c, command, false);
+            command.invoke(req, rsp, apiHandler);
+
+          } else {
+            if (commands == null || commands.isEmpty()) {
+              rsp.add("error", "No support for : " + method + " at :" + req.getPath());
+              return;
+            }
+            if (commands.size() > 1) {
+              for (ApiCommand command : commands) {
+                if (command.getName().equals(req.getPath())) {
+                  commands = Collections.singletonList(command);
+                  break;
+                }
+              }
+            }
+            wrapParams(req, new CommandOperation("", Collections.EMPTY_MAP), commands.get(0), true);
+            commands.get(0).invoke(req, rsp, apiHandler);
+          }
+
+        } catch (SolrException e) {
+          throw e;
+        } catch (Exception e) {
+          throw new SolrException(BAD_REQUEST, e);
+        } finally {
+          req.setParams(params);
+        }
+
+      }
+    };
+
+  }
+
+  private static void wrapParams(final SolrQueryRequest req, final CommandOperation co, final ApiCommand cmd, final boolean useRequestParams) {
+    final Map<String, String> pathValues = req.getPathTemplateValues();
+    final Map<String, Object> map = co == null || !(co.getCommandData() instanceof Map) ?
+        Collections.singletonMap("", co.getCommandData()) : co.getDataMap();
+    final SolrParams origParams = req.getParams();
+
+    req.setParams(
+        new SolrParams() {
+          @Override
+          public String get(String param) {
+            Object vals = getParams0(param);
+            if (vals == null) return null;
+            if (vals instanceof String) return (String) vals;
+            if (vals instanceof Boolean || vals instanceof Number) return String.valueOf(vals);
+            if (vals instanceof String[] && ((String[]) vals).length > 0) return ((String[]) vals)[0];
+            return null;
+          }
+
+          private Object getParams0(String param) {
+            param = cmd.getParamSubstitute(param);
+            Object o = param.indexOf('.') > 0 ?
+                Utils.getObjectByPath(map, true, splitSmart(param, '.')) :
+                map.get(param);
+            if (o == null) o = pathValues.get(param);
+            if (o == null && useRequestParams) o = origParams.getParams(param);
+            if (o instanceof List) {
+              List l = (List) o;
+              return l.toArray(new String[l.size()]);
+            }
+
+            return o;
+          }
+
+          @Override
+          public String[] getParams(String param) {
+            Object vals = getParams0(param);
+            return vals == null || vals instanceof String[] ?
+                (String[]) vals :
+                new String[]{vals.toString()};
+          }
+
+          @Override
+          public Iterator<String> getParameterNamesIterator() {
+            return cmd.getParamNames(co).iterator();
+
+          }
+
+
+        });
+
+  }
+
+
+  public static Collection<String> getParamNames(CommandOperation op, ApiCommand command) {
+    List<String> result = new ArrayList<>();
+    Object o = op.getCommandData();
+    if (o instanceof Map) {
+      Map map = (Map) o;
+      collectKeyNames(map, result, "");
+    }
+    return result;
+
+  }
+
+  public static void collectKeyNames(Map<String, Object> map, List<String> result, String prefix) {
+    for (Map.Entry<String, Object> e : map.entrySet()) {
+      if (e.getValue() instanceof Map) {
+        collectKeyNames((Map) e.getValue(), result, prefix + e.getKey() + ".");
+      } else {
+        result.add(prefix + e.getKey());
+      }
+    }
+  }
+
+  protected abstract List<ApiCommand> getCommands();
+
+  protected abstract List<V2EndPoint> getEndPoints();
+
+
+  public interface ApiCommand {
+    String getName();
+
+    /**
+     * the http method supported by this command
+     */
+    SolrRequest.METHOD getHttpMethod();
+
+    V2EndPoint getEndPoint();
+
+    default Collection<String> getParamNames(CommandOperation op) {
+      return BaseHandlerApiSupport.getParamNames(op, this);
+    }
+
+
+    default String getParamSubstitute(String name) {
+      return name;
+    }
+
+    void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception;
+  }
+
+  public interface V2EndPoint {
+
+    String getSpecName();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
new file mode 100644
index 0000000..581fe46
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
@@ -0,0 +1,319 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.handler.admin.CollectionsHandler.CollectionOperation;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.CommandOperation;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.cloud.OverseerCollectionMessageHandler.COLL_CONF;
+import static org.apache.solr.cloud.OverseerCollectionMessageHandler.CREATE_NODE_SET;
+import static org.apache.solr.common.params.CommonParams.NAME;
+import static org.apache.solr.handler.admin.CollectionsHandler.CollectionOperation.*;
+
+
+public class CollectionHandlerApi extends BaseHandlerApiSupport {
+  final CollectionsHandler handler;
+
+  public CollectionHandlerApi(CollectionsHandler handler) {
+    this.handler = handler;
+  }
+
+  @Override
+  protected List<ApiCommand> getCommands() {
+    return Arrays.asList(Cmd.values());
+  }
+
+  @Override
+  protected List<V2EndPoint> getEndPoints() {
+    return Arrays.asList(EndPoint.values());
+  }
+
+
+  enum Cmd implements ApiCommand {
+    GET_COLLECTIONS(EndPoint.COLLECTIONS, GET, LIST_OP),
+    GET_CLUSTER(EndPoint.CLUSTER, GET, LIST_OP, "/cluster", null),
+    GET_CLUSTER_OVERSEER(EndPoint.CLUSTER, GET, OVERSEERSTATUS_OP, "/cluster/overseer", null),
+    GET_CLUSTER_STATUS_CMD(EndPoint.CLUSTER_CMD_STATUS, GET, REQUESTSTATUS_OP),
+    DELETE_CLUSTER_STATUS(EndPoint.CLUSTER_CMD_STATUS_DELETE, DELETE, DELETESTATUS_OP),
+    GET_A_COLLECTION(EndPoint.COLLECTION_STATE, GET, CLUSTERSTATUS_OP),
+    CREATE_COLLECTION(EndPoint.COLLECTIONS_COMMANDS,
+        POST,
+        CREATE_OP,
+        CREATE_OP.action.toLower(),
+        ImmutableMap.of(
+            COLL_CONF, "config",
+            "createNodeSet.shuffle", "shuffleNodes",
+            "createNodeSet", "nodeSet"
+        ),
+        ImmutableMap.of("properties.", "property.")),
+
+    DELETE_COLL(EndPoint.PER_COLLECTION_DELETE,
+        DELETE,
+        DELETE_OP,
+        DELETE_OP.action.toLower(),
+        ImmutableMap.of(NAME, "collection")),
+
+    RELOAD_COLL(EndPoint.PER_COLLECTION,
+        POST,
+        RELOAD_OP,
+        RELOAD_OP.action.toLower(),
+        ImmutableMap.of(NAME, "collection")),
+    MODIFYCOLLECTION(EndPoint.PER_COLLECTION,
+        POST,
+        MODIFYCOLLECTION_OP,
+        "modify",null),
+    MIGRATE_DOCS(EndPoint.PER_COLLECTION,
+        POST,
+        MIGRATE_OP,
+        "migrate-docs",
+        ImmutableMap.of("split.key", "splitKey",
+            "target.collection", "target",
+            "forward.timeout", "forwardTimeout"
+        )),
+    REBALANCELEADERS(EndPoint.PER_COLLECTION,
+        POST,
+        REBALANCELEADERS_OP,
+        "rebalance-leaders", null),
+    CREATE_ALIAS(EndPoint.COLLECTIONS_COMMANDS,
+        POST,
+        CREATEALIAS_OP,
+        "create-alias",
+        null),
+
+    DELETE_ALIAS(EndPoint.COLLECTIONS_COMMANDS,
+        POST,
+        DELETEALIAS_OP,
+        "delete-alias",
+        null),
+    CREATE_SHARD(EndPoint.PER_COLLECTION_SHARDS_COMMANDS,
+        POST,
+        CREATESHARD_OP,
+        "create",
+        ImmutableMap.of(CREATE_NODE_SET, "nodeSet"),
+        ImmutableMap.of("coreProperties.", "property.")) {
+      @Override
+      public String getParamSubstitute(String param) {
+        return super.getParamSubstitute(param);
+      }
+    },
+
+    SPLIT_SHARD(EndPoint.PER_COLLECTION_SHARDS_COMMANDS,
+        POST,
+        SPLITSHARD_OP,
+        "split",
+        ImmutableMap.of(
+            "split.key", "splitKey"),
+        ImmutableMap.of("coreProperties.", "property.")),
+    DELETE_SHARD(EndPoint.PER_COLLECTION_PER_SHARD_DELETE,
+        DELETE,
+        DELETESHARD_OP),
+
+    CREATE_REPLICA(EndPoint.PER_COLLECTION_SHARDS_COMMANDS,
+        POST,
+        ADDREPLICA_OP,
+        "add-replica",
+        null,
+        ImmutableMap.of("coreProperties.", "property.")),
+
+    DELETE_REPLICA(EndPoint.PER_COLLECTION_PER_SHARD_PER_REPLICA_DELETE,
+        DELETE,
+        DELETEREPLICA_OP),
+
+    SYNC_SHARD(EndPoint.PER_COLLECTION_PER_SHARD_COMMANDS,
+        POST,
+        SYNCSHARD_OP,
+        "synch-shard",
+        null),
+    ADDREPLICAPROP(EndPoint.PER_COLLECTION,
+        POST,
+        ADDREPLICAPROP_OP,
+        "add-replica-property",
+        ImmutableMap.of("property", "name", "property.value", "value")),
+    DELETEREPLICAPROP(EndPoint.PER_COLLECTION,
+        POST,
+        DELETEREPLICAPROP_OP,
+        "delete-replica-property",
+        null),
+    ADDROLE(EndPoint.CLUSTER_CMD,
+        POST,
+        ADDROLE_OP,
+        "add-role",null),
+    REMOVEROLE(EndPoint.CLUSTER_CMD,
+        POST,
+        REMOVEROLE_OP,
+        "remove-role",null),
+
+    CLUSTERPROP(EndPoint.CLUSTER_CMD,
+        POST,
+        CLUSTERPROP_OP,
+        "set-property",null),
+
+    BACKUP(EndPoint.COLLECTIONS_COMMANDS,
+        POST,
+        BACKUP_OP,
+        "backup-collection", null
+        ),
+    RESTORE(EndPoint.COLLECTIONS_COMMANDS,
+        POST,
+        RESTORE_OP,
+        "restore-collection",
+        null
+    ),
+    GET_NODES(EndPoint.CLUSTER_NODES, GET, null) {
+      @Override
+      public void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception {
+        rsp.add("nodes", ((CollectionHandlerApi) apiHandler).handler.coreContainer.getZkController().getClusterState().getLiveNodes());
+      }
+    },
+    FORCELEADER(EndPoint.PER_COLLECTION_PER_SHARD_COMMANDS,POST, FORCELEADER_OP,"force-leader",null),
+    SYNCSHARD(EndPoint.PER_COLLECTION_PER_SHARD_COMMANDS,POST, SYNCSHARD_OP, "sync-shard",null),
+    BALANCESHARDUNIQUE(EndPoint.PER_COLLECTION, POST, BALANCESHARDUNIQUE_OP, "balance-shard-unique",null)
+
+    ;
+    public final String commandName;
+    public final EndPoint endPoint;
+    public final SolrRequest.METHOD method;
+    public final CollectionOperation target;
+    //mapping of http param name to json attribute
+    public final Map<String, String> paramstoAttr;
+    //mapping of old prefix to new for instance properties.a=val can be substituted with property:{a:val}
+    public final Map<String, String> prefixSubstitutes;
+
+    public SolrRequest.METHOD getMethod() {
+      return method;
+    }
+
+
+    Cmd(EndPoint endPoint, SolrRequest.METHOD method, CollectionOperation target) {
+      this(endPoint, method, target, null, null);
+    }
+
+    Cmd(EndPoint endPoint, SolrRequest.METHOD method, CollectionOperation target,
+        String commandName, Map<String, String> paramstoAttr) {
+      this(endPoint, method, target, commandName, paramstoAttr, Collections.EMPTY_MAP);
+
+    }
+
+    Cmd(EndPoint endPoint, SolrRequest.METHOD method, CollectionOperation target,
+        String commandName, Map<String, String> paramstoAttr, Map<String, String> prefixSubstitutes) {
+      this.commandName = commandName;
+      this.endPoint = endPoint;
+      this.method = method;
+      this.target = target;
+      this.paramstoAttr = paramstoAttr == null ? Collections.EMPTY_MAP : paramstoAttr;
+      this.prefixSubstitutes = prefixSubstitutes;
+
+    }
+
+    @Override
+    public String getName() {
+      return commandName;
+    }
+
+    @Override
+    public SolrRequest.METHOD getHttpMethod() {
+      return method;
+    }
+
+    @Override
+    public V2EndPoint getEndPoint() {
+      return endPoint;
+    }
+
+
+    @Override
+    public Collection<String> getParamNames(CommandOperation op) {
+      Collection<String> paramNames = BaseHandlerApiSupport.getParamNames(op, this);
+      if (!prefixSubstitutes.isEmpty()) {
+        Collection<String> result = new ArrayList<>(paramNames.size());
+        for (Map.Entry<String, String> e : prefixSubstitutes.entrySet()) {
+          for (String paramName : paramNames) {
+            if (paramName.startsWith(e.getKey())) {
+              result.add(paramName.replace(e.getKey(), e.getValue()));
+            } else {
+              result.add(paramName);
+            }
+          }
+          paramNames = result;
+        }
+      }
+
+      return paramNames;
+    }
+
+    @Override
+    public String getParamSubstitute(String param) {
+      String s = paramstoAttr.containsKey(param) ? paramstoAttr.get(param) : param;
+      if (prefixSubstitutes != null) {
+        for (Map.Entry<String, String> e : prefixSubstitutes.entrySet()) {
+          if (s.startsWith(e.getValue())) return s.replace(e.getValue(), e.getKey());
+        }
+      }
+      return s;
+    }
+
+    public void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler)
+        throws Exception {
+      ((CollectionHandlerApi) apiHandler).handler.invokeAction(req, rsp, ((CollectionHandlerApi) apiHandler).handler.coreContainer, target.action, target);
+    }
+
+  }
+
+  enum EndPoint implements V2EndPoint {
+    CLUSTER("cluster"),
+    CLUSTER_CMD("cluster.Commands"),
+    CLUSTER_NODES("cluster.nodes"),
+    CLUSTER_CMD_STATUS("cluster.commandstatus"),
+    CLUSTER_CMD_STATUS_DELETE("cluster.commandstatus.delete"),
+    COLLECTIONS_COMMANDS("collections.Commands"),
+    COLLECTIONS("collections"),
+    COLLECTION_STATE("collections.collection"),
+    PER_COLLECTION("collections.collection.Commands"),
+    PER_COLLECTION_DELETE("collections.collection.delete"),
+    PER_COLLECTION_SHARDS_COMMANDS("collections.collection.shards.Commands"),
+    PER_COLLECTION_PER_SHARD_COMMANDS("collections.collection.shards.shard.Commands"),
+    PER_COLLECTION_PER_SHARD_DELETE("collections.collection.shards.shard.delete"),
+    PER_COLLECTION_PER_SHARD_PER_REPLICA_DELETE("collections.collection.shards.shard.replica.delete");
+    final String specName;
+
+
+    EndPoint(String specName) {
+      this.specName = specName;
+    }
+
+    @Override
+    public String getSpecName() {
+      return specName;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 148d73c..d7759ca 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit;
 import com.google.common.collect.ImmutableSet;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
+import org.apache.solr.api.Api;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.HttpSolrClient.Builder;
@@ -106,6 +107,7 @@ import static org.apache.solr.cloud.OverseerCollectionMessageHandler.ONLY_IF_DOW
 import static org.apache.solr.cloud.OverseerCollectionMessageHandler.REQUESTID;
 import static org.apache.solr.cloud.OverseerCollectionMessageHandler.SHARDS_PROP;
 import static org.apache.solr.cloud.OverseerCollectionMessageHandler.SHARD_UNIQUE;
+import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
 import static org.apache.solr.common.cloud.DocCollection.DOC_ROUTER;
 import static org.apache.solr.common.cloud.DocCollection.RULE;
 import static org.apache.solr.common.cloud.DocCollection.SNITCH;
@@ -135,12 +137,14 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   protected final CoreContainer coreContainer;
+  private final CollectionHandlerApi v2Handler ;
 
   public CollectionsHandler() {
     super();
     // Unlike most request handlers, CoreContainer initialization
     // should happen in the constructor...
     this.coreContainer = null;
+    v2Handler = new CollectionHandlerApi(this);
   }
 
 
@@ -151,6 +155,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
    */
   public CollectionsHandler(final CoreContainer coreContainer) {
     this.coreContainer = coreContainer;
+    v2Handler = new CollectionHandlerApi(this);
   }
 
   @Override
@@ -205,33 +210,39 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
       CollectionOperation operation = CollectionOperation.get(action);
       log.info("Invoked Collection Action :{} with params {} and sendToOCPQueue={}", action.toLower(), req.getParamString(), operation.sendToOCPQueue);
 
-      SolrResponse response = null;
-      Map<String, Object> props = operation.execute(req, rsp, this);
-      String asyncId = req.getParams().get(ASYNC);
-      if (props != null) {
-        if (asyncId != null) {
-          props.put(ASYNC, asyncId);
-        }
-        props.put(QUEUE_OPERATION, operation.action.toLower());
-        ZkNodeProps zkProps = new ZkNodeProps(props);
-        if (operation.sendToOCPQueue) {
-          response = handleResponse(operation.action.toLower(), zkProps, rsp, operation.timeOut);
-        }
-        else Overseer.getStateUpdateQueue(coreContainer.getZkController().getZkClient()).offer(Utils.toJSON(props));
-        final String collectionName = zkProps.getStr(NAME);
-        if (action.equals(CollectionAction.CREATE) && asyncId == null) {
-          if (rsp.getException() == null) {
-            waitForActiveCollection(collectionName, zkProps, cores, response);
-          }
-        }
-      }
+      invokeAction(req, rsp, cores, action, operation);
     } else {
       throw new SolrException(ErrorCode.BAD_REQUEST, "action is a required param");
     }
     rsp.setHttpCaching(false);
   }
 
-
+  void invokeAction(SolrQueryRequest req, SolrQueryResponse rsp, CoreContainer cores, CollectionAction action, CollectionOperation operation) throws Exception {
+    if (!coreContainer.isZooKeeperAware()) {
+      throw new SolrException(BAD_REQUEST,
+          "Invalid request. collections can be accessed only in SolrCloud mode");
+    }
+    SolrResponse response = null;
+    Map<String, Object> props = operation.execute(req, rsp, this);
+    String asyncId = req.getParams().get(ASYNC);
+    if (props != null) {
+      if (asyncId != null) {
+        props.put(ASYNC, asyncId);
+      }
+      props.put(QUEUE_OPERATION, operation.action.toLower());
+      ZkNodeProps zkProps = new ZkNodeProps(props);
+      if (operation.sendToOCPQueue) {
+        response = handleResponse(operation.action.toLower(), zkProps, rsp, operation.timeOut);
+      }
+      else Overseer.getStateUpdateQueue(coreContainer.getZkController().getZkClient()).offer(Utils.toJSON(props));
+      final String collectionName = zkProps.getStr(NAME);
+      if (action.equals(CollectionAction.CREATE) && asyncId == null) {
+        if (rsp.getException() == null) {
+          waitForActiveCollection(collectionName, zkProps, cores, response);
+        }
+      }
+    }
+  }
 
 
   static final Set<String> KNOWN_ROLES = ImmutableSet.of("overseer");
@@ -387,7 +398,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
           COLL_CONF,
           NUM_SLICES,
           MAX_SHARDS_PER_NODE,
-          CREATE_NODE_SET, CREATE_NODE_SET_SHUFFLE,
+          CREATE_NODE_SET,
+          CREATE_NODE_SET_SHUFFLE,
           SHARDS_PROP,
           STATE_FORMAT,
           AUTO_ADD_REPLICAS,
@@ -863,7 +875,6 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
 
     }
 
-
     public static CollectionOperation get(CollectionAction action) {
       for (CollectionOperation op : values()) {
         if (op.action == action) return op;
@@ -1058,7 +1069,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
 
   interface CollectionOp {
     Map<String, Object> execute(SolrQueryRequest req, SolrQueryResponse rsp, CollectionsHandler h) throws Exception;
-    
+
   }
 
   public static final List<String> MODIFIABLE_COLL_PROPS = Arrays.asList(
@@ -1068,4 +1079,14 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
       MAX_SHARDS_PER_NODE,
       AUTO_ADD_REPLICAS,
       COLL_CONF);
+
+  @Override
+  public Collection<Api> getApis() {
+    return v2Handler.getApis();
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java
index f3a8dd2..5d6f02c 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java
@@ -18,11 +18,13 @@ package org.apache.solr.handler.admin;
 
 import java.lang.invoke.MethodHandles;
 
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.solr.api.Api;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.cloud.OverseerSolrResponse;
 import org.apache.solr.cloud.OverseerTaskQueue.QueueEvent;
@@ -61,6 +63,7 @@ public class ConfigSetsHandler extends RequestHandlerBase {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   protected final CoreContainer coreContainer;
   public static long DEFAULT_ZK_TIMEOUT = 300*1000;
+  private final ConfigSetsHandlerApi configSetsHandlerApi = new ConfigSetsHandlerApi(this);
 
   /**
    * Overloaded ctor to inject CoreContainer into the handler.
@@ -71,10 +74,6 @@ public class ConfigSetsHandler extends RequestHandlerBase {
     this.coreContainer = coreContainer;
   }
 
-  @Override
-  final public void init(NamedList args) {
-
-  }
 
   @Override
   public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
@@ -96,16 +95,7 @@ public class ConfigSetsHandler extends RequestHandlerBase {
       ConfigSetAction action = ConfigSetAction.get(a);
       if (action == null)
         throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown action: " + a);
-      ConfigSetOperation operation = ConfigSetOperation.get(action);
-      log.info("Invoked ConfigSet Action :{} with params {} ", action.toLower(), req.getParamString());
-      Map<String, Object> result = operation.call(req, rsp, this);
-      if (result != null) {
-        // We need to differentiate between collection and configsets actions since they currently
-        // use the same underlying queue.
-        result.put(QUEUE_OPERATION, CONFIGSETS_ACTION_PREFIX + operation.action.toLower());
-        ZkNodeProps props = new ZkNodeProps(result);
-        handleResponse(operation.action.toLower(), props, rsp, DEFAULT_ZK_TIMEOUT);
-      }
+      invokeAction(req, rsp, action);
     } else {
       throw new SolrException(ErrorCode.BAD_REQUEST, "action is a required param");
     }
@@ -113,6 +103,24 @@ public class ConfigSetsHandler extends RequestHandlerBase {
     rsp.setHttpCaching(false);
   }
 
+  void invokeAction(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetAction action) throws Exception {
+    ConfigSetOperation operation = ConfigSetOperation.get(action);
+    log.info("Invoked ConfigSet Action :{} with params {} ", action.toLower(), req.getParamString());
+    Map<String, Object> result = operation.call(req, rsp, this);
+    sendToZk(rsp, operation, result);
+  }
+
+  protected void sendToZk(SolrQueryResponse rsp, ConfigSetOperation operation, Map<String, Object> result)
+      throws KeeperException, InterruptedException {
+    if (result != null) {
+      // We need to differentiate between collection and configsets actions since they currently
+      // use the same underlying queue.
+      result.put(QUEUE_OPERATION, CONFIGSETS_ACTION_PREFIX + operation.action.toLower());
+      ZkNodeProps props = new ZkNodeProps(result);
+      handleResponse(operation.action.toLower(), props, rsp, DEFAULT_ZK_TIMEOUT);
+    }
+  }
+
   private void handleResponse(String operation, ZkNodeProps m,
       SolrQueryResponse rsp, long timeout) throws KeeperException, InterruptedException {
     long time = System.nanoTime();
@@ -160,7 +168,6 @@ public class ConfigSetsHandler extends RequestHandlerBase {
   public String getDescription() {
     return "Manage SolrCloud ConfigSets";
   }
-
   @Override
   public Category getCategory() {
     return Category.ADMIN;
@@ -209,4 +216,14 @@ public class ConfigSetsHandler extends RequestHandlerBase {
       throw new SolrException(ErrorCode.SERVER_ERROR, "No such action" + action);
     }
   }
+
+  @Override
+  public Collection<Api> getApis() {
+    return configSetsHandlerApi.getApis();
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandlerApi.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandlerApi.java
new file mode 100644
index 0000000..6037bcd
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandlerApi.java
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation.CREATE_OP;
+import static org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation.DELETE_OP;
+import static org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation.LIST_OP;
+
+public class ConfigSetsHandlerApi extends BaseHandlerApiSupport {
+
+  final ConfigSetsHandler configSetHandler;
+
+  public ConfigSetsHandlerApi(ConfigSetsHandler configSetHandler) {
+    this.configSetHandler = configSetHandler;
+  }
+
+
+  @Override
+  protected List<ApiCommand> getCommands() {
+    return Arrays.asList(Cmd.values());
+  }
+
+  @Override
+  protected List<V2EndPoint> getEndPoints() {
+    return Arrays.asList(EndPoint.values());
+  }
+
+  enum Cmd implements ApiCommand {
+    LIST(EndPoint.LIST_CONFIG, LIST_OP, GET),
+    CREATE(EndPoint.CONFIG_COMMANDS, CREATE_OP, POST, "create"),
+    DEL(EndPoint.CONFIG_DEL, DELETE_OP, DELETE)
+    ;
+    private final EndPoint endPoint;
+    private final ConfigSetOperation op;
+    private final SolrRequest.METHOD method;
+    private final String cmdName;
+
+    Cmd(EndPoint endPoint, ConfigSetOperation op, SolrRequest.METHOD method) {
+      this(endPoint, op, method, null);
+    }
+
+    Cmd(EndPoint endPoint, ConfigSetOperation op, SolrRequest.METHOD method, String cmdName) {
+      this.cmdName = cmdName;
+      this.endPoint = endPoint;
+      this.op = op;
+      this.method = method;
+    }
+
+    @Override
+    public String getName() {
+      return cmdName;
+    }
+
+    @Override
+    public SolrRequest.METHOD getHttpMethod() {
+      return method;
+    }
+
+    @Override
+    public V2EndPoint getEndPoint() {
+      return endPoint;
+    }
+
+    @Override
+    public void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception {
+      ((ConfigSetsHandlerApi) apiHandler).configSetHandler.invokeAction(req, rsp, op.action);
+    }
+
+  }
+  enum EndPoint implements V2EndPoint {
+    LIST_CONFIG("cluster.configs"),
+    CONFIG_COMMANDS("cluster.configs.Commands"),
+    CONFIG_DEL("cluster.configs.delete");
+
+    public final String spec;
+
+    EndPoint(String spec) {
+      this.spec = spec;
+    }
+
+    @Override
+    public String getSpecName() {
+      return spec;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
index a415d8a..275ec18 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
@@ -18,6 +18,7 @@ package org.apache.solr.handler.admin;
 
 import java.io.File;
 import java.lang.invoke.MethodHandles;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -28,6 +29,7 @@ import java.util.concurrent.ExecutorService;
 
 import com.google.common.collect.ImmutableMap;
 import org.apache.commons.lang.StringUtils;
+import org.apache.solr.api.Api;
 import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.SolrException;
@@ -66,6 +68,7 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   protected final CoreContainer coreContainer;
   protected final Map<String, Map<String, TaskObject>> requestStatusMap;
+  private final CoreAdminHandlerApi coreAdminHandlerApi;
 
   protected ExecutorService parallelExecutor = ExecutorUtil.newMDCAwareFixedThreadPool(50,
       new DefaultSolrThreadFactory("parallelCoreAdminExecutor"));
@@ -88,6 +91,7 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa
     map.put(COMPLETED, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
     map.put(FAILED, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
     requestStatusMap = Collections.unmodifiableMap(map);
+    coreAdminHandlerApi = new CoreAdminHandlerApi(this);
   }
 
 
@@ -103,6 +107,7 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa
     map.put(COMPLETED, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
     map.put(FAILED, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
     requestStatusMap = Collections.unmodifiableMap(map);
+    coreAdminHandlerApi = new CoreAdminHandlerApi(this);
   }
 
 
@@ -119,6 +124,10 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa
     parallelExecutor = MetricUtils.instrumentedExecutorService(parallelExecutor, manager.registry(registryName),
         SolrMetricManager.mkName("parallelCoreAdminExecutor", getCategory().name(),scope, "threadPool"));
   }
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 
   /**
    * The instance of CoreContainer this handler handles. This should be the CoreContainer instance that created this
@@ -381,6 +390,11 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa
 
   }
 
+  @Override
+  public Collection<Api> getApis() {
+    return coreAdminHandlerApi.getApis();
+  }
+
   static {
     for (CoreAdminOperation op : CoreAdminOperation.values())
       opMap.put(op.action.toString().toLowerCase(Locale.ROOT), op);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandlerApi.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandlerApi.java
new file mode 100644
index 0000000..9d256e6
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandlerApi.java
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.handler.admin.CoreAdminHandlerApi.EndPoint.CORES_COMMANDS;
+import static org.apache.solr.handler.admin.CoreAdminHandlerApi.EndPoint.CORES_STATUS;
+import static org.apache.solr.handler.admin.CoreAdminHandlerApi.EndPoint.NODEAPIS;
+import static org.apache.solr.handler.admin.CoreAdminHandlerApi.EndPoint.NODEINVOKE;
+import static org.apache.solr.handler.admin.CoreAdminHandlerApi.EndPoint.PER_CORE_COMMANDS;
+import static org.apache.solr.handler.admin.CoreAdminOperation.CREATE_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.FORCEPREPAREFORLEADERSHIP_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.INVOKE_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.MERGEINDEXES_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.OVERSEEROP_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.PREPRECOVERY_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.REJOINLEADERELECTION_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.RELOAD_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.RENAME_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.REQUESTAPPLYUPDATES_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.REQUESTBUFFERUPDATES_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.REQUESTRECOVERY_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.REQUESTSTATUS_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.REQUESTSYNCSHARD_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.SPLIT_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.STATUS_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.SWAP_OP;
+import static org.apache.solr.handler.admin.CoreAdminOperation.UNLOAD_OP;
+
+public class CoreAdminHandlerApi extends BaseHandlerApiSupport {
+  private final CoreAdminHandler handler;
+
+  public CoreAdminHandlerApi(CoreAdminHandler handler) {
+    this.handler = handler;
+  }
+
+  enum Cmd implements ApiCommand {
+    CREATE(CORES_COMMANDS, POST, CREATE_OP, null, ImmutableMap.of("config", "configSet")),
+    UNLOAD(PER_CORE_COMMANDS, POST, UNLOAD_OP, null, null),
+    RELOAD(PER_CORE_COMMANDS, POST, RELOAD_OP, null, null),
+    STATUS(CORES_STATUS, GET, STATUS_OP),
+    SWAP(PER_CORE_COMMANDS, POST, SWAP_OP, null, ImmutableMap.of("other", "with")),
+    RENAME(PER_CORE_COMMANDS, POST, RENAME_OP, null, null),
+    MERGEINDEXES(PER_CORE_COMMANDS, POST, MERGEINDEXES_OP, "merge-indexes", null),
+    SPLIT(PER_CORE_COMMANDS, POST, SPLIT_OP, null, ImmutableMap.of("split.key", "splitKey")),
+    PREPRECOVERY(PER_CORE_COMMANDS, POST, PREPRECOVERY_OP, "prep-recovery", null),
+    REQUESTRECOVERY(PER_CORE_COMMANDS, POST, REQUESTRECOVERY_OP, null, null),
+    REQUESTSYNCSHARD(PER_CORE_COMMANDS, POST, REQUESTSYNCSHARD_OP, "request-sync-shard", null),
+    REQUESTBUFFERUPDATES(PER_CORE_COMMANDS, POST, REQUESTBUFFERUPDATES_OP, "request-buffer-updates", null),
+    REQUESTAPPLYUPDATES(PER_CORE_COMMANDS, POST, REQUESTAPPLYUPDATES_OP, "request-apply-updates", null),
+    REQUESTSTATUS(PER_CORE_COMMANDS, POST, REQUESTSTATUS_OP, null, null),
+    OVERSEEROP(NODEAPIS, POST, OVERSEEROP_OP, "overseer-op", null),
+    REJOINLEADERELECTION(NODEAPIS, POST, REJOINLEADERELECTION_OP, "rejoin-leader-election", null),
+    INVOKE(NODEINVOKE, GET, INVOKE_OP, null, null),
+    FORCEPREPAREFORLEADERSHIP(PER_CORE_COMMANDS, POST, FORCEPREPAREFORLEADERSHIP_OP, "force-prepare-for-leadership", null);
+
+    public final String commandName;
+    public final BaseHandlerApiSupport.V2EndPoint endPoint;
+    public final SolrRequest.METHOD method;
+    public final Map<String, String> paramstoAttr;
+    final CoreAdminOperation target;
+
+
+    Cmd(EndPoint endPoint, SolrRequest.METHOD method, CoreAdminOperation target) {
+      this.endPoint = endPoint;
+      this.method = method;
+      this.target = target;
+      commandName = null;
+      paramstoAttr = Collections.EMPTY_MAP;
+
+    }
+
+
+    Cmd(EndPoint endPoint, SolrRequest.METHOD method, CoreAdminOperation target, String commandName,
+        Map<String, String> paramstoAttr) {
+      this.commandName = commandName == null ? target.action.toString().toLowerCase(Locale.ROOT) : commandName;
+      this.endPoint = endPoint;
+      this.method = method;
+      this.target = target;
+      this.paramstoAttr = paramstoAttr == null ? Collections.EMPTY_MAP : paramstoAttr;
+    }
+
+    @Override
+    public String getName() {
+      return commandName;
+    }
+
+    @Override
+    public SolrRequest.METHOD getHttpMethod() {
+      return method;
+    }
+
+    @Override
+    public V2EndPoint getEndPoint() {
+      return endPoint;
+    }
+
+    @Override
+    public String getParamSubstitute(String param) {
+      return paramstoAttr.containsKey(param) ? paramstoAttr.get(param) : param;
+    }
+
+    @Override
+    public void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception {
+      target.execute(new CoreAdminHandler.CallInfo(((CoreAdminHandlerApi) apiHandler).handler,
+          req,
+          rsp,
+          target));
+
+    }
+
+  }
+
+
+
+  enum EndPoint implements BaseHandlerApiSupport.V2EndPoint {
+    CORES_STATUS("cores.Status"),
+    CORES_COMMANDS("cores.Commands"),
+    PER_CORE_COMMANDS("cores.core.Commands"),
+    NODEINVOKE("node.invoke"),
+    NODEAPIS("node.Commands")
+    ;
+
+    final String specName;
+
+    EndPoint(String specName) {
+      this.specName = specName;
+    }
+
+    @Override
+    public String getSpecName() {
+      return specName;
+    }
+  }
+
+
+  @Override
+  protected List<ApiCommand> getCommands() {
+    return Arrays.asList(Cmd.values());
+  }
+
+  @Override
+  protected List<V2EndPoint> getEndPoints() {
+    return Arrays.asList(EndPoint.values());
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
index 8fdac21..c7cd052 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
@@ -16,24 +16,28 @@
  */
 package org.apache.solr.handler.admin;
 
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.solr.api.ApiBag.ReqHandlerToApi;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.api.Api;
 
+import static java.util.Collections.singletonList;
+import static org.apache.solr.api.ApiBag.getSpec;
 import static org.apache.solr.common.params.CommonParams.PATH;
 
-public class InfoHandler extends RequestHandlerBase {
+public class InfoHandler extends RequestHandlerBase  {
+
   protected final CoreContainer coreContainer;
-  
-  private ThreadDumpHandler threadDumpHandler = new ThreadDumpHandler();
-  private PropertiesRequestHandler propertiesHandler = new PropertiesRequestHandler();
-  private LoggingHandler loggingHandler;
-  private SystemInfoHandler systemInfoHandler;
 
   /**
    * Overloaded ctor to inject CoreContainer into the handler.
@@ -42,9 +46,10 @@ public class InfoHandler extends RequestHandlerBase {
    */
   public InfoHandler(final CoreContainer coreContainer) {
     this.coreContainer = coreContainer;
-    systemInfoHandler = new SystemInfoHandler(coreContainer);
-    loggingHandler = new LoggingHandler(coreContainer);
-    
+    handlers.put("threads", new ThreadDumpHandler());
+    handlers.put("properties", new PropertiesRequestHandler());
+    handlers.put("logging", new LoggingHandler(coreContainer));
+    handlers.put("system", new SystemInfoHandler(coreContainer));
   }
 
 
@@ -73,27 +78,19 @@ public class InfoHandler extends RequestHandlerBase {
     }
 
     String path = (String) req.getContext().get(PATH);
+    handle(req, rsp, path);
+  }
+
+  private void handle(SolrQueryRequest req, SolrQueryResponse rsp, String path) {
     int i = path.lastIndexOf('/');
     String name = path.substring(i + 1, path.length());
-    
-    if (name.equalsIgnoreCase("properties")) {
-      propertiesHandler.handleRequest(req, rsp);
-    } else if (name.equalsIgnoreCase("threads")) {
-      threadDumpHandler.handleRequest(req, rsp);
-    } else if (name.equalsIgnoreCase("logging")) {
-      loggingHandler.handleRequest(req, rsp);
-    }  else if (name.equalsIgnoreCase("system")) {
-      systemInfoHandler.handleRequest(req, rsp);
-    } else {
-      if (name.equalsIgnoreCase("info")) name = "";
-      throw new SolrException(ErrorCode.NOT_FOUND, "Info Handler not found: " + name);
+    RequestHandlerBase handler = handlers.get(name.toLowerCase(Locale.ROOT));
+    if(handler == null) {
+      throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "No handler by name "+name + " available names are "+ handlers.keySet());
     }
-    
+    handler.handleRequest(req, rsp);
     rsp.setHttpCaching(false);
   }
-  
-  
-
 
 
   //////////////////////// SolrInfoMBeans methods //////////////////////
@@ -109,39 +106,52 @@ public class InfoHandler extends RequestHandlerBase {
   }
 
   protected PropertiesRequestHandler getPropertiesHandler() {
-    return propertiesHandler;
+    return (PropertiesRequestHandler) handlers.get("properties");
+
   }
 
   protected ThreadDumpHandler getThreadDumpHandler() {
-    return threadDumpHandler;
+    return (ThreadDumpHandler) handlers.get("threads");
   }
 
   protected LoggingHandler getLoggingHandler() {
-    return loggingHandler;
+    return (LoggingHandler) handlers.get("logging");
   }
 
   protected SystemInfoHandler getSystemInfoHandler() {
-    return systemInfoHandler;
+    return (SystemInfoHandler) handlers.get("system");
   }
 
   protected void setPropertiesHandler(PropertiesRequestHandler propertiesHandler) {
-    this.propertiesHandler = propertiesHandler;
+    handlers.put("properties", propertiesHandler);
   }
 
   protected void setThreadDumpHandler(ThreadDumpHandler threadDumpHandler) {
-    this.threadDumpHandler = threadDumpHandler;
+    handlers.put("threads", threadDumpHandler);
   }
 
   protected void setLoggingHandler(LoggingHandler loggingHandler) {
-    this.loggingHandler = loggingHandler;
+    handlers.put("logging", loggingHandler);
   }
 
   protected void setSystemInfoHandler(SystemInfoHandler systemInfoHandler) {
-    this.systemInfoHandler = systemInfoHandler;
+    handlers.put("system", systemInfoHandler);
   }
 
   @Override
   public SolrRequestHandler getSubHandler(String subPath) {
     return this;
   }
+
+  private Map<String, RequestHandlerBase> handlers = new ConcurrentHashMap<>();
+
+  @Override
+  public Collection<Api> getApis() {
+    return singletonList(new ReqHandlerToApi(this, getSpec("node.Info")));
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
index 1b81722..eceb4b7 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
@@ -20,12 +20,15 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
+import com.google.common.collect.ImmutableList;
+import org.apache.solr.api.ApiBag;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.util.Utils;
@@ -34,10 +37,16 @@ import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.SolrConfigHandler;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthenticationPlugin;
 import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.security.AuthorizationPlugin;
 import org.apache.solr.security.ConfigEditablePlugin;
 import org.apache.solr.security.PermissionNameProvider;
 import org.apache.solr.util.CommandOperation;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag.ReqHandlerToApi;
+import org.apache.solr.api.SpecProvider;
+import org.apache.solr.util.JsonSchemaValidator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -244,5 +253,66 @@ public abstract class SecurityConfHandler extends RequestHandlerBase implements
       return "SecurityConfig: version=" + version + ", data=" + Utils.toJSONString(data);
     } 
   }
+
+  private Collection<Api> apis;
+  private AuthenticationPlugin authcPlugin;
+  private AuthorizationPlugin authzPlugin;
+
+  @Override
+  public Collection<Api> getApis() {
+    if (apis == null) {
+      synchronized (this) {
+        if (apis == null) {
+          Collection<Api> apis = new ArrayList<>();
+          final SpecProvider authcCommands = ApiBag.getSpec("cluster.security.authentication.Commands");
+          final SpecProvider authzCommands = ApiBag.getSpec("cluster.security.authorization.Commands");
+          apis.add(new ReqHandlerToApi(this, ApiBag.getSpec("cluster.security.authentication")));
+          apis.add(new ReqHandlerToApi(this, ApiBag.getSpec("cluster.security.authorization")));
+          SpecProvider authcSpecProvider = () -> {
+            AuthenticationPlugin authcPlugin = cores.getAuthenticationPlugin();
+            return authcPlugin != null && authcPlugin instanceof SpecProvider ?
+                ((SpecProvider) authcPlugin).getSpec() :
+                authcCommands.getSpec();
+          };
+
+          apis.add(new ReqHandlerToApi(this, authcSpecProvider) {
+            @Override
+            public synchronized Map<String, JsonSchemaValidator> getCommandSchema() {
+              //it is possible that the Authentication plugin is modified since the last call. invalidate the
+              // the cached commandSchema
+              if(SecurityConfHandler.this.authcPlugin != cores.getAuthenticationPlugin()) commandSchema = null;
+              SecurityConfHandler.this.authcPlugin = cores.getAuthenticationPlugin();
+              return super.getCommandSchema();
+            }
+          });
+
+          SpecProvider authzSpecProvider = () -> {
+            AuthorizationPlugin authzPlugin = cores.getAuthorizationPlugin();
+            return authzPlugin != null && authzPlugin instanceof SpecProvider ?
+                ((SpecProvider) authzPlugin).getSpec() :
+                authzCommands.getSpec();
+          };
+          apis.add(new ApiBag.ReqHandlerToApi(this, authzSpecProvider) {
+            @Override
+            public synchronized Map<String, JsonSchemaValidator> getCommandSchema() {
+              //it is possible that the Authorization plugin is modified since the last call. invalidate the
+              // the cached commandSchema
+              if(SecurityConfHandler.this.authzPlugin != cores.getAuthorizationPlugin()) commandSchema = null;
+              SecurityConfHandler.this.authzPlugin = cores.getAuthorizationPlugin();
+              return super.getCommandSchema();
+            }
+          });
+
+          this.apis = ImmutableList.copyOf(apis);
+        }
+      }
+    }
+    return this.apis;
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java b/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
index bcff0c2..66b9ab8 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
@@ -50,6 +50,7 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.PointField;
 import org.apache.solr.search.QueryParsing;
+import org.apache.solr.search.DocSet;
 import org.apache.solr.search.SyntaxError;
 import org.apache.solr.search.facet.FacetDebugInfo;
 import org.apache.solr.util.RTimer;
@@ -103,6 +104,11 @@ public class FacetComponent extends SearchComponent {
     }
   }
 
+  /* Custom facet components can return a custom SimpleFacets object */
+  protected SimpleFacets newSimpleFacets(SolrQueryRequest req, DocSet docSet, SolrParams params, ResponseBuilder rb) {
+    return new SimpleFacets(req, docSet, params, rb);
+  }
+
   /**
    * Encapsulates facet ranges and facet queries such that their parameters
    * are parsed and cached for efficient re-use.
@@ -253,7 +259,7 @@ public class FacetComponent extends SearchComponent {
 
     if (rb.doFacets) {
       SolrParams params = rb.req.getParams();
-      SimpleFacets f = new SimpleFacets(rb.req, rb.getResults().docSet, params, rb);
+      SimpleFacets f = newSimpleFacets(rb.req, rb.getResults().docSet, params, rb);
 
       RTimer timer = null;
       FacetDebugInfo fdebug = null;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java b/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java
index f129e73..aa3e3cb 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java
@@ -34,7 +34,6 @@ import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.schema.DateRangeField;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.IndexSchema;
-import org.apache.solr.schema.PointField;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.TrieDateField;
 import org.apache.solr.schema.TrieField;
@@ -144,9 +143,7 @@ public class RangeFacetRequest extends FacetComponent.FacetBase {
     FieldType ft = schemaField.getType();
 
     if (ft instanceof TrieField) {
-      final TrieField trie = (TrieField) ft;
-
-      switch (trie.getType()) {
+      switch (ft.getNumberType()) {
         case FLOAT:
           calc = new FloatRangeEndpointCalculator(this);
           break;
@@ -170,8 +167,7 @@ public class RangeFacetRequest extends FacetComponent.FacetBase {
     } else if (ft instanceof DateRangeField) {
       calc = new DateRangeEndpointCalculator(this, null);
     } else if (ft.isPointField()) {
-      final PointField pointField = (PointField) ft;
-      switch (pointField.getType()) {
+      switch (ft.getNumberType()) {
         case FLOAT:
           calc = new FloatRangeEndpointCalculator(this);
           break;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java b/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java
index a229a85..2f805f4 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java
@@ -199,8 +199,7 @@ public class SpellCheckComponent extends SearchComponent implements SolrCoreAwar
         boolean isCorrectlySpelled = hits > (maxResultsForSuggest==null ? 0 : maxResultsForSuggest);
 
         NamedList response = new SimpleOrderedMap();
-        NamedList suggestions = toNamedList(shardRequest, spellingResult, q, extendedResults);
-        response.add("suggestions", suggestions);
+        response.add("suggestions", toNamedList(shardRequest, spellingResult, q, extendedResults));
 
         if (extendedResults) {
           response.add("correctlySpelled", isCorrectlySpelled);
@@ -300,7 +299,7 @@ public class SpellCheckComponent extends SearchComponent implements SolrCoreAwar
     //even in cases when the internal rank is the same.
     Collections.sort(collations);
 
-    NamedList collationList = new NamedList();
+    NamedList collationList = new SimpleOrderedMap();
     for (SpellCheckCollation collation : collations) {
       if (collationExtendedResults) {
         NamedList extendedResult = new SimpleOrderedMap();
@@ -424,8 +423,7 @@ public class SpellCheckComponent extends SearchComponent implements SolrCoreAwar
 
     NamedList response = new SimpleOrderedMap();
 
-    NamedList suggestions = toNamedList(false, result, origQuery, extendedResults);
-    response.add("suggestions", suggestions);
+    response.add("suggestions", toNamedList(false, result, origQuery, extendedResults));
 
     if (extendedResults) {
       response.add("correctlySpelled", isCorrectlySpelled);
@@ -436,7 +434,7 @@ public class SpellCheckComponent extends SearchComponent implements SolrCoreAwar
           .toArray(new SpellCheckCollation[mergeData.collations.size()]);
       Arrays.sort(sortedCollations);
 
-      NamedList collations = new NamedList();
+      NamedList collations = new SimpleOrderedMap();
       int i = 0;
       while (i < maxCollations && i < sortedCollations.length) {
         SpellCheckCollation collation = sortedCollations[i];
@@ -636,7 +634,7 @@ public class SpellCheckComponent extends SearchComponent implements SolrCoreAwar
 
   protected NamedList toNamedList(boolean shardRequest,
       SpellingResult spellingResult, String origQuery, boolean extendedResults) {
-    NamedList result = new NamedList();
+    NamedList result = new SimpleOrderedMap();
     Map<Token,LinkedHashMap<String,Integer>> suggestions = spellingResult
         .getSuggestions();
     boolean hasFreqInfo = spellingResult.hasTokenFrequencyInfo();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/handler/component/StatsField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/StatsField.java b/solr/core/src/java/org/apache/solr/handler/component/StatsField.java
index 5df1b45..03bf814 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/StatsField.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/StatsField.java
@@ -29,7 +29,6 @@ import java.util.Map;
 import java.util.Set;
 
 import org.apache.commons.lang.StringUtils;
-import org.apache.lucene.legacy.LegacyNumericType;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.queries.function.FunctionQuery;
 import org.apache.lucene.queries.function.ValueSource;
@@ -46,6 +45,7 @@ import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.request.DocValuesStats;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.NumberType;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.DocIterator;
 import org.apache.solr.search.DocSet;
@@ -57,8 +57,8 @@ import org.apache.solr.search.SyntaxError;
 import org.apache.solr.util.hll.HLL;
 import org.apache.solr.util.hll.HLLType;
 
-import com.google.common.hash.Hashing;
 import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
 
 /**
  * Models all of the information associated with a single {@link StatsParams#STATS_FIELD}
@@ -636,13 +636,13 @@ public class StatsField {
         return null;
       }
 
-      final LegacyNumericType hashableNumType = getHashableNumericType(field);
+      final NumberType hashableNumType = getHashableNumericType(field);
 
       // some sane defaults
       int log2m = 13;   // roughly equivilent to "cardinality='0.33'"
       int regwidth = 6; // with decent hash, this is plenty for all valid long hashes
 
-      if (LegacyNumericType.FLOAT.equals(hashableNumType) || LegacyNumericType.INT.equals(hashableNumType)) {
+      if (NumberType.FLOAT.equals(hashableNumType) || NumberType.INTEGER.equals(hashableNumType)) {
         // for 32bit values, we can adjust our default regwidth down a bit
         regwidth--;
 
@@ -706,7 +706,7 @@ public class StatsField {
       if (null == hasher) {
         // if this is a function, or a non Long field, pre-hashed is invalid
         // NOTE: we ignore hashableNumType - it's LONG for non numerics like Strings
-        if (null == field || !LegacyNumericType.LONG.equals(field.getType().getNumericType())) {
+        if (null == field || !(NumberType.LONG.equals(field.getType().getNumberType()) || NumberType.DATE.equals(field.getType().getNumberType()))) { 
           throw new SolrException(ErrorCode.BAD_REQUEST, "hllPreHashed is only supported with Long based fields");
         }
       }
@@ -739,16 +739,16 @@ public class StatsField {
   }
 
   /**
-   * Returns the effective {@link LegacyNumericType} for the field for the purposes of hash values.
-   * ie: If the field has an explict LegacyNumericType that is returned; If the field has no explicit
-   * LegacyNumericType then {@link LegacyNumericType#LONG} is returned;  If field is null, then
-   * {@link LegacyNumericType#FLOAT} is assumed for ValueSource.
+   * Returns the effective {@link NumberType} for the field for the purposes of hash values.
+   * ie: If the field has an explict NumberType that is returned; If the field has no explicit
+   * NumberType then {@link NumberType#LONG} is returned;  If field is null, then
+   * {@link NumberType#FLOAT} is assumed for ValueSource.
    */
-  private static LegacyNumericType getHashableNumericType(SchemaField field) {
+  private static NumberType getHashableNumericType(SchemaField field) {
     if (null == field) {
-      return LegacyNumericType.FLOAT;
+      return NumberType.FLOAT;
     }
-    final LegacyNumericType result = field.getType().getNumericType();
-    return null == result ? LegacyNumericType.LONG : result;
+    final NumberType result = field.getType().getNumberType();
+    return null == result ? NumberType.LONG : result;
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/highlight/DefaultSolrHighlighter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/highlight/DefaultSolrHighlighter.java b/solr/core/src/java/org/apache/solr/highlight/DefaultSolrHighlighter.java
index e035a75..33ea575 100644
--- a/solr/core/src/java/org/apache/solr/highlight/DefaultSolrHighlighter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/DefaultSolrHighlighter.java
@@ -525,8 +525,7 @@ public class DefaultSolrHighlighter extends SolrHighlighter implements PluginInf
     }
 
     int maxCharsToAnalyze = params.getFieldInt(fieldName,
-        HighlightParams.MAX_CHARS,
-        Highlighter.DEFAULT_MAX_CHARS_TO_ANALYZE);
+        HighlightParams.MAX_CHARS, DEFAULT_MAX_CHARS);
     if (maxCharsToAnalyze < 0) {//e.g. -1
       maxCharsToAnalyze = Integer.MAX_VALUE;
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/highlight/PostingsSolrHighlighter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/highlight/PostingsSolrHighlighter.java b/solr/core/src/java/org/apache/solr/highlight/PostingsSolrHighlighter.java
index 513b38a..9fcf9f3 100644
--- a/solr/core/src/java/org/apache/solr/highlight/PostingsSolrHighlighter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/PostingsSolrHighlighter.java
@@ -66,7 +66,7 @@ import org.apache.solr.util.plugin.PluginInfoInitialized;
  *       &lt;str name="hl.bs.country"&gt;&lt;/str&gt;
  *       &lt;str name="hl.bs.variant"&gt;&lt;/str&gt;
  *       &lt;str name="hl.bs.type"&gt;SENTENCE&lt;/str&gt;
- *       &lt;int name="hl.maxAnalyzedChars"&gt;10000&lt;/int&gt;
+ *       &lt;int name="hl.maxAnalyzedChars"&gt;51200&lt;/int&gt;
  *       &lt;str name="hl.multiValuedSeparatorChar"&gt; &lt;/str&gt;
  *       &lt;bool name="hl.highlightMultiTerm"&gt;false&lt;/bool&gt;
  *     &lt;/lst&gt;
@@ -204,7 +204,7 @@ public class PostingsSolrHighlighter extends SolrHighlighter implements PluginIn
     protected final IndexSchema schema;
 
     public SolrExtendedPostingsHighlighter(SolrQueryRequest req) {
-      super(req.getParams().getInt(HighlightParams.MAX_CHARS, PostingsHighlighter.DEFAULT_MAX_LENGTH));
+      super(req.getParams().getInt(HighlightParams.MAX_CHARS, DEFAULT_MAX_CHARS));
       this.params = req.getParams();
       this.schema = req.getSchema();
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/highlight/SolrHighlighter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/highlight/SolrHighlighter.java b/solr/core/src/java/org/apache/solr/highlight/SolrHighlighter.java
index e9ebf0c..a8ee734 100644
--- a/solr/core/src/java/org/apache/solr/highlight/SolrHighlighter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/SolrHighlighter.java
@@ -31,6 +31,7 @@ import java.util.List;
 public abstract class SolrHighlighter
 {
 
+  public static int DEFAULT_MAX_CHARS = 51200;
   public static int DEFAULT_PHRASE_LIMIT = 5000;
 
   /**

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java b/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
index 2633522..c80e522 100644
--- a/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
@@ -62,8 +62,8 @@ import org.apache.solr.util.plugin.PluginInfoInitialized;
  * &lt;str name="hl.tag.post"&gt;&amp;lt;/em&amp;gt;&lt;/str&gt;
  * &lt;str name="hl.simple.pre"&gt;&amp;lt;em&amp;gt;&lt;/str&gt;
  * &lt;str name="hl.simple.post"&gt;&amp;lt;/em&amp;gt;&lt;/str&gt;
- * &lt;str name="hl.tag.ellipsis"&gt;... &lt;/str&gt;
- * &lt;bool name="hl.defaultSummary"&gt;true&lt;/bool&gt;
+ * &lt;str name="hl.tag.ellipsis"&gt;(internal/unspecified)&lt;/str&gt;
+ * &lt;bool name="hl.defaultSummary"&gt;false&lt;/bool&gt;
  * &lt;str name="hl.encoder"&gt;simple&lt;/str&gt;
  * &lt;float name="hl.score.k1"&gt;1.2&lt;/float&gt;
  * &lt;float name="hl.score.b"&gt;0.75&lt;/float&gt;
@@ -72,7 +72,7 @@ import org.apache.solr.util.plugin.PluginInfoInitialized;
  * &lt;str name="hl.bs.country"&gt;&lt;/str&gt;
  * &lt;str name="hl.bs.variant"&gt;&lt;/str&gt;
  * &lt;str name="hl.bs.type"&gt;SENTENCE&lt;/str&gt;
- * &lt;int name="hl.maxAnalyzedChars"&gt;10000&lt;/int&gt;
+ * &lt;int name="hl.maxAnalyzedChars"&gt;51200&lt;/int&gt;
  * &lt;bool name="hl.highlightMultiTerm"&gt;true&lt;/bool&gt;
  * &lt;bool name="hl.usePhraseHighlighter"&gt;true&lt;/bool&gt;
  * &lt;int name="hl.cacheFieldValCharsThreshold"&gt;524288&lt;/int&gt;
@@ -234,7 +234,7 @@ public class UnifiedSolrHighlighter extends SolrHighlighter implements PluginInf
       this.params = req.getParams();
       this.schema = req.getSchema();
       this.setMaxLength(
-          params.getInt(HighlightParams.MAX_CHARS, UnifiedHighlighter.DEFAULT_MAX_LENGTH));
+          params.getInt(HighlightParams.MAX_CHARS, DEFAULT_MAX_CHARS));
       this.setCacheFieldValCharsThreshold(
           params.getInt(HighlightParams.CACHE_FIELD_VAL_CHARS_THRESHOLD, DEFAULT_CACHE_CHARS_THRESHOLD));
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index 3bebcd3..eb5b687 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -73,9 +73,6 @@ public class SolrCoreMetricManager implements Closeable {
     }
     // close old reporters
     metricManager.closeReporters(oldRegistryName);
-    metricManager.moveMetrics(oldRegistryName, registryName, null);
-    // old registry is no longer used - we have moved the metrics
-    metricManager.removeRegistry(oldRegistryName);
     // load reporters again, using the new core name
     loadReporters();
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/java/org/apache/solr/metrics/SolrMetricInfo.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricInfo.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricInfo.java
index f0bc8a1..4d093eb 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricInfo.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricInfo.java
@@ -30,7 +30,7 @@ public final class SolrMetricInfo {
   /**
    * Creates a new instance of {@link SolrMetricInfo}.
    *
-   * @param category the category of the metric (e.g. `QUERYHANDLERS`)
+   * @param category the category of the metric (e.g. `QUERY`)
    * @param scope    the scope of the metric (e.g. `/admin/ping`)
    * @param name     the name of the metric (e.g. `Requests`)
    */
@@ -63,8 +63,8 @@ public final class SolrMetricInfo {
   /**
    * Returns the metric name defined by this object.
    * For example, if the name is `Requests`, scope is `/admin/ping`,
-   * and category is `QUERYHANDLERS`, then the metric name is
-   * `QUERYHANDLERS./admin/ping.Requests`.
+   * and category is `QUERY`, then the metric name is
+   * `QUERY./admin/ping.Requests`.
    *
    * @return the metric name defined by this object
    */


[02/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualsEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualsEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualsEvaluatorTest.java
new file mode 100644
index 0000000..2f9dd9c
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualsEvaluatorTest.java
@@ -0,0 +1,263 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.EqualsEvaluator;
+import org.apache.solr.client.solrj.io.eval.RawValueEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class EqualsEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public EqualsEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("eq", EqualsEvaluator.class)
+      .withFunctionName("val", RawValueEvaluator.class)
+      ;
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void operationFieldName() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(sum(a),val(9))");    
+    Object result;
+    
+    values.clear();
+    values.put("sum(a)", 9);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+
+  }
+  
+  @Test
+  public void eqTwoIntegers() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", -1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+
+  @Test
+  public void eqTwoStrings() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    Object result;
+    String foo = "foo";
+    String bar = "bar";
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "foo");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "bar");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, false);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar baz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar jaz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", foo);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", bar);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+  
+  @Test
+  public void eqTwoBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    Object result;
+    Boolean t = true;
+    Boolean f = false;
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b",true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b",false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, false);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", t);
+    values.put("b", f);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", t);
+    values.put("b", t);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", f);
+    values.put("b", f);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+  }
+
+  @Test(expected = IOException.class)
+  public void eqDifferentTypes1() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void eqDifferentTypes2() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b",false);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void eqDifferentTypes3() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    
+    values.clear();
+    values.put("a", "1");
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void eqDifferentTypes4() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    
+    values.clear();
+    values.put("a", "true");
+    values.put("b",true);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ExclusiveOrEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ExclusiveOrEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ExclusiveOrEvaluatorTest.java
new file mode 100644
index 0000000..c1cc677
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ExclusiveOrEvaluatorTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.ExclusiveOrEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class ExclusiveOrEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public ExclusiveOrEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("eor", ExclusiveOrEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void eorTwoBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eor(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+  
+  @Test
+  public void eorWithSubAndsBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eor(a,eor(b,c))");
+    Object result;
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", true);
+    values.put("c", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+
+    values.clear();
+    values.put("a", true);
+    values.put("b", true);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", false);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", true);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", false);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/GreaterThanEqualToEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/GreaterThanEqualToEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/GreaterThanEqualToEvaluatorTest.java
new file mode 100644
index 0000000..5968a15
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/GreaterThanEqualToEvaluatorTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEqualToEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class GreaterThanEqualToEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public GreaterThanEqualToEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("gte", GreaterThanEqualToEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void gteTwoIntegers() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gte(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", -1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 2);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 2);
+    values.put("b", 1.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 2.0);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 3);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", -2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 3);
+    values.put("b", 2.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 3.0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+  }
+  
+  @Test
+  public void gteTwoStrings() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gte(a,b)");
+    Object result;
+    String foo = "foo";
+    String bar = "bar";
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "foo");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "bar");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar baz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar jaz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", foo);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", bar);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+  }
+  
+  @Test(expected = IOException.class)
+  public void gteTwoBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gte(a,b)");
+
+    values.clear();
+    values.put("a", true);
+    values.put("b",true);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void gteDifferentTypes1() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gte(a,b)");
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void gteDifferentTypes2() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gte(a,b)");
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b",false);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void gteDifferentTypes3() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gte(a,b)");
+    
+    values.clear();
+    values.put("a", "1");
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void gteDifferentTypes4() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gte(a,b)");
+    
+    values.clear();
+    values.put("a", "true");
+    values.put("b",true);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/GreaterThanEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/GreaterThanEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/GreaterThanEvaluatorTest.java
new file mode 100644
index 0000000..d31a79c
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/GreaterThanEvaluatorTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class GreaterThanEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public GreaterThanEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("gt", GreaterThanEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void gtTwoIntegers() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gt(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", -1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 2);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 2);
+    values.put("b", 1.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 2.0);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 3);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", -2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 3);
+    values.put("b", 2.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 3.0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+  }
+  
+  @Test
+  public void gtTwoStrings() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gt(a,b)");
+    Object result;
+    String foo = "foo";
+    String bar = "bar";
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "foo");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "bar");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar baz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar jaz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", foo);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", bar);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+  }
+  
+  @Test(expected = IOException.class)
+  public void gtTwoBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gt(a,b)");
+
+    values.clear();
+    values.put("a", true);
+    values.put("b",true);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void gtDifferentTypes1() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gt(a,b)");
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void gtDifferentTypes2() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gt(a,b)");
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b",false);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void gtDifferentTypes3() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gt(a,b)");
+    
+    values.clear();
+    values.put("a", "1");
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void gtDifferentTypes4() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("gt(a,b)");
+    
+    values.clear();
+    values.put("a", "true");
+    values.put("b",true);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/LessThanEqualToEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/LessThanEqualToEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/LessThanEqualToEvaluatorTest.java
new file mode 100644
index 0000000..114ea2d
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/LessThanEqualToEvaluatorTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.LessThanEqualToEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class LessThanEqualToEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public LessThanEqualToEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("lte", LessThanEqualToEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void lteTwoIntegers() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lte(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", -1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 2);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 2);
+    values.put("b", 1.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 2.0);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 3);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", -2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", -2);
+    values.put("b", -1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 3);
+    values.put("b", 2.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 3.0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+  
+  @Test
+  public void lteTwoStrings() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lte(a,b)");
+    Object result;
+    String foo = "foo";
+    String bar = "bar";
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "foo");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "bar");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar baz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar jaz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", foo);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", bar);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+  
+  @Test(expected = IOException.class)
+  public void lteTwoBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lte(a,b)");
+
+    values.clear();
+    values.put("a", true);
+    values.put("b",true);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void lteDifferentTypes1() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lte(a,b)");
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void lteDifferentTypes2() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lte(a,b)");
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b",false);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void lteDifferentTypes3() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lte(a,b)");
+    
+    values.clear();
+    values.put("a", "1");
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void lteDifferentTypes4() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lte(a,b)");
+    
+    values.clear();
+    values.put("a", "true");
+    values.put("b",true);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/LessThanEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/LessThanEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/LessThanEvaluatorTest.java
new file mode 100644
index 0000000..5cc0274
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/LessThanEvaluatorTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.LessThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class LessThanEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public LessThanEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("lt", LessThanEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void ltTwoIntegers() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lt(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", -1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 2);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 2);
+    values.put("b", 1.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 2.0);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 3);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", -2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 3);
+    values.put("b", 2.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 3.0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+  
+  @Test
+  public void ltTwoStrings() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lt(a,b)");
+    Object result;
+    String foo = "foo";
+    String bar = "bar";
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "foo");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "bar");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar baz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar jaz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", foo);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", bar);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+  
+  @Test(expected = IOException.class)
+  public void ltTwoBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lt(a,b)");
+
+    values.clear();
+    values.put("a", true);
+    values.put("b",true);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void ltDifferentTypes1() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lt(a,b)");
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void ltDifferentTypes2() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lt(a,b)");
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b",false);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void ltDifferentTypes3() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lt(a,b)");
+    
+    values.clear();
+    values.put("a", "1");
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void ltDifferentTypes4() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("lt(a,b)");
+    
+    values.clear();
+    values.put("a", "true");
+    values.put("b",true);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/MultiplyEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/MultiplyEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/MultiplyEvaluatorTest.java
new file mode 100644
index 0000000..a2a6616
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/MultiplyEvaluatorTest.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for multitional 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.MultiplyEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class MultiplyEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public MultiplyEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("mult", MultiplyEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void multTwoFieldsWithValues() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("mult(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(2L, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(2.2D, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(2.31D, result);
+  }
+
+  @Test(expected = IOException.class)
+  public void multOneField() throws Exception{
+    factory.constructEvaluator("mult(a)");
+  }
+  
+  @Test
+  public void multTwoFieldWithNulls() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("mult(a,b)");
+    Object result;
+    
+    values.clear();
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+  }
+  
+  @Test
+  public void multTwoFieldsWithNull() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("mult(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", null);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+
+    values.clear();
+    values.put("a", null);
+    values.put("b", 1.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", null);    
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+  }
+
+  @Test
+  public void multTwoFieldsWithMissingField() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("mult(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("b", 1.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("a", 1.1);    
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+  }
+
+  @Test
+  public void multManyFieldsWithValues() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("mult(a,b,c,d)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(24L, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(26.4D, result);
+    
+    values.clear();
+    values.put("a", 10.1);
+    values.put("b", 2.1);
+    values.put("c", 3.1);
+    values.put("d", 4.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(269.5791D, result);
+  }
+  
+  @Test
+  public void multManyFieldsWithSubmults() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("mult(a,b,mult(c,d))");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(24L, result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NotEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NotEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NotEvaluatorTest.java
new file mode 100644
index 0000000..6116163
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NotEvaluatorTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.eval.NotEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class NotEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public NotEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("not", NotEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void notOneBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("not(a)");
+    Object result;
+    
+    values.clear();
+    values.put("a", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+  }
+  
+  @Test
+  public void notWithSubNotBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("not(not(a))");
+    Object result;
+    
+    values.clear();
+    values.put("a", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+
+    values.clear();
+    values.put("a", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);    
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/OrEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/OrEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/OrEvaluatorTest.java
new file mode 100644
index 0000000..00c6b7a
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/OrEvaluatorTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.OrEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class OrEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public OrEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("or", OrEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void orTwoBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("or(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+  
+  @Test
+  public void orWithSubAndsBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("or(a,or(b,c))");
+    Object result;
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", true);
+    values.put("c", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+
+    values.clear();
+    values.put("a", true);
+    values.put("b", true);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", false);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", true);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", false);
+    values.put("c", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RawValueEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RawValueEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RawValueEvaluatorTest.java
new file mode 100644
index 0000000..0d637e1
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RawValueEvaluatorTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.AddEvaluator;
+import org.apache.solr.client.solrj.io.eval.AndEvaluator;
+import org.apache.solr.client.solrj.io.eval.RawValueEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class RawValueEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public RawValueEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("val", RawValueEvaluator.class)
+      .withFunctionName("add", AddEvaluator.class)
+      .withFunctionName("and", AndEvaluator.class)
+      ;
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void rawTypes() throws Exception{
+    Tuple tuple = new Tuple(values);
+    
+    Assert.assertEquals(10L, factory.constructEvaluator("val(10)").evaluate(tuple));
+    Assert.assertEquals(-10L, factory.constructEvaluator("val(-10)").evaluate(tuple));
+    Assert.assertEquals(0L, factory.constructEvaluator("val(0)").evaluate(tuple));
+    Assert.assertEquals(10.5, factory.constructEvaluator("val(10.5)").evaluate(tuple));
+    Assert.assertEquals(-10.5, factory.constructEvaluator("val(-10.5)").evaluate(tuple));
+    Assert.assertEquals(true, factory.constructEvaluator("val(true)").evaluate(tuple));
+    Assert.assertEquals(false, factory.constructEvaluator("val(false)").evaluate(tuple));
+    Assert.assertNull(factory.constructEvaluator("val(null)").evaluate(tuple));
+  }
+  
+  public void rawTypesAsPartOfOther() throws Exception{
+    Tuple tuple = new Tuple(values);
+    
+    Assert.assertEquals(15L, factory.constructEvaluator("add(val(10),val(5))").evaluate(tuple));
+    Assert.assertEquals(true, factory.constructEvaluator("and(val(true),val(true))").evaluate(tuple));
+    Assert.assertEquals(false, factory.constructEvaluator("and(val(false),val(false))").evaluate(tuple));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/SubtractEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/SubtractEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/SubtractEvaluatorTest.java
new file mode 100644
index 0000000..58cef8d
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/SubtractEvaluatorTest.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for subitional 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.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.eval.SubtractEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+public class SubtractEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public SubtractEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("sub", SubtractEvaluator.class);
+    values = new HashedMap();
+  }
+    
+  @Test
+  public void subTwoFieldsWithValues() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("sub(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(-1L, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(-.9D, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(-1L, result);
+  }
+
+  @Test(expected = IOException.class)
+  public void subOneField() throws Exception{
+    factory.constructEvaluator("sub(a)");
+  }
+  
+  @Test
+  public void subTwoFieldWithNulls() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("sub(a,b)");
+    Object result;
+    
+    values.clear();
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+  }
+  
+  @Test
+  public void subTwoFieldsWithNull() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("sub(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", null);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", null);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", null);    
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+  }
+
+  @Test
+  public void subTwoFieldsWithMissingField() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("sub(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+    
+    values.clear();
+    values.put("a", 1.1);    
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertNull(result);
+  }
+
+  @Test
+  public void subManyFieldsWithValues() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("sub(a,b,c,d)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(-8L, result);
+    
+    values.clear();
+    values.put("a", 1.1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(-7.9D, result);
+    
+    values.clear();
+    values.put("a", 10.1);
+    values.put("b", 2.1);
+    values.put("c", 3.1);
+    values.put("d", 4.1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(.8D, result);
+  }
+  
+  @Test
+  public void subManyFieldsWithSubsubs() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("sub(a,b,sub(c,d))");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    values.put("c", 3);
+    values.put("d", 4);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(0L, result);
+        
+    values.clear();
+    values.put("a", 123456789123456789L);
+    values.put("b", 123456789123456789L);
+    values.put("c", 123456789123456789L);
+    values.put("d", 123456789123456789L);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(0L, result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java
index ef4dad7..19adf35 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestCoreAdmin.java
@@ -18,21 +18,29 @@ package org.apache.solr.client.solrj.request;
 
 import java.io.File;
 import java.lang.invoke.MethodHandles;
+import java.util.Collection;
 
 import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
 import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
+import com.codahale.metrics.MetricRegistry;
 import org.apache.commons.io.FileUtils;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.solr.SolrIgnoredThreadsFilter;
 import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.embedded.AbstractEmbeddedSolrServerTestCase;
 import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
 import org.apache.solr.client.solrj.request.CoreAdminRequest.Create;
 import org.apache.solr.client.solrj.response.CoreAdminResponse;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.SolrCore;
+import org.apache.solr.metrics.SolrCoreMetricManager;
+import org.apache.solr.metrics.SolrMetricManager;
 import org.junit.After;
 import org.junit.BeforeClass;
 import org.junit.Rule;
@@ -186,6 +194,93 @@ public class TestCoreAdmin extends AbstractEmbeddedSolrServerTestCase {
       assertTrue(exceptionMessage.contains("must consist entirely of periods, underscores, hyphens, and alphanumerics"));
     }
   }
+
+  @Test
+  public void testValidCoreRename() throws Exception {
+    Collection<String> names = cores.getAllCoreNames();
+    assertFalse(names.toString(), names.contains("coreRenamed"));
+    assertTrue(names.toString(), names.contains("core1"));
+    CoreAdminRequest.renameCore("core1", "coreRenamed", getSolrAdmin());
+    names = cores.getAllCoreNames();
+    assertTrue(names.toString(), names.contains("coreRenamed"));
+    assertFalse(names.toString(), names.contains("core1"));
+    // rename it back
+    CoreAdminRequest.renameCore("coreRenamed", "core1", getSolrAdmin());
+    names = cores.getAllCoreNames();
+    assertFalse(names.toString(), names.contains("coreRenamed"));
+    assertTrue(names.toString(), names.contains("core1"));
+  }
+
+  @Test
+  public void testCoreSwap() throws Exception {
+    // index marker docs to core0
+    SolrClient cli0 = getSolrCore0();
+    SolrInputDocument d = new SolrInputDocument("id", "core0-0");
+    cli0.add(d);
+    d = new SolrInputDocument("id", "core0-1");
+    cli0.add(d);
+    cli0.commit();
+    // index a marker doc to core1
+    SolrClient cli1 = getSolrCore1();
+    d = new SolrInputDocument("id", "core1-0");
+    cli1.add(d);
+    cli1.commit();
+
+    // initial state assertions
+    SolrQuery q = new SolrQuery("*:*");
+    QueryResponse rsp = cli0.query(q);
+    SolrDocumentList docs = rsp.getResults();
+    assertEquals(2, docs.size());
+    docs.forEach(doc -> {
+      assertTrue(doc.toString(), doc.getFieldValue("id").toString().startsWith("core0-"));
+    });
+
+    rsp = cli1.query(q);
+    docs = rsp.getResults();
+    assertEquals(1, docs.size());
+    docs.forEach(doc -> {
+      assertTrue(doc.toString(), doc.getFieldValue("id").toString().startsWith("core1-"));
+    });
+
+    // assert initial metrics
+    SolrMetricManager metricManager = cores.getMetricManager();
+    String core0RegistryName = SolrCoreMetricManager.createRegistryName(null, "core0");
+    String core1RegistryName = SolrCoreMetricManager.createRegistryName(null, "core1");
+    MetricRegistry core0Registry = metricManager.registry(core0RegistryName);
+    MetricRegistry core1Registry = metricManager.registry(core1RegistryName);
+
+    // 2 docs + 1 commit
+    assertEquals(3, core0Registry.counter("UPDATE./update.requests").getCount());
+    // 1 doc + 1 commit
+    assertEquals(2, core1Registry.counter("UPDATE./update.requests").getCount());
+
+    // swap
+    CoreAdminRequest.swapCore("core0", "core1", getSolrAdmin());
+
+    // assert state after swap
+    cli0 = getSolrCore0();
+    cli1 = getSolrCore1();
+
+    rsp = cli0.query(q);
+    docs = rsp.getResults();
+    assertEquals(1, docs.size());
+    docs.forEach(doc -> {
+      assertTrue(doc.toString(), doc.getFieldValue("id").toString().startsWith("core1-"));
+    });
+
+    rsp = cli1.query(q);
+    docs = rsp.getResults();
+    assertEquals(2, docs.size());
+    docs.forEach(doc -> {
+      assertTrue(doc.toString(), doc.getFieldValue("id").toString().startsWith("core0-"));
+    });
+
+    core0Registry = metricManager.registry(core0RegistryName);
+    core1Registry = metricManager.registry(core1RegistryName);
+
+    assertEquals(2, core0Registry.counter("UPDATE./update.requests").getCount());
+    assertEquals(3, core1Registry.counter("UPDATE./update.requests").getCount());
+  }
   
   @BeforeClass
   public static void before() {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/solrj/src/test/org/apache/solr/common/util/TestValidatingJsonMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/common/util/TestValidatingJsonMap.java b/solr/solrj/src/test/org/apache/solr/common/util/TestValidatingJsonMap.java
new file mode 100644
index 0000000..e5f8183
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/common/util/TestValidatingJsonMap.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.common.util;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.solr.SolrTestCaseJ4;
+
+import static org.apache.solr.common.util.Utils.makeMap;
+import static org.apache.solr.common.util.ValidatingJsonMap.ENUM_OF;
+import static org.apache.solr.common.util.ValidatingJsonMap.NOT_NULL;
+
+public class TestValidatingJsonMap extends SolrTestCaseJ4 {
+  public void testBasic() throws Exception {
+    ValidatingJsonMap m = ValidatingJsonMap.wrap(
+        makeMap("a", Boolean.TRUE,
+                "b", Boolean.FALSE,
+                "i", 10,
+                "l" , Arrays.asList("X", "Y"),
+            "c", makeMap("d", "D")));
+    assertEquals(Boolean.TRUE, m.getBool("a", Boolean.FALSE));
+    assertEquals(Boolean.FALSE, m.getBool("b", Boolean.TRUE));
+    assertEquals(new Integer(10), m.getInt("i",0));
+    try {
+      m.getList("l", ENUM_OF, ImmutableSet.of("X", "Z"));
+      fail("Must have failed with unexpected type");
+    } catch (RuntimeException e) { }
+
+    List l = m.getList("l", ENUM_OF, ImmutableSet.of("X", "Y", "Z"));
+    assertEquals(2,l.size());
+    m.getList("l", NOT_NULL);
+    assertNotNull(m.getMap("c"));
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index 59e74d90..0a1c1b2 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -51,8 +51,12 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
 
 import com.carrotsearch.randomizedtesting.RandomizedContext;
+import com.carrotsearch.randomizedtesting.TraceFormatting;
 import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
 import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
 import org.apache.commons.io.FileUtils;
@@ -154,6 +158,14 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
+  private static final List<String> DEFAULT_STACK_FILTERS = Arrays.asList(new String [] {
+      "org.junit.",
+      "junit.framework.",
+      "sun.",
+      "java.lang.reflect.",
+      "com.carrotsearch.randomizedtesting.",
+  });
+  
   public static final String DEFAULT_TEST_COLLECTION_NAME = "collection1";
   public static final String DEFAULT_TEST_CORENAME = DEFAULT_TEST_COLLECTION_NAME;
   protected static final String CORE_PROPERTIES_FILENAME = "core.properties";
@@ -285,7 +297,7 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
         // if the tests passed, make sure everything was closed / released
         if (!RandomizedContext.current().getTargetClass().isAnnotationPresent(SuppressObjectReleaseTracker.class)) {
           endTrackingSearchers(120, false);
-          String orr = ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty(30);
+          String orr = clearObjectTrackerAndCheckEmpty(120);
           assertNull(orr, orr);
         } else {
           endTrackingSearchers(15, false);
@@ -323,6 +335,41 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
     LogLevel.Configurer.restoreLogLevels(savedClassLogLevels);
     savedClassLogLevels.clear();
   }
+  
+  /**
+   * @return null if ok else error message
+   */
+  public static String clearObjectTrackerAndCheckEmpty(int waitSeconds) {
+    int retries = 0;
+    String result;
+    do {
+      result = ObjectReleaseTracker.checkEmpty();
+      if (result == null)
+        break;
+      try {
+        if (retries % 10 == 0) {
+          log.info("Waiting for all tracked resources to be released");
+          if (retries > 10) {
+            TraceFormatting tf = new TraceFormatting(DEFAULT_STACK_FILTERS);
+            Map<Thread,StackTraceElement[]> stacksMap = Thread.getAllStackTraces();
+            Set<Entry<Thread,StackTraceElement[]>> entries = stacksMap.entrySet();
+            for (Entry<Thread,StackTraceElement[]> entry : entries) {
+              String stack = tf.formatStackTrace(entry.getValue());
+              System.err.println(entry.getKey().getName() + ":\n" + stack);
+            }
+          }
+        }
+        TimeUnit.SECONDS.sleep(1);
+      } catch (InterruptedException e) { break; }
+    }
+    while (retries++ < waitSeconds);
+    
+    
+    log.info("------------------------------------------------------- Done waiting for tracked resources to be released");
+    ObjectReleaseTracker.clear();
+    
+    return result;
+  }
 
   private static Map<String, String> savedClassLogLevels = new HashMap<>();
 
@@ -541,6 +588,18 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
        if (retries++ > waitSeconds) {
          break;
        }
+       if (retries % 10 == 0) {
+         log.info("Waiting for all SolrIndexSearchers to be released at end of test");
+        if (retries > 10) {
+          TraceFormatting tf = new TraceFormatting();
+          Map<Thread,StackTraceElement[]> stacksMap = Thread.getAllStackTraces();
+          Set<Entry<Thread,StackTraceElement[]>> entries = stacksMap.entrySet();
+          for (Entry<Thread,StackTraceElement[]> entry : entries) {
+            String stack = tf.formatStackTrace(entry.getValue());
+            System.err.println(entry.getKey().getName() + ":\n" + stack);
+          }
+        }
+       }
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {}
@@ -548,6 +607,8 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
        endNumCloses = SolrIndexSearcher.numCloses.get();
      }
 
+     log.info("------------------------------------------------------- Done waiting for all SolrIndexSearchers to be released");
+     
      SolrIndexSearcher.numOpens.getAndSet(0);
      SolrIndexSearcher.numCloses.getAndSet(0);
 
@@ -595,7 +656,7 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
   protected static SolrConfig solrConfig;
 
   /**
-   * Harness initialized by initTestHarness.
+   * Harness initialized by create[Default]Core[Container].
    *
    * <p>
    * For use in test methods as needed.
@@ -604,7 +665,7 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
   protected static TestHarness h;
 
   /**
-   * LocalRequestFactory initialized by initTestHarness using sensible
+   * LocalRequestFactory initialized by create[Default]Core[Container] using sensible
    * defaults.
    *
    * <p>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudTestCase.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudTestCase.java b/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudTestCase.java
index 34dc8ac..7fc8257 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudTestCase.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudTestCase.java
@@ -70,7 +70,7 @@ import org.junit.Before;
  */
 public class SolrCloudTestCase extends SolrTestCaseJ4 {
 
-  public static final int DEFAULT_TIMEOUT = 30;
+  public static final int DEFAULT_TIMEOUT = 60;
 
   private static class Config {
     final String name;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/test-framework/src/java/org/apache/solr/util/ExternalPaths.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/util/ExternalPaths.java b/solr/test-framework/src/java/org/apache/solr/util/ExternalPaths.java
index 3e91d81..e5a83ef 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/ExternalPaths.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/ExternalPaths.java
@@ -63,7 +63,7 @@ public class ExternalPaths {
         }
       } catch (Exception e) {
         // If there is no "solr/conf" in the classpath, fall back to searching from the current directory.
-        file = new File(".");
+        file = new File(System.getProperty("tests.src.home", "."));
       }
       File base = file.getAbsoluteFile();
       while (!(new File(base, "solr/CHANGES.txt").exists()) && null != base) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java b/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java
index 0f1c8c9..11e28a6 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java
@@ -51,6 +51,14 @@ public class RestTestHarness extends BaseTestHarness implements Closeable {
     return serverProvider.getBaseURL();
   }
 
+  public void setServerProvider(RESTfulServerProvider serverProvider) {
+    this.serverProvider = serverProvider;
+  }
+
+  public RESTfulServerProvider getServerProvider() {
+    return this.serverProvider;
+  }
+
   public String getAdminURL() {
     return getBaseURL().replace("/collection1", "");
   }


[14/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
new file mode 100644
index 0000000..e38d9fe
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.document;
+
+import java.io.IOException;
+
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.GeoUtils;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.TwoPhaseIterator;
+import org.apache.lucene.search.Weight;
+
+/** Distance query for {@link LatLonDocValuesField}. */
+final class LatLonDocValuesDistanceQuery extends Query {
+
+  private final String field;
+  private final double latitude, longitude;
+  private final double radiusMeters;
+
+  LatLonDocValuesDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
+    if (Double.isFinite(radiusMeters) == false || radiusMeters < 0) {
+      throw new IllegalArgumentException("radiusMeters: '" + radiusMeters + "' is invalid");
+    }
+    GeoUtils.checkLatitude(latitude);
+    GeoUtils.checkLongitude(longitude);
+    if (field == null) {
+      throw new IllegalArgumentException("field must not be null");
+    }
+    this.field = field;
+    this.latitude = latitude;
+    this.longitude = longitude;
+    this.radiusMeters = radiusMeters;
+  }
+
+  @Override
+  public String toString(String field) {
+    StringBuilder sb = new StringBuilder();
+    if (!this.field.equals(field)) {
+      sb.append(this.field);
+      sb.append(':');
+    }
+    sb.append(latitude);
+    sb.append(",");
+    sb.append(longitude);
+    sb.append(" +/- ");
+    sb.append(radiusMeters);
+    sb.append(" meters");
+    return sb.toString();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (sameClassAs(obj) == false) {
+      return false;
+    }
+    LatLonDocValuesDistanceQuery other = (LatLonDocValuesDistanceQuery) obj;
+    return field.equals(other.field) &&
+        Double.doubleToLongBits(latitude) == Double.doubleToLongBits(other.latitude) &&
+        Double.doubleToLongBits(longitude) == Double.doubleToLongBits(other.longitude) &&
+        Double.doubleToLongBits(radiusMeters) == Double.doubleToLongBits(other.radiusMeters);
+  }
+
+  @Override
+  public int hashCode() {
+    int h = classHash();
+    h = 31 * h + field.hashCode();
+    h = 31 * h + Double.hashCode(latitude);
+    h = 31 * h + Double.hashCode(longitude);
+    h = 31 * h + Double.hashCode(radiusMeters);
+    return h;
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
+    return new ConstantScoreWeight(this, boost) {
+
+      private final GeoEncodingUtils.DistancePredicate distancePredicate = GeoEncodingUtils.createDistancePredicate(latitude, longitude, radiusMeters);
+
+      @Override
+      public Scorer scorer(LeafReaderContext context) throws IOException {
+        final SortedNumericDocValues values = context.reader().getSortedNumericDocValues(field);
+        if (values == null) {
+          return null;
+        }
+
+        final TwoPhaseIterator iterator = new TwoPhaseIterator(values) {
+
+          @Override
+          public boolean matches() throws IOException {
+            for (int i = 0, count = values.docValueCount(); i < count; ++i) {
+              final long value = values.nextValue();
+              final int lat = (int) (value >>> 32);
+              final int lon = (int) (value & 0xFFFFFFFF);
+              if (distancePredicate.test(lat, lon)) {
+                return true;
+              }
+            }
+            return false;
+          }
+
+          @Override
+          public float matchCost() {
+            return 100f; // TODO: what should it be?
+          }
+
+        };
+        return new ConstantScoreScorer(this, boost, iterator);
+      }
+    };
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesField.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesField.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesField.java
index 20154d2..08a7da7 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesField.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesField.java
@@ -24,6 +24,9 @@ import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
 import org.apache.lucene.index.DocValuesType;
 import org.apache.lucene.index.FieldInfo;
 import org.apache.lucene.search.FieldDoc;
+import org.apache.lucene.search.IndexOrDocValuesQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
+import org.apache.lucene.search.Query;
 import org.apache.lucene.search.SortField;
 
 /** 
@@ -132,4 +135,47 @@ public class LatLonDocValuesField extends Field {
   public static SortField newDistanceSort(String field, double latitude, double longitude) {
     return new LatLonPointSortField(field, latitude, longitude);
   }
+
+  /**
+   * Create a query for matching a bounding box using doc values.
+   * This query is usually slow as it does not use an index structure and needs
+   * to verify documents one-by-one in order to know whether they match. It is
+   * best used wrapped in an {@link IndexOrDocValuesQuery} alongside a
+   * {@link LatLonPoint#newBoxQuery}.
+   */
+  public static Query newBoxQuery(String field, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
+    // exact double values of lat=90.0D and lon=180.0D must be treated special as they are not represented in the encoding
+    // and should not drag in extra bogus junk! TODO: should encodeCeil just throw ArithmeticException to be less trappy here?
+    if (minLatitude == 90.0) {
+      // range cannot match as 90.0 can never exist
+      return new MatchNoDocsQuery("LatLonDocValuesField.newBoxQuery with minLatitude=90.0");
+    }
+    if (minLongitude == 180.0) {
+      if (maxLongitude == 180.0) {
+        // range cannot match as 180.0 can never exist
+        return new MatchNoDocsQuery("LatLonDocValuesField.newBoxQuery with minLongitude=maxLongitude=180.0");
+      } else if (maxLongitude < minLongitude) {
+        // encodeCeil() with dateline wrapping!
+        minLongitude = -180.0;
+      }
+    }
+    return new LatLonDocValuesBoxQuery(field, minLatitude, maxLatitude, minLongitude, maxLongitude);
+  }
+
+  /**
+   * Create a query for matching points within the specified distance of the supplied location.
+   * This query is usually slow as it does not use an index structure and needs
+   * to verify documents one-by-one in order to know whether they match. It is
+   * best used wrapped in an {@link IndexOrDocValuesQuery} alongside a
+   * {@link LatLonPoint#newDistanceQuery}.
+   * @param field field name. must not be null.
+   * @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
+   * @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
+   * @param radiusMeters maximum distance from the center in meters: must be non-negative and finite.
+   * @return query matching points within this distance
+   * @throws IllegalArgumentException if {@code field} is null, location has invalid coordinates, or radius is invalid.
+   */
+  public static Query newDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
+    return new LatLonDocValuesDistanceQuery(field, latitude, longitude, radiusMeters);
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java
index 7a00cef..71ddf3d 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java
@@ -18,6 +18,7 @@ package org.apache.lucene.document;
 
 import java.io.IOException;
 
+import org.apache.lucene.geo.GeoEncodingUtils;
 import org.apache.lucene.geo.GeoUtils;
 import org.apache.lucene.geo.Rectangle;
 import org.apache.lucene.index.FieldInfo;
@@ -31,10 +32,10 @@ import org.apache.lucene.search.ConstantScoreWeight;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.ScorerSupplier;
 import org.apache.lucene.search.Weight;
 import org.apache.lucene.util.DocIdSetBuilder;
 import org.apache.lucene.util.NumericUtils;
-import org.apache.lucene.util.SloppyMath;
 import org.apache.lucene.util.StringHelper;
 
 import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
@@ -102,8 +103,19 @@ final class LatLonPointDistanceQuery extends Query {
 
     return new ConstantScoreWeight(this, boost) {
 
+      final GeoEncodingUtils.DistancePredicate distancePredicate = GeoEncodingUtils.createDistancePredicate(latitude, longitude, radiusMeters);
+
       @Override
       public Scorer scorer(LeafReaderContext context) throws IOException {
+        ScorerSupplier scorerSupplier = scorerSupplier(context);
+        if (scorerSupplier == null) {
+          return null;
+        }
+        return scorerSupplier.get(false);
+      }
+
+      @Override
+      public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
         LeafReader reader = context.reader();
         PointValues values = reader.getPointValues(field);
         if (values == null) {
@@ -119,8 +131,7 @@ final class LatLonPointDistanceQuery extends Query {
         
         // matching docids
         DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
-
-        values.intersect(
+        final IntersectVisitor visitor =
                          new IntersectVisitor() {
 
                            DocIdSetBuilder.BulkAdder adder;
@@ -151,11 +162,9 @@ final class LatLonPointDistanceQuery extends Query {
                                return;
                              }
 
-                             double docLatitude = decodeLatitude(packedValue, 0);
-                             double docLongitude = decodeLongitude(packedValue, Integer.BYTES);
-
-                             // its a match only if its sortKey <= our sortKey
-                             if (SloppyMath.haversinSortKey(latitude, longitude, docLatitude, docLongitude) <= sortKey) {
+                             int docLatitude = NumericUtils.sortableBytesToInt(packedValue, 0);
+                             int docLongitude = NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES);
+                             if (distancePredicate.test(docLatitude, docLongitude)) {
                                adder.add(docID);
                              }
                            }
@@ -185,32 +194,30 @@ final class LatLonPointDistanceQuery extends Query {
                              double latMax = decodeLatitude(maxPackedValue, 0);
                              double lonMax = decodeLongitude(maxPackedValue, Integer.BYTES);
 
-                             if ((longitude < lonMin || longitude > lonMax) && (axisLat+ Rectangle.AXISLAT_ERROR < latMin || axisLat- Rectangle.AXISLAT_ERROR > latMax)) {
-                               // circle not fully inside / crossing axis
-                               if (SloppyMath.haversinSortKey(latitude, longitude, latMin, lonMin) > sortKey &&
-                                   SloppyMath.haversinSortKey(latitude, longitude, latMin, lonMax) > sortKey &&
-                                   SloppyMath.haversinSortKey(latitude, longitude, latMax, lonMin) > sortKey &&
-                                   SloppyMath.haversinSortKey(latitude, longitude, latMax, lonMax) > sortKey) {
-                                 // no points inside
-                                 return Relation.CELL_OUTSIDE_QUERY;
-                               }
-                             }
-
-                             if (lonMax - longitude < 90 && longitude - lonMin < 90 &&
-                                 SloppyMath.haversinSortKey(latitude, longitude, latMin, lonMin) <= sortKey &&
-                                 SloppyMath.haversinSortKey(latitude, longitude, latMin, lonMax) <= sortKey &&
-                                 SloppyMath.haversinSortKey(latitude, longitude, latMax, lonMin) <= sortKey &&
-                                 SloppyMath.haversinSortKey(latitude, longitude, latMax, lonMax) <= sortKey) {
-                               // we are fully enclosed, collect everything within this subtree
-                               return Relation.CELL_INSIDE_QUERY;
-                             } else {
-                               // recurse: its inside our bounding box(es), but not fully, or it wraps around.
-                               return Relation.CELL_CROSSES_QUERY;
-                             }
+                             return GeoUtils.relate(latMin, latMax, lonMin, lonMax, latitude, longitude, sortKey, axisLat);
                            }
-                         });
+                         };
+        final Weight weight = this;
+        return new ScorerSupplier() {
+
+          long cost = -1;
+
+          @Override
+          public Scorer get(boolean randomAccess) throws IOException {
+            values.intersect(visitor);
+            return new ConstantScoreScorer(weight, score(), result.build().iterator());
+          }
+
+          @Override
+          public long cost() {
+            if (cost == -1) {
+              cost = values.estimatePointCount(visitor);
+            }
+            assert cost >= 0;
+            return cost;
+          }
+        };
 
-        return new ConstantScoreScorer(this, score(), result.build().iterator());
       }
     };
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
index ec7c682..c272b4d 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
@@ -35,6 +35,7 @@ import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.util.DocIdSetBuilder;
 import org.apache.lucene.util.NumericUtils;
 import org.apache.lucene.util.StringHelper;
+import org.apache.lucene.geo.GeoEncodingUtils;
 import org.apache.lucene.geo.Polygon;
 import org.apache.lucene.geo.Polygon2D;
 
@@ -92,6 +93,7 @@ final class LatLonPointInPolygonQuery extends Query {
     NumericUtils.intToSortableBytes(encodeLongitude(box.maxLon), maxLon, 0);
 
     final Polygon2D tree = Polygon2D.create(polygons);
+    final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createPolygonPredicate(polygons, tree);
 
     return new ConstantScoreWeight(this, boost) {
 
@@ -130,8 +132,8 @@ final class LatLonPointInPolygonQuery extends Query {
 
                            @Override
                            public void visit(int docID, byte[] packedValue) {
-                             if (tree.contains(decodeLatitude(packedValue, 0), 
-                                               decodeLongitude(packedValue, Integer.BYTES))) {
+                             if (polygonPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
+                                                       NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES))) {
                                adder.add(docID);
                              }
                            }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/sandbox/src/java/org/apache/lucene/search/TermAutomatonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/TermAutomatonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/TermAutomatonQuery.java
index fbf3dc3..04c8736 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/search/TermAutomatonQuery.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/search/TermAutomatonQuery.java
@@ -378,7 +378,7 @@ public class TermAutomatonQuery extends Query {
       boolean any = false;
       for(Map.Entry<Integer,TermContext> ent : termStates.entrySet()) {
         TermContext termContext = ent.getValue();
-        assert termContext.topReaderContext == ReaderUtil.getTopLevelContext(context) : "The top-reader used to create Weight (" + termContext.topReaderContext + ") is not the same as the current reader's top-reader (" + ReaderUtil.getTopLevelContext(context);
+        assert termContext.wasBuiltFor(ReaderUtil.getTopLevelContext(context)) : "The top-reader used to create Weight is not the same as the current reader's top-reader (" + ReaderUtil.getTopLevelContext(context);
         BytesRef term = idToTerm.get(ent.getKey());
         TermState state = termContext.get(context.ord);
         if (state != null) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
new file mode 100644
index 0000000..3c8bf4e
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.search;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.LatLonDocValuesField;
+import org.apache.lucene.geo.BaseGeoPointTestCase;
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.Polygon;
+
+public class TestLatLonDocValuesQueries extends BaseGeoPointTestCase {
+
+  @Override
+  protected boolean supportsPolygons() {
+    return false;
+  }
+
+  @Override
+  protected void addPointToDoc(String field, Document doc, double lat, double lon) {
+    doc.add(new LatLonDocValuesField(field, lat, lon));
+  }
+
+  @Override
+  protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
+    return LatLonDocValuesField.newBoxQuery(field, minLat, maxLat, minLon, maxLon);
+  }
+
+  @Override
+  protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
+    return LatLonDocValuesField.newDistanceQuery(field, centerLat, centerLon, radiusMeters);
+  }
+
+  @Override
+  protected Query newPolygonQuery(String field, Polygon... polygons) {
+    fail();
+    return null;
+  }
+
+  @Override
+  protected double quantizeLat(double latRaw) {
+    return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(latRaw));
+  }
+
+  @Override
+  protected double quantizeLon(double lonRaw) {
+    return GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lonRaw));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/site/changes/changes2html.pl
----------------------------------------------------------------------
diff --git a/lucene/site/changes/changes2html.pl b/lucene/site/changes/changes2html.pl
index 5b866fc..dcdcaa4 100755
--- a/lucene/site/changes/changes2html.pl
+++ b/lucene/site/changes/changes2html.pl
@@ -44,7 +44,11 @@ my @lines = <STDIN>;                        # Get all input at once
 #
 # Cmdline args:  <LUCENE|SOLR>  <project-DOAP-rdf-file>  <lucene-javadoc-url>(only from Solr)
 #
-my $product = $ARGV[0];
+my $product = uc($ARGV[0]);
+if ($product !~ /^(LUCENE|SOLR)$/) {
+  print STDERR "Unknown product name '$ARGV[0]'\n";
+  exit(1);
+}
 my %release_dates = &setup_release_dates($ARGV[1]);
 my $lucene_javadoc_url = ($product eq 'SOLR' ? $ARGV[2] : ''); # Only Solr supplies this on the cmdline
 my $in_major_component_versions_section = 0;
@@ -825,7 +829,6 @@ sub get_release_date {
 sub setup_release_dates {
   my %release_dates = ();
   my $file = shift;
-print STDERR "file: $file\n";
   open(FILE, "<$file") || die "could not open $file: $!";
   my $version_list = <FILE>;
   my $created_list = <FILE>;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointField.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointField.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointField.java
deleted file mode 100644
index 9bc2408..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointField.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.document;
-
-import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.analysis.TokenStream;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.FieldType;
-import org.apache.lucene.index.DocValuesType;
-import org.apache.lucene.index.IndexOptions;
-import org.apache.lucene.geo.GeoUtils;
-import org.apache.lucene.util.BitUtil;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.BytesRefBuilder;
-
-import static org.apache.lucene.spatial.util.MortonEncoder.encode;
-import static org.apache.lucene.geo.GeoUtils.MIN_LAT_INCL;
-import static org.apache.lucene.geo.GeoUtils.MIN_LON_INCL;
-
-/**
- * <p>
- * Field that indexes <code>latitude</code> <code>longitude</code> decimal-degree values
- * for efficient encoding, sorting, and querying. This Geo capability is intended
- * to provide a basic and efficient out of the box field type for indexing and
- * querying 2 dimensional points in WGS-84 decimal degrees. An example usage is as follows:
- *
- * <pre class="prettyprint">
- *  document.add(new GeoPointField(name, -96.33, 32.66, Field.Store.NO));
- * </pre>
- *
- * <p>To perform simple geospatial queries against a <code>GeoPointField</code>,
- * see {@link org.apache.lucene.spatial.geopoint.search.GeoPointInBBoxQuery}, {@link org.apache.lucene.spatial.geopoint.search.GeoPointInPolygonQuery},
- * or {@link org.apache.lucene.spatial.geopoint.search.GeoPointDistanceQuery}
- *
- * @lucene.experimental
- */
-public final class GeoPointField extends Field {
-  /** encoding step value for GeoPoint prefix terms */
-  public static final int PRECISION_STEP = 9;
-
-  /** number of bits used for quantizing latitude and longitude values */
-  public static final short BITS = 31;
-  /** scaling factors to convert lat/lon into unsigned space */
-  private static final double LAT_SCALE = (0x1L<<BITS)/180.0D;
-  private static final double LON_SCALE = (0x1L<<BITS)/360.0D;
-
-  /**
-   * The maximum term length (used for <code>byte[]</code> buffer size)
-   * for encoding <code>geoEncoded</code> values.
-   * @see #geoCodedToPrefixCodedBytes(long, int, BytesRefBuilder)
-   */
-  private static final int BUF_SIZE_LONG = 28/8 + 1;
-
-  /**
-   * Type for a GeoPointField that is not stored:
-   * normalization factors, frequencies, and positions are omitted.
-   */
-  public static final FieldType TYPE_NOT_STORED = new FieldType();
-  static {
-    TYPE_NOT_STORED.setTokenized(false);
-    TYPE_NOT_STORED.setOmitNorms(true);
-    TYPE_NOT_STORED.setIndexOptions(IndexOptions.DOCS);
-    TYPE_NOT_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
-    TYPE_NOT_STORED.freeze();
-  }
-
-  /**
-   * Type for a stored GeoPointField:
-   * normalization factors, frequencies, and positions are omitted.
-   */
-  public static final FieldType TYPE_STORED = new FieldType();
-  static {
-    TYPE_STORED.setTokenized(false);
-    TYPE_STORED.setOmitNorms(true);
-    TYPE_STORED.setIndexOptions(IndexOptions.DOCS);
-    TYPE_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
-    TYPE_STORED.setStored(true);
-    TYPE_STORED.freeze();
-  }
-
-  /** Creates a stored or un-stored GeoPointField
-   *  @param name field name
-   *  @param latitude latitude double value [-90.0 : 90.0]
-   *  @param longitude longitude double value [-180.0 : 180.0]
-   *  @param stored Store.YES if the content should also be stored
-   *  @throws IllegalArgumentException if the field name is null.
-   */
-  public GeoPointField(String name, double latitude, double longitude, Store stored) {
-    this(name, latitude, longitude, getFieldType(stored));
-  }
-
-  /** Expert: allows you to customize the {@link
-   *  FieldType}.
-   *  @param name field name
-   *  @param latitude latitude double value [-90.0 : 90.0]
-   *  @param longitude longitude double value [-180.0 : 180.0]
-   *  @param type customized field type
-   *  @throws IllegalArgumentException if the field name or type is null
-   */
-  public GeoPointField(String name, double latitude, double longitude, FieldType type) {
-    super(name, type);
-
-    GeoUtils.checkLatitude(latitude);
-    GeoUtils.checkLongitude(longitude);
-
-    // field must be indexed
-    // todo does it make sense here to provide the ability to store a GeoPointField but not index?
-    if (type.indexOptions() == IndexOptions.NONE && type.stored() == false) {
-      throw new IllegalArgumentException("type.indexOptions() is set to NONE but type.stored() is false");
-    } else if (type.indexOptions() == IndexOptions.DOCS) {
-      if (type.docValuesType() != DocValuesType.SORTED_NUMERIC) {
-        throw new IllegalArgumentException("type.docValuesType() must be SORTED_NUMERIC but got " + type.docValuesType());
-      }
-    } else {
-      throw new IllegalArgumentException("type.indexOptions() must be one of NONE or DOCS but got " + type.indexOptions());
-    }
-
-    // set field data
-    fieldsData = encodeLatLon(latitude, longitude);
-  }
-
-  /**
-   * Static helper method for returning a valid FieldType based on termEncoding and stored options
-   */
-  private static FieldType getFieldType(Store stored) {
-    if (stored == Store.YES) {
-      return TYPE_STORED;
-    } else if (stored == Store.NO) {
-      return TYPE_NOT_STORED;
-    } else {
-      throw new IllegalArgumentException("stored option must be NO or YES but got " + stored);
-    }
-  }
-
-  @Override
-  public TokenStream tokenStream(Analyzer analyzer, TokenStream reuse) {
-    if (fieldType().indexOptions() == IndexOptions.NONE) {
-      // not indexed
-      return null;
-    }
-
-    if (reuse instanceof GeoPointTokenStream == false) {
-      reuse = new GeoPointTokenStream();
-    }
-
-    final GeoPointTokenStream gpts = (GeoPointTokenStream)reuse;
-    gpts.setGeoCode(((Number) fieldsData).longValue());
-
-    return reuse;
-  }
-
-  /** access latitude value */
-  public double getLat() {
-    return decodeLatitude((long) fieldsData);
-  }
-
-  /** access longitude value */
-  public double getLon() {
-    return decodeLongitude((long) fieldsData);
-  }
-
-  @Override
-  public String toString() {
-    if (fieldsData == null) {
-      return null;
-    }
-    StringBuilder sb = new StringBuilder();
-    sb.append(decodeLatitude((long) fieldsData));
-    sb.append(',');
-    sb.append(decodeLongitude((long) fieldsData));
-    return sb.toString();
-  }
-
-  /*************************
-   * 31 bit encoding utils *
-   *************************/
-  public static long encodeLatLon(final double lat, final double lon) {
-    long result = encode(lat, lon);
-    if (result == 0xFFFFFFFFFFFFFFFFL) {
-      return result & 0xC000000000000000L;
-    }
-    return result >>> 2;
-  }
-
-  /** decode longitude value from morton encoded geo point */
-  public static final double decodeLongitude(final long hash) {
-    return unscaleLon(BitUtil.deinterleave(hash));
-  }
-
-  /** decode latitude value from morton encoded geo point */
-  public static final double decodeLatitude(final long hash) {
-    return unscaleLat(BitUtil.deinterleave(hash >>> 1));
-  }
-
-  private static final double unscaleLon(final long val) {
-    return (val / LON_SCALE) + MIN_LON_INCL;
-  }
-
-  private static final double unscaleLat(final long val) {
-    return (val / LAT_SCALE) + MIN_LAT_INCL;
-  }
-
-  /** Convert a geocoded morton long into a prefix coded geo term */
-  public static void geoCodedToPrefixCoded(long hash, int shift, BytesRefBuilder bytes) {
-    geoCodedToPrefixCodedBytes(hash, shift, bytes);
-  }
-
-  /** Convert a prefix coded geo term back into the geocoded morton long */
-  public static long prefixCodedToGeoCoded(final BytesRef val) {
-    final long result = 0L
-        | (val.bytes[val.offset+0] & 255L) << 24
-        | (val.bytes[val.offset+1] & 255L) << 16
-        | (val.bytes[val.offset+2] & 255L) << 8
-        | val.bytes[val.offset+3] & 255L;
-
-    return result << 32;
-  }
-
-  /**
-   * GeoTerms are coded using 4 prefix bytes + 1 byte to record number of prefix bits
-   *
-   * example prefix at shift 54 (yields 10 significant prefix bits):
-   *  pppppppp pp000000 00000000 00000000 00001010
-   *  (byte 1) (byte 2) (byte 3) (byte 4) (sigbits)
-   */
-  private static void geoCodedToPrefixCodedBytes(final long hash, final int shift, final BytesRefBuilder bytes) {
-    // ensure shift is 32..63
-    if (shift < 32 || shift > 63) {
-      throw new IllegalArgumentException("Illegal shift value, must be 32..63; got shift=" + shift);
-    }
-    int nChars = BUF_SIZE_LONG + 1; // one extra for the byte that contains the number of significant bits
-    bytes.setLength(nChars);
-    bytes.grow(nChars--);
-    final int sigBits = 64 - shift;
-    bytes.setByteAt(BUF_SIZE_LONG, (byte)(sigBits));
-    long sortableBits = hash;
-    sortableBits >>>= shift;
-    sortableBits <<= 32 - sigBits;
-    do {
-      bytes.setByteAt(--nChars, (byte)(sortableBits));
-      sortableBits >>>= 8;
-    } while (nChars > 0);
-  }
-
-  /** Get the prefix coded geo term shift value */
-  public static int getPrefixCodedShift(final BytesRef val) {
-    final int shift = val.bytes[val.offset + BUF_SIZE_LONG];
-    if (shift > 63 || shift < 0)
-      throw new NumberFormatException("Invalid shift value (" + shift + ") in prefixCoded bytes (is encoded value really a geo point?)");
-    return shift;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointTokenStream.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointTokenStream.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointTokenStream.java
deleted file mode 100644
index 87b228b..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/GeoPointTokenStream.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.lucene.spatial.geopoint.document;
-
-import java.util.Objects;
-
-import org.apache.lucene.analysis.TokenStream;
-import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
-import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
-import org.apache.lucene.analysis.tokenattributes.TermToBytesRefAttribute;
-import org.apache.lucene.util.Attribute;
-import org.apache.lucene.util.AttributeFactory;
-import org.apache.lucene.util.AttributeImpl;
-import org.apache.lucene.util.AttributeReflector;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.BytesRefBuilder;
-
-import static org.apache.lucene.spatial.geopoint.document.GeoPointField.geoCodedToPrefixCoded;
-import static org.apache.lucene.spatial.geopoint.document.GeoPointField.PRECISION_STEP;
-
-/**
- * <b>Expert:</b> This class provides a {@link TokenStream} used by {@link GeoPointField}
- * for encoding GeoPoint terms.
- *
- * This class encodes terms up to a maximum of {@link #MAX_SHIFT} using a fixed precision step defined by
- * {@link GeoPointField#PRECISION_STEP}. This yields a total of 4 terms per GeoPoint
- * each consisting of 5 bytes (4 prefix bytes + 1 precision byte).
- *
- * Here's an example usage:
- *
- * <pre class="prettyprint">
- *   // using prefix terms
- *   GeoPointField geoPointField = new GeoPointField(fieldName1, lat, lon, GeoPointField.TYPE_NOT_STORED);
- *   document.add(geoPointField);
- *
- *   // query by bounding box
- *   Query q = new GeoPointInBBoxQuery(fieldName1, minLat, maxLat, minLon, maxLon);
- *
- *   // query by distance
- *   q = new GeoPointDistanceQuery(fieldName2, centerLat, centerLon, radiusMeters);
- * </pre>
- *
- * @lucene.experimental
- */
-final class GeoPointTokenStream extends TokenStream {
-  private static final int MAX_SHIFT = PRECISION_STEP * 4;
-
-  private final GeoPointTermAttribute geoPointTermAtt = addAttribute(GeoPointTermAttribute.class);
-  private final PositionIncrementAttribute posIncrAtt = addAttribute(PositionIncrementAttribute.class);
-
-  private boolean isInit = false;
-
-  /**
-   * Expert: Creates a token stream for geo point fields with the specified
-   * <code>precisionStep</code> using the given
-   * {@link org.apache.lucene.util.AttributeFactory}.
-   * The stream is not yet initialized,
-   * before using set a value using the various setGeoCode method.
-   */
-  public GeoPointTokenStream() {
-    super(new GeoPointAttributeFactory(AttributeFactory.DEFAULT_ATTRIBUTE_FACTORY));
-    assert PRECISION_STEP > 0;
-  }
-
-  public GeoPointTokenStream setGeoCode(final long geoCode) {
-    geoPointTermAtt.init(geoCode, MAX_SHIFT-PRECISION_STEP);
-    isInit = true;
-    return this;
-  }
-
-  @Override
-  public void reset() {
-    if (isInit == false) {
-      throw new IllegalStateException("call setGeoCode() before usage");
-    }
-  }
-
-  @Override
-  public boolean incrementToken() {
-    if (isInit == false) {
-      throw new IllegalStateException("call setGeoCode() before usage");
-    }
-
-    // this will only clear all other attributes in this TokenStream
-    clearAttributes();
-
-    final int shift = geoPointTermAtt.incShift();
-    posIncrAtt.setPositionIncrement((shift == MAX_SHIFT) ? 1 : 0);
-    return (shift < 63);
-  }
-
-  /**
-   * Tracks shift values during encoding
-   */
-  public interface GeoPointTermAttribute extends Attribute {
-    /** Returns current shift value, undefined before first token */
-    int getShift();
-
-    /** <em>Don't call this method!</em>
-     * @lucene.internal */
-    void init(long value, int shift);
-
-    /** <em>Don't call this method!</em>
-     * @lucene.internal */
-    int incShift();
-  }
-
-  // just a wrapper to prevent adding CTA
-  private static final class GeoPointAttributeFactory extends AttributeFactory {
-    private final AttributeFactory delegate;
-
-    GeoPointAttributeFactory(AttributeFactory delegate) {
-      this.delegate = delegate;
-    }
-
-    @Override
-    public AttributeImpl createAttributeInstance(Class<? extends Attribute> attClass) {
-      if (CharTermAttribute.class.isAssignableFrom(attClass)) {
-        throw new IllegalArgumentException("GeoPointTokenStream does not support CharTermAttribute.");
-      }
-      return delegate.createAttributeInstance(attClass);
-    }
-  }
-
-  public static final class GeoPointTermAttributeImpl extends AttributeImpl implements GeoPointTermAttribute,TermToBytesRefAttribute {
-    private long value = 0L;
-    private int shift = 0;
-    private BytesRefBuilder bytes = new BytesRefBuilder();
-
-    public GeoPointTermAttributeImpl() {
-      this.shift = MAX_SHIFT-PRECISION_STEP;
-    }
-
-    @Override
-    public BytesRef getBytesRef() {
-      geoCodedToPrefixCoded(value, shift, bytes);
-      return bytes.get();
-    }
-
-    @Override
-    public void init(long value, int shift) {
-      this.value = value;
-      this.shift = shift;
-    }
-
-    @Override
-    public int getShift() { return shift; }
-
-    @Override
-    public int incShift() {
-      return (shift += PRECISION_STEP);
-    }
-
-    @Override
-    public void clear() {
-      // this attribute has no contents to clear!
-      // we keep it untouched as it's fully controlled by outer class.
-    }
-
-    @Override
-    public void reflectWith(AttributeReflector reflector) {
-      reflector.reflect(TermToBytesRefAttribute.class, "bytes", getBytesRef());
-      reflector.reflect(GeoPointTermAttribute.class, "shift", shift);
-    }
-
-    @Override
-    public void copyTo(AttributeImpl target) {
-      final GeoPointTermAttribute a = (GeoPointTermAttribute) target;
-      a.init(value, shift);
-    }
-
-    @Override
-    public GeoPointTermAttributeImpl clone() {
-      GeoPointTermAttributeImpl t = (GeoPointTermAttributeImpl)super.clone();
-      // Do a deep clone
-      t.bytes = new BytesRefBuilder();
-      t.bytes.copyBytes(getBytesRef());
-      return t;
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(shift, value);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (this == obj) return true;
-      if (obj == null) return false;
-      if (getClass() != obj.getClass()) return false;
-      GeoPointTermAttributeImpl other = (GeoPointTermAttributeImpl) obj;
-      if (shift != other.shift) return false;
-      if (value != other.value) return false;
-      return true;
-    }
-  }
-
-  /** override toString because it can throw cryptic "illegal shift value": */
-  @Override
-  public String toString() {
-    return getClass().getSimpleName() + "(precisionStep=" + PRECISION_STEP + " shift=" + geoPointTermAtt.getShift() + ")";
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/package-info.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/package-info.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/package-info.java
deleted file mode 100644
index 2d23448..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/document/package-info.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * Geospatial Field Implementations for Core Lucene
- */
-package org.apache.lucene.spatial.geopoint.document;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQuery.java
deleted file mode 100644
index 743c116..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQuery.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.search;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.geo.Rectangle;
-import org.apache.lucene.geo.GeoUtils;
-
-/** Implements a simple point distance query on a GeoPoint field. This is based on
- * {@link GeoPointInBBoxQuery} and is implemented using a two phase approach. First,
- * like {@code GeoPointInBBoxQueryImpl} candidate terms are queried using the numeric ranges based on
- * the morton codes of the min and max lat/lon pairs that intersect the boundary of the point-radius
- * circle. Terms
- * passing this initial filter are then passed to a secondary {@code postFilter} method that verifies whether the
- * decoded lat/lon point fall within the specified query distance (see {@link org.apache.lucene.util.SloppyMath#haversinMeters(double, double, double, double)}.
- * Distance comparisons are subject to the accuracy of the haversine formula
- * (from R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159)
- *
- * <p>Note: This query currently uses haversine which is a sloppy distance calculation (see above reference). For large
- * queries one can expect upwards of 400m error. Vincenty shrinks this to ~40m error but pays a penalty for computing
- * using the spheroid
- *
- * @lucene.experimental */
-public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
-  /** latitude value (in degrees) for query location */
-  protected final double centerLat;
-  /** longitude value (in degrees) for query location */
-  protected final double centerLon;
-  /** distance (in meters) from lat, lon center location */
-  protected final double radiusMeters;
-  /** partial haversin computation */
-  protected final double sortKey;
-
-  // we must check these before passing to superclass or circleToBBox, or users can get a strange exception!
-  private static double checkRadius(double radiusMeters) {
-    if (Double.isFinite(radiusMeters) == false || radiusMeters < 0) {
-      throw new IllegalArgumentException("invalid radiusMeters " + radiusMeters);
-    }
-    return radiusMeters;
-  }
-
-  /**
-   * Constructs a Query for all {@link org.apache.lucene.spatial.geopoint.document.GeoPointField} types within a
-   * distance (in meters) from a given point
-   **/
-  public GeoPointDistanceQuery(final String field, final double centerLat, final double centerLon, final double radiusMeters) {
-    this(field, Rectangle.fromPointDistance(centerLat, centerLon, checkRadius(radiusMeters)), centerLat, centerLon, radiusMeters);
-  }
-
-  private GeoPointDistanceQuery(final String field, final Rectangle bbox,
-                                 final double centerLat, final double centerLon, final double radiusMeters) {
-    super(field, bbox.minLat, bbox.maxLat, bbox.minLon, bbox.maxLon);
-
-    this.centerLat = centerLat;
-    this.centerLon = centerLon;
-    this.radiusMeters = radiusMeters;
-    this.sortKey = GeoUtils.distanceQuerySortKey(radiusMeters);
-  }
-
-  @Override
-  public Query rewrite(IndexReader reader) {
-    // query crosses dateline; split into left and right queries
-    if (maxLon < minLon) {
-      BooleanQuery.Builder bqb = new BooleanQuery.Builder();
-
-      // unwrap the longitude iff outside the specified min/max lon range
-      double unwrappedLon = centerLon;
-      if (unwrappedLon > maxLon) {
-        // unwrap left
-        unwrappedLon += -360.0D;
-      }
-      GeoPointDistanceQueryImpl left = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
-                                                                     new Rectangle(minLat, maxLat, GeoUtils.MIN_LON_INCL, maxLon));
-      bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
-
-      if (unwrappedLon < maxLon) {
-        // unwrap right
-        unwrappedLon += 360.0D;
-      }
-      GeoPointDistanceQueryImpl right = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
-                                                                      new Rectangle(minLat, maxLat, minLon, GeoUtils.MAX_LON_INCL));
-      bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
-
-      return bqb.build();
-    }
-    return new GeoPointDistanceQueryImpl(field, this, centerLon,
-                                         new Rectangle(this.minLat, this.maxLat, this.minLon, this.maxLon));
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (!(o instanceof GeoPointDistanceQuery)) return false;
-    if (!super.equals(o)) return false;
-
-    GeoPointDistanceQuery that = (GeoPointDistanceQuery) o;
-
-    if (Double.compare(that.centerLat, centerLat) != 0) return false;
-    if (Double.compare(that.centerLon, centerLon) != 0) return false;
-    if (Double.compare(that.radiusMeters, radiusMeters) != 0) return false;
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = super.hashCode();
-    long temp;
-    temp = Double.doubleToLongBits(centerLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(centerLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(radiusMeters);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-
-  @Override
-  public String toString(String field) {
-    final StringBuilder sb = new StringBuilder();
-    sb.append(getClass().getSimpleName());
-    sb.append(':');
-    if (!this.field.equals(field)) {
-      sb.append(" field=");
-      sb.append(this.field);
-      sb.append(':');
-    }
-    return sb.append( " Center: [")
-        .append(centerLat)
-        .append(',')
-        .append(centerLon)
-        .append(']')
-        .append(" Distance: ")
-        .append(radiusMeters)
-        .append(" meters")
-        .append("]")
-        .toString();
-  }
-
-  /** getter method for center longitude value */
-  public double getCenterLon() {
-    return this.centerLon;
-  }
-
-  /** getter method for center latitude value */
-  public double getCenterLat() {
-    return this.centerLat;
-  }
-
-  /** getter method for distance value (in meters) */
-  public double getRadiusMeters() {
-    return this.radiusMeters;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java
deleted file mode 100644
index ea85240..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.search;
-
-import org.apache.lucene.geo.Rectangle;
-import org.apache.lucene.index.PointValues;
-import org.apache.lucene.search.MultiTermQuery;
-import org.apache.lucene.util.SloppyMath;
-
-/** Package private implementation for the public facing GeoPointDistanceQuery delegate class.
- *
- *    @lucene.experimental
- */
-final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
-  private final GeoPointDistanceQuery distanceQuery;
-  private final double centerLon;
-  
-  // optimization, used for detecting axis cross
-  final double axisLat;
-  
-  GeoPointDistanceQueryImpl(final String field, final GeoPointDistanceQuery q,
-                            final double centerLonUnwrapped, final Rectangle bbox) {
-    super(field, bbox.minLat, bbox.maxLat, bbox.minLon, bbox.maxLon);
-    distanceQuery = q;
-    centerLon = centerLonUnwrapped;
-    axisLat = Rectangle.axisLat(distanceQuery.centerLat, distanceQuery.radiusMeters);
-  }
-
-  @Override
-  public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
-    throw new UnsupportedOperationException("cannot change rewrite method");
-  }
-
-  @Override
-  protected CellComparator newCellComparator() {
-    return new GeoPointRadiusCellComparator(this);
-  }
-
-  private final class GeoPointRadiusCellComparator extends CellComparator {
-    GeoPointRadiusCellComparator(GeoPointDistanceQueryImpl query) {
-      super(query);
-    }
-
-    @Override
-    protected PointValues.Relation relate(final double minLat, final double maxLat, final double minLon, final double maxLon) {
-      // bounding box check
-      if (cellIntersectsMBR(minLat, maxLat, minLon, maxLon) == false) {
-        return PointValues.Relation.CELL_OUTSIDE_QUERY;
-      }
-      if ((centerLon < minLon || centerLon > maxLon) && (axisLat + Rectangle.AXISLAT_ERROR < minLat
-          || axisLat- Rectangle.AXISLAT_ERROR > maxLat)) {
-        if (SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, minLat, minLon) > distanceQuery.sortKey &&
-            SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, minLat, maxLon) > distanceQuery.sortKey &&
-            SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, maxLat, minLon) > distanceQuery.sortKey &&
-            SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, maxLat, maxLon) > distanceQuery.sortKey) {
-          return PointValues.Relation.CELL_OUTSIDE_QUERY;
-        }
-      }
-
-      if (maxLon - centerLon < 90 && centerLon - minLon < 90 &&
-          SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, minLat, minLon) <= distanceQuery.sortKey &&
-          SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, minLat, maxLon) <= distanceQuery.sortKey &&
-          SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, maxLat, minLon) <= distanceQuery.sortKey &&
-          SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, maxLat, maxLon) <= distanceQuery.sortKey) {
-        // we are fully enclosed, collect everything within this subtree
-        return PointValues.Relation.CELL_INSIDE_QUERY;
-      }
-
-      return PointValues.Relation.CELL_CROSSES_QUERY;
-    }
-
-    /**
-     * The two-phase query approach. The parent {@link GeoPointTermsEnum} class matches
-     * encoded terms that fall within the minimum bounding box of the point-radius circle. Those documents that pass
-     * the initial bounding box filter are then post filter compared to the provided distance using the
-     * {@link org.apache.lucene.util.SloppyMath#haversinMeters(double, double, double, double)} method.
-     */
-    @Override
-    protected boolean postFilter(final double lat, final double lon) {
-      // check bbox
-      if (lat < minLat || lat > maxLat || lon < minLon || lon > maxLon) {
-        return false;
-      }
-
-      // first check the partial distance, if its more than that, it can't be <= radiusMeters
-      double h1 = SloppyMath.haversinSortKey(distanceQuery.centerLat, centerLon, lat, lon);
-      if (h1 <= distanceQuery.sortKey) {
-        return true;
-      }
-
-      return false;
-    }
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (!(o instanceof GeoPointDistanceQueryImpl)) return false;
-    if (!super.equals(o)) return false;
-
-    GeoPointDistanceQueryImpl that = (GeoPointDistanceQueryImpl) o;
-
-    if (!distanceQuery.equals(that.distanceQuery)) return false;
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = super.hashCode();
-    result = 31 * result + distanceQuery.hashCode();
-    return result;
-  }
-
-  public double getRadiusMeters() {
-    return distanceQuery.getRadiusMeters();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQuery.java
deleted file mode 100644
index 5bc5861..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQuery.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.search;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.FieldValueQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.geo.GeoUtils;
-
-/** Implements a simple bounding box query on a GeoPoint field. This is implemented using a
- * two phase approach. First, candidate terms are queried using a numeric
- * range based on the morton codes of the min and max lat/lon pairs. Terms
- * passing this initial filter are passed to a final check that verifies whether
- * the decoded lat/lon falls within (or on the boundary) of the query bounding box.
- *
- * NOTES:
- *    1.  All latitude/longitude values must be in decimal degrees.
- *    2.  Complex computational geometry (e.g., dateline wrapping) is not supported
- *    3.  For more advanced GeoSpatial indexing and query operations see spatial module
- *    4.  This is well suited for small rectangles, large bounding boxes may result
- *        in many terms, depending whether the bounding box falls on the boundary of
- *        many cells (degenerate case)
- *
- * @lucene.experimental
- */
-public class GeoPointInBBoxQuery extends Query {
-  /** field name */
-  protected final String field;
-  /** minimum latitude value (in degrees) */
-  protected final double minLat;
-  /** minimum longitude value (in degrees) */
-  protected final double minLon;
-  /** maximum latitude value (in degrees) */
-  protected final double maxLat;
-  /** maximum longitude value (in degrees) */
-  protected final double maxLon;
-
-  /**
-   * Constructs a query for all {@link org.apache.lucene.spatial.geopoint.document.GeoPointField} types that fall within a
-   * defined bounding box.
-   */
-  public GeoPointInBBoxQuery(final String field, final double minLat, final double maxLat, final double minLon, final double maxLon) {
-    if (field == null) {
-      throw new IllegalArgumentException("field must not be null");
-    }
-    GeoUtils.checkLatitude(minLat);
-    GeoUtils.checkLatitude(maxLat);
-    GeoUtils.checkLongitude(minLon);
-    GeoUtils.checkLongitude(maxLon);
-    this.field = field;
-    this.minLat = minLat;
-    this.maxLat = maxLat;
-    this.minLon = minLon;
-    this.maxLon = maxLon;
-  }
-
-  @Override
-  public Query rewrite(IndexReader reader) {
-    // short-circuit to match all if specifying the whole map
-    if (minLat == GeoUtils.MIN_LAT_INCL && maxLat == GeoUtils.MAX_LAT_INCL &&
-        minLon == GeoUtils.MIN_LON_INCL && maxLon == GeoUtils.MAX_LON_INCL) {
-      // FieldValueQuery is valid since DocValues are *required* for GeoPointField
-      return new FieldValueQuery(field);
-    }
-
-    if (maxLon < minLon) {
-      BooleanQuery.Builder bqb = new BooleanQuery.Builder();
-
-      GeoPointInBBoxQueryImpl left = new GeoPointInBBoxQueryImpl(field, minLat, maxLat, -180.0D, maxLon);
-      bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
-      GeoPointInBBoxQueryImpl right = new GeoPointInBBoxQueryImpl(field, minLat, maxLat, minLon, 180.0D);
-      bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
-      return bqb.build();
-    }
-    return new GeoPointInBBoxQueryImpl(field, minLat, maxLat, minLon, maxLon);
-  }
-
-  @Override
-  public String toString(String field) {
-    final StringBuilder sb = new StringBuilder();
-    sb.append(getClass().getSimpleName());
-    sb.append(':');
-    if (!this.field.equals(field)) {
-      sb.append(" field=");
-      sb.append(this.field);
-      sb.append(':');
-    }
-    return sb.append(" Lower Left: [")
-        .append(minLat)
-        .append(',')
-        .append(minLon)
-        .append(']')
-        .append(" Upper Right: [")
-        .append(maxLat)
-        .append(',')
-        .append(maxLon)
-        .append("]")
-        .toString();
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    return sameClassAs(other) &&
-           equalsTo(getClass().cast(other));
-  }
-
-  private boolean equalsTo(GeoPointInBBoxQuery other) {
-    return Double.compare(other.minLat, minLat) == 0 &&
-           Double.compare(other.maxLat, maxLat) == 0 &&
-           Double.compare(other.minLon, minLon) == 0 &&
-           Double.compare(other.maxLon, maxLon) == 0 &&
-           field.equals(other.field);
-  }
-
-  @Override
-  public int hashCode() {
-    int result = classHash();
-    long temp;
-    result = 31 * result + field.hashCode();
-    temp = Double.doubleToLongBits(minLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(minLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-
-  /** getter method for retrieving the field name */
-  public final String getField() {
-    return this.field;
-  }
-
-  /** getter method for retrieving the minimum latitude (in degrees) */
-  public final double getMinLat() {
-    return this.minLat;
-  }
-
-  /** getter method for retrieving the maximum latitude (in degrees) */
-  public final double getMaxLat() {
-    return this.maxLat;
-  }
-
-  /** getter method for retrieving the minimum longitude (in degrees) */
-  public final double getMinLon() {
-    return this.minLon;
-  }
-
-  /** getter method for retrieving the maximum longitude (in degrees) */
-  public final double getMaxLon() {
-    return this.maxLon;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQueryImpl.java
deleted file mode 100644
index ee1f8da..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInBBoxQueryImpl.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.search;
-
-import org.apache.lucene.index.PointValues.Relation;
-import org.apache.lucene.search.MultiTermQuery;
-import org.apache.lucene.util.SloppyMath;
-import org.apache.lucene.spatial.geopoint.document.GeoPointField;
-import org.apache.lucene.spatial.util.GeoRelationUtils;
-
-/** Package private implementation for the public facing GeoPointInBBoxQuery delegate class.
- *
- *    @lucene.experimental
- */
-class GeoPointInBBoxQueryImpl extends GeoPointMultiTermQuery {
-  /**
-   * Constructs a new GeoBBoxQuery that will match encoded GeoPoint terms that fall within or on the boundary
-   * of the bounding box defined by the input parameters
-   * @param field the field name
-   * @param minLon lower longitude (x) value of the bounding box
-   * @param minLat lower latitude (y) value of the bounding box
-   * @param maxLon upper longitude (x) value of the bounding box
-   * @param maxLat upper latitude (y) value of the bounding box
-   */
-  GeoPointInBBoxQueryImpl(final String field, final double minLat, final double maxLat, final double minLon, final double maxLon) {
-    super(field, minLat, maxLat, minLon, maxLon);
-  }
-
-  @Override
-  public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
-    throw new UnsupportedOperationException("cannot change rewrite method");
-  }
-
-  @Override
-  protected short computeMaxShift() {
-    final short shiftFactor;
-
-    // compute diagonal radius
-    double midLat = (minLat + maxLat) * 0.5;
-    double midLon = (minLon + maxLon) * 0.5;
-
-    if (SloppyMath.haversinMeters(minLat, minLon, midLat, midLon) > 1000000) {
-      shiftFactor = 5;
-    } else {
-      shiftFactor = 4;
-    }
-
-    return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
-  }
-
-  @Override
-  protected CellComparator newCellComparator() {
-    return new GeoPointInBBoxCellComparator(this);
-  }
-
-  private final class GeoPointInBBoxCellComparator extends CellComparator {
-    GeoPointInBBoxCellComparator(GeoPointMultiTermQuery query) {
-      super(query);
-    }
-
-    @Override
-    protected Relation relate(final double minLat, final double maxLat, final double minLon, final double maxLon) {
-      if (GeoRelationUtils.rectCrosses(minLat, maxLat, minLon, maxLon, GeoPointInBBoxQueryImpl.this.minLat,
-          GeoPointInBBoxQueryImpl.this.maxLat, GeoPointInBBoxQueryImpl.this.minLon, GeoPointInBBoxQueryImpl.this.maxLon)) {
-        return Relation.CELL_CROSSES_QUERY;
-      } else if (GeoRelationUtils.rectWithin(minLat, maxLat, minLon, maxLon, GeoPointInBBoxQueryImpl.this.minLat,
-          GeoPointInBBoxQueryImpl.this.maxLat,
-          GeoPointInBBoxQueryImpl.this.minLon, GeoPointInBBoxQueryImpl.this.maxLon)) {
-        return Relation.CELL_INSIDE_QUERY;
-      }
-      return Relation.CELL_OUTSIDE_QUERY;
-    }
-
-    @Override
-    protected boolean postFilter(final double lat, final double lon) {
-      return GeoRelationUtils.pointInRectPrecise(lat, lon, minLat, maxLat, minLon, maxLon);
-    }
-  }
-
-  @Override
-  @SuppressWarnings({"unchecked","rawtypes"})
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    if (!super.equals(o)) return false;
-
-    GeoPointInBBoxQueryImpl that = (GeoPointInBBoxQueryImpl) o;
-
-    if (Double.compare(that.minLat, minLat) != 0) return false;
-    if (Double.compare(that.maxLat, maxLat) != 0) return false;
-    if (Double.compare(that.minLon, minLon) != 0) return false;
-    if (Double.compare(that.maxLon, maxLon) != 0) return false;
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = super.hashCode();
-    long temp;
-    temp = Double.doubleToLongBits(minLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(minLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-
-  @Override
-  public String toString(String field) {
-    final StringBuilder sb = new StringBuilder();
-    sb.append(getClass().getSimpleName());
-    sb.append(':');
-    if (!getField().equals(field)) {
-      sb.append(" field=");
-      sb.append(getField());
-      sb.append(':');
-    }
-    return sb.append(" Lower Left: [")
-        .append(minLat)
-        .append(',')
-        .append(minLon)
-        .append(']')
-        .append(" Upper Right: [")
-        .append(maxLat)
-        .append(',')
-        .append(maxLon)
-        .append("]")
-        .toString();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQuery.java
deleted file mode 100644
index ca61f02..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQuery.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.search;
-
-import java.util.Arrays;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.geo.Rectangle;
-import org.apache.lucene.geo.Polygon;
-
-/** Implements a simple point in polygon query on a GeoPoint field. This is based on
- * {@code GeoPointInBBoxQueryImpl} and is implemented using a
- * three phase approach. First, like {@code GeoPointInBBoxQueryImpl}
- * candidate terms are queried using a numeric range based on the morton codes
- * of the min and max lat/lon pairs. Terms passing this initial filter are passed
- * to a secondary filter that verifies whether the decoded lat/lon point falls within
- * (or on the boundary) of the bounding box query. Finally, the remaining candidate
- * term is passed to the final point in polygon check.
- *
- * @see Polygon
- * @lucene.experimental
- */
-public final class GeoPointInPolygonQuery extends GeoPointInBBoxQuery {
-  /** array of polygons being queried */
-  final Polygon[] polygons;
-
-  /** 
-   * Constructs a new GeoPolygonQuery that will match encoded {@link org.apache.lucene.spatial.geopoint.document.GeoPointField} terms
-   * that fall within or on the boundary of the polygons defined by the input parameters. 
-   */
-  public GeoPointInPolygonQuery(String field, Polygon... polygons) {
-    this(field, Rectangle.fromPolygon(polygons), polygons);
-  }
-  
-  // internal constructor
-  private GeoPointInPolygonQuery(String field, Rectangle boundingBox, Polygon... polygons) {
-    super(field, boundingBox.minLat, boundingBox.maxLat, boundingBox.minLon, boundingBox.maxLon);
-    this.polygons = polygons.clone();
-  }
-
-  /** throw exception if trying to change rewrite method */
-  @Override
-  public Query rewrite(IndexReader reader) {
-    return new GeoPointInPolygonQueryImpl(field, this, this.minLat, this.maxLat, this.minLon, this.maxLon);
-  }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = super.hashCode();
-    result = prime * result + Arrays.hashCode(polygons);
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj) return true;
-    if (!super.equals(obj)) return false;
-    if (getClass() != obj.getClass()) return false;
-    GeoPointInPolygonQuery other = (GeoPointInPolygonQuery) obj;
-    if (!Arrays.equals(polygons, other.polygons)) return false;
-    return true;
-  }
-
-  /** print out this polygon query */
-  @Override
-  public String toString(String field) {
-    final StringBuilder sb = new StringBuilder();
-    sb.append(getClass().getSimpleName());
-    sb.append(':');
-    if (!getField().equals(field)) {
-      sb.append(" field=");
-      sb.append(getField());
-      sb.append(':');
-    }
-    sb.append(" Polygon: ");
-    sb.append(Arrays.toString(polygons));
-
-    return sb.toString();
-  }
-
-  /**
-   * API utility method for returning copy of the polygon array
-   */
-  public Polygon[] getPolygons() {
-    return polygons.clone();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQueryImpl.java
deleted file mode 100644
index 425b40e..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointInPolygonQueryImpl.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.search;
-
-import java.util.Objects;
-
-import org.apache.lucene.search.MultiTermQuery;
-import org.apache.lucene.geo.Polygon2D;
-import org.apache.lucene.index.PointValues.Relation;
-
-/** Package private implementation for the public facing GeoPointInPolygonQuery delegate class.
- *
- *    @lucene.experimental
- */
-final class GeoPointInPolygonQueryImpl extends GeoPointInBBoxQueryImpl {
-  private final GeoPointInPolygonQuery polygonQuery;
-  private final Polygon2D polygons;
-
-  GeoPointInPolygonQueryImpl(final String field, final GeoPointInPolygonQuery q,
-                             final double minLat, final double maxLat, final double minLon, final double maxLon) {
-    super(field, minLat, maxLat, minLon, maxLon);
-    this.polygonQuery = Objects.requireNonNull(q);
-    this.polygons = Polygon2D.create(q.polygons);
-  }
-
-  @Override
-  public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
-    throw new UnsupportedOperationException("cannot change rewrite method");
-  }
-
-  @Override
-  protected CellComparator newCellComparator() {
-    return new GeoPolygonCellComparator(this);
-  }
-
-  /**
-   * Custom {@code org.apache.lucene.spatial.geopoint.search.GeoPointMultiTermQuery.CellComparator} that computes morton hash
-   * ranges based on the defined edges of the provided polygon.
-   */
-  private final class GeoPolygonCellComparator extends CellComparator {
-    GeoPolygonCellComparator(GeoPointMultiTermQuery query) {
-      super(query);
-    }
-
-    @Override
-    protected Relation relate(final double minLat, final double maxLat, final double minLon, final double maxLon) {
-      return polygons.relate(minLat, maxLat, minLon, maxLon);
-    }
-
-    /**
-     * The two-phase query approach. The parent
-     * {@link org.apache.lucene.spatial.geopoint.search.GeoPointTermsEnum#accept} method is called to match
-     * encoded terms that fall within the bounding box of the polygon. Those documents that pass the initial
-     * bounding box filter are then compared to the provided polygon using the
-     * {@link Polygon2D#contains(double, double)} method.
-     */
-    @Override
-    protected boolean postFilter(final double lat, final double lon) {
-      return polygons.contains(lat, lon);
-    }
-  }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = super.hashCode();
-    result = prime * result + polygonQuery.hashCode();
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj) return true;
-    if (!super.equals(obj)) return false;
-    if (getClass() != obj.getClass()) return false;
-    GeoPointInPolygonQueryImpl other = (GeoPointInPolygonQueryImpl) obj;
-    if (!polygonQuery.equals(other.polygonQuery)) return false;
-    return true;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java
deleted file mode 100644
index 5117206..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.geopoint.search;
-
-import java.io.IOException;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.PointValues.Relation;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.search.MultiTermQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.util.AttributeSource;
-import org.apache.lucene.spatial.geopoint.document.GeoPointField;
-import org.apache.lucene.spatial.util.GeoRelationUtils;
-import org.apache.lucene.util.BitUtil;
-import org.apache.lucene.util.SloppyMath;
-
-/**
- * TermQuery for GeoPointField for overriding {@link org.apache.lucene.search.MultiTermQuery} methods specific to
- * Geospatial operations
- *
- * @lucene.experimental
- */
-abstract class GeoPointMultiTermQuery extends MultiTermQuery {
-  // simple bounding box optimization - no objects used to avoid dependencies
-  protected final double minLon;
-  protected final long minEncoded;
-  protected final int minX;
-  protected final double minLat;
-  protected final int minY;
-  protected final double maxLon;
-  protected final int maxX;
-  protected final double maxLat;
-  protected final int maxY;
-
-  protected final short maxShift;
-  protected final CellComparator cellComparator;
-
-  /**
-   * Constructs a query matching terms that cannot be represented with a single
-   * Term.
-   */
-  public GeoPointMultiTermQuery(String field, final double minLat, final double maxLat, final double minLon, final double maxLon) {
-    super(field);
-
-    this.minEncoded = GeoPointField.encodeLatLon(minLat, minLon);
-    final long maxEncoded = GeoPointField.encodeLatLon(maxLat, maxLon);
-
-    this.minX = (int)BitUtil.deinterleave(minEncoded);
-    this.maxX = (int)BitUtil.deinterleave(maxEncoded);
-    this.minY = (int)BitUtil.deinterleave(minEncoded >>> 1);
-    this.maxY = (int)BitUtil.deinterleave(maxEncoded >>> 1);
-
-    this.minLat = minLat;
-    this.maxLat = maxLat;
-    this.minLon = minLon;
-    this.maxLon = maxLon;
-
-    this.maxShift = computeMaxShift();
-    this.cellComparator = newCellComparator();
-
-    this.rewriteMethod = GEO_CONSTANT_SCORE_REWRITE;
-  }
-
-  public static final RewriteMethod GEO_CONSTANT_SCORE_REWRITE = new RewriteMethod() {
-    @Override
-    public Query rewrite(IndexReader reader, MultiTermQuery query) {
-      return new GeoPointTermQueryConstantScoreWrapper<>((GeoPointMultiTermQuery)query);
-    }
-  };
-
-  @Override @SuppressWarnings("unchecked")
-  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
-    return new GeoPointTermsEnum(terms.iterator(), this);
-  }
-
-  /**
-   * Computes the maximum shift based on the diagonal distance of the bounding box
-   */
-  protected short computeMaxShift() {
-    // in this case a factor of 4 brings the detail level to ~0.001/0.002 degrees lat/lon respectively (or ~111m/222m)
-    final short shiftFactor;
-
-    // compute diagonal distance
-    double midLon = (minLon + maxLon) * 0.5;
-    double midLat = (minLat + maxLat) * 0.5;
-
-    if (SloppyMath.haversinMeters(minLat, minLon, midLat, midLon) > 1000000) {
-      shiftFactor = 5;
-    } else {
-      shiftFactor = 4;
-    }
-
-    return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
-  }
-
-  /**
-   * Abstract method to construct the class that handles all geo point relations
-   * (e.g., GeoPointInPolygon)
-   */
-  abstract protected CellComparator newCellComparator();
-
-  /**
-   * Base class for all geo point relation comparators
-   */
-  static abstract class CellComparator {
-    protected final GeoPointMultiTermQuery geoPointQuery;
-
-    CellComparator(GeoPointMultiTermQuery query) {
-      this.geoPointQuery = query;
-    }
-
-    /**
-     * Primary driver for cells intersecting shape boundaries
-     */
-    protected boolean cellIntersectsMBR(final double minLat, final double maxLat, final double minLon, final double maxLon) {
-      return GeoRelationUtils.rectIntersects(minLat, maxLat, minLon, maxLon, geoPointQuery.minLat, geoPointQuery.maxLat,
-                                             geoPointQuery.minLon, geoPointQuery.maxLon);
-    }
-
-    /** uses encoded values to check whether quad cell intersects the shape bounding box */
-    protected boolean cellIntersectsMBR(final long min, final long max) {
-      return !(Integer.compareUnsigned((int)BitUtil.deinterleave(max), geoPointQuery.minX) < 0
-          || Integer.compareUnsigned((int)BitUtil.deinterleave(min), geoPointQuery.maxX) > 0
-          || Integer.compareUnsigned((int)BitUtil.deinterleave(max >>> 1), geoPointQuery.minY) < 0
-          || Integer.compareUnsigned((int)BitUtil.deinterleave(min >>> 1), geoPointQuery.maxY) > 0);
-    }
-
-    abstract protected Relation relate(final double minLat, final double maxLat, final double minLon, final double maxLon);
-
-    abstract protected boolean postFilter(final double lat, final double lon);
-  }
-}


[18/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
Updating branch by merging latest changes from master


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/4fc5a9f0
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/4fc5a9f0
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/4fc5a9f0

Branch: refs/heads/jira/solr-5944
Commit: 4fc5a9f05a0bc84b562465a26d4b71dbd746b7e0
Parents: b7d78e3
Author: Ishan Chattopadhyaya <is...@localhost.localdomain>
Authored: Sun Feb 12 05:18:00 2017 -0800
Committer: Ishan Chattopadhyaya <is...@localhost.localdomain>
Committed: Sun Feb 12 05:18:00 2017 -0800

----------------------------------------------------------------------
 dev-tools/doap/lucene.rdf                       |   9 +
 dev-tools/doap/solr.rdf                         |   9 +
 lucene/CHANGES.txt                              |  87 ++-
 lucene/analysis/build.xml                       |   3 +
 .../miscellaneous/WordDelimiterGraphFilter.java |   2 -
 .../TestWordDelimiterGraphFilter.java           |  15 +-
 .../analysis/cn/smart/hhmm/WordDictionary.java  |   1 -
 .../lucene/legacy/doc-files/nrq-formula-1.png   | Bin 0 -> 3171 bytes
 .../lucene/legacy/doc-files/nrq-formula-2.png   | Bin 0 -> 3694 bytes
 .../index/TestBackwardsCompatibility.java       |   4 +-
 .../org/apache/lucene/index/index.6.4.1-cfs.zip | Bin 0 -> 15848 bytes
 .../apache/lucene/index/index.6.4.1-nocfs.zip   | Bin 0 -> 15838 bytes
 lucene/build.xml                                |   4 +
 lucene/common-build.xml                         |  10 +
 .../CompressingStoredFieldsWriter.java          |   5 +-
 .../codecs/compressing/CompressionMode.java     |  49 +-
 .../lucene/codecs/compressing/Compressor.java   |   3 +-
 .../document/SortedSetDocValuesRangeQuery.java  |   5 +-
 .../org/apache/lucene/geo/GeoEncodingUtils.java | 233 ++++++++
 .../java/org/apache/lucene/geo/GeoUtils.java    |  50 ++
 .../java/org/apache/lucene/index/FieldInfo.java |   3 +-
 .../org/apache/lucene/index/FieldInfos.java     |   5 +-
 .../apache/lucene/index/FilterCodecReader.java  |  24 +
 .../apache/lucene/index/IndexReaderContext.java |   7 +-
 .../org/apache/lucene/index/IndexWriter.java    |  17 +-
 .../org/apache/lucene/index/PointValues.java    |   3 +-
 .../apache/lucene/index/SegmentCoreReaders.java |   7 +
 .../org/apache/lucene/index/SegmentInfos.java   |   6 +-
 .../org/apache/lucene/index/TermContext.java    |  21 +-
 .../apache/lucene/search/BlendedTermQuery.java  |  12 +-
 .../org/apache/lucene/search/TermQuery.java     |   6 +-
 .../lucene/search/doc-files/nrq-formula-1.png   | Bin 3171 -> 0 bytes
 .../lucene/search/doc-files/nrq-formula-2.png   | Bin 3694 -> 0 bytes
 .../lucene/search/spans/SpanTermQuery.java      |   4 +-
 .../org/apache/lucene/util/QueryBuilder.java    | 122 +++-
 .../java/org/apache/lucene/util/Version.java    |   7 +
 .../util/automaton/FiniteStringsIterator.java   |  36 +-
 .../org/apache/lucene/util/bkd/BKDReader.java   |  19 +-
 .../graph/GraphTokenStreamFiniteStrings.java    | 212 ++++---
 .../lucene60/TestLucene60PointsFormat.java      |   9 +-
 .../org/apache/lucene/geo/TestGeoUtils.java     |  12 +
 .../lucene/index/TestFilterCodecReader.java     |  49 ++
 .../apache/lucene/index/TestIndexSorting.java   |   3 +-
 .../apache/lucene/util/TestQueryBuilder.java    |  71 +++
 .../TestGraphTokenStreamFiniteStrings.java      | 421 +++++++++++--
 lucene/join/build.xml                           |  21 -
 .../join/ParentChildrenBlockJoinQuery.java      | 199 +++++++
 .../search/join/ToParentBlockJoinCollector.java | 507 ----------------
 .../join/ToParentBlockJoinIndexSearcher.java    |  73 ---
 .../search/join/ToParentBlockJoinQuery.java     | 428 ++++++--------
 .../apache/lucene/search/join/package-info.java |  31 +-
 .../lucene/search/join/TestBlockJoin.java       | 591 ++++---------------
 .../search/join/TestBlockJoinValidation.java    |  19 -
 .../apache/lucene/search/join/TestJoinUtil.java | 110 ++--
 .../join/TestParentChildrenBlockJoinQuery.java  | 104 ++++
 .../apache/lucene/index/memory/MemoryIndex.java |  75 ++-
 .../lucene/index/memory/TestMemoryIndex.java    |  18 +-
 .../function/valuesource/DoubleFieldSource.java |   6 +-
 .../function/valuesource/FloatFieldSource.java  |   6 +-
 .../function/valuesource/IntFieldSource.java    |   8 +-
 .../function/valuesource/LongFieldSource.java   |   6 +-
 .../MultiValuedDoubleFieldSource.java           |  78 +++
 .../MultiValuedFloatFieldSource.java            |  78 +++
 .../valuesource/MultiValuedIntFieldSource.java  |  78 +++
 .../valuesource/MultiValuedLongFieldSource.java |  78 +++
 .../queries/function/FunctionTestSetup.java     |  58 ++
 .../function/TestDocValuesFieldSources.java     |  14 +-
 .../queries/function/TestFieldScoreQuery.java   |  26 +
 .../function/TestFunctionRangeQuery.java        |  37 +-
 .../queries/function/TestValueSources.java      | 113 ++--
 .../lucene/queryparser/xml/CoreParser.java      |  18 +-
 .../classic/TestMultiFieldQueryParser.java      |   4 +-
 .../queryparser/classic/TestQueryParser.java    |  58 +-
 .../lucene/queryparser/xml/TestCoreParser.java  |  19 +-
 .../document/LatLonDocValuesBoxQuery.java       | 145 +++++
 .../document/LatLonDocValuesDistanceQuery.java  | 132 +++++
 .../lucene/document/LatLonDocValuesField.java   |  46 ++
 .../document/LatLonPointDistanceQuery.java      |  71 ++-
 .../document/LatLonPointInPolygonQuery.java     |   6 +-
 .../lucene/search/TermAutomatonQuery.java       |   2 +-
 .../search/TestLatLonDocValuesQueries.java      |  62 ++
 lucene/site/changes/changes2html.pl             |   7 +-
 .../geopoint/document/GeoPointField.java        | 266 ---------
 .../geopoint/document/GeoPointTokenStream.java  | 218 -------
 .../spatial/geopoint/document/package-info.java |  21 -
 .../geopoint/search/GeoPointDistanceQuery.java  | 171 ------
 .../search/GeoPointDistanceQueryImpl.java       | 132 -----
 .../geopoint/search/GeoPointInBBoxQuery.java    | 171 ------
 .../search/GeoPointInBBoxQueryImpl.java         | 148 -----
 .../geopoint/search/GeoPointInPolygonQuery.java | 103 ----
 .../search/GeoPointInPolygonQueryImpl.java      |  94 ---
 .../geopoint/search/GeoPointMultiTermQuery.java | 148 -----
 .../GeoPointTermQueryConstantScoreWrapper.java  | 160 -----
 .../geopoint/search/GeoPointTermsEnum.java      | 199 -------
 .../spatial/geopoint/search/package-info.java   |  21 -
 .../geopoint/search/TestGeoPointQuery.java      |  85 ---
 .../lucene/spatial/util/TestGeoPointField.java  |  51 --
 .../analyzing/AnalyzingInfixSuggester.java      |   4 +-
 .../analyzing/AnalyzingInfixSuggesterTest.java  |  13 +-
 .../lucene/analysis/CannedTokenStream.java      |  28 +-
 .../dummy/DummyCompressingCodec.java            |   3 +
 .../apache/lucene/geo/BaseGeoPointTestCase.java |  29 +-
 .../index/BaseIndexFileFormatTestCase.java      |  51 +-
 .../lucene/index/BasePointsFormatTestCase.java  |  15 +-
 .../org/apache/lucene/util/LuceneTestCase.java  |  28 +
 solr/CHANGES.txt                                | 105 +++-
 solr/bin/solr                                   |  44 +-
 solr/bin/solr.cmd                               |  81 ++-
 solr/bin/solr.in.cmd                            |   4 +-
 solr/bin/solr.in.sh                             |   4 +-
 solr/build.xml                                  |   3 +
 .../facet/FieldFacetAccumulator.java            |   2 +-
 .../analytics/util/RangeEndpointCalculator.java |   3 +-
 .../util/valuesource/ConstDoubleSource.java     |   3 +-
 .../dataimport/TestMailEntityProcessor.java     |   1 +
 .../ltr/feature/TestFeatureLtrScoringModel.java |  71 ---
 .../solr/ltr/feature/TestFeatureStore.java      | 106 ----
 .../ltr/store/rest/TestManagedFeatureStore.java | 119 +++-
 .../apache/solr/hadoop/MorphlineMapperTest.java |   3 +-
 .../solr/hadoop/MorphlineReducerTest.java       |   2 +-
 solr/core/src/java/org/apache/solr/api/Api.java |  67 +++
 .../src/java/org/apache/solr/api/ApiBag.java    | 354 +++++++++++
 .../java/org/apache/solr/api/ApiSupport.java    |  46 ++
 .../java/org/apache/solr/api/SpecProvider.java  |  25 +
 .../java/org/apache/solr/api/V2HttpCall.java    | 340 +++++++++++
 .../java/org/apache/solr/api/package-info.java  |  21 +
 .../src/java/org/apache/solr/cloud/Assign.java  |  10 +-
 .../org/apache/solr/cloud/CreateShardCmd.java   |   2 +-
 .../apache/solr/cloud/DeleteCollectionCmd.java  |   7 +
 .../org/apache/solr/core/CoreContainer.java     |  21 +-
 .../org/apache/solr/core/DirectoryFactory.java  |  18 +-
 .../solr/core/EphemeralDirectoryFactory.java    |   8 +
 .../apache/solr/core/HdfsDirectoryFactory.java  |  23 +-
 .../solr/core/MetricsDirectoryFactory.java      |   4 +-
 .../java/org/apache/solr/core/PluginBag.java    |  71 ++-
 .../src/java/org/apache/solr/core/SolrCore.java |  84 +--
 .../java/org/apache/solr/core/SolrCores.java    |   4 +-
 .../solr/core/snapshots/SolrSnapshotsTool.java  |  15 +-
 .../org/apache/solr/handler/BlobHandler.java    |  14 +-
 .../apache/solr/handler/DumpRequestHandler.java |  11 +
 .../org/apache/solr/handler/IndexFetcher.java   |  98 ++-
 .../apache/solr/handler/PingRequestHandler.java |   5 +
 .../apache/solr/handler/RealTimeGetHandler.java |  14 +
 .../apache/solr/handler/ReplicationHandler.java |  25 +-
 .../apache/solr/handler/RequestHandlerBase.java |  12 +-
 .../org/apache/solr/handler/SchemaHandler.java  |  35 +-
 .../apache/solr/handler/SolrConfigHandler.java  |  16 +
 .../org/apache/solr/handler/StreamHandler.java  |  96 ++-
 .../solr/handler/UpdateRequestHandler.java      |   2 +
 .../solr/handler/UpdateRequestHandlerApi.java   |  73 +++
 .../handler/admin/BaseHandlerApiSupport.java    | 236 ++++++++
 .../handler/admin/CollectionHandlerApi.java     | 319 ++++++++++
 .../solr/handler/admin/CollectionsHandler.java  |  69 ++-
 .../solr/handler/admin/ConfigSetsHandler.java   |  47 +-
 .../handler/admin/ConfigSetsHandlerApi.java     | 112 ++++
 .../solr/handler/admin/CoreAdminHandler.java    |  14 +
 .../solr/handler/admin/CoreAdminHandlerApi.java | 175 ++++++
 .../apache/solr/handler/admin/InfoHandler.java  |  78 +--
 .../solr/handler/admin/SecurityConfHandler.java |  70 +++
 .../solr/handler/component/FacetComponent.java  |   8 +-
 .../handler/component/RangeFacetRequest.java    |   8 +-
 .../handler/component/SpellCheckComponent.java  |  12 +-
 .../solr/handler/component/StatsField.java      |  26 +-
 .../solr/highlight/DefaultSolrHighlighter.java  |   3 +-
 .../solr/highlight/PostingsSolrHighlighter.java |   4 +-
 .../apache/solr/highlight/SolrHighlighter.java  |   1 +
 .../solr/highlight/UnifiedSolrHighlighter.java  |   8 +-
 .../solr/metrics/SolrCoreMetricManager.java     |   3 -
 .../org/apache/solr/metrics/SolrMetricInfo.java |   6 +-
 .../apache/solr/metrics/SolrMetricManager.java  |  85 ++-
 .../org/apache/solr/query/SolrRangeQuery.java   |   4 +-
 .../apache/solr/request/DocValuesFacets.java    |  20 +-
 .../org/apache/solr/request/IntervalFacets.java |  42 +-
 .../org/apache/solr/request/NumericFacets.java  |  12 +-
 .../request/PerSegmentSingleValuedFaceting.java |  14 +-
 .../org/apache/solr/request/SimpleFacets.java   | 122 ++--
 .../apache/solr/request/SolrQueryRequest.java   |  24 +
 .../solr/request/SolrQueryRequestBase.java      |  35 ++
 .../solr/request/SubstringBytesRefFilter.java   |  52 ++
 .../java/org/apache/solr/schema/EnumField.java  |   9 +
 .../java/org/apache/solr/schema/FieldType.java  |  13 +-
 .../java/org/apache/solr/schema/NumberType.java |  25 +
 .../apache/solr/schema/NumericFieldType.java    |  29 +-
 .../org/apache/solr/schema/SchemaManager.java   |  13 +-
 .../schema/SpatialPointVectorFieldType.java     |   8 +-
 .../java/org/apache/solr/search/BitDocSet.java  |   2 +-
 .../src/java/org/apache/solr/search/DocSet.java |   4 +-
 .../java/org/apache/solr/search/DocSetBase.java |  21 +-
 .../org/apache/solr/search/DocSetCollector.java |   7 +
 .../java/org/apache/solr/search/DocSetUtil.java |  62 +-
 .../java/org/apache/solr/search/DocSlice.java   |   8 +-
 .../java/org/apache/solr/search/HashDocSet.java |   2 +-
 .../org/apache/solr/search/SolrCoreParser.java  |  42 +-
 .../apache/solr/search/SolrIndexSearcher.java   |  90 ++-
 .../solr/search/SolrSpanQueryBuilder.java       |  33 ++
 .../org/apache/solr/search/SortedIntDocSet.java |   2 +-
 .../apache/solr/search/facet/FacetField.java    |   4 +-
 .../FacetFieldProcessorByEnumTermsStream.java   |   2 +-
 .../facet/FacetFieldProcessorByHashDV.java      |   2 +-
 .../apache/solr/search/facet/FacetRange.java    |  13 +-
 .../org/apache/solr/search/facet/HLLAgg.java    |   2 +-
 .../org/apache/solr/search/facet/UniqueAgg.java |   2 +-
 .../solr/search/function/OrdFieldSource.java    |   2 +-
 .../search/function/ReverseOrdFieldSource.java  |   2 +-
 .../solr/search/grouping/CommandHandler.java    |   5 +-
 .../distributed/command/GroupConverter.java     |   4 +-
 .../command/SearchGroupsFieldCommand.java       |   6 +-
 .../command/TopGroupsFieldCommand.java          |   4 +-
 .../search/join/BlockJoinFacetAccsHolder.java   |  14 -
 .../apache/solr/search/mlt/CloudMLTQParser.java |   2 +-
 .../solr/search/mlt/SimpleMLTQParser.java       |   4 +-
 .../apache/solr/security/BasicAuthPlugin.java   |  10 +-
 .../apache/solr/security/HadoopAuthPlugin.java  |  37 ++
 .../security/RuleBasedAuthorizationPlugin.java  |  11 +-
 .../security/Sha256AuthenticationProvider.java  |   8 +
 .../org/apache/solr/servlet/HttpSolrCall.java   | 158 ++++-
 .../org/apache/solr/servlet/ResponseUtils.java  |   6 +
 .../apache/solr/servlet/SolrDispatchFilter.java |  18 +-
 .../apache/solr/servlet/SolrRequestParsers.java |  27 +-
 .../solr/spelling/suggest/SolrSuggester.java    |  11 +-
 .../solr/store/blockcache/BlockCache.java       |  41 +-
 .../store/blockcache/BlockCacheLocation.java    |   4 +-
 .../org/apache/solr/update/SolrCoreState.java   |   6 +
 .../apache/solr/update/UpdateShardHandler.java  |   4 +-
 .../org/apache/solr/util/CommandOperation.java  |   4 +
 .../apache/solr/util/JsonSchemaValidator.java   | 370 ++++++++++++
 .../src/java/org/apache/solr/util/PathTrie.java | 195 ++++++
 .../src/java/org/apache/solr/util/SolrCLI.java  |  39 --
 solr/core/src/resources/ImplicitPlugins.json    |   6 +-
 .../src/resources/apispec/cluster.Commands.json |  74 +++
 .../apispec/cluster.commandstatus.delete.json   |  10 +
 .../apispec/cluster.commandstatus.json          |  20 +
 .../apispec/cluster.configs.Commands.json       |  34 ++
 .../apispec/cluster.configs.delete.json         |  12 +
 .../src/resources/apispec/cluster.configs.json  |  12 +
 solr/core/src/resources/apispec/cluster.json    |  14 +
 .../src/resources/apispec/cluster.nodes.json    |  12 +
 .../cluster.security.BasicAuth.Commands.json    |  23 +
 ...cluster.security.RuleBasedAuthorization.json | 129 ++++
 ...luster.security.authentication.Commands.json |  12 +
 .../cluster.security.authentication.json        |  12 +
 ...cluster.security.authorization.Commands.json |  13 +
 .../apispec/cluster.security.authorization.json |  13 +
 .../resources/apispec/collections.Commands.json | 206 +++++++
 .../collections.collection.Commands.json        | 137 +++++
 .../collections.collection.Commands.modify.json |  36 ++
 .../collections.collection.Commands.reload.json |  11 +
 .../apispec/collections.collection.delete.json  |  13 +
 .../apispec/collections.collection.json         |  19 +
 .../collections.collection.shards.Commands.json | 109 ++++
 ...ctions.collection.shards.shard.Commands.json |  24 +
 ...lections.collection.shards.shard.delete.json |  27 +
 ....collection.shards.shard.replica.delete.json |  39 ++
 .../core/src/resources/apispec/collections.json |  13 +
 .../src/resources/apispec/core.RealtimeGet.json |  26 +
 .../apispec/core.SchemaEdit.addCopyField.json   |  27 +
 .../apispec/core.SchemaEdit.addField.json       |  98 +++
 .../core.SchemaEdit.addFieldType.analyzers.json |  51 ++
 .../apispec/core.SchemaEdit.addFieldType.json   |  53 ++
 .../core.SchemaEdit.deleteCopyField.json        |  19 +
 .../core.SchemaEdit.deleteDynamicField.json     |  12 +
 .../apispec/core.SchemaEdit.deleteField.json    |  12 +
 .../core.SchemaEdit.deleteFieldType.json        |  14 +
 .../src/resources/apispec/core.SchemaEdit.json  |  47 ++
 .../apispec/core.SchemaRead.copyFields.json     |  26 +
 ...ore.SchemaRead.dynamicFields_fieldTypes.json |  20 +
 .../apispec/core.SchemaRead.fields.json         |  34 ++
 .../src/resources/apispec/core.SchemaRead.json  |  18 +
 .../core/src/resources/apispec/core.Update.json |  17 +
 ...g.Commands.addRequestHandler.properties.json |  25 +
 .../apispec/core.config.Commands.generic.json   |  19 +
 .../resources/apispec/core.config.Commands.json | 215 +++++++
 .../core.config.Commands.runtimeLib.json        |  23 +
 .../apispec/core.config.Params.Commands.json    |  31 +
 .../resources/apispec/core.config.Params.json   |  13 +
 .../core/src/resources/apispec/core.config.json |  18 +
 .../src/resources/apispec/core.system.blob.json |  20 +
 .../apispec/core.system.blob.upload.json        |  12 +
 .../src/resources/apispec/cores.Commands.json   |  85 +++
 .../src/resources/apispec/cores.Status.json     |  20 +
 .../resources/apispec/cores.core.Commands.json  | 136 +++++
 .../apispec/cores.core.Commands.split.json      |  34 ++
 solr/core/src/resources/apispec/emptySpec.json  |  11 +
 .../src/resources/apispec/node.Commands.json    |  24 +
 solr/core/src/resources/apispec/node.Info.json  |  11 +
 .../core/src/resources/apispec/node.invoke.json |  16 +
 .../conf/solrconfig-infixsuggesters.xml         | 101 ++++
 .../conf/solrconfig-managed-schema.xml          |   2 +-
 .../conf/solrconfig-master-throttled.xml        |   2 +-
 .../org/apache/solr/TestRandomDVFaceting.java   |   2 +-
 .../test/org/apache/solr/api/TestPathTrie.java  |  61 ++
 .../cloud/CdcrReplicationDistributedZkTest.java |   2 +
 .../apache/solr/cloud/CleanupOldIndexTest.java  |   2 +
 .../cloud/CollectionsAPIDistributedZkTest.java  |  10 +-
 ...ConcurrentDeleteAndCreateCollectionTest.java |  33 +-
 .../cloud/SharedFSAutoReplicaFailoverTest.java  |   2 +-
 .../TestSolrCloudWithDelegationTokens.java      |   1 +
 .../cloud/hdfs/HdfsBasicDistributedZkTest.java  |   2 +
 .../HdfsCollectionsAPIDistributedZkTest.java    |   2 +-
 .../apache/solr/cloud/hdfs/HdfsTestUtil.java    |   8 +-
 .../HdfsWriteToMultipleCollectionsTest.java     |  16 +-
 .../org/apache/solr/cloud/rule/RulesTest.java   |  18 +
 .../solr/core/BlobStoreTestRequestHandler.java  |   1 +
 .../solr/core/HdfsDirectoryFactoryTest.java     |   2 +-
 .../test/org/apache/solr/core/SolrCoreTest.java |   1 +
 .../apache/solr/core/TestDynamicLoading.java    |   2 +-
 .../org/apache/solr/core/TestLazyCores.java     |  76 +++
 .../apache/solr/core/TestSolrConfigHandler.java |  97 ++-
 .../core/snapshots/TestSolrCloudSnapshots.java  |  18 +
 .../apache/solr/handler/TestBlobHandler.java    |  44 +-
 .../solr/handler/TestReplicationHandler.java    |  98 ++-
 .../solr/handler/TestSystemCollAutoCreate.java  |  29 +
 .../solr/handler/V2ApiIntegrationTest.java      |  98 +++
 .../solr/handler/admin/MBeansHandlerTest.java   |  14 +-
 .../solr/handler/admin/TestApiFramework.java    | 219 +++++++
 .../solr/handler/admin/TestCollectionAPIs.java  | 231 ++++++++
 .../solr/handler/admin/TestConfigsApi.java      |  59 ++
 .../solr/handler/admin/TestCoreAdminApis.java   | 115 ++++
 .../handler/component/InfixSuggestersTest.java  | 136 +++++
 .../component/SpellCheckComponentTest.java      |  79 ++-
 .../solr/metrics/SolrMetricManagerTest.java     |  27 +-
 .../apache/solr/request/SimpleFacetsTest.java   | 172 +++++-
 .../request/SubstringBytesRefFilterTest.java    |  51 ++
 .../solr/rest/schema/TestBulkSchemaAPI.java     |  15 +
 .../ApacheLuceneSolrNearQueryBuilder.java       |  12 +-
 .../solr/search/ChooseOneWordQueryBuilder.java  |  62 ++
 .../apache/solr/search/HandyQueryBuilder.java   |  25 +-
 .../org/apache/solr/search/TestFiltering.java   |  58 +-
 .../apache/solr/search/TestSolrCoreParser.java  | 108 ++++
 .../solr/security/BasicAuthIntegrationTest.java |   4 +
 .../TestRuleBasedAuthorizationPlugin.java       |   8 +-
 .../hadoop/TestDelegationWithHadoopAuth.java    |   1 +
 .../solr/servlet/SolrRequestParserTest.java     |   4 +-
 .../suggest/RandomTestDictionaryFactory.java    | 117 ++++
 .../solr/store/blockcache/BlockCacheTest.java   | 134 +++++
 .../apache/solr/update/SoftAutoCommitTest.java  | 125 ++--
 .../solr/update/SolrIndexMetricsTest.java       |   2 +-
 .../solr/update/TestInPlaceUpdatesDistrib.java  |  26 +-
 .../org/apache/solr/util/JsonValidatorTest.java | 189 ++++++
 .../solr/util/TestObjectReleaseTracker.java     |  13 +-
 solr/example/example-DIH/build.xml              |   1 +
 solr/server/build.xml                           |   1 +
 .../conf/solrconfig.xml                         |   8 +-
 .../apache/solr/client/solrj/SolrRequest.java   |  13 +-
 .../solr/client/solrj/impl/CloudSolrClient.java |  16 +-
 .../solr/client/solrj/impl/HttpClientUtil.java  |  37 +-
 .../solrj/impl/Krb5HttpClientBuilder.java       |   5 +-
 .../solr/client/solrj/impl/PreemptiveAuth.java  |  59 ++
 ...PreemptiveBasicAuthClientBuilderFactory.java | 132 +++++
 .../solrj/io/eval/AbsoluteValueEvaluator.java   |  60 ++
 .../solr/client/solrj/io/eval/AddEvaluator.java |  61 ++
 .../solr/client/solrj/io/eval/AndEvaluator.java |  90 +++
 .../client/solrj/io/eval/BooleanEvaluator.java  |  86 +++
 .../client/solrj/io/eval/ComplexEvaluator.java  |  99 ++++
 .../solrj/io/eval/ConditionalEvaluator.java     |  59 ++
 .../client/solrj/io/eval/DivideEvaluator.java   |  78 +++
 .../client/solrj/io/eval/EqualsEvaluator.java   | 112 ++++
 .../solrj/io/eval/ExclusiveOrEvaluator.java     |  67 +++
 .../client/solrj/io/eval/FieldEvaluator.java    |  62 ++
 .../io/eval/GreaterThanEqualToEvaluator.java    |  99 ++++
 .../solrj/io/eval/GreaterThanEvaluator.java     |  99 ++++
 .../solrj/io/eval/IfThenElseEvaluator.java      |  62 ++
 .../solrj/io/eval/LessThanEqualToEvaluator.java |  99 ++++
 .../client/solrj/io/eval/LessThanEvaluator.java |  99 ++++
 .../client/solrj/io/eval/MultiplyEvaluator.java |  62 ++
 .../solr/client/solrj/io/eval/NotEvaluator.java |  62 ++
 .../client/solrj/io/eval/NumberEvaluator.java   |  79 +++
 .../solr/client/solrj/io/eval/OrEvaluator.java  |  90 +++
 .../client/solrj/io/eval/RawValueEvaluator.java |  90 +++
 .../client/solrj/io/eval/SimpleEvaluator.java   |  29 +
 .../client/solrj/io/eval/StreamEvaluator.java   |  30 +
 .../client/solrj/io/eval/SubtractEvaluator.java |  61 ++
 .../solr/client/solrj/io/ops/AndOperation.java  | 101 ----
 .../client/solrj/io/ops/BooleanOperation.java   |  26 -
 .../client/solrj/io/ops/EqualsOperation.java    |  70 ---
 .../io/ops/GreaterThanEqualToOperation.java     |  70 ---
 .../solrj/io/ops/GreaterThanOperation.java      |  70 ---
 .../solr/client/solrj/io/ops/LeafOperation.java |  67 ---
 .../solrj/io/ops/LessThanEqualToOperation.java  |  70 ---
 .../client/solrj/io/ops/LessThanOperation.java  |  70 ---
 .../solr/client/solrj/io/ops/NotOperation.java  |  87 ---
 .../solr/client/solrj/io/ops/OrOperation.java   |  71 ---
 .../client/solrj/io/stream/HavingStream.java    |  46 +-
 .../client/solrj/io/stream/SelectStream.java    |  71 ++-
 .../solrj/io/stream/expr/Explanation.java       |   1 +
 .../solrj/io/stream/expr/StreamFactory.java     |  34 ++
 .../client/solrj/request/CoreAdminRequest.java  |  18 +
 .../org/apache/solr/common/cloud/Replica.java   |   3 +
 .../apache/solr/common/params/FacetParams.java  |   5 +
 .../solr/common/params/HighlightParams.java     |   2 +-
 .../solr/common/util/ObjectReleaseTracker.java  |  22 -
 .../org/apache/solr/common/util/StrUtils.java   |  10 +-
 .../java/org/apache/solr/common/util/Utils.java |   5 +-
 .../solr/common/util/ValidatingJsonMap.java     | 349 +++++++++++
 .../AbstractEmbeddedSolrServerTestCase.java     |  22 +-
 .../solrj/embedded/SolrExampleJettyTest.java    |   9 +-
 .../io/stream/SelectWithEvaluatorsTest.java     | 259 ++++++++
 .../solrj/io/stream/StreamExpressionTest.java   |  55 +-
 .../stream/eval/AbsoluteValueEvaluatorTest.java |  96 +++
 .../solrj/io/stream/eval/AddEvaluatorTest.java  | 336 +++++++++++
 .../solrj/io/stream/eval/AndEvaluatorTest.java  | 123 ++++
 .../io/stream/eval/CompoundEvaluatorTest.java   |  85 +++
 .../io/stream/eval/DivideEvaluatorTest.java     | 164 +++++
 .../io/stream/eval/EqualsEvaluatorTest.java     | 263 +++++++++
 .../stream/eval/ExclusiveOrEvaluatorTest.java   | 123 ++++
 .../eval/GreaterThanEqualToEvaluatorTest.java   | 249 ++++++++
 .../stream/eval/GreaterThanEvaluatorTest.java   | 249 ++++++++
 .../eval/LessThanEqualToEvaluatorTest.java      | 256 ++++++++
 .../io/stream/eval/LessThanEvaluatorTest.java   | 249 ++++++++
 .../io/stream/eval/MultiplyEvaluatorTest.java   | 179 ++++++
 .../solrj/io/stream/eval/NotEvaluatorTest.java  |  80 +++
 .../solrj/io/stream/eval/OrEvaluatorTest.java   | 123 ++++
 .../io/stream/eval/RawValueEvaluatorTest.java   |  69 +++
 .../io/stream/eval/SubtractEvaluatorTest.java   | 188 ++++++
 .../client/solrj/request/TestCoreAdmin.java     |  95 +++
 .../solr/common/util/TestValidatingJsonMap.java |  52 ++
 .../java/org/apache/solr/SolrTestCaseJ4.java    |  67 ++-
 .../apache/solr/cloud/SolrCloudTestCase.java    |   2 +-
 .../org/apache/solr/util/ExternalPaths.java     |   2 +-
 .../org/apache/solr/util/RestTestHarness.java   |   8 +
 solr/webapp/build.xml                           |   1 +
 .../web/js/angular/controllers/dataimport.js    |   2 +-
 .../web/js/angular/controllers/plugins.js       |   1 +
 solr/webapp/web/js/scripts/dataimport.js        |   2 +-
 424 files changed, 18086 insertions(+), 5675 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/dev-tools/doap/lucene.rdf
----------------------------------------------------------------------
diff --git a/dev-tools/doap/lucene.rdf b/dev-tools/doap/lucene.rdf
index 58d5328..1e7e2c6 100644
--- a/dev-tools/doap/lucene.rdf
+++ b/dev-tools/doap/lucene.rdf
@@ -68,10 +68,19 @@
 
     <release>
       <Version>
+        <name>lucene-6.4.1</name>
+        <created>2017-02-06</created>
+        <revision>6.4.1</revision>
+      </Version>
+    </release>
+    <release>
+      <Version>
         <name>lucene-6.4.0</name>
         <created>2017-01-23</created>
         <revision>6.4.0</revision>
       </Version>
+    </release>
+    <release>
       <Version>
         <name>lucene-6.3.0</name>
         <created>2016-11-08</created>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/dev-tools/doap/solr.rdf
----------------------------------------------------------------------
diff --git a/dev-tools/doap/solr.rdf b/dev-tools/doap/solr.rdf
index 4032956..229d4ef 100644
--- a/dev-tools/doap/solr.rdf
+++ b/dev-tools/doap/solr.rdf
@@ -68,10 +68,19 @@
 
     <release>
       <Version>
+        <name>solr-6.4.1</name>
+        <created>2017-02-06</created>
+        <revision>6.4.1</revision>
+      </Version>
+    </release>
+    <release>
+      <Version>
         <name>solr-6.4.0</name>
         <created>2017-01-23</created>
         <revision>6.4.0</revision>
       </Version>
+    </release>
+    <release>
       <Version>
         <name>solr-6.3.0</name>
         <created>2016-11-08</created>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index deb7078..f9c464b 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -77,6 +77,17 @@ API Changes
 * LUCENE-7643: Replaced doc-values queries in lucene/sandbox with factory
   methods on the *DocValuesField classes. (Adrien Grand)
 
+* LUCENE-7659: Added a IndexWriter#getFieldNames() method (experimental) to return
+  all field names as visible from the IndexWriter. This would be useful for
+  IndexWriter#updateDocValues() calls, to prevent calling with non-existent
+  docValues fields (Ishan Chattopadhyaya, Adrien Grand, Mike McCandless)
+
+* LUCENE-6959: Removed ToParentBlockJoinCollector in favour of
+  ParentChildrenBlockJoinQuery, that can return the matching children documents per
+  parent document. This query should be executed for each matching parent document
+  after the main query has been executed. (Adrien Grand, Martijn van Groningen,
+  Mike McCandless)
+
 New Features
 
 * LUCENE-7623: Add FunctionScoreQuery and FunctionMatchQuery (Alan Woodward,
@@ -87,11 +98,26 @@ New Features
   proximity queries at search time will produce correct results (Mike
   McCandless)
 
+* LUCENE-7656: Added the LatLonDocValuesField.new(Box/Distance)Query() factory
+  methods that are the equivalent of factory methods on LatLonPoint but operate
+  on doc values. These new methods should be wrapped in an IndexOrDocValuesQuery
+  for best performance. (Adrien Grand)
+
+* LUCENE-7673: Added MultiValued[Int/Long/Float/Double]FieldSource that given a
+  SortedNumericSelector.Type can give a ValueSource view of a 
+  SortedNumericDocValues field. (Tom�s Fern�ndez L�bbe)
+
 Bug Fixes
 
 * LUCENE-7630: Fix (Edge)NGramTokenFilter to no longer drop payloads
   and preserve all attributes. (Nathan Gass via Uwe Schindler)
 
+* LUCENE-7676: Fixed FilterCodecReader to override more super-class methods.
+  Also added TestFilterCodecReader class. (Christine Poerschke)
+
+* LUCENE-7679: MemoryIndex was ignoring omitNorms settings on passed-in
+  IndexableFields. (Alan Woodward)
+
 Improvements
 
 * LUCENE-7055: Added Weight#scorerSupplier, which allows to estimate the cost
@@ -103,12 +129,58 @@ Improvements
   either points or doc values depending on which one is more efficient.
   (Adrien Grand)
 
+* LUCENE-7662: If index files are missing, throw CorruptIndexException instead
+  of the less descriptive FileNotFound or NoSuchFileException (Mike Drob via 
+  Mike McCandless, Erick Erickson)
+
 Optimizations
 
 * LUCENE-7641: Optimized point range queries to compute documents that do not
   match the range on single-valued fields when more than half the documents in
   the index would match. (Adrien Grand)
 
+* LUCENE-7656: Speed up for LatLonPointDistanceQuery by computing distances even
+  less often. (Adrien Grand)
+
+* LUCENE-7661: Speed up for LatLonPointInPolygonQuery by pre-computing the
+  relation of the polygon with a grid. (Adrien Grand)
+
+* LUCENE-7660: Speed up LatLonPointDistanceQuery by improving the detection of
+  whether BKD cells are entirely within the distance close to the dateline.
+  (Adrien Grand)
+
+* LUCENE-7654: ToParentBlockJoinQuery now implements two-phase iteration and
+  computes scores lazily in order to be faster when used in conjunctions.
+  (Adrien Grand)
+
+* LUCENE-7667: BKDReader now calls `IntersectVisitor.grow()` on larger
+  increments. (Adrien Grand)
+
+* LUCENE-7638: Query parsers now analyze the token graph for articulation
+  points (or cut vertices) in order to create more efficient queries for
+  multi-token synonyms. (Jim Ferenczi)
+
+Build
+
+* LUCENE-7653: Update randomizedtesting to version 2.5.0. (Dawid Weiss)
+
+* LUCENE-7665: Remove grouping dependency from the join module.
+  (Martijn van Groningen)
+  
+* SOLR-10023: Add non-recursive 'test-nocompile' target: Only runs unit tests.
+  Jars are not downloaded; compilation is not updated; and Clover is not enabled.
+  (Steve Rowe)
+
+Other
+
+* LUCENE-7666: Fix typos in lucene-join package info javadoc.
+  (Tom Saleeba via Christine Poerschke)
+
+* LUCENE-7658: queryparser/xml CoreParser now implements SpanQueryBuilder interface.
+  (Daniel Collins, Christine Poerschke)
+
+======================= Lucene 6.4.1 =======================
+
 Build
 
 * LUCENE-7651: Fix Javadocs build for Java 8u121 by injecting "Google Code
@@ -116,7 +188,17 @@ Build
   Also update Prettify to latest version to fix Google Chrome issue.
   (Uwe Schindler)
 
-* LUCENE-7653: Update randomizedtesting to version 2.5.0. (Dawid Weiss)
+Bug Fixes
+
+* LUCENE-7657: Fixed potential memory leak in the case that a (Span)TermQuery
+  with a TermContext is cached. (Adrien Grand)
+
+* LUCENE-7647: Made stored fields reclaim native memory more aggressively when
+  configured with BEST_COMPRESSION. This could otherwise result in out-of-memory
+  issues. (Adrien Grand)
+
+* LUCENE-7670: AnalyzingInfixSuggester should not immediately open an
+  IndexWriter over an already-built index. (Steve Rowe)
 
 ======================= Lucene 6.4.0 =======================
 
@@ -1042,6 +1124,9 @@ Other
 * LUCENE-7095: Add point values support to the numeric field query time join.
   (Martijn van Groningen, Mike McCandless)
 
+======================= Lucene 5.5.4 =======================
+(No Changes)
+
 ======================= Lucene 5.5.3 =======================
 (No Changes)
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/analysis/build.xml
----------------------------------------------------------------------
diff --git a/lucene/analysis/build.xml b/lucene/analysis/build.xml
index bc85ad1..5212cd6 100644
--- a/lucene/analysis/build.xml
+++ b/lucene/analysis/build.xml
@@ -105,6 +105,9 @@
   <target name="test">
     <forall-analyzers target="test"/>
   </target>
+  <target name="test-nocompile">
+    <fail message="Target 'test-nocompile' will not run recursively.  First change directory to the module you want to test."/>
+  </target>
   <target name="beast">
     <fail message="The Beast only works inside of individual modules"/>
   </target>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
index fe8ed72..a6ade19 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
@@ -27,7 +27,6 @@ import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
-import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
 import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.AttributeSource;
@@ -173,7 +172,6 @@ public final class WordDelimiterGraphFilter extends TokenFilter {
   private final OffsetAttribute offsetAttribute = addAttribute(OffsetAttribute.class);
   private final PositionIncrementAttribute posIncAttribute = addAttribute(PositionIncrementAttribute.class);
   private final PositionLengthAttribute posLenAttribute = addAttribute(PositionLengthAttribute.class);
-  private final TypeAttribute typeAttribute = addAttribute(TypeAttribute.class);
 
   // used for iterating word delimiter breaks
   private final WordDelimiterIterator iterator;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterGraphFilter.java b/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterGraphFilter.java
index 2daf886..f4e8b79 100644
--- a/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterGraphFilter.java
+++ b/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterGraphFilter.java
@@ -155,6 +155,19 @@ public class TestWordDelimiterGraphFilter extends BaseTokenStreamTestCase {
     doSplitPossessive(0, "ra's", "ra", "s");
   }
   
+  public void testTokenType() throws Exception {
+    int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;
+    // test that subwords and catenated subwords have
+    // the correct offsets.
+    Token token = new Token("foo-bar", 5, 12);
+    token.setType("mytype");
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(new CannedTokenStream(token), DEFAULT_WORD_DELIM_TABLE, flags, null);
+
+    assertTokenStreamContents(wdf, 
+                              new String[] {"foobar", "foo", "bar"},
+                              new String[] {"mytype", "mytype", "mytype"});
+  }
+  
   /*
    * Set a large position increment gap of 10 if the token is "largegap" or "/"
    */
@@ -177,7 +190,7 @@ public class TestWordDelimiterGraphFilter extends BaseTokenStreamTestCase {
       }
     }  
   }
-  
+
   public void testPositionIncrements() throws Exception {
     final int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;
     final CharArraySet protWords = new CharArraySet(new HashSet<>(Arrays.asList("NUTCH")), false);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/analysis/smartcn/src/java/org/apache/lucene/analysis/cn/smart/hhmm/WordDictionary.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/smartcn/src/java/org/apache/lucene/analysis/cn/smart/hhmm/WordDictionary.java b/lucene/analysis/smartcn/src/java/org/apache/lucene/analysis/cn/smart/hhmm/WordDictionary.java
index 5d28a7b..c6ba30f 100644
--- a/lucene/analysis/smartcn/src/java/org/apache/lucene/analysis/cn/smart/hhmm/WordDictionary.java
+++ b/lucene/analysis/smartcn/src/java/org/apache/lucene/analysis/cn/smart/hhmm/WordDictionary.java
@@ -163,7 +163,6 @@ class WordDictionary extends AbstractDictionary {
       output.writeObject(charIndexTable);
       output.writeObject(wordItem_charArrayTable);
       output.writeObject(wordItem_frequencyTable);
-      output.close();
       // log.info("serialize core dict.");
     } catch (Exception e) {
       // log.warn(e.getMessage());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/backward-codecs/src/java/org/apache/lucene/legacy/doc-files/nrq-formula-1.png
----------------------------------------------------------------------
diff --git a/lucene/backward-codecs/src/java/org/apache/lucene/legacy/doc-files/nrq-formula-1.png b/lucene/backward-codecs/src/java/org/apache/lucene/legacy/doc-files/nrq-formula-1.png
new file mode 100644
index 0000000..fd7d936
Binary files /dev/null and b/lucene/backward-codecs/src/java/org/apache/lucene/legacy/doc-files/nrq-formula-1.png differ

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/backward-codecs/src/java/org/apache/lucene/legacy/doc-files/nrq-formula-2.png
----------------------------------------------------------------------
diff --git a/lucene/backward-codecs/src/java/org/apache/lucene/legacy/doc-files/nrq-formula-2.png b/lucene/backward-codecs/src/java/org/apache/lucene/legacy/doc-files/nrq-formula-2.png
new file mode 100644
index 0000000..93cb308
Binary files /dev/null and b/lucene/backward-codecs/src/java/org/apache/lucene/legacy/doc-files/nrq-formula-2.png differ

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/backward-codecs/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java
----------------------------------------------------------------------
diff --git a/lucene/backward-codecs/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java b/lucene/backward-codecs/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java
index 73b7271..63ba413 100644
--- a/lucene/backward-codecs/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java
+++ b/lucene/backward-codecs/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java
@@ -293,7 +293,9 @@ public class TestBackwardsCompatibility extends LuceneTestCase {
     "6.3.0-cfs",
     "6.3.0-nocfs",
     "6.4.0-cfs",
-    "6.4.0-nocfs"
+    "6.4.0-nocfs",
+    "6.4.1-cfs",
+    "6.4.1-nocfs"
   };
   
   final String[] unsupportedNames = {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/backward-codecs/src/test/org/apache/lucene/index/index.6.4.1-cfs.zip
----------------------------------------------------------------------
diff --git a/lucene/backward-codecs/src/test/org/apache/lucene/index/index.6.4.1-cfs.zip b/lucene/backward-codecs/src/test/org/apache/lucene/index/index.6.4.1-cfs.zip
new file mode 100644
index 0000000..477859e
Binary files /dev/null and b/lucene/backward-codecs/src/test/org/apache/lucene/index/index.6.4.1-cfs.zip differ

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/backward-codecs/src/test/org/apache/lucene/index/index.6.4.1-nocfs.zip
----------------------------------------------------------------------
diff --git a/lucene/backward-codecs/src/test/org/apache/lucene/index/index.6.4.1-nocfs.zip b/lucene/backward-codecs/src/test/org/apache/lucene/index/index.6.4.1-nocfs.zip
new file mode 100644
index 0000000..7411118
Binary files /dev/null and b/lucene/backward-codecs/src/test/org/apache/lucene/index/index.6.4.1-nocfs.zip differ

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/build.xml
----------------------------------------------------------------------
diff --git a/lucene/build.xml b/lucene/build.xml
index 8b73ca6..f004a19 100644
--- a/lucene/build.xml
+++ b/lucene/build.xml
@@ -56,6 +56,10 @@
   <target name="test" depends="-clover.load, -init-totals, test-core, test-test-framework, test-modules, -check-totals"
           description="Runs all unit tests (core, modules and back-compat)"
   />
+  <target name="test-nocompile">
+    <fail message="Target 'test-nocompile' will not run recursively.  First change directory to the module you want to test."/>
+  </target>
+
 
   <target name="pitest" depends="pitest-modules"
           description="Runs pitests (core, modules and back-compat)"

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/common-build.xml
----------------------------------------------------------------------
diff --git a/lucene/common-build.xml b/lucene/common-build.xml
index 44d7353..2a988eb 100644
--- a/lucene/common-build.xml
+++ b/lucene/common-build.xml
@@ -1060,6 +1060,8 @@
             <!-- turn jenkins blood red for hashmap bugs, even on jdk7 -->
             <sysproperty key="jdk.map.althashing.threshold" value="0"/>
 
+            <sysproperty key="tests.src.home" value="${user.dir}" />
+
             <!-- Only pass these to the test JVMs if defined in ANT. -->
             <syspropertyset>
                 <propertyref prefix="tests.maxfailures" />
@@ -1334,6 +1336,11 @@ ant -f lucene/build.xml test-updatecache
 # Miscellaneous. --------------------------------------------------
 #
 
+# Only run test(s), non-recursively. Faster than "ant test".
+# WARNING: Skips jar download and compilation. Clover not supported.
+ant test-nocompile
+ant -Dtestcase=... test-nocompile
+
 # Run all tests without stopping on errors (inspect log files!).
 ant -Dtests.haltonfailure=false test
 
@@ -1391,6 +1398,9 @@ ${tests-output}/junit4-*.suites     - per-JVM executed suites
   <target name="test" depends="clover,compile-test,install-junit4-taskdef,validate,-init-totals,-test,-check-totals" description="Runs unit tests"/>
   <target name="beast" depends="clover,compile-test,install-junit4-taskdef,validate,-init-totals,-beast,-check-totals" description="Runs unit tests in a loop (-Dbeast.iters=n)"/>
 
+  <target name="test-nocompile" depends="-clover.disable,install-junit4-taskdef,-init-totals,-test,-check-totals"
+          description="Only runs unit tests.  Jars are not downloaded; compilation is not updated; and Clover is not enabled."/>
+
   <target name="-jacoco-install">
     <!-- download jacoco from ivy if needed -->
     <ivy:cachepath organisation="org.jacoco" module="org.jacoco.ant" type="jar" inline="true" revision="0.7.4.201502262128"

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressingStoredFieldsWriter.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressingStoredFieldsWriter.java b/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressingStoredFieldsWriter.java
index 7ab20af..5b42870 100644
--- a/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressingStoredFieldsWriter.java
+++ b/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressingStoredFieldsWriter.java
@@ -81,7 +81,7 @@ public final class CompressingStoredFieldsWriter extends StoredFieldsWriter {
   private CompressingStoredFieldsIndexWriter indexWriter;
   private IndexOutput fieldsStream;
 
-  private final Compressor compressor;
+  private Compressor compressor;
   private final CompressionMode compressionMode;
   private final int chunkSize;
   private final int maxDocsPerChunk;
@@ -141,10 +141,11 @@ public final class CompressingStoredFieldsWriter extends StoredFieldsWriter {
   @Override
   public void close() throws IOException {
     try {
-      IOUtils.close(fieldsStream, indexWriter);
+      IOUtils.close(fieldsStream, indexWriter, compressor);
     } finally {
       fieldsStream = null;
       indexWriter = null;
+      compressor = null;
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressionMode.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressionMode.java b/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressionMode.java
index 326eba3..53a84cb 100644
--- a/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressionMode.java
+++ b/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressionMode.java
@@ -164,6 +164,10 @@ public abstract class CompressionMode {
       LZ4.compress(bytes, off, len, out, ht);
     }
 
+    @Override
+    public void close() throws IOException {
+      // no-op
+    }
   }
 
   private static final class LZ4HighCompressor extends Compressor {
@@ -180,15 +184,17 @@ public abstract class CompressionMode {
       LZ4.compressHC(bytes, off, len, out, ht);
     }
 
+    @Override
+    public void close() throws IOException {
+      // no-op
+    }
   }
 
   private static final class DeflateDecompressor extends Decompressor {
 
-    final Inflater decompressor;
     byte[] compressed;
 
     DeflateDecompressor() {
-      decompressor = new Inflater(true);
       compressed = new byte[0];
     }
 
@@ -207,20 +213,24 @@ public abstract class CompressionMode {
       in.readBytes(compressed, 0, compressedLength);
       compressed[compressedLength] = 0; // explicitly set dummy byte to 0
 
-      decompressor.reset();
-      // extra "dummy byte"
-      decompressor.setInput(compressed, 0, paddedLength);
-
-      bytes.offset = bytes.length = 0;
-      bytes.bytes = ArrayUtil.grow(bytes.bytes, originalLength);
+      final Inflater decompressor = new Inflater(true);
       try {
-        bytes.length = decompressor.inflate(bytes.bytes, bytes.length, originalLength);
-      } catch (DataFormatException e) {
-        throw new IOException(e);
-      }
-      if (!decompressor.finished()) {
-        throw new CorruptIndexException("Invalid decoder state: needsInput=" + decompressor.needsInput() 
-                                                            + ", needsDict=" + decompressor.needsDictionary(), in);
+        // extra "dummy byte"
+        decompressor.setInput(compressed, 0, paddedLength);
+
+        bytes.offset = bytes.length = 0;
+        bytes.bytes = ArrayUtil.grow(bytes.bytes, originalLength);
+        try {
+          bytes.length = decompressor.inflate(bytes.bytes, bytes.length, originalLength);
+        } catch (DataFormatException e) {
+          throw new IOException(e);
+        }
+        if (!decompressor.finished()) {
+          throw new CorruptIndexException("Invalid decoder state: needsInput=" + decompressor.needsInput()
+                                                              + ", needsDict=" + decompressor.needsDictionary(), in);
+        }
+      } finally {
+        decompressor.end();
       }
       if (bytes.length != originalLength) {
         throw new CorruptIndexException("Lengths mismatch: " + bytes.length + " != " + originalLength, in);
@@ -240,6 +250,7 @@ public abstract class CompressionMode {
 
     final Deflater compressor;
     byte[] compressed;
+    boolean closed;
 
     DeflateCompressor(int level) {
       compressor = new Deflater(level, true);
@@ -275,6 +286,14 @@ public abstract class CompressionMode {
       out.writeBytes(compressed, totalCount);
     }
 
+    @Override
+    public void close() throws IOException {
+      if (closed == false) {
+        compressor.end();
+        closed = true;
+      }
+    }
+
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/codecs/compressing/Compressor.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/codecs/compressing/Compressor.java b/lucene/core/src/java/org/apache/lucene/codecs/compressing/Compressor.java
index bd2fadb..f95246c 100644
--- a/lucene/core/src/java/org/apache/lucene/codecs/compressing/Compressor.java
+++ b/lucene/core/src/java/org/apache/lucene/codecs/compressing/Compressor.java
@@ -17,6 +17,7 @@
 package org.apache.lucene.codecs.compressing;
 
 
+import java.io.Closeable;
 import java.io.IOException;
 
 import org.apache.lucene.store.DataOutput;
@@ -24,7 +25,7 @@ import org.apache.lucene.store.DataOutput;
 /**
  * A data compressor.
  */
-public abstract class Compressor {
+public abstract class Compressor implements Closeable {
 
   /** Sole constructor, typically called from sub-classes. */
   protected Compressor() {}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/document/SortedSetDocValuesRangeQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/SortedSetDocValuesRangeQuery.java b/lucene/core/src/java/org/apache/lucene/document/SortedSetDocValuesRangeQuery.java
index 3bc1b9c..30af45f 100644
--- a/lucene/core/src/java/org/apache/lucene/document/SortedSetDocValuesRangeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/SortedSetDocValuesRangeQuery.java
@@ -19,6 +19,7 @@ package org.apache.lucene.document;
 import java.io.IOException;
 import java.util.Objects;
 
+import org.apache.lucene.index.DocValues;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.LeafReader;
 import org.apache.lucene.index.LeafReaderContext;
@@ -143,11 +144,9 @@ abstract class SortedSetDocValuesRangeQuery extends Query {
           return null;
         }
 
-        final SortedDocValues singleton = null; // TODO: LUCENE-7649, re-consider optimization that broke SOLR-10013
-        // final SortedDocValues singleton = DocValues.unwrapSingleton(values);
+        final SortedDocValues singleton = DocValues.unwrapSingleton(values);
         final TwoPhaseIterator iterator;
         if (singleton != null) {
-          assert false : "imposible code -- or: someone re-enabled singleton optinization w/o reading the whole method";
           iterator = new TwoPhaseIterator(singleton) {
             @Override
             public boolean matches() throws IOException {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java b/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
index 671f779..663cb2e 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
@@ -16,7 +16,9 @@
  */
 package org.apache.lucene.geo;
 
+import org.apache.lucene.index.PointValues.Relation;
 import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.util.SloppyMath;
 
 import static org.apache.lucene.geo.GeoUtils.MAX_LAT_INCL;
 import static org.apache.lucene.geo.GeoUtils.MAX_LON_INCL;
@@ -25,6 +27,8 @@ import static org.apache.lucene.geo.GeoUtils.MIN_LAT_INCL;
 import static org.apache.lucene.geo.GeoUtils.checkLatitude;
 import static org.apache.lucene.geo.GeoUtils.checkLongitude;
 
+import java.util.function.Function;
+
 /**
  * reusable geopoint encoding methods
  *
@@ -144,4 +148,233 @@ public final class GeoEncodingUtils {
   public static double decodeLongitude(byte[] src, int offset) {
     return decodeLongitude(NumericUtils.sortableBytesToInt(src, offset));
   }
+
+  /** Create a predicate that checks whether points are within a distance of a given point.
+   *  It works by computing the bounding box around the circle that is defined
+   *  by the given points/distance and splitting it into between 1024 and 4096
+   *  smaller boxes (4096*0.75^2=2304 on average). Then for each sub box, it
+   *  computes the relation between this box and the distance query. Finally at
+   *  search time, it first computes the sub box that the point belongs to,
+   *  most of the time, no distance computation will need to be performed since
+   *  all points from the sub box will either be in or out of the circle.
+   *  @lucene.internal */
+  public static DistancePredicate createDistancePredicate(double lat, double lon, double radiusMeters) {
+    final Rectangle boundingBox = Rectangle.fromPointDistance(lat, lon, radiusMeters);
+    final double axisLat = Rectangle.axisLat(lat, radiusMeters);
+    final double distanceSortKey = GeoUtils.distanceQuerySortKey(radiusMeters);
+    final Function<Rectangle, Relation> boxToRelation = box -> GeoUtils.relate(
+        box.minLat, box.maxLat, box.minLon, box.maxLon, lat, lon, distanceSortKey, axisLat);
+    final Grid subBoxes = createSubBoxes(boundingBox, boxToRelation);
+
+    return new DistancePredicate(
+        subBoxes.latShift, subBoxes.lonShift,
+        subBoxes.latBase, subBoxes.lonBase,
+        subBoxes.maxLatDelta, subBoxes.maxLonDelta,
+        subBoxes.relations,
+        lat, lon, distanceSortKey);
+  }
+
+  /** Create a predicate that checks whether points are within a polygon.
+   *  It works the same way as {@link #createDistancePredicate}.
+   *  @lucene.internal */
+  public static PolygonPredicate createPolygonPredicate(Polygon[] polygons, Polygon2D tree) {
+    final Rectangle boundingBox = Rectangle.fromPolygon(polygons);
+    final Function<Rectangle, Relation> boxToRelation = box -> tree.relate(
+        box.minLat, box.maxLat, box.minLon, box.maxLon);
+    final Grid subBoxes = createSubBoxes(boundingBox, boxToRelation);
+
+    return new PolygonPredicate(
+        subBoxes.latShift, subBoxes.lonShift,
+        subBoxes.latBase, subBoxes.lonBase,
+        subBoxes.maxLatDelta, subBoxes.maxLonDelta,
+        subBoxes.relations,
+        tree);
+  }
+
+  private static Grid createSubBoxes(Rectangle boundingBox, Function<Rectangle, Relation> boxToRelation) {
+    final int minLat = encodeLatitudeCeil(boundingBox.minLat);
+    final int maxLat = encodeLatitude(boundingBox.maxLat);
+    final int minLon = encodeLongitudeCeil(boundingBox.minLon);
+    final int maxLon = encodeLongitude(boundingBox.maxLon);
+
+    if (maxLat < minLat || (boundingBox.crossesDateline() == false && maxLon < minLon)) {
+      // the box cannot match any quantized point
+      return new Grid(1, 1, 0, 0, 0, 0, new byte[0]);
+    }
+
+    final int latShift, lonShift;
+    final int latBase, lonBase;
+    final int maxLatDelta, maxLonDelta;
+    {
+      long minLat2 = (long) minLat - Integer.MIN_VALUE;
+      long maxLat2 = (long) maxLat - Integer.MIN_VALUE;
+      latShift = computeShift(minLat2, maxLat2);
+      latBase = (int) (minLat2 >>> latShift);
+      maxLatDelta = (int) (maxLat2 >>> latShift) - latBase + 1;
+      assert maxLatDelta > 0;
+    }
+    {
+      long minLon2 = (long) minLon - Integer.MIN_VALUE;
+      long maxLon2 = (long) maxLon - Integer.MIN_VALUE;
+      if (boundingBox.crossesDateline()) {
+        maxLon2 += 1L << 32; // wrap
+      }
+      lonShift = computeShift(minLon2, maxLon2);
+      lonBase = (int) (minLon2 >>> lonShift);
+      maxLonDelta = (int) (maxLon2 >>> lonShift) - lonBase + 1;
+      assert maxLonDelta > 0;
+    }
+
+    final byte[] relations = new byte[maxLatDelta * maxLonDelta];
+    for (int i = 0; i < maxLatDelta; ++i) {
+      for (int j = 0; j < maxLonDelta; ++j) {
+        final int boxMinLat = ((latBase + i) << latShift) + Integer.MIN_VALUE;
+        final int boxMinLon = ((lonBase + j) << lonShift) + Integer.MIN_VALUE;
+        final int boxMaxLat = boxMinLat + (1 << latShift) - 1;
+        final int boxMaxLon = boxMinLon + (1 << lonShift) - 1;
+
+        relations[i * maxLonDelta + j] = (byte) boxToRelation.apply(new Rectangle(
+            decodeLatitude(boxMinLat), decodeLatitude(boxMaxLat),
+            decodeLongitude(boxMinLon), decodeLongitude(boxMaxLon))).ordinal();
+      }
+    }
+
+    return new Grid(
+        latShift, lonShift,
+        latBase, lonBase,
+        maxLatDelta, maxLonDelta,
+        relations);
+  }
+
+  /** Compute the minimum shift value so that
+   * {@code (b>>>shift)-(a>>>shift)} is less that {@code ARITY}. */
+  private static int computeShift(long a, long b) {
+    assert a <= b;
+    // We enforce a shift of at least 1 so that when we work with unsigned ints
+    // by doing (lat - MIN_VALUE), the result of the shift (lat - MIN_VALUE) >>> shift
+    // can be used for comparisons without particular care: the sign bit has
+    // been cleared so comparisons work the same for signed and unsigned ints
+    for (int shift = 1; ; ++shift) {
+      final long delta = (b >>> shift) - (a >>> shift);
+      if (delta >= 0 && delta < Grid.ARITY) {
+        return shift;
+      }
+    }
+  }
+
+  private static class Grid {
+    static final int ARITY = 64;
+
+    final int latShift, lonShift;
+    final int latBase, lonBase;
+    final int maxLatDelta, maxLonDelta;
+    final byte[] relations;
+
+    private Grid(
+        int latShift, int lonShift,
+        int latBase, int lonBase,
+        int maxLatDelta, int maxLonDelta,
+        byte[] relations) {
+      if (latShift < 1 || latShift > 31) {
+        throw new IllegalArgumentException();
+      }
+      if (lonShift < 1 || lonShift > 31) {
+        throw new IllegalArgumentException();
+      }
+      this.latShift = latShift;
+      this.lonShift = lonShift;
+      this.latBase = latBase;
+      this.lonBase = lonBase;
+      this.maxLatDelta = maxLatDelta;
+      this.maxLonDelta = maxLonDelta;
+      this.relations = relations;
+    }
+  }
+
+  /** A predicate that checks whether a given point is within a distance of another point. */
+  public static class DistancePredicate extends Grid {
+
+    private final double lat, lon;
+    private final double distanceKey;
+
+    private DistancePredicate(
+        int latShift, int lonShift,
+        int latBase, int lonBase,
+        int maxLatDelta, int maxLonDelta,
+        byte[] relations,
+        double lat, double lon, double distanceKey) {
+      super(latShift, lonShift, latBase, lonBase, maxLatDelta, maxLonDelta, relations);
+      this.lat = lat;
+      this.lon = lon;
+      this.distanceKey = distanceKey;
+    }
+
+    /** Check whether the given point is within a distance of another point.
+     *  NOTE: this operates directly on the encoded representation of points. */
+    public boolean test(int lat, int lon) {
+      final int lat2 = ((lat - Integer.MIN_VALUE) >>> latShift);
+      if (lat2 < latBase || lat2 >= latBase + maxLatDelta) {
+        return false;
+      }
+      int lon2 = ((lon - Integer.MIN_VALUE) >>> lonShift);
+      if (lon2 < lonBase) { // wrap
+        lon2 += 1 << (32 - lonShift);
+      }
+      assert Integer.toUnsignedLong(lon2) >= lonBase;
+      assert lon2 - lonBase >= 0;
+      if (lon2 - lonBase >= maxLonDelta) {
+        return false;
+      }
+
+      final int relation = relations[(lat2 - latBase) * maxLonDelta + (lon2 - lonBase)];
+      if (relation == Relation.CELL_CROSSES_QUERY.ordinal()) {
+        return SloppyMath.haversinSortKey(
+            decodeLatitude(lat), decodeLongitude(lon),
+            this.lat, this.lon) <= distanceKey;
+      } else {
+        return relation == Relation.CELL_INSIDE_QUERY.ordinal();
+      }
+    }
+  }
+
+  /** A predicate that checks whether a given point is within a polygon. */
+  public static class PolygonPredicate extends Grid {
+
+    private final Polygon2D tree;
+
+    private PolygonPredicate(
+        int latShift, int lonShift,
+        int latBase, int lonBase,
+        int maxLatDelta, int maxLonDelta,
+        byte[] relations,
+        Polygon2D tree) {
+      super(latShift, lonShift, latBase, lonBase, maxLatDelta, maxLonDelta, relations);
+      this.tree = tree;
+    }
+
+    /** Check whether the given point is within the considered polygon.
+     *  NOTE: this operates directly on the encoded representation of points. */
+    public boolean test(int lat, int lon) {
+      final int lat2 = ((lat - Integer.MIN_VALUE) >>> latShift);
+      if (lat2 < latBase || lat2 >= latBase + maxLatDelta) {
+        return false;
+      }
+      int lon2 = ((lon - Integer.MIN_VALUE) >>> lonShift);
+      if (lon2 < lonBase) { // wrap
+        lon2 += 1 << (32 - lonShift);
+      }
+      assert Integer.toUnsignedLong(lon2) >= lonBase;
+      assert lon2 - lonBase >= 0;
+      if (lon2 - lonBase >= maxLonDelta) {
+        return false;
+      }
+
+      final int relation = relations[(lat2 - latBase) * maxLonDelta + (lon2 - lonBase)];
+      if (relation == Relation.CELL_CROSSES_QUERY.ordinal()) {
+        return tree.contains(decodeLatitude(lat), decodeLongitude(lon));
+      } else {
+        return relation == Relation.CELL_INSIDE_QUERY.ordinal();
+      }
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/geo/GeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/geo/GeoUtils.java b/lucene/core/src/java/org/apache/lucene/geo/GeoUtils.java
index 723cbaf..aaca549 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/GeoUtils.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/GeoUtils.java
@@ -20,6 +20,10 @@ import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
 import static org.apache.lucene.util.SloppyMath.cos;
 import static org.apache.lucene.util.SloppyMath.haversinMeters;
 
+import org.apache.lucene.index.PointValues;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.util.SloppyMath;
+
 /**
  * Basic reusable geo-spatial utility methods
  *
@@ -124,4 +128,50 @@ public final class GeoUtils {
     assert haversinMeters(ceil) > radius;
     return ceil;
   }
+
+  /**
+   * Compute the relation between the provided box and distance query.
+   * This only works for boxes that do not cross the dateline.
+   */
+  public static PointValues.Relation relate(
+      double minLat, double maxLat, double minLon, double maxLon,
+      double lat, double lon, double distanceSortKey, double axisLat) {
+
+    if (minLon > maxLon) {
+      throw new IllegalArgumentException("Box crosses the dateline");
+    }
+
+    if ((lon < minLon || lon > maxLon) && (axisLat + Rectangle.AXISLAT_ERROR < minLat || axisLat - Rectangle.AXISLAT_ERROR > maxLat)) {
+      // circle not fully inside / crossing axis
+      if (SloppyMath.haversinSortKey(lat, lon, minLat, minLon) > distanceSortKey &&
+          SloppyMath.haversinSortKey(lat, lon, minLat, maxLon) > distanceSortKey &&
+          SloppyMath.haversinSortKey(lat, lon, maxLat, minLon) > distanceSortKey &&
+          SloppyMath.haversinSortKey(lat, lon, maxLat, maxLon) > distanceSortKey) {
+        // no points inside
+        return Relation.CELL_OUTSIDE_QUERY;
+      }
+    }
+
+    if (within90LonDegrees(lon, minLon, maxLon) &&
+        SloppyMath.haversinSortKey(lat, lon, minLat, minLon) <= distanceSortKey &&
+        SloppyMath.haversinSortKey(lat, lon, minLat, maxLon) <= distanceSortKey &&
+        SloppyMath.haversinSortKey(lat, lon, maxLat, minLon) <= distanceSortKey &&
+        SloppyMath.haversinSortKey(lat, lon, maxLat, maxLon) <= distanceSortKey) {
+      // we are fully enclosed, collect everything within this subtree
+      return Relation.CELL_INSIDE_QUERY;
+    }
+
+    return Relation.CELL_CROSSES_QUERY;
+  }
+
+  /** Return whether all points of {@code [minLon,maxLon]} are within 90 degrees of {@code lon}. */
+  static boolean within90LonDegrees(double lon, double minLon, double maxLon) {
+    if (maxLon <= lon - 180) {
+      lon -= 360;
+    } else if (minLon >= lon + 180) {
+      lon += 360;
+    }
+    return maxLon - lon < 90 && lon - minLon < 90;
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/index/FieldInfo.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/FieldInfo.java b/lucene/core/src/java/org/apache/lucene/index/FieldInfo.java
index 4e02320..422292b 100644
--- a/lucene/core/src/java/org/apache/lucene/index/FieldInfo.java
+++ b/lucene/core/src/java/org/apache/lucene/index/FieldInfo.java
@@ -203,7 +203,8 @@ public final class FieldInfo {
     return pointNumBytes;
   }
 
-  void setDocValuesType(DocValuesType type) {
+  /** Record that this field is indexed with docvalues, with the specified type */
+  public void setDocValuesType(DocValuesType type) {
     if (type == null) {
       throw new NullPointerException("DocValuesType must not be null (field: \"" + name + "\")");
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/index/FieldInfos.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/FieldInfos.java b/lucene/core/src/java/org/apache/lucene/index/FieldInfos.java
index 7c3e23c..890dcca 100644
--- a/lucene/core/src/java/org/apache/lucene/index/FieldInfos.java
+++ b/lucene/core/src/java/org/apache/lucene/index/FieldInfos.java
@@ -20,6 +20,7 @@ package org.apache.lucene.index;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
@@ -326,9 +327,9 @@ public class FieldInfos implements Iterable<FieldInfo> {
     }
     
     synchronized Set<String> getFieldNames() {
-      return Collections.unmodifiableSet(nameToNumber.keySet());
+      return Collections.unmodifiableSet(new HashSet<String>(nameToNumber.keySet()));
     }
-    
+
     synchronized void clear() {
       numberToName.clear();
       nameToNumber.clear();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/index/FilterCodecReader.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/FilterCodecReader.java b/lucene/core/src/java/org/apache/lucene/index/FilterCodecReader.java
index 48cbda7..c0ea8fc 100644
--- a/lucene/core/src/java/org/apache/lucene/index/FilterCodecReader.java
+++ b/lucene/core/src/java/org/apache/lucene/index/FilterCodecReader.java
@@ -17,6 +17,8 @@
 package org.apache.lucene.index;
 
 
+import java.io.IOException;
+import java.util.Collection;
 import java.util.Objects;
 
 import org.apache.lucene.codecs.DocValuesProducer;
@@ -26,6 +28,7 @@ import org.apache.lucene.codecs.PointsReader;
 import org.apache.lucene.codecs.StoredFieldsReader;
 import org.apache.lucene.codecs.TermVectorsReader;
 import org.apache.lucene.search.Sort;
+import org.apache.lucene.util.Accountable;
 import org.apache.lucene.util.Bits;
 
 /** 
@@ -111,4 +114,25 @@ public abstract class FilterCodecReader extends CodecReader {
   public void removeCoreClosedListener(CoreClosedListener listener) {
     in.removeCoreClosedListener(listener);
   }
+
+  @Override
+  protected void doClose() throws IOException {
+    in.doClose();
+  }
+
+  @Override
+  public long ramBytesUsed() {
+    return in.ramBytesUsed();
+  }
+
+  @Override
+  public Collection<Accountable> getChildResources() {
+    return in.getChildResources();
+  }
+
+  @Override
+  public void checkIntegrity() throws IOException {
+    in.checkIntegrity();
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/index/IndexReaderContext.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/IndexReaderContext.java b/lucene/core/src/java/org/apache/lucene/index/IndexReaderContext.java
index 247fa57..dada3ff 100644
--- a/lucene/core/src/java/org/apache/lucene/index/IndexReaderContext.java
+++ b/lucene/core/src/java/org/apache/lucene/index/IndexReaderContext.java
@@ -32,7 +32,12 @@ public abstract class IndexReaderContext {
   public final int docBaseInParent;
   /** the ord for this reader in the parent, <tt>0</tt> if parent is null */
   public final int ordInParent;
-  
+
+  // An object that uniquely identifies this context without referencing
+  // segments. The goal is to make it fine to have references to this
+  // identity object, even after the index reader has been closed
+  final Object identity = new Object();
+
   IndexReaderContext(CompositeReaderContext parent, int ordInParent, int docBaseInParent) {
     if (!(this instanceof CompositeReaderContext || this instanceof LeafReaderContext))
       throw new Error("This class should never be extended by custom code!");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java b/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
index 53347ad..cbf2ae2 100644
--- a/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
+++ b/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
@@ -1621,8 +1621,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
   public long updateNumericDocValue(Term term, String field, long value) throws IOException {
     ensureOpen();
     if (!globalFieldNumberMap.contains(field, DocValuesType.NUMERIC)) {
-      throw new IllegalArgumentException("can only update existing numeric-docvalues fields! Attempted"
-          + " to update field: " + field + "=" + value);
+      throw new IllegalArgumentException("can only update existing numeric-docvalues fields!");
     }
     if (config.getIndexSortFields().contains(field)) {
       throw new IllegalArgumentException("cannot update docvalues field involved in the index sort, field=" + field + ", sort=" + config.getIndexSort());
@@ -1785,11 +1784,19 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
   }
 
   /**
-   * Return a set of all field names as seen by this IndexWriter, across all segments
-   * of the index.
+   * Return an unmodifiable set of all field names as visible
+   * from this IndexWriter, across all segments of the index.
+   * Useful for knowing which fields exist, before {@link #updateDocValues(Term, Field...)} is
+   * attempted. We could phase out this method if
+   * {@link #updateDocValues(Term, Field...)} could create the non-existent
+   * docValues fields as necessary, instead of throwing
+   * IllegalArgumentException for attempts to update non-existent
+   * docValues fields.
+   * @lucene.internal
+   * @lucene.experimental
    */
   public Set<String> getFieldNames() {
-    return Collections.unmodifiableSet(globalFieldNumberMap.getFieldNames());
+    return globalFieldNumberMap.getFieldNames(); // FieldNumbers#getFieldNames() returns an unmodifiableSet
   }
 
   final String newSegmentName() {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/index/PointValues.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/PointValues.java b/lucene/core/src/java/org/apache/lucene/index/PointValues.java
index 01f77e4..dab9140 100644
--- a/lucene/core/src/java/org/apache/lucene/index/PointValues.java
+++ b/lucene/core/src/java/org/apache/lucene/index/PointValues.java
@@ -211,8 +211,7 @@ public abstract class PointValues {
      *  determine how to further recurse down the tree. */
     Relation compare(byte[] minPackedValue, byte[] maxPackedValue);
 
-    /** Notifies the caller that this many documents (from one block) are about
-     *  to be visited */
+    /** Notifies the caller that this many documents are about to be visited */
     default void grow(int count) {};
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/index/SegmentCoreReaders.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/SegmentCoreReaders.java b/lucene/core/src/java/org/apache/lucene/index/SegmentCoreReaders.java
index 21ac4a1..270a2d5 100644
--- a/lucene/core/src/java/org/apache/lucene/index/SegmentCoreReaders.java
+++ b/lucene/core/src/java/org/apache/lucene/index/SegmentCoreReaders.java
@@ -17,7 +17,10 @@
 package org.apache.lucene.index;
 
 
+import java.io.EOFException;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.file.NoSuchFileException;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.Set;
@@ -133,6 +136,10 @@ final class SegmentCoreReaders {
         pointsReader = null;
       }
       success = true;
+    } catch (EOFException | FileNotFoundException e) {
+      throw new CorruptIndexException("Problem reading index from " + dir, dir.toString(), e);
+    } catch (NoSuchFileException e) {
+      throw new CorruptIndexException("Problem reading index.", e.getFile(), e);
     } finally {
       if (!success) {
         decRef();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java b/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
index 3e8b1f8..aaa1d89 100644
--- a/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
+++ b/lucene/core/src/java/org/apache/lucene/index/SegmentInfos.java
@@ -18,8 +18,10 @@ package org.apache.lucene.index;
 
 
 import java.io.EOFException;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.nio.file.NoSuchFileException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -280,8 +282,8 @@ public final class SegmentInfos implements Cloneable, Iterable<SegmentCommitInfo
     try (ChecksumIndexInput input = directory.openChecksumInput(segmentFileName, IOContext.READ)) {
       try {
         return readCommit(directory, input, generation);
-      } catch (EOFException e) {
-        throw new CorruptIndexException("Unexpected end of file while reading index.", input, e);
+      } catch (EOFException | NoSuchFileException | FileNotFoundException e) {
+        throw new CorruptIndexException("Unexpected file read error while reading index.", input, e);
       }
     }
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/index/TermContext.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/TermContext.java b/lucene/core/src/java/org/apache/lucene/index/TermContext.java
index e55aeba..ed25564 100644
--- a/lucene/core/src/java/org/apache/lucene/index/TermContext.java
+++ b/lucene/core/src/java/org/apache/lucene/index/TermContext.java
@@ -33,12 +33,8 @@ import java.util.Arrays;
  */
 public final class TermContext {
 
-  /** Holds the {@link IndexReaderContext} of the top-level
-   *  {@link IndexReader}, used internally only for
-   *  asserting.
-   *
-   *  @lucene.internal */
-  public final IndexReaderContext topReaderContext;
+  // Important: do NOT keep hard references to index readers
+  private final Object topReaderContextIdentity;
   private final TermState[] states;
   private int docFreq;
   private long totalTermFreq;
@@ -50,7 +46,7 @@ public final class TermContext {
    */
   public TermContext(IndexReaderContext context) {
     assert context != null && context.isTopLevel;
-    topReaderContext = context;
+    topReaderContextIdentity = context.identity;
     docFreq = 0;
     totalTermFreq = 0;
     final int len;
@@ -61,7 +57,16 @@ public final class TermContext {
     }
     states = new TermState[len];
   }
-  
+
+  /**
+   * Expert: Return whether this {@link TermContext} was built for the given
+   * {@link IndexReaderContext}. This is typically used for assertions.
+   * @lucene.internal
+   */
+  public boolean wasBuiltFor(IndexReaderContext context) {
+    return topReaderContextIdentity == context.identity;
+  }
+
   /**
    * Creates a {@link TermContext} with an initial {@link TermState},
    * {@link IndexReader} pair.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/search/BlendedTermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/BlendedTermQuery.java b/lucene/core/src/java/org/apache/lucene/search/BlendedTermQuery.java
index 85b8b0a..3a0cdc5 100644
--- a/lucene/core/src/java/org/apache/lucene/search/BlendedTermQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/BlendedTermQuery.java
@@ -22,6 +22,7 @@ import java.util.Arrays;
 import java.util.List;
 
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexReaderContext;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermContext;
@@ -264,7 +265,7 @@ public final class BlendedTermQuery extends Query {
   public final Query rewrite(IndexReader reader) throws IOException {
     final TermContext[] contexts = Arrays.copyOf(this.contexts, this.contexts.length);
     for (int i = 0; i < contexts.length; ++i) {
-      if (contexts[i] == null || contexts[i].topReaderContext != reader.getContext()) {
+      if (contexts[i] == null || contexts[i].wasBuiltFor(reader.getContext()) == false) {
         contexts[i] = TermContext.build(reader.getContext(), terms[i]);
       }
     }
@@ -284,7 +285,7 @@ public final class BlendedTermQuery extends Query {
     }
 
     for (int i = 0; i < contexts.length; ++i) {
-      contexts[i] = adjustFrequencies(contexts[i], df, ttf);
+      contexts[i] = adjustFrequencies(reader.getContext(), contexts[i], df, ttf);
     }
 
     Query[] termQueries = new Query[terms.length];
@@ -297,15 +298,16 @@ public final class BlendedTermQuery extends Query {
     return rewriteMethod.rewrite(termQueries);
   }
 
-  private static TermContext adjustFrequencies(TermContext ctx, int artificialDf, long artificialTtf) {
-    List<LeafReaderContext> leaves = ctx.topReaderContext.leaves();
+  private static TermContext adjustFrequencies(IndexReaderContext readerContext,
+      TermContext ctx, int artificialDf, long artificialTtf) {
+    List<LeafReaderContext> leaves = readerContext.leaves();
     final int len;
     if (leaves == null) {
       len = 1;
     } else {
       len = leaves.size();
     }
-    TermContext newCtx = new TermContext(ctx.topReaderContext);
+    TermContext newCtx = new TermContext(readerContext);
     for (int i = 0; i < len; ++i) {
       TermState termState = ctx.get(i);
       if (termState == null) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/search/TermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/TermQuery.java b/lucene/core/src/java/org/apache/lucene/search/TermQuery.java
index 73170b9..e3e299f 100644
--- a/lucene/core/src/java/org/apache/lucene/search/TermQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/TermQuery.java
@@ -86,7 +86,7 @@ public class TermQuery extends Query {
 
     @Override
     public Scorer scorer(LeafReaderContext context) throws IOException {
-      assert termStates == null || termStates.topReaderContext == ReaderUtil.getTopLevelContext(context) : "The top-reader used to create Weight (" + termStates.topReaderContext + ") is not the same as the current reader's top-reader (" + ReaderUtil.getTopLevelContext(context);;
+      assert termStates == null || termStates.wasBuiltFor(ReaderUtil.getTopLevelContext(context)) : "The top-reader used to create Weight is not the same as the current reader's top-reader (" + ReaderUtil.getTopLevelContext(context);;
       final TermsEnum termsEnum = getTermsEnum(context);
       if (termsEnum == null) {
         return null;
@@ -103,7 +103,7 @@ public class TermQuery extends Query {
     private TermsEnum getTermsEnum(LeafReaderContext context) throws IOException {
       if (termStates != null) {
         // TermQuery either used as a Query or the term states have been provided at construction time
-        assert termStates.topReaderContext == ReaderUtil.getTopLevelContext(context) : "The top-reader used to create Weight (" + termStates.topReaderContext + ") is not the same as the current reader's top-reader (" + ReaderUtil.getTopLevelContext(context);
+        assert termStates.wasBuiltFor(ReaderUtil.getTopLevelContext(context)) : "The top-reader used to create Weight is not the same as the current reader's top-reader (" + ReaderUtil.getTopLevelContext(context);
         final TermState state = termStates.get(context.ord);
         if (state == null) { // term is not present in that reader
           assert termNotInReader(context.reader(), term) : "no termstate found but term exists in reader term=" + term;
@@ -181,7 +181,7 @@ public class TermQuery extends Query {
     final IndexReaderContext context = searcher.getTopReaderContext();
     final TermContext termState;
     if (perReaderTermState == null
-        || perReaderTermState.topReaderContext != context) {
+        || perReaderTermState.wasBuiltFor(context) == false) {
       if (needsScores) {
         // make TermQuery single-pass if we don't have a PRTS or if the context
         // differs!

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/search/doc-files/nrq-formula-1.png
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/doc-files/nrq-formula-1.png b/lucene/core/src/java/org/apache/lucene/search/doc-files/nrq-formula-1.png
deleted file mode 100644
index fd7d936..0000000
Binary files a/lucene/core/src/java/org/apache/lucene/search/doc-files/nrq-formula-1.png and /dev/null differ

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/search/doc-files/nrq-formula-2.png
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/doc-files/nrq-formula-2.png b/lucene/core/src/java/org/apache/lucene/search/doc-files/nrq-formula-2.png
deleted file mode 100644
index 93cb308..0000000
Binary files a/lucene/core/src/java/org/apache/lucene/search/doc-files/nrq-formula-2.png and /dev/null differ

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/lucene/core/src/java/org/apache/lucene/search/spans/SpanTermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/spans/SpanTermQuery.java b/lucene/core/src/java/org/apache/lucene/search/spans/SpanTermQuery.java
index 2746a0c..3e13be7 100644
--- a/lucene/core/src/java/org/apache/lucene/search/spans/SpanTermQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/spans/SpanTermQuery.java
@@ -67,7 +67,7 @@ public class SpanTermQuery extends SpanQuery {
   public SpanWeight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
     final TermContext context;
     final IndexReaderContext topContext = searcher.getTopReaderContext();
-    if (termContext == null || termContext.topReaderContext != topContext) {
+    if (termContext == null || termContext.wasBuiltFor(topContext) == false) {
       context = TermContext.build(topContext, term);
     }
     else {
@@ -99,7 +99,7 @@ public class SpanTermQuery extends SpanQuery {
     @Override
     public Spans getSpans(final LeafReaderContext context, Postings requiredPostings) throws IOException {
 
-      assert termContext.topReaderContext == ReaderUtil.getTopLevelContext(context) : "The top-reader used to create Weight (" + termContext.topReaderContext + ") is not the same as the current reader's top-reader (" + ReaderUtil.getTopLevelContext(context);
+      assert termContext.wasBuiltFor(ReaderUtil.getTopLevelContext(context)) : "The top-reader used to create Weight is not the same as the current reader's top-reader (" + ReaderUtil.getTopLevelContext(context);
 
       final TermState state = termContext.get(context.ord);
       if (state == null) { // term is not present in that reader


[08/18] lucene-solr:jira/solr-5944: Updating branch by merging latest changes from master

Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.security.RuleBasedAuthorization.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.security.RuleBasedAuthorization.json b/solr/core/src/resources/apispec/cluster.security.RuleBasedAuthorization.json
new file mode 100644
index 0000000..eb9a11c
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.security.RuleBasedAuthorization.json
@@ -0,0 +1,129 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Rule-Based+Authorization+Plugin",
+  "description": "Defines roles for accessing Solr, and assigns users to those roles. Use this API to change user authorizations to each of Solr's components.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/security/authorization"
+    ]
+  },
+  "commands": {
+    "set-permission": {
+      "type":"object",
+      "description": "Create a new permission, overwrite an existing permission definition, or assign a pre-defined permission to a role.",
+      "properties": {
+        "name":{
+          "type":"string",
+          "description": "The name of the permission. The name will be used to update or delete the permission later."
+        },
+        "method":{
+          "type":"string",
+          "enum":["GET", "POST", "DELETE","PUT"],
+          "description": "HTTP methods that are allowed for this permission. You could allow only GET requests, or have a role that allows PUT and POST requests. The method values that are allowed for this property are GET, POST, PUT, DELETE and HEAD."
+        },
+
+        "collection":{
+          "type":"array",
+          "items": {
+            "type": "string"
+          },
+          "description":"The collection or collections the permission will apply to. When the path that will be allowed is collection-specific, such as when setting permissions to allow use of the Schema API, omitting the collection property will allow the defined path and/or method for all collections. However, when the path is one that is non-collection-specific, such as the Collections API, the collection value must be null. In this case, two permissions may need to be created; one for collection-specific API paths allowing access to all collections, and another for non-collection-specific paths defining no collection limitations."
+        },
+
+        "path":{
+          "type":"array",
+          "items": {
+            "type": "string"
+          },
+          "description":"A request handler name, such as /update or /select. A wild card is supported, to allow for all paths as appropriate (such as, /update/*)."
+        },
+        "index": {
+          "type": "integer",
+          "description": "The index of the permission you wish to overwrite. Skip this if it is a new permission that should be created."
+        },
+        "before":{
+          "type": "integer",
+          "description":"This property allows ordering of permissions. The value for this property is the name of the permission that this new permission should be placed before in security.json."
+        },
+        "params":{
+          "type":"object",
+          "additionalProperties":true,
+          "description": "The names and values of request parameters. This property can be omitted if all request parameters are allowed, but will restrict access only to the values provided if defined."
+        },
+        "role": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "description": "The name of the role(s) to give this permission. This name will be used to map user IDs to the role to grant these permissions. The value can be wildcard such as (*), which means that any user is OK, but no user is NOT OK."
+          }
+        }
+      },
+      "required": [
+        "role"
+      ]
+    },
+    "update-permission": {
+      "type":"object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The name of the permission. The name will be used to update or delete the permission later."
+        },
+        "method": {
+          "type": "string",
+          "description": "HTTP methods that are allowed for this permission. You could allow only GET requests, or have a role that allows PUT and POST requests. The method values that are allowed for this property are GET, POST, PUT, DELETE and HEAD."
+        },
+        "collection": {
+          "type":"array",
+          "items": {
+            "type": "string"
+          },
+          "description": "The collection or collections the permission will apply to. When the path that will be allowed is collection-specific, such as when setting permissions to allow use of the Schema API, omitting the collection property will allow the defined path and/or method for all collections. However, when the path is one that is non-collection-specific, such as the Collections API, the collection value must be null. In this case, two permissions may need to be created; one for collection-specific API paths allowing access to all collections, and another for non-collection-specific paths defining no collection limitations."
+        },
+        "path": {
+          "type":"array",
+          "items": {
+            "type": "string"
+          },
+          "description": "A request handler name, such as /update or /select. A wild card is supported, to allow for all paths as appropriate (such as, /update/*)."
+        },
+        "index": {
+          "type": "integer",
+          "description": "The index of the permission you wish to overwrite."
+        },
+        "before": {
+          "type": "integer",
+          "description": "This property allows ordering of permissions. The value for this property is the index of the permission that this new permission should be placed before in security.json."
+        },
+        "role": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "description": "The name of the role(s) to give this permission. This name will be used to map user IDs to the role to grant these permissions. The value can be wildcard such as (*), which means that any user is OK, but no user is NOT OK."
+          }
+        },
+        "params": {
+          "type": "object",
+          "additionalProperties": true,
+          "description": "The names and values of request parameters. This property can be omitted if all request parameters are allowed, but will restrict access only to the values provided if defined."
+        }
+      },
+      "required": [
+        "role",
+        "index"
+      ]
+    },
+    "delete-permission":{
+      "description":"delete a permission by its index",
+      "type":"integer"
+    },
+    "set-user-role": {
+      "type":"object",
+      "description": "A single command allows roles to be mapped to users. To remove a user's permission, you should set the role to null. The key is always a user id and the value is one or more role names.",
+      "additionalProperties":true
+
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.security.authentication.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.security.authentication.Commands.json b/solr/core/src/resources/apispec/cluster.security.authentication.Commands.json
new file mode 100644
index 0000000..e1f9030
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.security.authentication.Commands.json
@@ -0,0 +1,12 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Securing+Solr",
+  "description":"This is a placeholder output when no authentication is configured",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/security/authentication"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.security.authentication.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.security.authentication.json b/solr/core/src/resources/apispec/cluster.security.authentication.json
new file mode 100644
index 0000000..48757c3
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.security.authentication.json
@@ -0,0 +1,12 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Authentication+and+Authorization+Plugins",
+  "description": "Shows the configuration for authentication, including users, classes (type of authentication) and other parameters.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/security/authentication"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.security.authorization.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.security.authorization.Commands.json b/solr/core/src/resources/apispec/cluster.security.authorization.Commands.json
new file mode 100644
index 0000000..fe74065
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.security.authorization.Commands.json
@@ -0,0 +1,13 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Securing+Solr",
+  "description":"This is a placeholder output when no authorization is configured",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/security/authorization"
+    ]
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/cluster.security.authorization.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.security.authorization.json b/solr/core/src/resources/apispec/cluster.security.authorization.json
new file mode 100644
index 0000000..da09f8a
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.security.authorization.json
@@ -0,0 +1,13 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Authentication+and+Authorization+Plugins",
+  "description":"Shows the configuration for authorization, including the classes (type of authorization), permissions, user-roles, and other parameters.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/security/authorization"
+    ]
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.Commands.json b/solr/core/src/resources/apispec/collections.Commands.json
new file mode 100644
index 0000000..60ddd4e
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.Commands.json
@@ -0,0 +1,206 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api1",
+  "description": "Create collections and collection aliases, backup or restore collections, and delete collections and aliases.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/collections",
+      "/c"
+    ]
+  },
+  "commands": {
+    "create": {
+      "type": "object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api1",
+      "description": "Create a collection.",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The name of the collection to be created."
+        },
+        "config": {
+          "type": "string",
+          "description": "The name of the configuration set (which must already be stored in ZooKeeper) to use for this collection. If not provided, Solr will default to the collection name as the configuration set name."
+        },
+        "router": {
+          "type": "object",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Shards+and+Indexing+Data+in+SolrCloud",
+          "description": "These properties define how to distribute documents across a collection's shards.",
+          "properties": {
+            "name": {
+              "type": "string",
+              "enum":["implicit","compositeId"],
+              "description": "The router implementation to use for this collection. There are two options: compositeId or implicit. The compositeId option has Solr decide how to distribute documents (with some possibilities for customization). The implicit option requires you define your own routing strategy, and puts the balancing of documents in shards entirely in your hands.",
+              "default": "compositeId"
+            },
+            "field": {
+              "type": "string",
+              "description": "A field to be used by Solr to identify the shard a document should be routed to. By default, the field defined as the unique ID for each document is used, but an alternative field can be defined with this parameter."
+            }
+          }
+        },
+        "numShards": {
+          "type": "number",
+          "description": "The number of shards to be created as part of the collection. Shards are logical partitions of a single collection. Each shard has at least one replica, but more replicas for each shard can be defined with the replicationFactor property. This is a required parameter when using the 'compositeId' router."
+        },
+        "shards": {
+          "type": "string",
+          "description": "A comma-separated list of shard names, e.g., shard-x,shard-y,shard-z. This is a required parameter when using the 'implicit' router."
+        },
+        "replicationFactor": {
+          "type": "number",
+          "description": "The number of replicas to be created for each shard. Replicas are physical copies of each shard, acting as failover for the shard."
+        },
+        "nodeSet": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "description": "Defines nodes to spread the new collection across. If not provided, the collection will be spread across all live Solr nodes. The names to use are the 'node_name', which can be found by a request to the cluster/nodes endpoint. A special value of EMPTY will create no shards or replicas for the new collection. In this case, shards and replicas can be added later with the add-replica command available on the /collections/{collection}/shards endpoint."
+        },
+        "shuffleNodes": {
+          "type": "boolean",
+          "description": "Controls whether or not the shard-replicas created for this collection will be assigned to the nodes specified by the nodeSet property in a sequential manner, or if the list of nodes should be shuffled prior to creating individual replicas. A 'false' value makes the results of a collection creation predictable and gives more exact control over the location of the individual shard-replicas, but 'true' can be a better choice for ensuring replicas are distributed evenly across nodes. This property is ignored if nodeSet is not also specified."
+        },
+        "maxShardsPerNode": {
+          "type": "integer",
+          "description": "When creating collections, the shards and/or replicas are spread across all available, live, nodes, and two replicas of the same shard will never be on the same node. If a node is not live when the collection is created, it will not get any parts of the new collection, which could lead to too many replicas being created on a single live node. Defining maxShardsPerNode sets a limit on the number of replicas can be spread to each node. If the entire collection can not be fit into the live nodes, no collection will be created at all."
+        },
+        "autoAddReplicas": {
+          "type": "boolean",
+          "description": "When set to true, enables auto addition of replicas on shared file systems (such as HDFS). See https://cwiki.apache.org/confluence/display/solr/Running+Solr+on+HDFS for more details on settings and overrides.",
+          "default": "false"
+        },
+        "rule": {
+          "type": "array",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Rule-based+Replica+Placement",
+          "description": "Defines rules for where replicas should be located in a cluster.",
+          "items": {
+            "type": "string"
+          }
+        },
+        "snitch": {
+          "type": "array",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Rule-based+Replica+Placement",
+          "description": "",
+          "items": {
+            "type": "string"
+          }
+        },
+        "properties": {
+          "type": "object",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Defining+core.properties",
+          "description": "Allows adding core.properties for the collection. Some examples of core properties you may want to modify include the config set, the node name, the data directory, among others.",
+          "additionalProperties": true
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously."
+        }
+      },
+      "required": [
+        "name"
+      ]
+    },
+    "create-alias": {
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api4",
+      "description": "Allows one or more collections to be known by another name. If this command is used on an existing alias, the existing alias will be replaced with the new collection details.",
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The alias name to be created."
+        },
+        "collections": {
+          "type": "array",
+          "description": "The list of collections to be known as this alias.",
+          "items": {
+            "type": "string"
+          }
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously."
+        }
+      },
+      "required": [
+        "name",
+        "collections"
+      ]
+    },
+    "delete-alias": {
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api5",
+      "description": "Deletes a collection alias",
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The name of the alias to delete."
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously."
+        }
+      },
+      "required":["name"]
+    },
+    "backup-collection": {
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-Backup",
+      "description": "Backup Solr indexes and configurations for a specific collection. One copy of the indexes will be taken from each shard, and the config set for the collection will also be copied.",
+      "type": "object",
+      "properties": {
+        "collection": {
+          "type": "string",
+          "description": "The name of the collection to back up."
+        },
+        "name": {
+          "type": "string",
+          "description": "The name of the backup."
+        },
+        "location": {
+          "type": "string",
+          "description": "A location on a shared drive for the backup-collection command to write to. Alternately, it can be set as a cluster property with the cluster endpoint, which also supports setting a location."
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously."
+        }
+      },
+      "required": [
+        "collection",
+        "name",
+        "location"
+      ]
+    },
+    "restore-collection": {
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-Restore",
+      "description": "Restore Solr indexes and configurations from a backup. You cannot restore into the same collection you took the backup from. The target collection must not exist before calling this command, as it will be created by the restore action. The new collection will have the same number of shards and replicas as the original collection, and all routing strategies will be retained.",
+      "type": "object",
+      "properties": {
+        "collection": {
+          "type": "string",
+          "description": "The name of the collection the backup will be restored to. This collection must not exist prior to this "
+        },
+        "name": {
+          "type": "string",
+          "description": "The name of the backup file."
+        },
+        "location": {
+          "type": "string",
+          "description": "The location on the shared drive for the restore-collection command to read from. Alternately, it can be set as a cluster property with the cluster endpoint, which also supports setting a location."
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously."
+        }
+      },
+      "required": [
+        "collection",
+        "name",
+        "location"
+      ]
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.collection.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.collection.Commands.json b/solr/core/src/resources/apispec/collections.collection.Commands.json
new file mode 100644
index 0000000..eb3b1da
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.collection.Commands.json
@@ -0,0 +1,137 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API",
+  "description": "Several collection-level operations are supported with this endpoint: modify collection attributes; reload a collection; migrate documents to a different collection; rebalance collection leaders; balance properties across shards; and add or delete a replica property.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/collections/{collection}",
+      "/c/{collection}"
+    ]
+  },
+  "commands": {
+    "modify": {
+      "#include": "collections.collection.Commands.modify"
+    },
+    "reload": {
+      "#include": "collections.collection.Commands.reload"
+    },
+    "migrate-docs":{
+      "type":"object",
+      "documentation":"https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api12",
+      "description": "Moves documents with a given routing key to another collection. The source collection will continue to have the same documents, but will start re-routing write requests to the new collection. This command only works on collections using the 'compositeId' type of document routing.",
+      "properties":{
+        "target":{
+          "type":"string",
+          "description":"The name of the collection to which documents will be migrated."
+        },
+        "splitKey":{
+          "type":"string",
+          "description":"The routing key prefix. For example, if uniqueKey is a!123, then you would use split.key=a! This key may span multiple shards on source and target collections. The migration will be completed shard-by-shard in a single thread."
+        },
+        "forwardTimeout":{
+          "type":"integer",
+          "description":"The timeout, in seconds, until which write requests made to the source collection for the given splitKey will be forwarded to the target shard. Once this time is up, write requests will be routed to the target collection. Any applications sending read or write requests should be modified once the migration is complete to send documents to the right collection.",
+          "default": "60"
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined. This command can be long-running, so running it asynchronously is recommended."
+        }
+      },
+      "required":["target", "splitKey"]
+    },
+    "balance-shard-unique":{
+      "type":"object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-BalanceSliceUnique",
+      "description": "Insures a property is distributed equally across all physical nodes of a collection. If the property already exists on a replica, effort is made to leave it there. However, if it does not exist on any repica, a shard will be chosen and the property added.",
+      "properties":{
+        "property":{
+          "type":"string",
+          "description": "The property to balance across nodes. This can be entered as 'property.<property>' or simply '<property>'. If the 'property.' prefix is not defined, it will be added automatically."
+       },
+        "onlyactivenodes":{
+          "type":"boolean",
+          "description": "Normally, a property is instantiated on active nodes only. If this parameter is specified as 'false', then inactive nodes are also included for distribution.",
+          "default": "true"
+        },
+        "shardUnique":{
+          "type":"boolean",
+          "description": "There is one pre-defined property (preferredLeader) that defaults this value to 'true'. For all other properties that are balanced, this must be set to 'true' or an error message is returned."
+        }
+      },
+      "required":["property"]
+    },
+    "rebalance-leaders" :{
+      "type":"object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-RebalanceLeaders",
+      "description": "Reassign leaders in a collection according to the preferredLeader property across active nodes. This command should be run after the preferredLeader property has been set with the balance-shards or add-replica-property commands.",
+      "properties":{
+        "maxAtOnce":{
+          "type":"number",
+          "description":"The maximum number of reassignments to have in the queue at one time. Values <=0 use the default value Integer.MAX_VALUE. When this number is reached, the process waits for one or more leaders to be successfully assigned before adding more to the queue."
+        },
+        "maxWaitSeconds":{
+          "type":"number",
+          "description":"Timeout, in seconds, when waiting for leaders to be reassigned. If maxAtOnce is less than the number of reassignments pending, this is the maximum interval for any single reassignment.",
+          "default": "60"
+        }
+      }
+    },
+    "add-replica-property": {
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-AddReplicaProp",
+      "description": "Assign an arbitrary property to a particular replica and give it the value specified. If the property already exists, it will be overwritten with the new value.",
+      "type": "object",
+      "properties": {
+        "shard": {
+          "type": "string",
+          "description": "The name of the shard the replica belongs to."
+        },
+        "replica": {
+          "type": "string",
+          "description": "The name of the replica."
+        },
+        "name": {
+          "type": "string",
+          "description": "The name of the property. This can be entered as 'property.<property>' or simply '<property>'. If the 'property.' prefix is not defined, it will be added automatically."
+        },
+        "value": {
+          "type": "string",
+          "description": "The value to assign to the property."
+        },
+        "shardUnique": {
+          "type": "boolean",
+          "description": "If true, setting this property in one replica will remove the property from all other replicas in that shard.",
+          "default": "false"
+        }
+      },
+      "required": [
+        "name",
+        "value",
+        "shard",
+        "replica"
+      ]
+    },
+    "delete-replica-property": {
+      "description": "Deletes an arbitrary property from a particular replica",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-DeleteReplicaProp",
+      "type": "object",
+      "properties": {
+        "shard": {
+          "type": "string",
+          "description": "The name of the shard the replica belongs to."
+        },
+        "replica": {
+          "type": "string",
+          "description": "The name of the replica."
+        },
+        "property": {
+          "type": "string",
+          "description": "The name of the property to remove."
+        }
+      },
+      "required":["shard","replica","property"]
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.collection.Commands.modify.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.collection.Commands.modify.json b/solr/core/src/resources/apispec/collections.collection.Commands.modify.json
new file mode 100644
index 0000000..0255329
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.collection.Commands.modify.json
@@ -0,0 +1,36 @@
+{
+  "documentation":"https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-modifycoll",
+  "description":"Modifies specific attributes of a collection. Multiple attributes can be changed at one time.",
+  "type": "object",
+  "properties":{
+    "rule": {
+      "type": "array",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Rule-based+Replica+Placement",
+      "description": "Modifies the rules for where replicas should be located in a cluster.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "snitch": {
+      "type": "array",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Rule-based+Replica+Placement",
+      "description": "Details of the snitch provider",
+      "items": {
+        "type": "string"
+      }
+    },
+    "autoAddReplicas": {
+      "type": "boolean",
+      "description": "When set to true, enables auto addition of replicas on shared file systems (such as HDFS). See https://cwiki.apache.org/confluence/display/solr/Running+Solr+on+HDFS for more details on settings and overrides."
+    },
+    "replicationFactor": {
+      "type": "string",
+      "description": "The number of replicas to be created for each shard. Replicas are physical copies of each shard, acting as failover for the shard. Note that changing this value on an existing collection does not automatically add more replicas to the collection. However, it will allow add-replica commands to succeed."
+    },
+    "maxShardsPerNode": {
+      "type": "integer",
+      "description": "When creating collections, the shards and/or replicas are spread across all available, live, nodes, and two replicas of the same shard will never be on the same node. If a node is not live when the collection is created, it will not get any parts of the new collection, which could lead to too many replicas being created on a single live node. Defining maxShardsPerNode sets a limit on the number of replicas can be spread to each node. If the entire collection can not be fit into the live nodes, no collection will be created at all."
+    }
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.collection.Commands.reload.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.collection.Commands.reload.json b/solr/core/src/resources/apispec/collections.collection.Commands.reload.json
new file mode 100644
index 0000000..fe7e379
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.collection.Commands.reload.json
@@ -0,0 +1,11 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api2",
+  "description": "Reloads a collection so new configuration changes can take effect and be available for use by the system.",
+  "type" : "object",
+  "properties":{
+    "async": {
+      "type": "string",
+      "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.collection.delete.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.collection.delete.json b/solr/core/src/resources/apispec/collections.collection.delete.json
new file mode 100644
index 0000000..0ab4562
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.collection.delete.json
@@ -0,0 +1,13 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api6",
+  "description": "Deletes a collection.",
+  "methods": [
+    "DELETE"
+  ],
+  "url": {
+    "paths": [
+      "/collections/{collection}",
+      "/c/{collection}"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.collection.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.collection.json b/solr/core/src/resources/apispec/collections.collection.json
new file mode 100644
index 0000000..34008be
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.collection.json
@@ -0,0 +1,19 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api1",
+  "description": "Lists all collections, with details on shards and replicas in each collection.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/collections/{collection}",
+      "/c/{collection}",
+      "/collections/{collection}/shards",
+      "/c/{collection}/shards",
+      "/collections/{collection}/shards/{shard}",
+      "/c/{collection}/shards/{shard}",
+      "/collections/{collection}/shards/{shard}/{replica}",
+      "/c/{collection}/shards/{shard}/{replica}"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.collection.shards.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.collection.shards.Commands.json b/solr/core/src/resources/apispec/collections.collection.shards.Commands.json
new file mode 100644
index 0000000..c3bf7bf
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.collection.shards.Commands.json
@@ -0,0 +1,109 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API",
+  "description": "Allows you to create a shard, split an existing shard or add a new replica.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/collections/{collection}/shards",
+      "/c/{collection}/shards"
+    ]
+  },
+  "commands": {
+    "split": {
+      "type" : "object",
+      "documentation":"https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api3",
+      "description": "Splits an existing shard into two or more new shards. During this action, the existing shard will continue to contain the original data, but new data will be routed to the new shards once the split is complete. New shards will have as many replicas as the existing shards. A soft commit will be done automatically. An explicit commit request is not required because the index is automatically saved to disk during the split operation. New shards will use the original shard name as the basis for their names, adding an underscore and a number to differentiate the new shard. For example, 'shard1' would become 'shard1_0' and 'shard1_1'. Note that this operation can take a long time to complete.",
+      "properties": {
+        "shard":{
+          "type":"string",
+          "description":"The name of the shard to be split."
+        },
+        "ranges" : {
+          "description" : "A comma-separated list of hexadecimal hash ranges that will be used to split the shard into new shards containing each defined range, e.g. ranges=0-1f4,1f5-3e8,3e9-5dc. This is the only option that allows splitting a single shard into more than 2 additional shards. If neither this parameter nor splitKey are defined, the shard will be split into two equal new shards.",
+          "type":"string"
+        },
+        "splitKey":{
+          "description" : "A route key to use for splitting the index. If this is defined, the shard parameter is not required because the route key will identify the correct shard. A route key that spans more than a single shard is not supported. If neither this parameter nor ranges are defined, the shard will be split into two equal new shards.",
+          "type":"string"
+        },
+        "coreProperties":{
+          "type":"object",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Defining+core.properties",
+          "description": "Allows adding core.properties for the collection. Some examples of core properties you may want to modify include the config set, the node name, the data directory, among others.",
+          "additionalProperties":true
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined. This command can be long-running, so running it asynchronously is recommended."
+        }
+      }
+    },
+    "create": {
+      "type":"object",
+      "properties": {
+        "nodeSet": {
+          "description": "Defines nodes to spread the new collection across. If not provided, the collection will be spread across all live Solr nodes. The names to use are the 'node_name', which can be found by a request to the cluster/nodes endpoint.",
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "shard": {
+          "description": "The name of the shard to be created.",
+          "type": "string"
+        },
+        "coreProperties": {
+          "type": "object",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Defining+core.properties",
+          "description": "Allows adding core.properties for the collection. Some examples of core properties you may want to modify include the config set, the node name, the data directory, among others.",
+          "additionalProperties": true
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      },
+      "required":["shard"]
+    },
+    "add-replica": {
+      "documentation":"https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api_addreplica",
+      "description": "",
+      "type" : "object",
+      "properties": {
+        "shard": {
+          "type": "string",
+          "description": "The name of the shard in which this replica should be created. If this parameter is not specified, then '_route_' must be defined."
+        },
+        "_route_": {
+          "type": "string",
+          "description": "If the exact shard name is not known, users may pass the _route_ value and the system would identify the name of the shard. Ignored if the shard param is also specified. If the 'shard' parameter is also defined, this parameter will be ignored."
+        },
+        "node": {
+          "type": "string",
+          "description": "The name of the node where the replica should be created."
+        },
+        "instanceDir": {
+          "type": "string",
+          "description": "An optional custom instanceDir for this replica."
+        },
+        "dataDir": {
+          "type": "string",
+          "description": "An optional custom directory used to store index data for this replica."
+        },
+        "coreProperties": {
+          "type": "object",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Defining+core.properties",
+          "description": "Allows adding core.properties for the collection. Some examples of core properties you may want to modify include the config set and the node name, among others.",
+          "additionalProperties": true
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      },
+      "required":["shard"]
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.collection.shards.shard.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.collection.shards.shard.Commands.json b/solr/core/src/resources/apispec/collections.collection.shards.shard.Commands.json
new file mode 100644
index 0000000..83f7ddf
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.collection.shards.shard.Commands.json
@@ -0,0 +1,24 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API",
+  "description": "Commands to force leader election and synchronize shards.",
+  "methods": [
+    "POST",
+    "DELETE"
+  ],
+  "url": {
+    "paths": [
+      "/collections/{collection}/shards/{shard}",
+      "/c/{collection}/shards/{shard}"
+    ]
+  },
+  "commands": {
+    "force-leader": {
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-ForceLeader",
+      "description": "In the unlikely event of a shard losing its leader, this command can be invoked to force the election of a new leader",
+      "type": "object"
+    },
+    "sync-shard": {
+      "type": "object"
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.collection.shards.shard.delete.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.collection.shards.shard.delete.json b/solr/core/src/resources/apispec/collections.collection.shards.shard.delete.json
new file mode 100644
index 0000000..53c7965
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.collection.shards.shard.delete.json
@@ -0,0 +1,27 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api7",
+  "description": "Deletes a shard by unloading all replicas of the shard, removing it from clusterstate.json, and by default deleting the instanceDir and dataDir. Only inactive shards or those which have no range for custom sharding will be deleted.",
+  "methods": [
+    "DELETE"
+  ],
+  "url": {
+    "paths": [
+      "/collections/{collection}/shards/{shard}",
+      "/c/{collection}/shards/{shard}"
+    ],
+    "params":{
+      "deleteInstanceDir":{
+        "type": "boolean",
+        "description":"By default Solr will delete the entire instanceDir of each replica that is deleted. Set this to false to prevent the instance directory from being deleted."
+      },
+      "deleteDataDir":{
+        "type":"boolean",
+        "description":"y default Solr will delete the dataDir of each replica that is deleted. Set this to false to prevent the data directory from being deleted."
+      },
+      "async": {
+        "type": "string",
+        "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined. This command can be long-running, so running it asynchronously is recommended."
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.collection.shards.shard.replica.delete.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.collection.shards.shard.replica.delete.json b/solr/core/src/resources/apispec/collections.collection.shards.shard.replica.delete.json
new file mode 100644
index 0000000..a0c8ee6
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.collection.shards.shard.replica.delete.json
@@ -0,0 +1,39 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api9",
+  "description": "Deletes a replica. If the responding node is up, the core is unloaded, the entry removed from clusterstate.json, and the instanceDir and dataDir removed. If the node is not up, the entry for the replica is removed from clusterstate.json; if the nodes comes up later, the replica is automatically de-registered.",
+  "methods": [
+    "DELETE"
+  ],
+  "url": {
+    "paths": [
+      "/collections/{collection}/shards/{shard}/{replica}",
+      "/c/{collection}/shards/{shard}/{replica}"
+    ],
+    "params": {
+      "onlyIfDown": {
+        "type": "boolean",
+        "default": "false",
+        "description": "When set to 'true', no action will be taken if the replica is active."
+      },
+      "deleteIndex": {
+        "type": "boolean",
+        "default": "true",
+        "description": "By default Solr will delete the index of the replica that is deleted. Set this to false to prevent the index directory from being deleted."
+      },
+      "deleteDataDir": {
+        "type": "boolean",
+        "default": "true",
+        "description": "By default Solr will delete the dataDir of the replica that is deleted. Set this to false to prevent the data directory from being deleted."
+      },
+      "deleteInstanceDir": {
+        "type": "boolean",
+        "default": "true",
+        "description": "By default Solr will delete the entire instanceDir of the replica that is deleted. Set this to false to prevent the instance directory from being deleted."
+      },
+      "async":{
+        "type":"string",
+        "description":"Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/collections.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/collections.json b/solr/core/src/resources/apispec/collections.json
new file mode 100644
index 0000000..49ca976
--- /dev/null
+++ b/solr/core/src/resources/apispec/collections.json
@@ -0,0 +1,13 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api1",
+  "description": "List all available collections and their properties.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/collections",
+      "/c"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.RealtimeGet.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.RealtimeGet.json b/solr/core/src/resources/apispec/core.RealtimeGet.json
new file mode 100644
index 0000000..308870e
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.RealtimeGet.json
@@ -0,0 +1,26 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/RealTime+Get",
+  "description": "RealTime Get allows retrieving documents by ID before the documents have been committed to the index. It is useful when you need access to documents as soon as they are indexed but your commit times are high for other reasons.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/get"
+    ],
+    "params": {
+      "id": {
+        "type": "string",
+        "description": "A single document ID to retrieve."
+      },
+      "ids": {
+        "type": "string",
+        "description": "One or more document IDs to retrieve. Separate by commas if more than one ID is specified."
+      },
+      "fq":{
+        "type": "string",
+        "description": "An optional filter query to add to the query. One use case for this is security filtering, in case users or groups should not be able to retrieve the document ID requested."
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaEdit.addCopyField.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaEdit.addCopyField.json b/solr/core/src/resources/apispec/core.SchemaEdit.addCopyField.json
new file mode 100644
index 0000000..26c4eff
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaEdit.addCopyField.json
@@ -0,0 +1,27 @@
+{
+  "documentation" : "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-AddaNewCopyFieldRule",
+  "description": "Adds a new copy field rule, to allow one field to be populated with the contents of one or more other fields.",
+  "type": "object",
+  "properties": {
+    "source": {
+      "type": "string",
+      "description": "The field to copy from."
+    },
+    "dest": {
+      "type":"array",
+      "items": {
+        "type": "string"
+      },
+      "description": "A field or an array of fields to which the source field will be copied. A wildcard for a dynamic field can be used, but only if the source field also contains a dynamic field."
+    },
+    "maxChars": {
+      "type": "integer",
+      "description": "An upper limit for the number of characters to be copied. This would be useful if index size is a concern. If a limit is not specified, the entire field will be copied."
+    }
+  },
+  "required": [
+    "source",
+    "dest"
+  ]
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaEdit.addField.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaEdit.addField.json b/solr/core/src/resources/apispec/core.SchemaEdit.addField.json
new file mode 100644
index 0000000..d4a6996
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaEdit.addField.json
@@ -0,0 +1,98 @@
+{
+  "documentation" :"https://cwiki.apache.org/confluence/display/solr/Schema+API",
+  "type":"object",
+  "properties":{
+    "name": {
+      "type": "string",
+      "description": "The name of the field. Names should be alphanumeric or underscore characters only, and not start with a digit. Names also cannot begin and end with an underscore, as such field names are reserved by the system."
+    },
+    "type": {
+      "type": "string",
+      "description":"The name of the fieldType for this field."
+    },
+    "defaultValue": {
+      "type": "string",
+      "description": "An optional default value that should be added automatically to any document that does not have a value for this field."
+    },
+    "indexed": {
+      "type": "boolean",
+      "description": "If true, the field will be indexed and will be available for use in queries to retrieve matching documents. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to true.",
+      "default": "true"
+    },
+    "stored": {
+      "type": "boolean",
+      "description": "If true, the actual value of the field can be retrieved by queries and be displayed in results. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to true.",
+      "default":"true"
+    },
+    "omitNorms": {
+      "type": "boolean",
+      "description": "If true, length normalization and index-time boosting for a field are omitted from the index. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to true for all primitive field types (such as dates, strings, boolean, and numeric fields), but will default to false for non-primitive field types."
+    },
+    "omitTermFreqAndPositions": {
+      "type": "boolean",
+      "description": "If true, all term frequency, positions, and payloads will not be indexed. This means that phrase queries, proximity queries and similar queries that rely on analysis of the frequency of a query term or the position of a term to other terms will not be supported. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to true for all field types that are not text fields."
+    },
+    "termVectors": {
+      "type": "boolean",
+      "description": "If true, term vectors will be stored to be able to compute similarity between two documents. This is required to use More Like This. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to false. Do not enable this if using the PostingsHighlighter.",
+      "default": "false"
+    },
+    "termPositions": {
+      "type": "boolean",
+      "description": "If true, term positions will be stored for use with highlighting. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to false. Do not enable this if using the PostingsHighlighter.",
+      "default": "false"
+    },
+    "termOffsets": {
+      "type": "boolean",
+      "description": "If true, term offsets will be stored for use with highlighting. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to false. Do not enable this if using the PostingsHighlighter.",
+      "default": "false"
+    },
+    "multiValued": {
+      "type": "boolean",
+      "description": "If true, a single document can have multiple values in a single field, and these values will be indexed. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to false.",
+      "default": "false"
+    },
+    "sortMissingFirst": {
+      "type": "boolean",
+      "description": "If true, when sorting by the field, any documents missing a value for the field will be placed at the top of the list. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to false. If sortMissingFirst and sortMissingLast are both false, documents missing this field will be placed at the top when sorting in ascending order (asc) or at the bottom when sorting in descending order (desc).",
+      "default": "false"
+    },
+    "sortMissingLast": {
+      "type": "boolean",
+      "description": "If true, when sorting by the field, any documents missing a value for the field will be placed at the bottom of the list. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to false. If sortMissingFirst and sortMissingLast are both false, documents missing this field will be placed at the top when sorting in ascending order (asc) or at the bottom when sorting in descending order (desc).",
+      "default": "false"
+    },
+    "required": {
+      "type": "boolean",
+      "description": "If true, any document that does not have a value for the field will be rejected. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to false.",
+      "default": "false"
+    },
+    "omitPositions": {
+      "type": "boolean",
+      "description": "If true, information about the position of terms in a document will not be stored in the index, which means phrase queries, proximity queries, and similar will not be supported for this field. It is similar to 'omitTermFreqAndPositions', but 'omitPositions' will allow term frequency information to be stored. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to true for all field types that are not text fields."
+    },
+    "storeOffsetsWithPositions": {
+      "type": "boolean",
+      "description": "If true, term offsets will be stored with positions in the postings list in the index. This is required if using the PostingsHighlighter. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to false.",
+      "default": "false"
+    },
+    "docValues": {
+      "type": "boolean",
+      "description": "If true, field values will be stored in a column-oriented docValues structure. This can be more efficient for some fields, particularly those used for faceting. More information is available from https://cwiki.apache.org/confluence/display/solr/DocValues. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to true for all non-text fields (such as dates, integers, longs, etc.)."
+    },
+    "termPayloads": {
+      "type": "boolean",
+      "description": "If true, term payloads will be stored for use with highlighting. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to false. Do not enable this if using the PostingsHighlighter.",
+      "default": "false"
+    },
+    "useDocValuesAsStored": {
+      "type": "boolean",
+      "description": "If true and docValues are enabled for the field, the field will be returned when all fields are requested (using '*' with the fl parameter), even if it is not stored. If this is not defined, it will inherit the value from the fieldType. If the fieldType does not define a value, it will default to true.",
+      "default": "true"
+    }
+  },
+  "required": [
+    "name",
+    "type"
+  ]
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaEdit.addFieldType.analyzers.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaEdit.addFieldType.analyzers.json b/solr/core/src/resources/apispec/core.SchemaEdit.addFieldType.analyzers.json
new file mode 100644
index 0000000..2974a60
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaEdit.addFieldType.analyzers.json
@@ -0,0 +1,51 @@
+{
+  "type": "object",
+  "properties": {
+    "class": {
+      "type": "string"
+    },
+    "charFilters": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "properties": {
+          "class": {
+            "type": "string"
+          }
+        },
+        "required": [
+          "class"
+        ],
+        "additionalProperties": true
+      }
+    },
+    "tokenizer": {
+      "type": "object",
+      "properties": {
+        "class": {
+          "type": "string"
+        }
+      },
+      "required": [
+        "class"
+      ],
+      "additionalProperties": true
+    },
+    "filters": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "properties": {
+          "class": {
+            "type": "string"
+          }
+        },
+        "required": [
+          "class"
+        ],
+        "additionalProperties": true
+      }
+    }
+  },
+  "additionalProperties": true
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaEdit.addFieldType.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaEdit.addFieldType.json b/solr/core/src/resources/apispec/core.SchemaEdit.addFieldType.json
new file mode 100644
index 0000000..e24572e
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaEdit.addFieldType.json
@@ -0,0 +1,53 @@
+{
+  "type":"object",
+  "properties": {
+    "name": {
+      "type": "string",
+      "description": "The name of the field type. This name is used when defining a field. It is strongly recommended that field type names consist only of alphanumeric or underscore characters and not start with a digit."
+    },
+    "class": {
+      "type": "string",
+      "description": "The class name to use for the field type. Class names do not need to be fully qualified if they are included with Solr, so instead of 'org.apache.solr.schema.TextField', you can abbreviate the name as 'solr.TextField'. Custom or third-party class names may need to be fully qualified, however."
+    },
+    "positionIncrementGap": {
+      "type": "number",
+      "description": "The distance between the values of a multivalued field. This is used to prevent inaccurate phrase matches across two separate values of the same field.",
+      "default": "0"
+    },
+    "autoGeneratePhraseQueries": {
+      "type": "boolean",
+      "description": "If true, phrase queries will automatically be generated for adjacent terms. If false, terms must also be enclosed in double-quotes to be treated as phrases.",
+      "default": "false"
+    },
+    "docValuesFormat": {
+      "type": "string",
+      "description": "Defines a custom DocValuesFormat to use for fields of this type. A custom DocValuesFormat requires that a schema-aware codec has also been configured in solrconfig.xml."
+    },
+    "postingsFormat": {
+      "type": "string",
+      "description": "Defines a custom PostingsFormat to use for fields of this type. A custom PostingsFormat requires that a schema-aware codec has also been configured in solrconfig.xml."
+    },
+    "queryAnalyzer": {
+      "description": "A query analyzer section defines how incoming queries to Solr will be analyzed for a field of this type.",
+      "#include": "core.SchemaEdit.addFieldType.analyzers"
+    },
+    "indexAnalyzer": {
+      "description": "An index analyzer section defines how incoming text in documents will be analyzed for a field of this type.",
+      "#include": "core.SchemaEdit.addFieldType.analyzers"
+    },
+    "multiTermAnalyzer": {
+      "description": "A multiterm analyzer section defines how incoming queries that results in Multi-Term expansion will be analyzed for a field of this type.",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Analyzers#Analyzers-AnalysisforMulti-TermExpansion",
+      "#include": "core.SchemaEdit.addFieldType.analyzers"
+    },
+    "analyzer": {
+      "description": "An analyzer defines how both incoming text in documents and queries are analyzed for a field of this type. If a query analyzer and an index analyzer have both been defined, a general analyzer does not need to be defined for this type.",
+      "#include": "core.SchemaEdit.addFieldType.analyzers"
+    }
+  },
+  "additionalProperties": true,
+  "required": [
+    "name",
+    "class"
+  ]
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaEdit.deleteCopyField.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaEdit.deleteCopyField.json b/solr/core/src/resources/apispec/core.SchemaEdit.deleteCopyField.json
new file mode 100644
index 0000000..dd6ff3a
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaEdit.deleteCopyField.json
@@ -0,0 +1,19 @@
+{
+  "type":"object",
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-DeleteaCopyFieldRule",
+  "description": "Deletes a copy field rule. Both sides of the copy rule (source and destination) are required in order to delete the rule.",
+  "properties":{
+    "source": {
+      "type":"string",
+      "description": "The field the copy rule is defined to copy from."
+    },
+    "dest": {
+      "type": "string",
+      "description": "The field the copy rule is defined to copy to."
+    }
+  },
+  "required": [
+    "source",
+    "dest"
+  ]
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaEdit.deleteDynamicField.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaEdit.deleteDynamicField.json b/solr/core/src/resources/apispec/core.SchemaEdit.deleteDynamicField.json
new file mode 100644
index 0000000..9550548
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaEdit.deleteDynamicField.json
@@ -0,0 +1,12 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-DeleteaDynamicFieldRule",
+  "description": "Deletes a dynamic field.",
+  "type":"object",
+  "properties": {
+    "name": {
+      "type": "string",
+      "description": "The name of the dynamic field to delete."
+    }
+  },
+  "required":["name"]
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaEdit.deleteField.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaEdit.deleteField.json b/solr/core/src/resources/apispec/core.SchemaEdit.deleteField.json
new file mode 100644
index 0000000..6c2cb00
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaEdit.deleteField.json
@@ -0,0 +1,12 @@
+{
+  "documentation" : "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-DeleteaField",
+  "description": "Deletes a field from the schema.",
+  "type":"object",
+  "properties":{
+    "name":{
+     "description" :"The name of the field to delete.",
+      "type" : "string"
+    }
+  },
+  "required" : ["name"]
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaEdit.deleteFieldType.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaEdit.deleteFieldType.json b/solr/core/src/resources/apispec/core.SchemaEdit.deleteFieldType.json
new file mode 100644
index 0000000..712462a
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaEdit.deleteFieldType.json
@@ -0,0 +1,14 @@
+{
+  "documentation":"https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-DeleteaFieldType",
+  "description": "Deletes a field type from the schema.",
+  "type":"object",
+  "properties": {
+    "name": {
+      "type": "string",
+      "description": "The name of the field type to delete."
+    }
+  },
+  "required": [
+    "name"
+  ]
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaEdit.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaEdit.json b/solr/core/src/resources/apispec/core.SchemaEdit.json
new file mode 100644
index 0000000..bbf4082
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaEdit.json
@@ -0,0 +1,47 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API",
+  "description": "The Schema API provides viewing, editing, adding, and deleting elements of Solr's schema. This API can only be used if Managed Schema is enabled and the schema is defined as mutable. See https://cwiki.apache.org/confluence/display/solr/Schema+Factory+Definition+in+SolrConfig for more information about enabling Managed Schema.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "$handlerName"
+    ]
+  },
+  "commands": {
+    "add-field": {
+      "#include": "core.SchemaEdit.addField"
+    },
+    "delete-field": {
+      "#include": "core.SchemaEdit.deleteField"
+    },
+    "replace-field": {
+      "#include": "core.SchemaEdit.addField"
+    },
+    "add-dynamic-field": {
+      "#include": "core.SchemaEdit.addField"
+    },
+    "delete-dynamic-field": {
+      "#include": "core.SchemaEdit.deleteDynamicField"
+    },
+    "replace-dynamic-field": {
+      "#include": "core.SchemaEdit.addField"
+    },
+    "add-field-type": {
+      "#include": "core.SchemaEdit.addFieldType"
+    },
+    "delete-field-type": {
+      "#include": "core.SchemaEdit.deleteFieldType"
+    },
+    "replace-field-type": {
+      "#include": "core.SchemaEdit.addFieldType"
+    },
+    "add-copy-field": {
+      "#include": "core.SchemaEdit.addCopyField"
+    },
+    "delete-copy-field": {
+      "#include": "core.SchemaEdit.deleteCopyField"
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaRead.copyFields.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaRead.copyFields.json b/solr/core/src/resources/apispec/core.SchemaRead.copyFields.json
new file mode 100644
index 0000000..4cf822e
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaRead.copyFields.json
@@ -0,0 +1,26 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-ListCopyFields",
+  "description": "Lists all copy fields.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "schema/copyfields"
+    ],
+    "params": {
+      "wt": {
+         "type": "string",
+         "description": "The format of the response. Valid options are xml or json."
+      },
+      "source.fl": {
+        "type": "string",
+        "description": "Comma- or space-separated list of one or more source fields to include in the response. copyField directives with all other source fields will be excluded from the response. If not specified, all copyFields will be included in the response"
+      },
+      "dest.fl": {
+        "type": "string",
+        "description": "Comma or space-separated list of one or more copyField dest (destination) fields to include in the response. copyField directives with all other dest fields will be excluded. If not specified, all copyFields will be included in the response."
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaRead.dynamicFields_fieldTypes.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaRead.dynamicFields_fieldTypes.json b/solr/core/src/resources/apispec/core.SchemaRead.dynamicFields_fieldTypes.json
new file mode 100644
index 0000000..0642491
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaRead.dynamicFields_fieldTypes.json
@@ -0,0 +1,20 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/schema/dynamicfields",
+      "/schema/dynamicfields/{name}",
+      "/schema/fieldtypes",
+      "/schema/fieldtypes/{name}"
+    ],
+    "params":{
+      "showDefaults":{
+        "type":"boolean",
+        "description":"If true, all default field properties from each field's field type will be included in the response (e.g.   tokenized  for   solr.TextField). If false, only explicitly specified field properties will be included."
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaRead.fields.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaRead.fields.json b/solr/core/src/resources/apispec/core.SchemaRead.fields.json
new file mode 100644
index 0000000..3b6c787
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaRead.fields.json
@@ -0,0 +1,34 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-ListFields",
+  "description": "Get only the fields defined in the schema.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/schema/fields",
+      "/schema/fields/{name}"
+    ],
+    "params": {
+      "wt": {
+         "type": "string",
+         "description": "The format of the response. Valid options are xml or json.",
+         "default": "json"
+      },
+      "fl": {
+         "type": "string",
+         "description": "A comma- or space-separated list fields to return. If not specified, all fields will be returned. Note a single field can be requested by adding the field name to the endpoint."
+      },
+      "includeDynamic": {
+        "type": "boolean",
+        "description": "If true, dynamic fields will be returned in the response.",
+        "default": false
+      },
+      "showDefaults": {
+        "type": "boolean",
+        "description": "If true, all field properties from each field's field type will be included in the response, even if they are not explicitly defined on the field. If false, only explicitly defined field properties will be included.",
+        "default": false
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.SchemaRead.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.SchemaRead.json b/solr/core/src/resources/apispec/core.SchemaRead.json
new file mode 100644
index 0000000..da62c06
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.SchemaRead.json
@@ -0,0 +1,18 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/schema",
+      "/schema/name",
+      "/schema/uniquekey",
+      "/schema/version",
+      "/schema/similarity",
+      "/schema/solrqueryparser",
+      "/schema/zkversion",
+      "/schema/solrqueryparser/defaultoperator"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.Update.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.Update.json b/solr/core/src/resources/apispec/core.Update.json
new file mode 100644
index 0000000..f9e80c1
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.Update.json
@@ -0,0 +1,17 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/update",
+      "/update/xml",
+      "/update/csv",
+      "/update/json",
+      "/update/bin",
+      "/update/json/commands"
+    ]
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.config.Commands.addRequestHandler.properties.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.Commands.addRequestHandler.properties.json b/solr/core/src/resources/apispec/core.config.Commands.addRequestHandler.properties.json
new file mode 100644
index 0000000..731c3d8
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.Commands.addRequestHandler.properties.json
@@ -0,0 +1,25 @@
+{
+  "type": "object",
+  "properties": {
+    "name": {
+      "type": "string",
+      "description": "The name of the request handler. This name will be used to update or remove the request handler later if necessary."
+    },
+    "class": {
+      "type": "string",
+      "description": "The request handler class. Class names do not need to be fully qualified if they are included with Solr, so you can abbreviate the name as 'solr.SearchHandler'. Custom or third-party class names may need to be fully qualified, however."
+    },
+    "runtimeLib": {
+      "type": "boolean",
+      "description": "An optional parameter to use a custom .jar file that has been uploaded to Solr's blobstore. This additionally requires that the .jar has also been registered with the 'add-runtimelib' command, which is one of the available commands for the Config API."
+    },
+    "startup": {
+      "type": "string",
+      "description": "Allows the request handler to only start when requested. The only option is 'lazy'.",
+      "enum": [
+        "lazy"
+      ]
+    }
+  },
+  "additionalProperties": true
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.config.Commands.generic.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.Commands.generic.json b/solr/core/src/resources/apispec/core.config.Commands.generic.json
new file mode 100644
index 0000000..9d2b01d
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.Commands.generic.json
@@ -0,0 +1,19 @@
+{
+  "type": "object",
+  "properties": {
+    "name": {
+      "type": "string",
+      "description": "The name of this configuration item. This name will be used to update or remove this later if necessary."
+   },
+    "class": {
+      "type": "string",
+      "description": "The configuration item class. Class names do not need to be fully qualified if they are included with Solr, so you can abbreviate the name as 'solr.SearchHandler'. Custom or third-party class names may need to be fully qualified, however."
+   },
+    "runtimeLib": {
+      "type": "boolean",
+      "description": "An optional parameter to use a custom .jar file that has been uploaded to Solr's blobstore. This additionally requires that the .jar has also been registered with the 'add-runtimelib' command, which is one of the available commands for the Config API."
+   }
+  },
+  "required": [ "name", "class"],
+  "additionalProperties": true
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.config.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.Commands.json b/solr/core/src/resources/apispec/core.config.Commands.json
new file mode 100644
index 0000000..256306b
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.Commands.json
@@ -0,0 +1,215 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+API",
+  "description": "The Config API enables manipulating various aspects of your solrconfig.xml using REST-like API calls. All properties set with this API update a file called configoverlay.json, but not the solrconfig.xml file itself.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/config"
+    ]
+  },
+  "commands": {
+    "set-property:": {
+      "type": "object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+API#ConfigAPI-Commandstomodifytheconfig",
+      "description": "Sets one or more of several pre-defined properties. These properties set cache sizes and classes, commit rules, JMX settings, and request dispatcher settings. See the documentation for the list of properties that are supported. If a property is set that already exists, it will be overwritten.",
+      "additionalProperties": true
+    },
+    "unset-property": {
+      "type":"array",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+API#ConfigAPI-Commandstomodifytheconfig",
+      "description": "Removes one or more of several pre-defined properties. These properties set cache sizes and classes, commit rules, JMX settings, and request dispatcher settings. See the documentation for the list of properties that are supported. The value of the property does not need to be defined with the list of properties, only the name of the property.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-requesthandler": {
+      "#include":"core.config.Commands.addRequestHandler.properties",
+      "required": [
+        "name",
+        "class"
+      ]
+    },
+    "update-requesthandler": {
+      "#include":"core.config.Commands.addRequestHandler.properties",
+      "required": [
+        "name"
+      ]
+    },
+    "delete-requesthandler": {
+      "type": "array",
+      "description": "Deletes one or more request handlers, using the name given when the request handler was created. Define more than one request handler by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-searchcomponent": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-searchcomponent": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-searchcomponent": {
+      "type": "array",
+      "description": "Deletes one or more search components, using the name given when the search component was created. Define more than one search component by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-initparams": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "Name by which it is added, so that it can be updated by name"
+        }
+      },
+      "additionalProperties": true
+    },
+    "update-initparams": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "Name by which it is added"
+        }
+      },
+      "required": [
+        "name"
+      ],
+      "additionalProperties": true
+    },
+    "delete-initparams": {
+      "type": "array",
+      "description": "Deletes one or more init params, using the name given when the init param set was created. Define more than one init params by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-queryresponsewriter": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-queryresponsewriter": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-queryresponsewriter": {
+      "type": "array",
+      "description": "Deletes one or more query response writers, using the name given when the response writer was created. Define more than one response writer by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-queryparser": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-queryparser": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-queryparser": {
+      "type": "array",
+      "items": {
+        "type": "string"
+      },
+      "description": "Deletes one or more query parsers, using the name given when the query parser was created. Define more than one query parser by separating the list of names with commas."
+    },
+    "add-valuesourceparser": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-valuesourceparser": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-valuesourceparser": {
+      "type": "array",
+      "description": "Deletes one or more ValueSourceParsers, using the name given when the ValueSourceParser was created. Define more than one ValueSourceParsers by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-transformer": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-transformer": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-transformer": {
+      "type": "array",
+      "description": "Deletes one or more document transformers, using the name given when the document transformer was created. Define more than one document transformers by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-updateprocessor": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-updateprocessor": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-updateprocessor": {
+      "type": "array",
+      "description": "Deletes one or more update processors, using the name given when the update processor was created. Define more than one update processors by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-queryconverter": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-queryconverter": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-queryconverter": {
+      "type": "array",
+      "description": "Deletes one or more query converters, using the name given when the query converter was created. Define more than one query converters by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-listener": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "Name by which it is added, so that it can be updated by name"
+        }
+      },
+      "required": [
+        "name"
+      ],
+      "additionalProperties": true
+    },
+    "update-listener": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "Name by which it is added"
+        }
+      },
+      "required": [
+        "name"
+      ],
+      "additionalProperties": true
+    },
+    "delete-listener": {
+      "type": "array",
+      "description": "Deletes one or more listeners, using the name given when the listener was created. Define more than one listener by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-runtimelib": {
+      "#include": "core.config.Commands.runtimeLib"
+    },
+    "update-runtimelib": {
+      "#include": "core.config.Commands.runtimeLib"
+    },
+    "delete-runtimelib": {
+      "type":"array",
+      "description": "Deletes one or more runtime libraries (runtimeLibs), using the name given when the runtimeLib was created. Define more than one runtimeLibs by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4fc5a9f0/solr/core/src/resources/apispec/core.config.Commands.runtimeLib.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.Commands.runtimeLib.json b/solr/core/src/resources/apispec/core.config.Commands.runtimeLib.json
new file mode 100644
index 0000000..8e2fb2d
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.Commands.runtimeLib.json
@@ -0,0 +1,23 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Adding+Custom+Plugins+in+SolrCloud+Mode",
+  "description": "Allows you to register .jars that have been uploaded to the .system collection in Solr. Note that uploading the .jar must occur before using this API.",
+  "type": "object",
+  "properties": {
+    "name": {
+      "description": "The name of the .jar blob in .system collection. This is the name you provided when you uploaded it.",
+      "type": "string"
+    },
+    "version": {
+      "type": "integer",
+      "description": "The version of the blob in .system collection. Be sure to use the correct version if you have multiple versions of the same .jar uploaded."
+    },
+    "sig": {
+      "type": "string",
+      "description": "The sha1 signature of the .jar, if it was signed before uploading. If you signed the sha1 digest of your .jar file prior to uploading it to the .system collection, this is where you need to provide the signature."
+    }
+  },
+  "required": [
+    "name",
+    "version"
+  ]
+}