You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2017/01/31 05:57:55 UTC
[5/5] lucene-solr:master: SOLR-8029: Added new style APIs and a
framework for creating new APIs and mapping old APIs to new
SOLR-8029: Added new style APIs and a framework for creating new APIs and mapping old APIs to new
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/71abe130
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/71abe130
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/71abe130
Branch: refs/heads/master
Commit: 71abe130697b0f279d6e3613145f1f8f052c7848
Parents: c8edbe8
Author: Noble Paul <no...@apache.org>
Authored: Tue Jan 31 16:27:33 2017 +1030
Committer: Noble Paul <no...@apache.org>
Committed: Tue Jan 31 16:27:33 2017 +1030
----------------------------------------------------------------------
solr/CHANGES.txt | 3 +
solr/core/src/java/org/apache/solr/api/Api.java | 67 ++++
.../src/java/org/apache/solr/api/ApiBag.java | 354 ++++++++++++++++++
.../java/org/apache/solr/api/ApiSupport.java | 46 +++
.../java/org/apache/solr/api/SpecProvider.java | 25 ++
.../java/org/apache/solr/api/V2HttpCall.java | 340 +++++++++++++++++
.../java/org/apache/solr/api/package-info.java | 21 ++
.../src/java/org/apache/solr/cloud/Assign.java | 10 +-
.../org/apache/solr/cloud/CreateShardCmd.java | 2 +-
.../java/org/apache/solr/core/PluginBag.java | 71 +++-
.../org/apache/solr/handler/BlobHandler.java | 14 +-
.../apache/solr/handler/DumpRequestHandler.java | 11 +
.../apache/solr/handler/PingRequestHandler.java | 5 +
.../apache/solr/handler/RealTimeGetHandler.java | 14 +
.../apache/solr/handler/RequestHandlerBase.java | 12 +-
.../org/apache/solr/handler/SchemaHandler.java | 35 +-
.../apache/solr/handler/SolrConfigHandler.java | 16 +
.../solr/handler/UpdateRequestHandler.java | 2 +
.../solr/handler/UpdateRequestHandlerApi.java | 73 ++++
.../handler/admin/BaseHandlerApiSupport.java | 236 ++++++++++++
.../handler/admin/CollectionHandlerApi.java | 319 ++++++++++++++++
.../solr/handler/admin/CollectionsHandler.java | 69 ++--
.../solr/handler/admin/ConfigSetsHandler.java | 47 ++-
.../handler/admin/ConfigSetsHandlerApi.java | 112 ++++++
.../solr/handler/admin/CoreAdminHandler.java | 14 +
.../solr/handler/admin/CoreAdminHandlerApi.java | 175 +++++++++
.../apache/solr/handler/admin/InfoHandler.java | 78 ++--
.../solr/handler/admin/SecurityConfHandler.java | 70 ++++
.../apache/solr/request/SolrQueryRequest.java | 24 ++
.../solr/request/SolrQueryRequestBase.java | 35 ++
.../org/apache/solr/schema/SchemaManager.java | 13 +-
.../apache/solr/security/BasicAuthPlugin.java | 10 +-
.../security/RuleBasedAuthorizationPlugin.java | 11 +-
.../security/Sha256AuthenticationProvider.java | 8 +
.../org/apache/solr/servlet/HttpSolrCall.java | 115 ++++--
.../org/apache/solr/servlet/ResponseUtils.java | 6 +
.../apache/solr/servlet/SolrDispatchFilter.java | 18 +-
.../apache/solr/servlet/SolrRequestParsers.java | 27 +-
.../org/apache/solr/util/CommandOperation.java | 4 +
.../apache/solr/util/JsonSchemaValidator.java | 370 +++++++++++++++++++
.../src/java/org/apache/solr/util/PathTrie.java | 195 ++++++++++
solr/core/src/resources/ImplicitPlugins.json | 6 +-
.../src/resources/apispec/cluster.Commands.json | 74 ++++
.../apispec/cluster.commandstatus.delete.json | 10 +
.../apispec/cluster.commandstatus.json | 20 +
.../apispec/cluster.configs.Commands.json | 34 ++
.../apispec/cluster.configs.delete.json | 12 +
.../src/resources/apispec/cluster.configs.json | 12 +
solr/core/src/resources/apispec/cluster.json | 14 +
.../src/resources/apispec/cluster.nodes.json | 12 +
.../cluster.security.BasicAuth.Commands.json | 23 ++
...cluster.security.RuleBasedAuthorization.json | 129 +++++++
...luster.security.authentication.Commands.json | 12 +
.../cluster.security.authentication.json | 12 +
...cluster.security.authorization.Commands.json | 13 +
.../apispec/cluster.security.authorization.json | 13 +
.../resources/apispec/collections.Commands.json | 206 +++++++++++
.../collections.collection.Commands.json | 137 +++++++
.../collections.collection.Commands.modify.json | 36 ++
.../collections.collection.Commands.reload.json | 11 +
.../apispec/collections.collection.delete.json | 13 +
.../apispec/collections.collection.json | 19 +
.../collections.collection.shards.Commands.json | 109 ++++++
...ctions.collection.shards.shard.Commands.json | 24 ++
...lections.collection.shards.shard.delete.json | 27 ++
....collection.shards.shard.replica.delete.json | 39 ++
.../core/src/resources/apispec/collections.json | 13 +
.../src/resources/apispec/core.RealtimeGet.json | 26 ++
.../apispec/core.SchemaEdit.addCopyField.json | 27 ++
.../apispec/core.SchemaEdit.addField.json | 98 +++++
.../core.SchemaEdit.addFieldType.analyzers.json | 51 +++
.../apispec/core.SchemaEdit.addFieldType.json | 53 +++
.../core.SchemaEdit.deleteCopyField.json | 19 +
.../core.SchemaEdit.deleteDynamicField.json | 12 +
.../apispec/core.SchemaEdit.deleteField.json | 12 +
.../core.SchemaEdit.deleteFieldType.json | 14 +
.../src/resources/apispec/core.SchemaEdit.json | 47 +++
.../apispec/core.SchemaRead.copyFields.json | 26 ++
...ore.SchemaRead.dynamicFields_fieldTypes.json | 20 +
.../apispec/core.SchemaRead.fields.json | 34 ++
.../src/resources/apispec/core.SchemaRead.json | 18 +
.../core/src/resources/apispec/core.Update.json | 17 +
...g.Commands.addRequestHandler.properties.json | 25 ++
.../apispec/core.config.Commands.generic.json | 19 +
.../resources/apispec/core.config.Commands.json | 215 +++++++++++
.../core.config.Commands.runtimeLib.json | 23 ++
.../apispec/core.config.Params.Commands.json | 31 ++
.../resources/apispec/core.config.Params.json | 13 +
.../core/src/resources/apispec/core.config.json | 18 +
.../src/resources/apispec/core.system.blob.json | 20 +
.../apispec/core.system.blob.upload.json | 12 +
.../src/resources/apispec/cores.Commands.json | 85 +++++
.../src/resources/apispec/cores.Status.json | 20 +
.../resources/apispec/cores.core.Commands.json | 136 +++++++
.../apispec/cores.core.Commands.split.json | 34 ++
solr/core/src/resources/apispec/emptySpec.json | 11 +
.../src/resources/apispec/node.Commands.json | 24 ++
solr/core/src/resources/apispec/node.Info.json | 11 +
.../core/src/resources/apispec/node.invoke.json | 16 +
.../conf/solrconfig-managed-schema.xml | 2 +-
.../test/org/apache/solr/api/TestPathTrie.java | 61 +++
.../org/apache/solr/cloud/rule/RulesTest.java | 18 +
.../solr/core/BlobStoreTestRequestHandler.java | 1 +
.../test/org/apache/solr/core/SolrCoreTest.java | 1 +
.../apache/solr/core/TestDynamicLoading.java | 2 +-
.../apache/solr/core/TestSolrConfigHandler.java | 85 ++++-
.../solr/handler/V2ApiIntegrationTest.java | 98 +++++
.../solr/handler/admin/TestApiFramework.java | 219 +++++++++++
.../solr/handler/admin/TestCollectionAPIs.java | 231 ++++++++++++
.../solr/handler/admin/TestConfigsApi.java | 59 +++
.../solr/handler/admin/TestCoreAdminApis.java | 115 ++++++
.../solr/rest/schema/TestBulkSchemaAPI.java | 15 +
.../solr/security/BasicAuthIntegrationTest.java | 4 +
.../TestRuleBasedAuthorizationPlugin.java | 8 +-
.../solr/servlet/SolrRequestParserTest.java | 4 +-
.../org/apache/solr/util/JsonValidatorTest.java | 189 ++++++++++
.../conf/solrconfig.xml | 8 +-
.../apache/solr/client/solrj/SolrRequest.java | 13 +-
.../solr/client/solrj/impl/CloudSolrClient.java | 16 +-
.../org/apache/solr/common/cloud/Replica.java | 3 +
.../org/apache/solr/common/util/StrUtils.java | 10 +-
.../java/org/apache/solr/common/util/Utils.java | 5 +-
.../solr/common/util/ValidatingJsonMap.java | 349 +++++++++++++++++
.../solrj/embedded/SolrExampleJettyTest.java | 9 +-
.../solr/common/util/TestValidatingJsonMap.java | 52 +++
.../org/apache/solr/util/RestTestHarness.java | 8 +
126 files changed, 6715 insertions(+), 164 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 27a9c7f..754c971 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -118,6 +118,9 @@ New Features
* SOLR-9481: Authentication and Authorization plugins now work in standalone mode if security.json is placed in
SOLR_HOME on every node. Editing config through API is supported but affects only that one node. (janhoy)
+* SOLR-8029: Added new style APIs and a framework for creating new APIs and mapping old APIs to new
+ (noble, Steve Rowe, Cassandra Targett, Timothy Potter)
+
Bug Fixes
----------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/api/Api.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/Api.java b/solr/core/src/java/org/apache/solr/api/Api.java
new file mode 100644
index 0000000..8512c89
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/Api.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.api;
+
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.JsonSchemaValidator;
+
+/** Every version 2 API must extend the this class. It's mostly like a request handler
+ * but it has extra methods to provide the json schema of the end point
+ *
+ */
+public abstract class Api implements SpecProvider {
+ protected SpecProvider spec;
+ protected volatile Map<String, JsonSchemaValidator> commandSchema;
+
+ protected Api(SpecProvider spec) {
+ this.spec = spec;
+ }
+
+ /**This method helps to cache the schema validator object
+ */
+ public Map<String, JsonSchemaValidator> getCommandSchema() {
+ if (commandSchema == null) {
+ synchronized (this) {
+ if(commandSchema == null) {
+ ValidatingJsonMap commands = getSpec().getMap("commands", null);
+ commandSchema = commands != null ?
+ ImmutableMap.copyOf(ApiBag.getParsedSchema(commands)) :
+ ImmutableMap.of();
+ }
+ }
+ }
+ return commandSchema;
+ }
+
+ /** The method that gets called for each request
+ */
+ public abstract void call(SolrQueryRequest req , SolrQueryResponse rsp);
+
+ /**Get the specification of the API as a Map
+ */
+ @Override
+ public ValidatingJsonMap getSpec() {
+ return spec.getSpec();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/api/ApiBag.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/ApiBag.java b/solr/core/src/java/org/apache/solr/api/ApiBag.java
new file mode 100644
index 0000000..82d6a39
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/ApiBag.java
@@ -0,0 +1,354 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.api;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.core.PluginBag;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.handler.RequestHandlerUtils;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.request.SolrRequestHandler;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.security.PermissionNameProvider;
+import org.apache.solr.util.CommandOperation;
+import org.apache.solr.util.JsonSchemaValidator;
+import org.apache.solr.util.PathTrie;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.client.solrj.SolrRequest.SUPPORTED_METHODS;
+import static org.apache.solr.common.params.CommonParams.NAME;
+import static org.apache.solr.common.util.StrUtils.formatString;
+import static org.apache.solr.common.util.ValidatingJsonMap.ENUM_OF;
+import static org.apache.solr.common.util.ValidatingJsonMap.NOT_NULL;
+
+public class ApiBag {
+ private final boolean isCoreSpecific;
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private final Map<String, PathTrie<Api>> apis = new ConcurrentHashMap<>();
+
+ public ApiBag(boolean isCoreSpecific) {
+ this.isCoreSpecific = isCoreSpecific;
+ }
+
+ public synchronized void register(Api api, Map<String, String> nameSubstitutes) {
+ try {
+ validateAndRegister(api, nameSubstitutes);
+ } catch (Exception e) {
+ log.error("Unable to register plugin:" + api.getClass().getName() + "with spec :" + Utils.toJSONString(api.getSpec()), e);
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ } else {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+ }
+
+ }
+ }
+
+ private void validateAndRegister(Api api, Map<String, String> nameSubstitutes) {
+ ValidatingJsonMap spec = api.getSpec();
+ Api introspect = new IntrospectApi(api, isCoreSpecific);
+ List<String> methods = spec.getList("methods", ENUM_OF, SUPPORTED_METHODS);
+ for (String method : methods) {
+ PathTrie<Api> registry = apis.get(method);
+
+ if (registry == null) apis.put(method, registry = new PathTrie<>(ImmutableSet.of("_introspect")));
+ ValidatingJsonMap url = spec.getMap("url", NOT_NULL);
+ ValidatingJsonMap params = url.getMap("params", null);
+ if (params != null) {
+ for (Object o : params.keySet()) {
+ ValidatingJsonMap param = params.getMap(o.toString(), NOT_NULL);
+ param.get("type", ENUM_OF, KNOWN_TYPES);
+ }
+ }
+ List<String> paths = url.getList("paths", NOT_NULL);
+ ValidatingJsonMap parts = url.getMap("parts", null);
+ if (parts != null) {
+ Set<String> wildCardNames = getWildCardNames(paths);
+ for (Object o : parts.keySet()) {
+ if (!wildCardNames.contains(o.toString()))
+ throw new RuntimeException("" + o + " is not a valid part name");
+ ValidatingJsonMap pathMeta = parts.getMap(o.toString(), NOT_NULL);
+ pathMeta.get("type", ENUM_OF, ImmutableSet.of("enum", "string", "int", "number", "boolean"));
+ }
+ }
+ verifyCommands(api.getSpec());
+ for (String path : paths) {
+ registry.insert(path, nameSubstitutes, api);
+ registerIntrospect(nameSubstitutes, registry, path, introspect);
+ }
+ }
+ }
+
+ public static void registerIntrospect(Map<String, String> nameSubstitutes, PathTrie<Api> registry, String path, Api introspect) {
+ List<String> l = PathTrie.getPathSegments(path);
+ registerIntrospect(l, registry, nameSubstitutes, introspect);
+ int lastIdx = l.size() - 1;
+ for (int i = lastIdx; i >= 0; i--) {
+ String itemAt = l.get(i);
+ if (PathTrie.templateName(itemAt) == null) break;
+ l.remove(i);
+ if (registry.lookup(l, new HashMap<>()) != null) break;
+ registerIntrospect(l, registry, nameSubstitutes, introspect);
+ }
+ }
+
+ static void registerIntrospect(List<String> l, PathTrie<Api> registry, Map<String, String> substitutes, Api introspect) {
+ ArrayList<String> copy = new ArrayList<>(l);
+ copy.add("_introspect");
+ registry.insert(copy, substitutes, introspect);
+ }
+
+ public static class IntrospectApi extends Api {
+ Api baseApi;
+ final boolean isCoreSpecific;
+
+ public IntrospectApi(Api base, boolean isCoreSpecific) {
+ super(EMPTY_SPEC);
+ this.baseApi = base;
+ this.isCoreSpecific = isCoreSpecific;
+ }
+
+ public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+
+ String cmd = req.getParams().get("command");
+ ValidatingJsonMap result = null;
+ if (cmd == null) {
+ result = isCoreSpecific ? ValidatingJsonMap.getDeepCopy(baseApi.getSpec(), 5, true) : baseApi.getSpec();
+ } else {
+ ValidatingJsonMap specCopy = ValidatingJsonMap.getDeepCopy(baseApi.getSpec(), 5, true);
+ ValidatingJsonMap commands = specCopy.getMap("commands", null);
+ if (commands != null) {
+ ValidatingJsonMap m = commands.getMap(cmd, null);
+ specCopy.put("commands", Collections.singletonMap(cmd, m));
+ }
+ result = specCopy;
+ }
+ if (isCoreSpecific) {
+ List<String> pieces = req.getHttpSolrCall() == null ? null : ((V2HttpCall) req.getHttpSolrCall()).pieces;
+ if (pieces != null) {
+ String prefix = "/" + pieces.get(0) + "/" + pieces.get(1);
+ List<String> paths = result.getMap("url", NOT_NULL).getList("paths", NOT_NULL);
+ result.getMap("url", NOT_NULL).put("paths",
+ paths.stream()
+ .map(s -> prefix + s)
+ .collect(Collectors.toList()));
+ }
+ }
+ List l = (List) rsp.getValues().get("spec");
+ if (l == null) rsp.getValues().add("spec", l = new ArrayList());
+ l.add(result);
+ RequestHandlerUtils.addExperimentalFormatWarning(rsp);
+ }
+ }
+
+ public static Map<String, JsonSchemaValidator> getParsedSchema(ValidatingJsonMap commands) {
+ Map<String, JsonSchemaValidator> validators = new HashMap<>();
+ for (Object o : commands.entrySet()) {
+ Map.Entry cmd = (Map.Entry) o;
+ try {
+ validators.put((String) cmd.getKey(), new JsonSchemaValidator((Map) cmd.getValue()));
+ } catch (Exception e) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in api spec", e);
+ }
+ }
+ return validators;
+ }
+
+
+ private void verifyCommands(ValidatingJsonMap spec) {
+ ValidatingJsonMap commands = spec.getMap("commands", null);
+ if (commands == null) return;
+ getParsedSchema(commands);
+
+ }
+
+ private Set<String> getWildCardNames(List<String> paths) {
+ Set<String> wildCardNames = new HashSet<>();
+ for (String path : paths) {
+ List<String> p = PathTrie.getPathSegments(path);
+ for (String s : p) {
+ String wildCard = PathTrie.templateName(s);
+ if (wildCard != null) wildCardNames.add(wildCard);
+ }
+ }
+ return wildCardNames;
+ }
+
+
+ public Api lookup(String path, String httpMethod, Map<String, String> parts) {
+ if (httpMethod == null) {
+ for (PathTrie<Api> trie : apis.values()) {
+ Api api = trie.lookup(path, parts);
+ if (api != null) return api;
+ }
+ return null;
+ } else {
+ PathTrie<Api> registry = apis.get(httpMethod);
+ if (registry == null) return null;
+ return registry.lookup(path, parts);
+ }
+ }
+
+ public static SpecProvider getSpec(final String name) {
+ return () -> {
+ return ValidatingJsonMap.parse(APISPEC_LOCATION + name + ".json", APISPEC_LOCATION);
+ };
+ }
+
+ public static class ReqHandlerToApi extends Api implements PermissionNameProvider {
+ SolrRequestHandler rh;
+
+ public ReqHandlerToApi(SolrRequestHandler rh, SpecProvider spec) {
+ super(spec);
+ this.rh = rh;
+ }
+
+ @Override
+ public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+ rh.handleRequest(req, rsp);
+ }
+
+ @Override
+ public Name getPermissionName(AuthorizationContext ctx) {
+ if (rh instanceof PermissionNameProvider) {
+ return ((PermissionNameProvider) rh).getPermissionName(ctx);
+ }
+ return null;
+ }
+ }
+
+ public static List<Api> wrapRequestHandlers(final SolrRequestHandler rh, String... specs) {
+ ImmutableList.Builder<Api> b = ImmutableList.builder();
+ for (String spec : specs) b.add(new ReqHandlerToApi(rh, ApiBag.getSpec(spec)));
+ return b.build();
+ }
+
+ public static final String APISPEC_LOCATION = "apispec/";
+ public static final String INTROSPECT = "/_introspect";
+
+
+ public static final SpecProvider EMPTY_SPEC = () -> ValidatingJsonMap.EMPTY;
+ public static final String HANDLER_NAME = "handlerName";
+ public static final Set<String> KNOWN_TYPES = ImmutableSet.of("string", "boolean", "list", "int", "double", "object");
+
+ public PathTrie<Api> getRegistry(String method) {
+ return apis.get(method);
+ }
+
+ public void registerLazy(PluginBag.PluginHolder<SolrRequestHandler> holder, PluginInfo info) {
+ String specName = info.attributes.get("spec");
+ if (specName == null) specName = "emptySpec";
+ register(new LazyLoadedApi(ApiBag.getSpec(specName), holder), Collections.singletonMap(HANDLER_NAME, info.attributes.get(NAME)));
+ }
+
+ public static SpecProvider constructSpec(PluginInfo info) {
+ Object specObj = info == null ? null : info.attributes.get("spec");
+ if (specObj == null) specObj = "emptySpec";
+ if (specObj instanceof Map) {
+ Map map = (Map) specObj;
+ return () -> ValidatingJsonMap.getDeepCopy(map, 4, false);
+ } else {
+ return ApiBag.getSpec((String) specObj);
+ }
+ }
+
+ public static List<CommandOperation> getCommandOperations(Reader reader, Map<String, JsonSchemaValidator> validators, boolean validate) {
+ List<CommandOperation> parsedCommands = null;
+ try {
+ parsedCommands = CommandOperation.parse(reader);
+ } catch (IOException e) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
+ }
+ if (validators == null || !validate) { // no validation possible because we do not have a spec
+ return parsedCommands;
+ }
+
+ List<CommandOperation> commandsCopy = CommandOperation.clone(parsedCommands);
+
+ for (CommandOperation cmd : commandsCopy) {
+ JsonSchemaValidator validator = validators.get(cmd.name);
+ if (validator == null) {
+ cmd.addError(formatString("Unknown operation ''{0}'' available ops are ''{1}''", cmd.name,
+ validators.keySet()));
+ continue;
+ } else {
+ List<String> errs = validator.validateJson(cmd.getCommandData());
+ if (errs != null) for (String err : errs) cmd.addError(err);
+ }
+
+ }
+ List<Map> errs = CommandOperation.captureErrors(commandsCopy);
+ if (!errs.isEmpty()) {
+ throw new ExceptionWithErrObject(SolrException.ErrorCode.BAD_REQUEST, "Error in command payload", errs);
+ }
+ return commandsCopy;
+ }
+
+ public static class ExceptionWithErrObject extends SolrException {
+ private List<Map> errs;
+
+ public ExceptionWithErrObject(ErrorCode code, String msg, List<Map> errs) {
+ super(code, msg);
+ this.errs = errs;
+ }
+
+ public List<Map> getErrs() {
+ return errs;
+ }
+ }
+
+ public static class LazyLoadedApi extends Api {
+
+ private final PluginBag.PluginHolder<SolrRequestHandler> holder;
+ private Api delegate;
+
+ protected LazyLoadedApi(SpecProvider specProvider, PluginBag.PluginHolder<SolrRequestHandler> lazyPluginHolder) {
+ super(specProvider);
+ this.holder = lazyPluginHolder;
+ }
+
+ @Override
+ public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+ if (!holder.isLoaded()) {
+ delegate = new ReqHandlerToApi(holder.get(), ApiBag.EMPTY_SPEC);
+ }
+ delegate.call(req, rsp);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/api/ApiSupport.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/ApiSupport.java b/solr/core/src/java/org/apache/solr/api/ApiSupport.java
new file mode 100644
index 0000000..ca1e866
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/ApiSupport.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.api;
+
+import java.util.Collection;
+
+/**The interface that is implemented by a request handler to support the V2 end point
+ *
+ */
+public interface ApiSupport {
+
+ /**It is possible to support multiple v2 apis by a single requesthandler
+ *
+ * @return the list of v2 api implementations
+ */
+ Collection<Api> getApis();
+
+ /**Whether this should be made available at the regular legacy path
+ */
+ default Boolean registerV1() {
+ return Boolean.TRUE;
+ }
+
+ /**Whether this request handler must be made available at the /v2/ path
+ */
+ default Boolean registerV2() {
+ return Boolean.FALSE;
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/api/SpecProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/SpecProvider.java b/solr/core/src/java/org/apache/solr/api/SpecProvider.java
new file mode 100644
index 0000000..c373c99
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/SpecProvider.java
@@ -0,0 +1,25 @@
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.api;
+import org.apache.solr.common.util.ValidatingJsonMap;
+
+/**A generic interface for any class that is capable of providing its specification as a json schema
+ */
+public interface SpecProvider {
+ ValidatingJsonMap getSpec();
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
new file mode 100644
index 0000000..4a053dc
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
@@ -0,0 +1,340 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.api;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.cloud.DocCollection;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.PluginBag;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.request.SolrRequestHandler;
+import org.apache.solr.response.QueryResponseWriter;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.servlet.HttpSolrCall;
+import org.apache.solr.servlet.SolrDispatchFilter;
+import org.apache.solr.servlet.SolrRequestParsers;
+import org.apache.solr.util.JsonSchemaValidator;
+import org.apache.solr.util.PathTrie;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.common.params.CommonParams.JSON;
+import static org.apache.solr.common.params.CommonParams.WT;
+import static org.apache.solr.servlet.SolrDispatchFilter.Action.ADMIN;
+import static org.apache.solr.servlet.SolrDispatchFilter.Action.PASSTHROUGH;
+import static org.apache.solr.servlet.SolrDispatchFilter.Action.PROCESS;
+import static org.apache.solr.util.PathTrie.getPathSegments;
+
+// class that handle the '/v2' path
+public class V2HttpCall extends HttpSolrCall {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ private Api api;
+ List<String> pieces;
+ private String prefix;
+ HashMap<String, String> parts = new HashMap<>();
+ static final Set<String> knownPrefixes = ImmutableSet.of("cluster", "node", "collections", "cores", "c");
+
+ public V2HttpCall(SolrDispatchFilter solrDispatchFilter, CoreContainer cc,
+ HttpServletRequest request, HttpServletResponse response, boolean retry) {
+ super(solrDispatchFilter, cc, request, response, retry);
+ }
+
+ protected void init() throws Exception {
+ String path = this.path;
+ String fullPath = path = path.substring(3);//strip off '/v2'
+ try {
+ pieces = getPathSegments(path);
+ if (pieces.size() == 0) {
+ prefix = "c";
+ path = "/c";
+ } else {
+ prefix = pieces.get(0);
+ }
+
+ boolean isCompositeApi = false;
+ if (knownPrefixes.contains(prefix)) {
+ api = getApiInfo(cores.getRequestHandlers(), path, req.getMethod(), fullPath, parts);
+ if (api != null) {
+ isCompositeApi = api instanceof CompositeApi;
+ if (!isCompositeApi) {
+ initAdminRequest(path);
+ return;
+ }
+ }
+ }
+
+ if ("c".equals(prefix) || "collections".equals(prefix)) {
+ String collectionName = origCorename = corename = pieces.get(1);
+ DocCollection collection = getDocCollection(collectionName);
+ if (collection == null) {
+ if ( ! path.endsWith(ApiBag.INTROSPECT)) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "no such collection or alias");
+ }
+ } else {
+ boolean isPreferLeader = false;
+ if (path.endsWith("/update") || path.contains("/update/")) {
+ isPreferLeader = true;
+ }
+ core = getCoreByCollection(collection.getName(), isPreferLeader);
+ if (core == null) {
+ //this collection exists , but this node does not have a replica for that collection
+ //todo find a better way to compute remote
+ extractRemotePath(corename, origCorename, 0);
+ return;
+ }
+ }
+ } else if ("cores".equals(prefix)) {
+ origCorename = corename = pieces.get(1);
+ core = cores.getCore(corename);
+ }
+ if (core == null) {
+ log.error(">> path: '" + path + "'");
+ if (path.endsWith(ApiBag.INTROSPECT)) {
+ initAdminRequest(path);
+ return;
+ } else {
+ throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "no core retrieved for " + corename);
+ }
+ }
+
+ this.path = path = path.substring(prefix.length() + pieces.get(1).length() + 2);
+ Api apiInfo = getApiInfo(core.getRequestHandlers(), path, req.getMethod(), fullPath, parts);
+ if (isCompositeApi && apiInfo instanceof CompositeApi) {
+ ((CompositeApi) this.api).add(apiInfo);
+ } else {
+ api = apiInfo;
+ }
+ MDCLoggingContext.setCore(core);
+ parseRequest();
+
+ if (usingAliases) {
+ processAliases(aliases, collectionsList);
+ }
+
+ action = PROCESS;
+ // we are done with a valid handler
+ } catch (RuntimeException rte) {
+ log.error("Error in init()", rte);
+ throw rte;
+ } finally {
+ if (api == null) action = PASSTHROUGH;
+ if (solrReq != null) solrReq.getContext().put(CommonParams.PATH, path);
+ }
+ }
+
+ private void initAdminRequest(String path) throws Exception {
+ solrReq = SolrRequestParsers.DEFAULT.parse(null, path, req);
+ solrReq.getContext().put(CoreContainer.class.getName(), cores);
+ requestType = AuthorizationContext.RequestType.ADMIN;
+ action = ADMIN;
+ }
+
+ protected void parseRequest() throws Exception {
+ config = core.getSolrConfig();
+ // get or create/cache the parser for the core
+ SolrRequestParsers parser = config.getRequestParsers();
+
+ // With a valid handler and a valid core...
+
+ if (solrReq == null) solrReq = parser.parse(core, path, req);
+ }
+
+ protected DocCollection getDocCollection(String collectionName) {
+ if (!cores.isZooKeeperAware()) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Solr not running in cloud mode ");
+ }
+ ZkStateReader zkStateReader = cores.getZkController().getZkStateReader();
+ DocCollection collection = zkStateReader.getClusterState().getCollectionOrNull(collectionName);
+ if (collection == null) {
+ collectionName = corename = lookupAliases(collectionName);
+ collection = zkStateReader.getClusterState().getCollectionOrNull(collectionName);
+ }
+ return collection;
+ }
+
+ public static Api getApiInfo(PluginBag<SolrRequestHandler> requestHandlers,
+ String path, String method,
+ String fullPath,
+ Map<String, String> parts) {
+ fullPath = fullPath == null ? path : fullPath;
+ Api api = requestHandlers.v2lookup(path, method, parts);
+ if (api == null && path.endsWith(ApiBag.INTROSPECT)) {
+ // the particular http method does not have any ,
+ // just try if any other method has this path
+ api = requestHandlers.v2lookup(path, null, parts);
+ }
+
+ if (api == null) {
+ return getSubPathApi(requestHandlers, path, fullPath, new CompositeApi(null));
+ }
+
+ if (api instanceof ApiBag.IntrospectApi) {
+ final Map<String, Api> apis = new LinkedHashMap<>();
+ for (String m : SolrRequest.SUPPORTED_METHODS) {
+ Api x = requestHandlers.v2lookup(path, m, parts);
+ if (x != null) apis.put(m, x);
+ }
+ api = new CompositeApi(new Api(ApiBag.EMPTY_SPEC) {
+ @Override
+ public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+ String method = req.getParams().get("method");
+ Set<Api> added = new HashSet<>();
+ for (Map.Entry<String, Api> e : apis.entrySet()) {
+ if (method == null || e.getKey().equals(method)) {
+ if (!added.contains(e.getValue())) {
+ e.getValue().call(req, rsp);
+ added.add(e.getValue());
+ }
+ }
+ }
+ }
+ });
+ getSubPathApi(requestHandlers,path, fullPath, (CompositeApi) api);
+ }
+
+
+ return api;
+ }
+
+ private static CompositeApi getSubPathApi(PluginBag<SolrRequestHandler> requestHandlers, String path, String fullPath, CompositeApi compositeApi) {
+
+ String newPath = path.endsWith(ApiBag.INTROSPECT) ? path.substring(0, path.length() - ApiBag.INTROSPECT.length()) : path;
+ Map<String, Set<String>> subpaths = new LinkedHashMap<>();
+
+ getSubPaths(newPath, requestHandlers.getApiBag(), subpaths);
+ final Map<String, Set<String>> subPaths = subpaths;
+ if (subPaths.isEmpty()) return null;
+ return compositeApi.add(new Api(() -> ValidatingJsonMap.EMPTY) {
+ @Override
+ public void call(SolrQueryRequest req1, SolrQueryResponse rsp) {
+ String prefix = null;
+ prefix = fullPath.endsWith(ApiBag.INTROSPECT) ?
+ fullPath.substring(0, fullPath.length() - ApiBag.INTROSPECT.length()) :
+ fullPath;
+ LinkedHashMap<String, Set<String>> result = new LinkedHashMap<>(subPaths.size());
+ for (Map.Entry<String, Set<String>> e : subPaths.entrySet()) {
+ if (e.getKey().endsWith(ApiBag.INTROSPECT)) continue;
+ result.put(prefix + e.getKey(), e.getValue());
+ }
+
+ Map m = (Map) rsp.getValues().get("availableSubPaths");
+ if(m != null){
+ m.putAll(result);
+ } else {
+ rsp.add("availableSubPaths", result);
+ }
+ }
+ });
+ }
+
+ private static void getSubPaths(String path, ApiBag bag, Map<String, Set<String>> pathsVsMethod) {
+ for (SolrRequest.METHOD m : SolrRequest.METHOD.values()) {
+ PathTrie<Api> registry = bag.getRegistry(m.toString());
+ if (registry != null) {
+ HashSet<String> subPaths = new HashSet<>();
+ registry.lookup(path, new HashMap<>(), subPaths);
+ for (String subPath : subPaths) {
+ Set<String> supportedMethods = pathsVsMethod.get(subPath);
+ if (supportedMethods == null) pathsVsMethod.put(subPath, supportedMethods = new HashSet<>());
+ supportedMethods.add(m.toString());
+ }
+ }
+ }
+ }
+
+ public static class CompositeApi extends Api {
+ private LinkedList<Api> apis = new LinkedList<>();
+
+ public CompositeApi(Api api) {
+ super(ApiBag.EMPTY_SPEC);
+ if (api != null) apis.add(api);
+ }
+
+ @Override
+ public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+ for (Api api : apis) {
+ api.call(req, rsp);
+ }
+
+ }
+
+ public CompositeApi add(Api api) {
+ apis.add(api);
+ return this;
+ }
+ }
+
+ @Override
+ protected void handleAdmin(SolrQueryResponse solrResp) {
+ api.call(this.solrReq, solrResp);
+ }
+
+ @Override
+ protected void execute(SolrQueryResponse rsp) {
+ try {
+ api.call(solrReq, rsp);
+ } catch (RuntimeException e) {
+ throw e;
+ }
+ }
+
+ @Override
+ protected Object _getHandler() {
+ return api;
+ }
+
+ public Map<String,String> getUrlParts(){
+ return parts;
+ }
+
+ @Override
+ protected QueryResponseWriter getResponseWriter() {
+ String wt = solrReq.getParams().get(WT, JSON);
+ if (core != null) return core.getResponseWriters().get(wt);
+ return SolrCore.DEFAULT_RESPONSE_WRITERS.get(wt);
+ }
+
+ @Override
+ protected ValidatingJsonMap getSpec() {
+ return api == null ? null : api.getSpec();
+ }
+
+ @Override
+ protected Map<String, JsonSchemaValidator> getValidators() {
+ return api == null ? null : api.getCommandSchema();
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/api/package-info.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/package-info.java b/solr/core/src/java/org/apache/solr/api/package-info.java
new file mode 100644
index 0000000..c3574c7
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Commonly used classes for Solr V2 API.
+ */
+package org.apache.solr.api;
+
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/cloud/Assign.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/Assign.java b/solr/core/src/java/org/apache/solr/cloud/Assign.java
index e6e08f9..ba03ccd 100644
--- a/solr/core/src/java/org/apache/solr/cloud/Assign.java
+++ b/solr/core/src/java/org/apache/solr/cloud/Assign.java
@@ -146,10 +146,16 @@ public class Assign {
// could be created on live nodes given maxShardsPerNode, Replication factor (if from createShard) etc.
public static List<ReplicaCount> getNodesForNewReplicas(ClusterState clusterState, String collectionName,
String shard, int numberOfNodes,
- String createNodeSetStr, CoreContainer cc) {
+ Object createNodeSet, CoreContainer cc) {
DocCollection coll = clusterState.getCollection(collectionName);
Integer maxShardsPerNode = coll.getInt(MAX_SHARDS_PER_NODE, 1);
- List<String> createNodeList = createNodeSetStr == null ? null: StrUtils.splitSmart(createNodeSetStr, ",", true);
+ List<String> createNodeList = null;
+
+ if (createNodeSet instanceof List) {
+ createNodeList = (List) createNodeSet;
+ } else {
+ createNodeList = createNodeSet == null ? null : StrUtils.splitSmart((String) createNodeSet, ",", true);
+ }
HashMap<String, ReplicaCount> nodeNameVsShardCount = getNodeNameVsShardCount(collectionName, clusterState, createNodeList);
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/cloud/CreateShardCmd.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/CreateShardCmd.java b/solr/core/src/java/org/apache/solr/cloud/CreateShardCmd.java
index 3d5aa41..52df32b 100644
--- a/solr/core/src/java/org/apache/solr/cloud/CreateShardCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/CreateShardCmd.java
@@ -68,7 +68,7 @@ public class CreateShardCmd implements Cmd {
ShardHandler shardHandler = ocmh.shardHandlerFactory.getShardHandler();
DocCollection collection = clusterState.getCollection(collectionName);
int repFactor = message.getInt(REPLICATION_FACTOR, collection.getInt(REPLICATION_FACTOR, 1));
- String createNodeSetStr = message.getStr(OverseerCollectionMessageHandler.CREATE_NODE_SET);
+ Object createNodeSetStr = message.get(OverseerCollectionMessageHandler.CREATE_NODE_SET);
List<Assign.ReplicaCount> sortedNodeList = getNodesForNewReplicas(clusterState, collectionName, sliceName, repFactor,
createNodeSetStr, ocmh.overseer.getZkController().getCoreContainer());
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/core/PluginBag.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/PluginBag.java b/solr/core/src/java/org/apache/solr/core/PluginBag.java
index 77e2379..ad8bdec 100644
--- a/solr/core/src/java/org/apache/solr/core/PluginBag.java
+++ b/solr/core/src/java/org/apache/solr/core/PluginBag.java
@@ -46,10 +46,15 @@ import org.apache.solr.util.SimplePostTool;
import org.apache.solr.util.plugin.NamedListInitializedPlugin;
import org.apache.solr.util.plugin.PluginInfoInitialized;
import org.apache.solr.util.plugin.SolrCoreAware;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.api.ApiSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static java.util.Collections.singletonMap;
import static org.apache.solr.common.params.CommonParams.NAME;
+import static org.apache.solr.api.ApiBag.HANDLER_NAME;
/**
* This manages the lifecycle of a set of plugin of the same type .
@@ -63,11 +68,13 @@ public class PluginBag<T> implements AutoCloseable {
private final Class klass;
private SolrCore core;
private final SolrConfig.SolrPluginInfo meta;
+ private final ApiBag apiBag;
/**
* Pass needThreadSafety=true if plugins can be added and removed concurrently with lookups.
*/
public PluginBag(Class<T> klass, SolrCore core, boolean needThreadSafety) {
+ this.apiBag = klass == SolrRequestHandler.class ? new ApiBag(core != null) : null;
this.core = core;
this.klass = klass;
// TODO: since reads will dominate writes, we could also think about creating a new instance of a map each time it changes.
@@ -174,16 +181,52 @@ public class PluginBag<T> implements AutoCloseable {
*/
public T put(String name, T plugin) {
if (plugin == null) return null;
- PluginHolder<T> old = put(name, new PluginHolder<T>(null, plugin));
+ PluginHolder<T> pluginHolder = new PluginHolder<>(null, plugin);
+ pluginHolder.registerAPI = false;
+ PluginHolder<T> old = put(name, pluginHolder);
return old == null ? null : old.get();
}
-
PluginHolder<T> put(String name, PluginHolder<T> plugin) {
- PluginHolder<T> old = registry.put(name, plugin);
- if (plugin.pluginInfo != null && plugin.pluginInfo.isDefault()) {
- setDefault(name);
+ Boolean registerApi = null;
+ Boolean disableHandler = null;
+ if (plugin.pluginInfo != null) {
+ String registerAt = plugin.pluginInfo.attributes.get("registerPath");
+ if (registerAt != null) {
+ List<String> strs = StrUtils.splitSmart(registerAt, ',');
+ disableHandler = !strs.contains("/");
+ registerApi = strs.contains("/v2");
+ }
+ }
+
+ if (apiBag != null) {
+ if (plugin.isLoaded()) {
+ T inst = plugin.get();
+ if (inst instanceof ApiSupport) {
+ ApiSupport apiSupport = (ApiSupport) inst;
+ if (registerApi == null) registerApi = apiSupport.registerV2();
+ if (disableHandler == null) disableHandler = !apiSupport.registerV1();
+
+ if(registerApi) {
+ Collection<Api> apis = apiSupport.getApis();
+ if (apis != null) {
+ Map<String, String> nameSubstitutes = singletonMap(HANDLER_NAME, name);
+ for (Api api : apis) {
+ apiBag.register(api, nameSubstitutes);
+ }
+ }
+ }
+
+ }
+ } else {
+ if (registerApi != null && registerApi)
+ apiBag.registerLazy((PluginHolder<SolrRequestHandler>) plugin, plugin.pluginInfo);
+ }
}
+ if(disableHandler == null) disableHandler = Boolean.FALSE;
+ PluginHolder<T> old = null;
+ if(!disableHandler) old = registry.put(name, plugin);
+ if (plugin.pluginInfo != null && plugin.pluginInfo.isDefault()) setDefault(name);
if (plugin.isLoaded()) registerMBean(plugin.get(), core, name);
return old;
}
@@ -249,7 +292,7 @@ public class PluginBag<T> implements AutoCloseable {
return result.isLoaded();
}
- private static void registerMBean(Object inst, SolrCore core, String pluginKey) {
+ private void registerMBean(Object inst, SolrCore core, String pluginKey) {
if (core == null) return;
if (inst instanceof SolrInfoMBean) {
SolrInfoMBean mBean = (SolrInfoMBean) inst;
@@ -280,6 +323,7 @@ public class PluginBag<T> implements AutoCloseable {
public static class PluginHolder<T> implements AutoCloseable {
private T inst;
protected final PluginInfo pluginInfo;
+ boolean registerAPI = false;
public PluginHolder(PluginInfo info) {
this.pluginInfo = info;
@@ -321,7 +365,7 @@ public class PluginBag<T> implements AutoCloseable {
* A class that loads plugins Lazily. When the get() method is invoked
* the Plugin is initialized and returned.
*/
- public static class LazyPluginHolder<T> extends PluginHolder<T> {
+ public class LazyPluginHolder<T> extends PluginHolder<T> {
private volatile T lazyInst;
private final SolrConfig.SolrPluginInfo pluginMeta;
protected SolrException solrException;
@@ -516,4 +560,17 @@ public class PluginBag<T> implements AutoCloseable {
}
}
}
+
+
+ public Api v2lookup(String path, String method, Map<String, String> parts) {
+ if (apiBag == null) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "this should not happen, looking up for v2 API at the wrong place");
+ }
+ return apiBag.lookup(path, method, parts);
+ }
+
+ public ApiBag getApiBag() {
+ return apiBag;
+ }
+
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/BlobHandler.java b/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
index 25b3b14..f5b49ea 100644
--- a/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/BlobHandler.java
@@ -22,6 +22,7 @@ import java.lang.invoke.MethodHandles;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
+import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -34,6 +35,8 @@ import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopFieldDocs;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.CommonParams;
@@ -72,7 +75,7 @@ public class BlobHandler extends RequestHandlerBase implements PluginInfoInitial
@Override
public void handleRequestBody(final SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- String httpMethod = (String) req.getContext().get("httpMethod");
+ String httpMethod = req.getHttpMethod();
String path = (String) req.getContext().get("path");
SolrConfigHandler.setWt(req, JSON);
@@ -277,4 +280,13 @@ public class BlobHandler extends RequestHandlerBase implements PluginInfoInitial
req.getCore().getRequestHandler(handler).handleRequest(r, rsp);
}
+ @Override
+ public Boolean registerV2() {
+ return Boolean.TRUE;
+ }
+
+ @Override
+ public Collection<Api> getApis() {
+ return ApiBag.wrapRequestHandlers(this, "core.system.blob", "core.system.blob.upload");
+ }
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
index ecafb52..d7d5b71 100644
--- a/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
@@ -19,7 +19,9 @@ package org.apache.solr.handler;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.solr.common.util.ContentStream;
@@ -39,6 +41,15 @@ public class DumpRequestHandler extends RequestHandlerBase
{
// Show params
rsp.add( "params", req.getParams().toNamedList() );
+ String[] parts = req.getParams().getParams("urlTemplateValues");
+ if (parts != null && parts.length > 0) {
+ Map map = new LinkedHashMap<>();
+ rsp.getValues().add("urlTemplateValues", map);
+ for (String part : parts) {
+ map.put(part, req.getPathTemplateValues().get(part));
+ }
+ }
+
String[] returnParams = req.getParams().getParams("param");
if(returnParams !=null) {
NamedList params = (NamedList) rsp.getValues().get("params");
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
index 04b930a..8230bf5 100644
--- a/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
@@ -330,6 +330,11 @@ public class PingRequestHandler extends RequestHandlerBase implements SolrCoreAw
}
@Override
+ public Boolean registerV2() {
+ return Boolean.TRUE;
+ }
+
+ @Override
public Category getCategory() {
return Category.ADMIN;
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/RealTimeGetHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/RealTimeGetHandler.java b/solr/core/src/java/org/apache/solr/handler/RealTimeGetHandler.java
index 6c9b0a9..9049318 100644
--- a/solr/core/src/java/org/apache/solr/handler/RealTimeGetHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/RealTimeGetHandler.java
@@ -16,12 +16,16 @@
*/
package org.apache.solr.handler;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
import org.apache.solr.handler.component.*;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+
public class RealTimeGetHandler extends SearchHandler {
@Override
protected List<String> getDefaultComponents()
@@ -42,6 +46,16 @@ public class RealTimeGetHandler extends SearchHandler {
public URL[] getDocs() {
return null;
}
+
+ @Override
+ public Collection<Api> getApis() {
+ return ApiBag.wrapRequestHandlers(this, "core.RealtimeGet");
+ }
+
+ @Override
+ public Boolean registerV2() {
+ return Boolean.TRUE;
+ }
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index b70c096..3c6f5fa 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -18,7 +18,9 @@ package org.apache.solr.handler;
import java.lang.invoke.MethodHandles;
import java.net.URL;
+import java.util.Collection;
+import com.google.common.collect.ImmutableList;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Timer;
@@ -37,6 +39,9 @@ import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.util.SolrPluginUtils;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.api.ApiSupport;
import org.apache.solr.util.stats.MetricUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,7 +51,7 @@ import static org.apache.solr.core.RequestParams.USEPARAM;
/**
*
*/
-public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfoMBean, SolrMetricProducer, NestedRequestHandler {
+public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfoMBean, SolrMetricProducer, NestedRequestHandler,ApiSupport {
protected NamedList initArgs = null;
protected SolrParams defaults;
@@ -290,6 +295,11 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
MetricUtils.addMetrics(lst, requestTimes);
return lst;
}
+
+ @Override
+ public Collection<Api> getApis() {
+ return ImmutableList.of(new ApiBag.ReqHandlerToApi(this, ApiBag.constructSpec(pluginInfo)));
+ }
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java b/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java
index 9c2d45c..f3e503e 100644
--- a/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java
@@ -19,17 +19,19 @@ package org.apache.solr.handler;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
import org.apache.solr.cloud.ZkSolrResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
@@ -86,15 +88,12 @@ public class SchemaHandler extends RequestHandlerBase implements SolrCoreAware,
return;
}
- for (ContentStream stream : req.getContentStreams()) {
- try {
- List errs = new SchemaManager(req).performOperations(stream.getReader());
- if (!errs.isEmpty()) rsp.add("errors", errs);
- } catch (IOException e) {
- rsp.add("errors", Collections.singletonList("Error reading input String " + e.getMessage()));
- rsp.setException(e);
- }
- break;
+ try {
+ List errs = new SchemaManager(req).performOperations();
+ if (!errs.isEmpty()) rsp.add("errors", errs);
+ } catch (IOException e) {
+ rsp.add("errors", Collections.singletonList("Error reading input String " + e.getMessage()));
+ rsp.setException(e);
}
} else {
handleGET(req, rsp);
@@ -260,4 +259,20 @@ public class SchemaHandler extends RequestHandlerBase implements SolrCoreAware,
public void inform(SolrCore core) {
isImmutableConfigSet = SolrConfigHandler.getImmutable(core);
}
+
+ @Override
+ public Collection<Api> getApis() {
+ return ApiBag.wrapRequestHandlers(this, "core.SchemaRead",
+ "core.SchemaRead.fields",
+ "core.SchemaRead.copyFields",
+ "core.SchemaEdit",
+ "core.SchemaRead.dynamicFields_fieldTypes"
+ );
+
+ }
+
+ @Override
+ public Boolean registerV2() {
+ return Boolean.TRUE;
+ }
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
index 1c584b1..2660cba 100644
--- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
@@ -76,6 +76,8 @@ import org.apache.solr.util.DefaultSolrThreadFactory;
import org.apache.solr.util.RTimer;
import org.apache.solr.util.SolrPluginUtils;
import org.apache.solr.util.plugin.SolrCoreAware;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -895,4 +897,18 @@ public class SolrConfigHandler extends RequestHandlerBase implements SolrCoreAwa
return null;
}
}
+
+ @Override
+ public Collection<Api> getApis() {
+ return ApiBag.wrapRequestHandlers(this,
+ "core.config",
+ "core.config.Commands",
+ "core.config.Params",
+ "core.config.Params.Commands");
+ }
+
+ @Override
+ public Boolean registerV2() {
+ return Boolean.TRUE;
+ }
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandler.java
index 6628368..fd7a754 100644
--- a/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandler.java
@@ -150,6 +150,7 @@ public class UpdateRequestHandler extends ContentStreamHandlerBase implements Pe
pathVsLoaders.put(JSON_PATH,registry.get("application/json"));
pathVsLoaders.put(DOC_PATH,registry.get("application/json"));
pathVsLoaders.put(CSV_PATH,registry.get("application/csv"));
+ pathVsLoaders.put(BIN_PATH,registry.get("application/javabin"));
return registry;
}
@@ -178,6 +179,7 @@ public class UpdateRequestHandler extends ContentStreamHandlerBase implements Pe
public static final String DOC_PATH = "/update/json/docs";
public static final String JSON_PATH = "/update/json";
public static final String CSV_PATH = "/update/csv";
+ public static final String BIN_PATH = "/update/bin";
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandlerApi.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandlerApi.java
new file mode 100644
index 0000000..6ba3229
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/UpdateRequestHandlerApi.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+
+public class UpdateRequestHandlerApi extends UpdateRequestHandler {
+
+
+ @Override
+ public Collection<Api> getApis() {
+ return Collections.singleton(getApiImpl());
+ }
+
+ private Api getApiImpl() {
+ return new Api(ApiBag.getSpec("core.Update")) {
+ @Override
+ public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+ String path = req.getPath();
+ String target = mapping.get(path);
+ if(target != null) req.getContext().put("path", target);
+ try {
+ handleRequest(req, rsp);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e){
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,e );
+ }
+ }
+ };
+ }
+
+ @Override
+ public Boolean registerV1() {
+ return Boolean.FALSE;
+ }
+
+ @Override
+ public Boolean registerV2() {
+ return Boolean.TRUE;
+ }
+
+ private static final Map<String, String> mapping = ImmutableMap.<String,String>builder()
+ .put("/update", DOC_PATH)
+ .put(JSON_PATH, DOC_PATH)
+ .put("/update/json/commands", JSON_PATH)
+ .build();
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java b/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
new file mode 100644
index 0000000..0e58ccc
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
@@ -0,0 +1,236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.CommandOperation;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.api.ApiSupport;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
+import static org.apache.solr.common.util.StrUtils.splitSmart;
+
+/**
+ * This is a utility class to provide an easy mapping of request handlers which support multiple commands
+ * to the V2 API format (core admin api, collections api). This helps in automatically mapping paths
+ * to actions and old parameter names to new parameter names
+ */
+public abstract class BaseHandlerApiSupport implements ApiSupport {
+ protected final Map<SolrRequest.METHOD, Map<V2EndPoint, List<ApiCommand>>> commandsMapping;
+
+ protected BaseHandlerApiSupport() {
+ commandsMapping = new HashMap<>();
+ for (ApiCommand cmd : getCommands()) {
+ Map<V2EndPoint, List<ApiCommand>> m = commandsMapping.get(cmd.getHttpMethod());
+ if (m == null) commandsMapping.put(cmd.getHttpMethod(), m = new HashMap<>());
+ List<ApiCommand> list = m.get(cmd.getEndPoint());
+ if (list == null) m.put(cmd.getEndPoint(), list = new ArrayList<>());
+ list.add(cmd);
+ }
+ }
+
+ @Override
+ public synchronized Collection<Api> getApis() {
+ ImmutableList.Builder<Api> l = ImmutableList.builder();
+ for (V2EndPoint op : getEndPoints()) l.add(getApi(op));
+ return l.build();
+ }
+
+
+ private Api getApi(final V2EndPoint op) {
+ final BaseHandlerApiSupport apiHandler = this;
+ return new Api(ApiBag.getSpec(op.getSpecName())) {
+ @Override
+ public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+ SolrParams params = req.getParams();
+ SolrRequest.METHOD method = SolrRequest.METHOD.valueOf(req.getHttpMethod());
+ List<ApiCommand> commands = commandsMapping.get(method).get(op);
+ try {
+ if (method == POST) {
+ List<CommandOperation> cmds = req.getCommands(true);
+ if (cmds.size() > 1)
+ throw new SolrException(BAD_REQUEST, "Only one command is allowed");
+ CommandOperation c = cmds.size() == 0 ? null : cmds.get(0);
+ ApiCommand command = null;
+ String commandName = c == null ? null : c.name;
+ for (ApiCommand cmd : commands) {
+ if (Objects.equals(cmd.getName(), commandName)) {
+ command = cmd;
+ break;
+ }
+ }
+
+ if (command == null) {
+ throw new SolrException(BAD_REQUEST, " no such command " + c);
+ }
+ wrapParams(req, c, command, false);
+ command.invoke(req, rsp, apiHandler);
+
+ } else {
+ if (commands == null || commands.isEmpty()) {
+ rsp.add("error", "No support for : " + method + " at :" + req.getPath());
+ return;
+ }
+ if (commands.size() > 1) {
+ for (ApiCommand command : commands) {
+ if (command.getName().equals(req.getPath())) {
+ commands = Collections.singletonList(command);
+ break;
+ }
+ }
+ }
+ wrapParams(req, new CommandOperation("", Collections.EMPTY_MAP), commands.get(0), true);
+ commands.get(0).invoke(req, rsp, apiHandler);
+ }
+
+ } catch (SolrException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new SolrException(BAD_REQUEST, e);
+ } finally {
+ req.setParams(params);
+ }
+
+ }
+ };
+
+ }
+
+ private static void wrapParams(final SolrQueryRequest req, final CommandOperation co, final ApiCommand cmd, final boolean useRequestParams) {
+ final Map<String, String> pathValues = req.getPathTemplateValues();
+ final Map<String, Object> map = co == null || !(co.getCommandData() instanceof Map) ?
+ Collections.singletonMap("", co.getCommandData()) : co.getDataMap();
+ final SolrParams origParams = req.getParams();
+
+ req.setParams(
+ new SolrParams() {
+ @Override
+ public String get(String param) {
+ Object vals = getParams0(param);
+ if (vals == null) return null;
+ if (vals instanceof String) return (String) vals;
+ if (vals instanceof Boolean || vals instanceof Number) return String.valueOf(vals);
+ if (vals instanceof String[] && ((String[]) vals).length > 0) return ((String[]) vals)[0];
+ return null;
+ }
+
+ private Object getParams0(String param) {
+ param = cmd.getParamSubstitute(param);
+ Object o = param.indexOf('.') > 0 ?
+ Utils.getObjectByPath(map, true, splitSmart(param, '.')) :
+ map.get(param);
+ if (o == null) o = pathValues.get(param);
+ if (o == null && useRequestParams) o = origParams.getParams(param);
+ if (o instanceof List) {
+ List l = (List) o;
+ return l.toArray(new String[l.size()]);
+ }
+
+ return o;
+ }
+
+ @Override
+ public String[] getParams(String param) {
+ Object vals = getParams0(param);
+ return vals == null || vals instanceof String[] ?
+ (String[]) vals :
+ new String[]{vals.toString()};
+ }
+
+ @Override
+ public Iterator<String> getParameterNamesIterator() {
+ return cmd.getParamNames(co).iterator();
+
+ }
+
+
+ });
+
+ }
+
+
+ public static Collection<String> getParamNames(CommandOperation op, ApiCommand command) {
+ List<String> result = new ArrayList<>();
+ Object o = op.getCommandData();
+ if (o instanceof Map) {
+ Map map = (Map) o;
+ collectKeyNames(map, result, "");
+ }
+ return result;
+
+ }
+
+ public static void collectKeyNames(Map<String, Object> map, List<String> result, String prefix) {
+ for (Map.Entry<String, Object> e : map.entrySet()) {
+ if (e.getValue() instanceof Map) {
+ collectKeyNames((Map) e.getValue(), result, prefix + e.getKey() + ".");
+ } else {
+ result.add(prefix + e.getKey());
+ }
+ }
+ }
+
+ protected abstract List<ApiCommand> getCommands();
+
+ protected abstract List<V2EndPoint> getEndPoints();
+
+
+ public interface ApiCommand {
+ String getName();
+
+ /**
+ * the http method supported by this command
+ */
+ SolrRequest.METHOD getHttpMethod();
+
+ V2EndPoint getEndPoint();
+
+ default Collection<String> getParamNames(CommandOperation op) {
+ return BaseHandlerApiSupport.getParamNames(op, this);
+ }
+
+
+ default String getParamSubstitute(String name) {
+ return name;
+ }
+
+ void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception;
+ }
+
+ public interface V2EndPoint {
+
+ String getSpecName();
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
new file mode 100644
index 0000000..581fe46
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.handler.admin.CollectionsHandler.CollectionOperation;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.CommandOperation;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.cloud.OverseerCollectionMessageHandler.COLL_CONF;
+import static org.apache.solr.cloud.OverseerCollectionMessageHandler.CREATE_NODE_SET;
+import static org.apache.solr.common.params.CommonParams.NAME;
+import static org.apache.solr.handler.admin.CollectionsHandler.CollectionOperation.*;
+
+
+public class CollectionHandlerApi extends BaseHandlerApiSupport {
+ final CollectionsHandler handler;
+
+ public CollectionHandlerApi(CollectionsHandler handler) {
+ this.handler = handler;
+ }
+
+ @Override
+ protected List<ApiCommand> getCommands() {
+ return Arrays.asList(Cmd.values());
+ }
+
+ @Override
+ protected List<V2EndPoint> getEndPoints() {
+ return Arrays.asList(EndPoint.values());
+ }
+
+
+ enum Cmd implements ApiCommand {
+ GET_COLLECTIONS(EndPoint.COLLECTIONS, GET, LIST_OP),
+ GET_CLUSTER(EndPoint.CLUSTER, GET, LIST_OP, "/cluster", null),
+ GET_CLUSTER_OVERSEER(EndPoint.CLUSTER, GET, OVERSEERSTATUS_OP, "/cluster/overseer", null),
+ GET_CLUSTER_STATUS_CMD(EndPoint.CLUSTER_CMD_STATUS, GET, REQUESTSTATUS_OP),
+ DELETE_CLUSTER_STATUS(EndPoint.CLUSTER_CMD_STATUS_DELETE, DELETE, DELETESTATUS_OP),
+ GET_A_COLLECTION(EndPoint.COLLECTION_STATE, GET, CLUSTERSTATUS_OP),
+ CREATE_COLLECTION(EndPoint.COLLECTIONS_COMMANDS,
+ POST,
+ CREATE_OP,
+ CREATE_OP.action.toLower(),
+ ImmutableMap.of(
+ COLL_CONF, "config",
+ "createNodeSet.shuffle", "shuffleNodes",
+ "createNodeSet", "nodeSet"
+ ),
+ ImmutableMap.of("properties.", "property.")),
+
+ DELETE_COLL(EndPoint.PER_COLLECTION_DELETE,
+ DELETE,
+ DELETE_OP,
+ DELETE_OP.action.toLower(),
+ ImmutableMap.of(NAME, "collection")),
+
+ RELOAD_COLL(EndPoint.PER_COLLECTION,
+ POST,
+ RELOAD_OP,
+ RELOAD_OP.action.toLower(),
+ ImmutableMap.of(NAME, "collection")),
+ MODIFYCOLLECTION(EndPoint.PER_COLLECTION,
+ POST,
+ MODIFYCOLLECTION_OP,
+ "modify",null),
+ MIGRATE_DOCS(EndPoint.PER_COLLECTION,
+ POST,
+ MIGRATE_OP,
+ "migrate-docs",
+ ImmutableMap.of("split.key", "splitKey",
+ "target.collection", "target",
+ "forward.timeout", "forwardTimeout"
+ )),
+ REBALANCELEADERS(EndPoint.PER_COLLECTION,
+ POST,
+ REBALANCELEADERS_OP,
+ "rebalance-leaders", null),
+ CREATE_ALIAS(EndPoint.COLLECTIONS_COMMANDS,
+ POST,
+ CREATEALIAS_OP,
+ "create-alias",
+ null),
+
+ DELETE_ALIAS(EndPoint.COLLECTIONS_COMMANDS,
+ POST,
+ DELETEALIAS_OP,
+ "delete-alias",
+ null),
+ CREATE_SHARD(EndPoint.PER_COLLECTION_SHARDS_COMMANDS,
+ POST,
+ CREATESHARD_OP,
+ "create",
+ ImmutableMap.of(CREATE_NODE_SET, "nodeSet"),
+ ImmutableMap.of("coreProperties.", "property.")) {
+ @Override
+ public String getParamSubstitute(String param) {
+ return super.getParamSubstitute(param);
+ }
+ },
+
+ SPLIT_SHARD(EndPoint.PER_COLLECTION_SHARDS_COMMANDS,
+ POST,
+ SPLITSHARD_OP,
+ "split",
+ ImmutableMap.of(
+ "split.key", "splitKey"),
+ ImmutableMap.of("coreProperties.", "property.")),
+ DELETE_SHARD(EndPoint.PER_COLLECTION_PER_SHARD_DELETE,
+ DELETE,
+ DELETESHARD_OP),
+
+ CREATE_REPLICA(EndPoint.PER_COLLECTION_SHARDS_COMMANDS,
+ POST,
+ ADDREPLICA_OP,
+ "add-replica",
+ null,
+ ImmutableMap.of("coreProperties.", "property.")),
+
+ DELETE_REPLICA(EndPoint.PER_COLLECTION_PER_SHARD_PER_REPLICA_DELETE,
+ DELETE,
+ DELETEREPLICA_OP),
+
+ SYNC_SHARD(EndPoint.PER_COLLECTION_PER_SHARD_COMMANDS,
+ POST,
+ SYNCSHARD_OP,
+ "synch-shard",
+ null),
+ ADDREPLICAPROP(EndPoint.PER_COLLECTION,
+ POST,
+ ADDREPLICAPROP_OP,
+ "add-replica-property",
+ ImmutableMap.of("property", "name", "property.value", "value")),
+ DELETEREPLICAPROP(EndPoint.PER_COLLECTION,
+ POST,
+ DELETEREPLICAPROP_OP,
+ "delete-replica-property",
+ null),
+ ADDROLE(EndPoint.CLUSTER_CMD,
+ POST,
+ ADDROLE_OP,
+ "add-role",null),
+ REMOVEROLE(EndPoint.CLUSTER_CMD,
+ POST,
+ REMOVEROLE_OP,
+ "remove-role",null),
+
+ CLUSTERPROP(EndPoint.CLUSTER_CMD,
+ POST,
+ CLUSTERPROP_OP,
+ "set-property",null),
+
+ BACKUP(EndPoint.COLLECTIONS_COMMANDS,
+ POST,
+ BACKUP_OP,
+ "backup-collection", null
+ ),
+ RESTORE(EndPoint.COLLECTIONS_COMMANDS,
+ POST,
+ RESTORE_OP,
+ "restore-collection",
+ null
+ ),
+ GET_NODES(EndPoint.CLUSTER_NODES, GET, null) {
+ @Override
+ public void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception {
+ rsp.add("nodes", ((CollectionHandlerApi) apiHandler).handler.coreContainer.getZkController().getClusterState().getLiveNodes());
+ }
+ },
+ FORCELEADER(EndPoint.PER_COLLECTION_PER_SHARD_COMMANDS,POST, FORCELEADER_OP,"force-leader",null),
+ SYNCSHARD(EndPoint.PER_COLLECTION_PER_SHARD_COMMANDS,POST, SYNCSHARD_OP, "sync-shard",null),
+ BALANCESHARDUNIQUE(EndPoint.PER_COLLECTION, POST, BALANCESHARDUNIQUE_OP, "balance-shard-unique",null)
+
+ ;
+ public final String commandName;
+ public final EndPoint endPoint;
+ public final SolrRequest.METHOD method;
+ public final CollectionOperation target;
+ //mapping of http param name to json attribute
+ public final Map<String, String> paramstoAttr;
+ //mapping of old prefix to new for instance properties.a=val can be substituted with property:{a:val}
+ public final Map<String, String> prefixSubstitutes;
+
+ public SolrRequest.METHOD getMethod() {
+ return method;
+ }
+
+
+ Cmd(EndPoint endPoint, SolrRequest.METHOD method, CollectionOperation target) {
+ this(endPoint, method, target, null, null);
+ }
+
+ Cmd(EndPoint endPoint, SolrRequest.METHOD method, CollectionOperation target,
+ String commandName, Map<String, String> paramstoAttr) {
+ this(endPoint, method, target, commandName, paramstoAttr, Collections.EMPTY_MAP);
+
+ }
+
+ Cmd(EndPoint endPoint, SolrRequest.METHOD method, CollectionOperation target,
+ String commandName, Map<String, String> paramstoAttr, Map<String, String> prefixSubstitutes) {
+ this.commandName = commandName;
+ this.endPoint = endPoint;
+ this.method = method;
+ this.target = target;
+ this.paramstoAttr = paramstoAttr == null ? Collections.EMPTY_MAP : paramstoAttr;
+ this.prefixSubstitutes = prefixSubstitutes;
+
+ }
+
+ @Override
+ public String getName() {
+ return commandName;
+ }
+
+ @Override
+ public SolrRequest.METHOD getHttpMethod() {
+ return method;
+ }
+
+ @Override
+ public V2EndPoint getEndPoint() {
+ return endPoint;
+ }
+
+
+ @Override
+ public Collection<String> getParamNames(CommandOperation op) {
+ Collection<String> paramNames = BaseHandlerApiSupport.getParamNames(op, this);
+ if (!prefixSubstitutes.isEmpty()) {
+ Collection<String> result = new ArrayList<>(paramNames.size());
+ for (Map.Entry<String, String> e : prefixSubstitutes.entrySet()) {
+ for (String paramName : paramNames) {
+ if (paramName.startsWith(e.getKey())) {
+ result.add(paramName.replace(e.getKey(), e.getValue()));
+ } else {
+ result.add(paramName);
+ }
+ }
+ paramNames = result;
+ }
+ }
+
+ return paramNames;
+ }
+
+ @Override
+ public String getParamSubstitute(String param) {
+ String s = paramstoAttr.containsKey(param) ? paramstoAttr.get(param) : param;
+ if (prefixSubstitutes != null) {
+ for (Map.Entry<String, String> e : prefixSubstitutes.entrySet()) {
+ if (s.startsWith(e.getValue())) return s.replace(e.getValue(), e.getKey());
+ }
+ }
+ return s;
+ }
+
+ public void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler)
+ throws Exception {
+ ((CollectionHandlerApi) apiHandler).handler.invokeAction(req, rsp, ((CollectionHandlerApi) apiHandler).handler.coreContainer, target.action, target);
+ }
+
+ }
+
+ enum EndPoint implements V2EndPoint {
+ CLUSTER("cluster"),
+ CLUSTER_CMD("cluster.Commands"),
+ CLUSTER_NODES("cluster.nodes"),
+ CLUSTER_CMD_STATUS("cluster.commandstatus"),
+ CLUSTER_CMD_STATUS_DELETE("cluster.commandstatus.delete"),
+ COLLECTIONS_COMMANDS("collections.Commands"),
+ COLLECTIONS("collections"),
+ COLLECTION_STATE("collections.collection"),
+ PER_COLLECTION("collections.collection.Commands"),
+ PER_COLLECTION_DELETE("collections.collection.delete"),
+ PER_COLLECTION_SHARDS_COMMANDS("collections.collection.shards.Commands"),
+ PER_COLLECTION_PER_SHARD_COMMANDS("collections.collection.shards.shard.Commands"),
+ PER_COLLECTION_PER_SHARD_DELETE("collections.collection.shards.shard.delete"),
+ PER_COLLECTION_PER_SHARD_PER_REPLICA_DELETE("collections.collection.shards.shard.replica.delete");
+ final String specName;
+
+
+ EndPoint(String specName) {
+ this.specName = specName;
+ }
+
+ @Override
+ public String getSpecName() {
+ return specName;
+ }
+ }
+
+}