You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2016/09/09 11:54:19 UTC

[1/2] lucene-solr:apiv2: SOLR-8029: 'enum' support in schema

Repository: lucene-solr
Updated Branches:
  refs/heads/apiv2 ea739f735 -> cf1bbc3d7


SOLR-8029: 'enum' support in schema


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

Branch: refs/heads/apiv2
Commit: 66f09139197ce54292dc103e2d66b1b384bb79a6
Parents: ea739f7
Author: Noble Paul <no...@apache.org>
Authored: Thu Sep 8 16:48:25 2016 +0530
Committer: Noble Paul <no...@apache.org>
Committed: Thu Sep 8 16:48:25 2016 +0530

----------------------------------------------------------------------
 .../apache/solr/util/JsonSchemaValidator.java   | 101 +++++++++++++------
 ...cluster.security.RuleBasedAuthorization.json |   5 +-
 .../resources/apispec/cores.core.Commands.json  |   8 +-
 .../org/apache/solr/util/JsonValidatorTest.java |  62 +++++++-----
 4 files changed, 114 insertions(+), 62 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/66f09139/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
index 10b008c..62d3247 100644
--- a/solr/core/src/java/org/apache/solr/util/JsonSchemaValidator.java
+++ b/solr/core/src/java/org/apache/solr/util/JsonSchemaValidator.java
@@ -18,6 +18,7 @@
 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;
@@ -39,6 +40,10 @@ import static java.util.stream.Collectors.toMap;
 
 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;
