lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From no...@apache.org
Subject [1/6] lucene-solr:branch_6x: SOLR-8029: Added new style APIs and a framework for creating new APIs and mapping old APIs to new
Date Sat, 04 Feb 2017 20:16:28 GMT
Repository: lucene-solr
Updated Branches:
  refs/heads/branch_6x 42b71955c -> cceba3cc0


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d7263481/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/d7263481/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/d7263481/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/d7263481/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 48ccc76..a722176 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/d7263481/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/d7263481/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 279708b..298ec82 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/d7263481/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/d7263481/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/d7263481/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/d7263481/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/d7263481/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/d7263481/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/d7263481/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 934384f..5e187b2 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", "");
   }


Mime
View raw message