lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kris...@apache.org
Subject [18/35] lucene-solr:jira/solr-8593: SOLR-8029: Added new style APIs and a framework for creating new APIs and mapping old APIs to new
Date Wed, 01 Feb 2017 17:00:46 GMT
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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..b244015 100644
--- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
+++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
@@ -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,6 +72,8 @@ 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.ValidatingJsonMap;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.common.util.StrUtils;
@@ -97,6 +100,8 @@ 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.zookeeper.KeeperException;
 import org.slf4j.Logger;
@@ -149,6 +154,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 +184,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 +212,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 +268,7 @@ public class HttpSolrCall {
           core = cores.getCore(corename);
           if (core != null) {
             path = path.substring(idx);
-          } 
+          }
         }
       }
       if (core == null) {
@@ -321,13 +330,27 @@ public class HttpSolrCall {
 
     action = PASSTHROUGH;
   }
-  
+
+  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 +393,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 +491,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 +684,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 +792,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 +933,10 @@ public class HttpSolrCall {
     return null;
   }
 
+  protected Object _getHandler(){
+    return handler;
+  }
+
   private AuthorizationContext getAuthCtx() {
 
     String resource = getPath();
@@ -987,7 +1026,7 @@ public class HttpSolrCall {
 
       @Override
       public Object getHandler() {
-        return handler;
+        return _getHandler();
       }
 
       @Override
@@ -1021,6 +1060,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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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/71abe130/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"
+      ]
+    }
+  }
+}


Mime
View raw message