@@ -52,8 +57,9 @@ public class JsonSchemaValidator {
   private static class SchemaNode {
     final SchemaNode parent;
     Type type;
-    Type arrayElementType;
+    Type elementType;
     boolean isRequired = false;
+    Object validationInfo;
     Boolean additionalProperties;
     Map<String, SchemaNode> children;
 
@@ -62,15 +68,22 @@ public class JsonSchemaValidator {
     }
 
     private void validateSchema(Map jsonSchema, List<String> errs) {
-      for (SchemaAttribute attr : SchemaAttribute.values()) {
-        attr.validate(jsonSchema, this, errs);
+      Object typeStr = jsonSchema.get("type");
+      if (typeStr == null) {
+        errs.add("'type' is missing ");
+      }
+      Type type = Type.get(typeStr);
+      if (type == null) throw new RuntimeException("Unknown type " + typeStr);
+      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;
-      Type type = Type.get(jsonSchema.get("type"));
-      if (type == null) throw new RuntimeException("Unknown type " + jsonSchema.get("type"));
+
       if (type == Type.OBJECT) {
         Map m = (Map) jsonSchema.get("properties");
         if (m != null) {
@@ -129,18 +142,12 @@ public class JsonSchemaValidator {
   }
 
   enum SchemaAttribute {
-    type(true, Type.STRING) {
-      @Override
-      public void validate(Map attrSchema, SchemaNode attr, List<String> errors) {
-        super.validate(attrSchema, attr, errors);
-        attr.type = Type.get(attrSchema.get(key));
-      }
-    },
+    type(true, Type.STRING),
     properties(false, Type.OBJECT) {
       @Override
-      public void validate(Map attrSchema, SchemaNode attr, List<String> errors) {
-        super.validate(attrSchema, attr, errors);
-        if (attr.type != Type.OBJECT) return;
+      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);
@@ -153,11 +160,11 @@ public class JsonSchemaValidator {
     additionalProperties(false, Type.BOOLEAN),
     items(false, Type.OBJECT) {
       @Override
-      public void validate(Map attrSchema, SchemaNode attr, List<String> errors) {
-        super.validate(attrSchema, attr, errors);
+      public void validateSchema(Map attrSchema, SchemaNode schemaNode, List<String> errors) {
+        super.validateSchema(attrSchema, schemaNode, errors);
         Object itemsVal = attrSchema.get(key);
         if (itemsVal != null) {
-          if (attr.type != Type.ARRAY) {
+          if (schemaNode.type != Type.ARRAY) {
             errors.add("Only 'array' can have 'items'");
             return;
           } else {
@@ -168,7 +175,7 @@ public class JsonSchemaValidator {
               if (t == null) {
                 errors.add("Unknown array type " + Utils.toJSONString(attrSchema));
               } else {
-                attr.arrayElementType = t;
+                schemaNode.elementType = t;
               }
             }
           }
@@ -179,6 +186,33 @@ public class JsonSchemaValidator {
     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),
@@ -205,7 +239,7 @@ public class JsonSchemaValidator {
       return key;
     }
 
-    void validate(Map attrSchema, SchemaNode attr, List<String> errors) {
+    void validateSchema(Map attrSchema, SchemaNode schemaNode, List<String> errors) {
       Object val = attrSchema.get(key);
       if (val == null) {
         if (_required)
@@ -215,7 +249,7 @@ public class JsonSchemaValidator {
       }
     }
 
-    void postValidateSchema(Map attrSchema, SchemaNode attr, List<String> errs) {
+    void postValidateSchema(Map attrSchema, SchemaNode schemaNode, List<String> errs) {
     }
 
     SchemaAttribute(boolean required, Type type) {
@@ -226,23 +260,23 @@ public class JsonSchemaValidator {
   }
 
   interface TypeValidator {
-    void valdateData(String key, Object o, SchemaNode attr, List<String> errs);
+    void valdateData(String key, Object o, SchemaNode schemaNode, List<String> errs);
   }
 
   enum Type {
     STRING(o -> o instanceof String),
-    ARRAY(o -> o instanceof List, (key, o, attr, errs) -> {
+    ARRAY(o -> o instanceof List, (key, o, schemaNode, errs) -> {
       List l = o instanceof List ? (List) o : Collections.singletonList(o);
-      if (attr.arrayElementType != null) {
+      if (schemaNode.elementType != null) {
         for (Object elem : l) {
-          if (!attr.arrayElementType.validate(elem)) {
+          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, attr, errs) -> {
+    NUMBER(o -> o instanceof Number, (key, o, schemaNode, errs) -> {
       if (o instanceof String) {
         try {
           Double.parseDouble((String) o);
@@ -253,7 +287,7 @@ public class JsonSchemaValidator {
       }
 
     }),
-    INTEGER(o -> o instanceof Integer, (key, o, attr, errs) -> {
+    INTEGER(o -> o instanceof Integer, (key, o, schemaNode, errs) -> {
       if (o instanceof String) {
         try {
           Integer.parseInt((String) o);
@@ -262,7 +296,7 @@ public class JsonSchemaValidator {
         }
       }
     }),
-    BOOLEAN(o -> o instanceof Boolean, (key, o, attr, errs) -> {
+    BOOLEAN(o -> o instanceof Boolean, (key, o, schemaNode, errs) -> {
       if (o instanceof String) {
         try {
           Boolean.parseBoolean((String) o);
@@ -271,6 +305,14 @@ public class JsonSchemaValidator {
         }
       }
     }),
+    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;
@@ -298,7 +340,8 @@ public class JsonSchemaValidator {
         validator.valdateData(key, o, attr, errs);
         return;
       }
-      if (!typeValidator.test(o)) errs.add("Expected type : " + _name + " but found : " + Utils.toJSONString(o));
+      if (!typeValidator.test(o))
+        errs.add("Expected type : " + _name + " but found : " + o + "in object : " + Utils.toJSONString(o));
     }
 
     static Type get(Object type) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/66f09139/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
index 42386e6..5a0fb37 100644
--- a/solr/core/src/resources/apispec/cluster.security.RuleBasedAuthorization.json
+++ b/solr/core/src/resources/apispec/cluster.security.RuleBasedAuthorization.json
@@ -19,6 +19,7 @@
         },
         "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."
         },
 
@@ -39,7 +40,7 @@
         },
         "index": {
           "type": "integer",
-          "description": "The index of the permission you wish to overwrite"
+          "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",
@@ -100,7 +101,7 @@
       ]
     },
     "delete-permission":{
-      "description":"delete a permission by it's index",
+      "description":"delete a permission by its index",
       "type":"integer"
     },
     "set-user-role": {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/66f09139/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
index 65cac8b..32d49cf 100644
--- a/solr/core/src/resources/apispec/cores.core.Commands.json
+++ b/solr/core/src/resources/apispec/cores.core.Commands.json
@@ -71,11 +71,11 @@
             "type": "string"
           },
           "description": "Multi-valued, source cores that would be merged."
+        },
+        "async": {
+          "type": "string",
+          "description": "Request ID to track this action which will be processed asynchronously"
         }
-      },
-      "async": {
-        "type": "string",
-        "description": "Request ID to track this action which will be processed asynchronously"
       }
     },
     "request-recovery": {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/66f09139/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
index 7f0bc2c..e4ba071 100644
--- a/solr/core/src/test/org/apache/solr/util/JsonValidatorTest.java
+++ b/solr/core/src/test/org/apache/solr/util/JsonValidatorTest.java
@@ -57,10 +57,10 @@ public class JsonValidatorTest extends SolrTestCaseJ4 {
     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]}"));
-    assertFalse(toJSONString(errs), errs.isEmpty());
+    assertNotNull(toJSONString(errs), errs);
     assertTrue(toJSONString(errs), errs.get(0).contains("Unknown"));
     errs = validator.validateJson(Utils.fromJSONString("{name : 123, collections: c1 }"));
-    assertFalse(toJSONString(errs), errs.isEmpty());
+    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());
@@ -69,13 +69,12 @@ public class JsonValidatorTest extends SolrTestCaseJ4 {
     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"));
-    Map schema = (Map) Utils.fromJSONString("{" +
+    validator = new JsonSchemaValidator("{" +
         "  type:object," +
         "  properties: {" +
         "   age : {type: number}," +
         "   adult : {type: boolean}," +
         "   name: {type: string}}}");
-    validator = new JsonSchemaValidator(schema);
     errs = validator.validateJson(Utils.fromJSONString("{name:x, age:21, adult:true}"));
     assertNull(errs);
     errs = validator.validateJson(Utils.fromJSONString("{name:x, age:'21', adult:'true'}"));
@@ -83,45 +82,54 @@ public class JsonValidatorTest extends SolrTestCaseJ4 {
 
     errs = validator.validateJson(Utils.fromJSONString("{name:x, age:'x21', adult:'true'}"));
     assertEquals(1, errs.size());
-    schema = (Map) Utils.fromJSONString("{" +
-        "  type:object," +
-        "  properties: {" +
-        "   age : {type: int}," +
-        "   adult : {type: Boolean}," +
-        "   name: {type: string}}}");
     try {
-      validator = new JsonSchemaValidator(schema);
+      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"));
     }
 
-    schema = (Map) Utils.fromJSONString("{" +
-        "  type:object," +
-        "   x : y,"+
-        "  properties: {" +
-        "   age : {type: number}," +
-        "   adult : {type: boolean}," +
-        "   name: {type: string}}}");
     try {
-      validator = new JsonSchemaValidator(schema);
+      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"));
     }
-
-    schema = (Map) Utils.fromJSONString("{" +
-        "  type:object," +
-        "  propertes: {" +
-        "   age : {type: number}," +
-        "   adult : {type: boolean}," +
-        "   name: {type: string}}}");
     try {
-      validator = new JsonSchemaValidator(schema);
+      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("'properties' tag is missing"));
     }
+
+    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"));
   }
 
   private void checkSchema(String name) {


[2/2] lucene-solr:apiv2: SOLR-8029: general refactoring

Posted by no...@apache.org.
SOLR-8029: general refactoring


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

Branch: refs/heads/apiv2
Commit: cf1bbc3d74fd11f81e6fa51c5c5f205fdc4e2c21
Parents: 66f0913
Author: Noble Paul <no...@apache.org>
Authored: Fri Sep 9 17:23:50 2016 +0530
Committer: Noble Paul <no...@apache.org>
Committed: Fri Sep 9 17:23:50 2016 +0530

----------------------------------------------------------------------
 .../src/java/org/apache/solr/api/ApiBag.java    |  4 +--
 .../java/org/apache/solr/api/ApiHandler.java    | 26 ++++++++++++++++
 .../java/org/apache/solr/api/V2HttpCall.java    |  4 +--
 .../apache/solr/handler/ReplicationHandler.java |  3 --
 .../apache/solr/handler/admin/ApiCommand.java   |  5 ++++
 .../handler/admin/BaseHandlerApiSupport.java    | 12 ++++++--
 .../handler/admin/CollectionHandlerApi.java     | 25 +++++++++-------
 .../handler/admin/ConfigSetsHandlerApi.java     | 19 +++++-------
 .../solr/handler/admin/CoreAdminHandlerApi.java | 21 +++++++------
 .../src/java/org/apache/solr/util/PathTrie.java | 31 ++++++++++----------
 .../src/resources/apispec/cluster.nodes.json    | 11 +++++++
 .../src/resources/apispec/core.RealtimeGet.json |  8 +++--
 .../apispec/core.SchemaEdit.addField.json       |  6 ----
 .../core/src/resources/apispec/core.Update.json |  1 +
 14 files changed, 109 insertions(+), 67 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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
index 17582cf..8077485 100644
--- a/solr/core/src/java/org/apache/solr/api/ApiBag.java
+++ b/solr/core/src/java/org/apache/solr/api/ApiBag.java
@@ -183,9 +183,9 @@ public class ApiBag {
   private Set<String> getWildCardNames(List<String> paths) {
     Set<String> wildCardNames = new HashSet<>();
     for (String path : paths) {
-      List<String> p = PathTrie.getTemplateVariables(path);
+      List<String> p = PathTrie.getPathSegments(path);
       for (String s : p) {
-        String wildCard = PathTrie.wildCardName(s);
+        String wildCard = PathTrie.templateName(s);
         if (wildCard != null) wildCardNames.add(wildCard);
       }
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/solr/core/src/java/org/apache/solr/api/ApiHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/ApiHandler.java b/solr/core/src/java/org/apache/solr/api/ApiHandler.java
new file mode 100644
index 0000000..a988d30
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/ApiHandler.java
@@ -0,0 +1,26 @@
+/*
+ * 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.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+interface ApiHandler {
+  void handle(SolrQueryRequest req, SolrQueryResponse rsp);
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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
index 91f2f66..98dff29 100644
--- a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
+++ b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
@@ -52,7 +52,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static org.apache.solr.servlet.SolrDispatchFilter.Action.PASSTHROUGH;
-import static org.apache.solr.util.PathTrie.getTemplateVariables;
+import static org.apache.solr.util.PathTrie.getPathSegments;
 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;
@@ -77,7 +77,7 @@ public class V2HttpCall extends HttpSolrCall {
     String path = this.path;
     String fullPath = path = path.substring(3);//strip off '/v2'
     try {
-      pieces = getTemplateVariables(path);
+      pieces = getPathSegments(path);
       if (pieces.size() == 0) {
         prefix = "c";
         path = "/c";

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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 c511310..84e1ba2 100644
--- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
@@ -1664,9 +1664,6 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
     }
   }
 
-
-  private static final String LOCATION = "location";
-
   private static final String SUCCESS = "success";
 
   private static final String FAILED = "failed";

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/solr/core/src/java/org/apache/solr/handler/admin/ApiCommand.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ApiCommand.java b/solr/core/src/java/org/apache/solr/handler/admin/ApiCommand.java
index cd9d4b3..5a85c14 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ApiCommand.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ApiCommand.java
@@ -20,11 +20,14 @@ package org.apache.solr.handler.admin;
 import java.util.Collection;
 
 import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.util.CommandOperation;
 
 public interface ApiCommand {
   String getName();
 
+  // the method supported by this command
   SolrRequest.METHOD getHttpMethod();
 
   V2EndPoint getEndPoint();
@@ -37,4 +40,6 @@ public interface ApiCommand {
   default String getParamSubstitute(String name) {
     return name;
   }
+
+  void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception;
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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
index 9bb3044..198d788 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
@@ -65,6 +65,7 @@ public abstract class BaseHandlerApiSupport implements ApiSupport {
 
 
   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) {
@@ -90,7 +91,8 @@ public abstract class BaseHandlerApiSupport implements ApiSupport {
               throw new SolrException(BAD_REQUEST, " no such command " + c);
             }
             wrapParams(req, c, command, false);
-            invokeCommand(req, rsp,command, c);
+            command.invoke(req,rsp, apiHandler);
+//            invokeCommand(req, rsp, command, c);
 
           } else {
             if (commands == null || commands.isEmpty()) {
@@ -106,7 +108,8 @@ public abstract class BaseHandlerApiSupport implements ApiSupport {
               }
             }
             wrapParams(req, new CommandOperation("", Collections.EMPTY_MAP), commands.get(0), true);
-            invokeUrl(commands.get(0), req, rsp);
+            commands.get(0).invoke(req,rsp, apiHandler);
+//            invokeUrl(commands.get(0), req, rsp);
           }
 
         } catch (SolrException e) {
@@ -195,10 +198,13 @@ public abstract class BaseHandlerApiSupport implements ApiSupport {
       }
     }
   }
+/*
 
-  protected abstract void invokeCommand(SolrQueryRequest  req, SolrQueryResponse rsp, ApiCommand command, CommandOperation c) throws Exception;
+  protected abstract void invokeCommand(SolrQueryRequest  req, SolrQueryResponse rsp, ApiCommand command, CommandOperation c)
+      throws Exception;
 
   protected abstract void invokeUrl(ApiCommand command, SolrQueryRequest req, SolrQueryResponse rsp) throws Exception;
+*/
 
   protected abstract List<ApiCommand> getCommands();
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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
index 249794c..df35a99 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
@@ -52,16 +52,6 @@ public class CollectionHandlerApi extends BaseHandlerApiSupport {
   }
 
   @Override
-  protected void invokeCommand(SolrQueryRequest req, SolrQueryResponse rsp, ApiCommand command, CommandOperation c) throws Exception {
-    handler.invokeAction(req, rsp, handler.coreContainer, ((Cmd) command).target.action, ((Cmd) command).target);
-  }
-
-  @Override
-  protected void invokeUrl(ApiCommand command, SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
-    handler.invokeAction(req, rsp, handler.coreContainer, ((Cmd) command).target.action, ((Cmd) command).target);
-  }
-
-  @Override
   protected List<V2EndPoint> getEndPoints() {
     return Arrays.asList(EndPoint.values());
   }
@@ -188,7 +178,14 @@ public class CollectionHandlerApi extends BaseHandlerApiSupport {
         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());
+        ;
+      }
+    }
     ;
     public final String commandName;
     public final EndPoint endPoint;
@@ -270,11 +267,17 @@ public class CollectionHandlerApi extends BaseHandlerApiSupport {
       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"),
     COLLECTIONS_COMMANDS("collections.Commands"),
     COLLECTIONS("collections"),

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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
index 546040f..83e5bd4 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandlerApi.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandlerApi.java
@@ -41,17 +41,6 @@ public class ConfigSetsHandlerApi extends BaseHandlerApiSupport {
     this.configSetHandler = configSetHandler;
   }
 
-  @Override
-  protected void invokeCommand(SolrQueryRequest req, SolrQueryResponse rsp, ApiCommand command, CommandOperation c)
-      throws Exception {
-    ((Cmd) command).op.call(req, rsp, this.configSetHandler);
-
-  }
-
-  @Override
-  protected void invokeUrl(ApiCommand command, SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
-    ((Cmd)command).op.call(req, rsp,configSetHandler);
-  }
 
   @Override
   protected List<ApiCommand> getCommands() {
@@ -98,6 +87,14 @@ public class ConfigSetsHandlerApi extends BaseHandlerApiSupport {
     public V2EndPoint getEndPoint() {
       return endPoint;
     }
+
+    @Override
+    public void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception {
+          op.call(req, rsp,
+              ((ConfigSetsHandlerApi)apiHandler).configSetHandler);
+
+    }
+
   }
   enum EndPoint implements V2EndPoint {
     LIST_CONFIG("cluster.config"),

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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
index a8fb159..577954c 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandlerApi.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandlerApi.java
@@ -109,6 +109,16 @@ public class CoreAdminHandlerApi extends BaseHandlerApiSupport {
     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));
+
+    }
+
   }
 
 
@@ -135,17 +145,6 @@ public class CoreAdminHandlerApi extends BaseHandlerApiSupport {
 
 
   @Override
-  protected void invokeCommand(SolrQueryRequest req, SolrQueryResponse rsp, ApiCommand command,
-                               CommandOperation c) throws Exception {
-    ((Cmd) command).target.execute(new CoreAdminHandler.CallInfo(handler, req, rsp, ((Cmd) command).target));
-  }
-
-  @Override
-  protected void invokeUrl(ApiCommand command, SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
-    ((Cmd) command).target.execute(new CoreAdminHandler.CallInfo(handler, req, rsp, ((Cmd) command).target));
-  }
-
-  @Override
   protected List<ApiCommand> getCommands() {
     return Arrays.asList(Cmd.values());
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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
index 655bb8b..3326737 100644
--- a/solr/core/src/java/org/apache/solr/util/PathTrie.java
+++ b/solr/core/src/java/org/apache/solr/util/PathTrie.java
@@ -41,7 +41,7 @@ public class PathTrie<T> {
 
 
   public void insert(String path, Map<String, String> replacements, T o) {
-    List<String> parts = getTemplateVariables(path);
+    List<String> parts = getPathSegments(path);
     if (parts.isEmpty()) {
       root.obj = o;
       return;
@@ -62,7 +62,8 @@ public class PathTrie<T> {
     root.insert(parts, o);
   }
 
-  public static List<String> getTemplateVariables(String path) {
+  // /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
@@ -76,17 +77,17 @@ public class PathTrie<T> {
   }
 
 
-  public T lookup(String uri, Map<String, String> parts) {
-    return root.lookup(getTemplateVariables(uri), 0, parts);
+  public T lookup(String uri, Map<String, String> templateValues) {
+    return root.lookup(getPathSegments(uri), 0, templateValues);
   }
 
-  public T lookup(String path, Map<String, String> parts, Set<String> paths) {
-    return root.lookup(getTemplateVariables(path), 0, parts, paths);
+  public T lookup(String path, Map<String, String> templateValues, Set<String> paths) {
+    return root.lookup(getPathSegments(path), 0, templateValues, paths);
   }
 
-  public static String wildCardName(String part) {
-    return part.startsWith("{") && part.endsWith("}") ?
-        part.substring(1, part.length() - 1) :
+  public static String templateName(String templateStr) {
+    return templateStr.startsWith("{") && templateStr.endsWith("}") ?
+        templateStr.substring(1, templateStr.length() - 1) :
         null;
 
   }
@@ -95,7 +96,7 @@ public class PathTrie<T> {
     String name;
     Map<String, Node> children;
     T obj;
-    String varName;
+    String templateName;
 
     Node(List<String> path, T o) {
       if (path.isEmpty()) {
@@ -103,7 +104,7 @@ public class PathTrie<T> {
         return;
       }
       String part = path.get(0);
-      varName = wildCardName(part);
+      templateName = templateName(part);
       name = part;
       if (path.isEmpty()) obj = o;
     }
@@ -114,7 +115,7 @@ public class PathTrie<T> {
       Node matchedChild = null;
       if (children == null) children = new ConcurrentHashMap<>();
 
-      String varName = wildCardName(part);
+      String varName = templateName(part);
       String key = varName == null ? part : "";
 
       matchedChild = children.get(key);
@@ -122,8 +123,8 @@ public class PathTrie<T> {
         children.put(key, matchedChild = new Node(path, o));
       }
       if (varName != null) {
-        if (!matchedChild.varName.equals(varName)) {
-          throw new RuntimeException("wildcard name must be " + matchedChild.varName);
+        if (!matchedChild.templateName.equals(varName)) {
+          throw new RuntimeException("wildcard name must be " + matchedChild.templateName);
         }
       }
       path.remove(0);
@@ -166,7 +167,7 @@ public class PathTrie<T> {
      * @param availableSubPaths If not null , available sub paths will be returned in this set
      */
     public T lookup(List<String> pieces, int index, Map<String, String> templateVariables, Set<String> availableSubPaths) {
-      if (varName != null) templateVariables.put(varName, pieces.get(index - 1));
+      if (templateName != null) templateVariables.put(templateName, pieces.get(index - 1));
       if (pieces.size() < index + 1) {
         findAvailableChildren("", availableSubPaths);
         return obj;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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..310577e
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.nodes.json
@@ -0,0 +1,11 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+API",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/cluster/nodes"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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
index a1f7dfd..af7fd66 100644
--- a/solr/core/src/resources/apispec/core.RealtimeGet.json
+++ b/solr/core/src/resources/apispec/core.RealtimeGet.json
@@ -5,9 +5,7 @@
   ],
   "url": {
     "paths": [
-      "/get",
-      "/get/versions",
-      "/get/updates"
+      "/get"
     ],
     "params": {
       "id": {
@@ -17,6 +15,10 @@
       "ids": {
         "type": "string",
         "description": "one or more ids. Separate by commas if there are more than one"
+      },
+      "fq":{
+        "type": "string",
+        "description": "Filter query"
       }
     }
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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
index 13e5646..798b81b 100644
--- a/solr/core/src/resources/apispec/core.SchemaEdit.addField.json
+++ b/solr/core/src/resources/apispec/core.SchemaEdit.addField.json
@@ -14,15 +14,9 @@
     "indexed": {
       "type": "boolean"
     },
-    "tokenized": {
-      "type": "boolean"
-    },
     "stored": {
       "type": "boolean"
     },
-    "binary": {
-      "type": "boolean"
-    },
     "omitNorms": {
       "type": "boolean"
     },

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cf1bbc3d/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
index 2d521df..f9e80c1 100644
--- a/solr/core/src/resources/apispec/core.Update.json
+++ b/solr/core/src/resources/apispec/core.Update.json
@@ -9,6 +9,7 @@
       "/update/xml",
       "/update/csv",
       "/update/json",
+      "/update/bin",
       "/update/json/commands"
     ]