SOLR-8029: Added new style APIs and a framework for creating new APIs and mapping old APIs to new

This commit is contained in:
Noble Paul 2017-01-31 16:27:33 +10:30
parent c8edbe8663
commit 71abe13069
126 changed files with 6715 additions and 164 deletions

View File

@ -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
----------------------

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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());

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -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");

View File

@ -329,6 +329,11 @@ public class PingRequestHandler extends RequestHandlerBase implements SolrCoreAw
return "Reports application health to a load-balancer";
}
@Override
public Boolean registerV2() {
return Boolean.TRUE;
}
@Override
public Category getCategory() {
return Category.ADMIN;

View File

@ -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;
}
}

View File

@ -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)));
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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";
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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");
}

View File

@ -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

View File

@ -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()) {

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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();

View File

@ -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())));
}

View File

@ -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);
}
}
}

View File

@ -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 @@
}
}
}
}
}

View File

@ -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"
]
}
}
}

View File

@ -0,0 +1,10 @@
{
"methods": [
"DELETE"
],
"url": {
"paths": [
"/cluster/command-status/{id}"
]
}
}

View File

@ -0,0 +1,20 @@
{
"methods": [
"GET"
],
"url": {
"paths": [
"/cluster/command-status"
],
"params": {
"flush": {
"type": "boolean",
"default": false
},
"id":{
"type":"string",
"description": "The command id"
}
}
}
}

View File

@ -0,0 +1,34 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/ConfigSets+API",
"description": "Create ConfigSets.",
"methods": [
"POST"
],
"url": {
"paths": [
"/cluster/configs"]
},
"commands": {
"create": {
"type" :"object",
"description": "Create a ConfigSet, based on another ConfigSet already in ZooKeeper.",
"documentation": "https://cwiki.apache.org/confluence/display/solr/ConfigSets+API#ConfigSetsAPI-create",
"properties": {
"name" :{
"type" :"string",
"description" : "The name of the ConfigSet to be created."
},
"baseConfigSet":{
"type" : "string",
"description" :"The existing ConfigSet to copy as the basis for the new one."
},
"properties" : {
"type":"object",
"description": "Additional key-value pairs, in the form of 'ConfigSetProp.<key>=<value>', as needed. These properties will override the same properties in the base ConfigSet.",
"additionalProperties" : true
}
},
"required" : ["name", "baseConfigSet"]
}
}
}

View File

@ -0,0 +1,12 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/ConfigSets+API#ConfigSetsAPI-delete",
"description": "Delete ConfigSets. The name of the ConfigSet to delete must be provided as a path parameter.",
"methods": [
"DELETE"
],
"url": {
"paths": [
"/cluster/configs/{name}"
]
}
}

View File

@ -0,0 +1,12 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/ConfigSets+API#ConfigSetsAPI-list",
"description": "List all ConfigSets in the cluster.",
"methods": [
"GET"
],
"url": {
"paths": [
"/cluster/configs"
]
}
}

View File

@ -0,0 +1,14 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API",
"description": "General information about the cluster, including defined collections (with the 'cluster' endpoint), status of the overseer (with the 'cluster/overseer' endpoint), and available nodes (with the 'cluster/nodes' endpoint).",
"methods": [
"GET"
],
"url": {
"paths": [
"/cluster",
"/cluster/overseer",
"/cluster/nodes"
]
}
}

View File

@ -0,0 +1,12 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API",
"description": "Provides general information about the available nodes of the cluster.",
"methods": [
"GET"
],
"url": {
"paths": [
"/cluster/nodes"
]
}
}

View File

@ -0,0 +1,23 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Basic+Authentication+Plugin",
"description": "Modifies the configuration of Basic authentication, allowing you to add or remove users and their passwords.",
"methods": [
"POST"
],
"url": {
"paths": [
"/cluster/security/authentication"
]
},
"commands": {
"set-user": {
"type":"object",
"description": "The set-user command allows you to add users and change their passwords. Usernames and passwords are expressed as key-value pairs in a JSON object.",
"additionalProperties": true
},
"delete-user": {
"description": "Delete a user or a list of users. Passwords do not need to be provided, simply list the users in a JSON array, separated by colons.",
"type":"string"
}
}
}

View File

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

View File

@ -0,0 +1,12 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Securing+Solr",
"description":"This is a placeholder output when no authentication is configured",
"methods": [
"POST"
],
"url": {
"paths": [
"/cluster/security/authentication"
]
}
}

View File

@ -0,0 +1,12 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Authentication+and+Authorization+Plugins",
"description": "Shows the configuration for authentication, including users, classes (type of authentication) and other parameters.",
"methods": [
"GET"
],
"url": {
"paths": [
"/cluster/security/authentication"
]
}
}

View File

@ -0,0 +1,13 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Securing+Solr",
"description":"This is a placeholder output when no authorization is configured",
"methods": [
"POST"
],
"url": {
"paths": [
"/cluster/security/authorization"
]
}
}

View File

@ -0,0 +1,13 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Authentication+and+Authorization+Plugins",
"description":"Shows the configuration for authorization, including the classes (type of authorization), permissions, user-roles, and other parameters.",
"methods": [
"GET"
],
"url": {
"paths": [
"/cluster/security/authorization"
]
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,11 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api2",
"description": "Reloads a collection so new configuration changes can take effect and be available for use by the system.",
"type" : "object",
"properties":{
"async": {
"type": "string",
"description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
}
}
}

View File

@ -0,0 +1,13 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api6",
"description": "Deletes a collection.",
"methods": [
"DELETE"
],
"url": {
"paths": [
"/collections/{collection}",
"/c/{collection}"
]
}
}

View File

@ -0,0 +1,19 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api1",
"description": "Lists all collections, with details on shards and replicas in each collection.",
"methods": [
"GET"
],
"url": {
"paths": [
"/collections/{collection}",
"/c/{collection}",
"/collections/{collection}/shards",
"/c/{collection}/shards",
"/collections/{collection}/shards/{shard}",
"/c/{collection}/shards/{shard}",
"/collections/{collection}/shards/{shard}/{replica}",
"/c/{collection}/shards/{shard}/{replica}"
]
}
}

View File

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

View File

@ -0,0 +1,24 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API",
"description": "Commands to force leader election and synchronize shards.",
"methods": [
"POST",
"DELETE"
],
"url": {
"paths": [
"/collections/{collection}/shards/{shard}",
"/c/{collection}/shards/{shard}"
]
},
"commands": {
"force-leader": {
"documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-ForceLeader",
"description": "In the unlikely event of a shard losing its leader, this command can be invoked to force the election of a new leader",
"type": "object"
},
"sync-shard": {
"type": "object"
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,13 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api1",
"description": "List all available collections and their properties.",
"methods": [
"GET"
],
"url": {
"paths": [
"/collections",
"/c"
]
}
}

View File

@ -0,0 +1,26 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/RealTime+Get",
"description": "RealTime Get allows retrieving documents by ID before the documents have been committed to the index. It is useful when you need access to documents as soon as they are indexed but your commit times are high for other reasons.",
"methods": [
"GET"
],
"url": {
"paths": [
"/get"
],
"params": {
"id": {
"type": "string",
"description": "A single document ID to retrieve."
},
"ids": {
"type": "string",
"description": "One or more document IDs to retrieve. Separate by commas if more than one ID is specified."
},
"fq":{
"type": "string",
"description": "An optional filter query to add to the query. One use case for this is security filtering, in case users or groups should not be able to retrieve the document ID requested."
}
}
}
}

View File

@ -0,0 +1,27 @@
{
"documentation" : "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-AddaNewCopyFieldRule",
"description": "Adds a new copy field rule, to allow one field to be populated with the contents of one or more other fields.",
"type": "object",
"properties": {
"source": {
"type": "string",
"description": "The field to copy from."
},
"dest": {
"type":"array",
"items": {
"type": "string"
},
"description": "A field or an array of fields to which the source field will be copied. A wildcard for a dynamic field can be used, but only if the source field also contains a dynamic field."
},
"maxChars": {
"type": "integer",
"description": "An upper limit for the number of characters to be copied. This would be useful if index size is a concern. If a limit is not specified, the entire field will be copied."
}
},
"required": [
"source",
"dest"
]
}

View File

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

View File

@ -0,0 +1,51 @@
{
"type": "object",
"properties": {
"class": {
"type": "string"
},
"charFilters": {
"type": "array",
"items": {
"type": "object",
"properties": {
"class": {
"type": "string"
}
},
"required": [
"class"
],
"additionalProperties": true
}
},
"tokenizer": {
"type": "object",
"properties": {
"class": {
"type": "string"
}
},
"required": [
"class"
],
"additionalProperties": true
},
"filters": {
"type": "array",
"items": {
"type": "object",
"properties": {
"class": {
"type": "string"
}
},
"required": [
"class"
],
"additionalProperties": true
}
}
},
"additionalProperties": true
}

View File

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

View File

@ -0,0 +1,19 @@
{
"type":"object",
"documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-DeleteaCopyFieldRule",
"description": "Deletes a copy field rule. Both sides of the copy rule (source and destination) are required in order to delete the rule.",
"properties":{
"source": {
"type":"string",
"description": "The field the copy rule is defined to copy from."
},
"dest": {
"type": "string",
"description": "The field the copy rule is defined to copy to."
}
},
"required": [
"source",
"dest"
]
}

View File

@ -0,0 +1,12 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-DeleteaDynamicFieldRule",
"description": "Deletes a dynamic field.",
"type":"object",
"properties": {
"name": {
"type": "string",
"description": "The name of the dynamic field to delete."
}
},
"required":["name"]
}

View File

@ -0,0 +1,12 @@
{
"documentation" : "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-DeleteaField",
"description": "Deletes a field from the schema.",
"type":"object",
"properties":{
"name":{
"description" :"The name of the field to delete.",
"type" : "string"
}
},
"required" : ["name"]
}

View File

@ -0,0 +1,14 @@
{
"documentation":"https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-DeleteaFieldType",
"description": "Deletes a field type from the schema.",
"type":"object",
"properties": {
"name": {
"type": "string",
"description": "The name of the field type to delete."
}
},
"required": [
"name"
]
}

View File

@ -0,0 +1,47 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API",
"description": "The Schema API provides viewing, editing, adding, and deleting elements of Solr's schema. This API can only be used if Managed Schema is enabled and the schema is defined as mutable. See https://cwiki.apache.org/confluence/display/solr/Schema+Factory+Definition+in+SolrConfig for more information about enabling Managed Schema.",
"methods": [
"POST"
],
"url": {
"paths": [
"$handlerName"
]
},
"commands": {
"add-field": {
"#include": "core.SchemaEdit.addField"
},
"delete-field": {
"#include": "core.SchemaEdit.deleteField"
},
"replace-field": {
"#include": "core.SchemaEdit.addField"
},
"add-dynamic-field": {
"#include": "core.SchemaEdit.addField"
},
"delete-dynamic-field": {
"#include": "core.SchemaEdit.deleteDynamicField"
},
"replace-dynamic-field": {
"#include": "core.SchemaEdit.addField"
},
"add-field-type": {
"#include": "core.SchemaEdit.addFieldType"
},
"delete-field-type": {
"#include": "core.SchemaEdit.deleteFieldType"
},
"replace-field-type": {
"#include": "core.SchemaEdit.addFieldType"
},
"add-copy-field": {
"#include": "core.SchemaEdit.addCopyField"
},
"delete-copy-field": {
"#include": "core.SchemaEdit.deleteCopyField"
}
}
}

View File

@ -0,0 +1,26 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-ListCopyFields",
"description": "Lists all copy fields.",
"methods": [
"GET"
],
"url": {
"paths": [
"schema/copyfields"
],
"params": {
"wt": {
"type": "string",
"description": "The format of the response. Valid options are xml or json."
},
"source.fl": {
"type": "string",
"description": "Comma- or space-separated list of one or more source fields to include in the response. copyField directives with all other source fields will be excluded from the response. If not specified, all copyFields will be included in the response"
},
"dest.fl": {
"type": "string",
"description": "Comma or space-separated list of one or more copyField dest (destination) fields to include in the response. copyField directives with all other dest fields will be excluded. If not specified, all copyFields will be included in the response."
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API",
"methods": [
"GET"
],
"url": {
"paths": [
"/schema/dynamicfields",
"/schema/dynamicfields/{name}",
"/schema/fieldtypes",
"/schema/fieldtypes/{name}"
],
"params":{
"showDefaults":{
"type":"boolean",
"description":"If true, all default field properties from each field's field type will be included in the response (e.g. tokenized for solr.TextField). If false, only explicitly specified field properties will be included."
}
}
}
}

View File

@ -0,0 +1,34 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API#SchemaAPI-ListFields",
"description": "Get only the fields defined in the schema.",
"methods": [
"GET"
],
"url": {
"paths": [
"/schema/fields",
"/schema/fields/{name}"
],
"params": {
"wt": {
"type": "string",
"description": "The format of the response. Valid options are xml or json.",
"default": "json"
},
"fl": {
"type": "string",
"description": "A comma- or space-separated list fields to return. If not specified, all fields will be returned. Note a single field can be requested by adding the field name to the endpoint."
},
"includeDynamic": {
"type": "boolean",
"description": "If true, dynamic fields will be returned in the response.",
"default": false
},
"showDefaults": {
"type": "boolean",
"description": "If true, all field properties from each field's field type will be included in the response, even if they are not explicitly defined on the field. If false, only explicitly defined field properties will be included.",
"default": false
}
}
}
}

View File

@ -0,0 +1,18 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API",
"methods": [
"GET"
],
"url": {
"paths": [
"/schema",
"/schema/name",
"/schema/uniquekey",
"/schema/version",
"/schema/similarity",
"/schema/solrqueryparser",
"/schema/zkversion",
"/schema/solrqueryparser/defaultoperator"
]
}
}

View File

@ -0,0 +1,17 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API",
"methods": [
"POST"
],
"url": {
"paths": [
"/update",
"/update/xml",
"/update/csv",
"/update/json",
"/update/bin",
"/update/json/commands"
]
}
}

View File

@ -0,0 +1,25 @@
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the request handler. This name will be used to update or remove the request handler later if necessary."
},
"class": {
"type": "string",
"description": "The request handler class. Class names do not need to be fully qualified if they are included with Solr, so you can abbreviate the name as 'solr.SearchHandler'. Custom or third-party class names may need to be fully qualified, however."
},
"runtimeLib": {
"type": "boolean",
"description": "An optional parameter to use a custom .jar file that has been uploaded to Solr's blobstore. This additionally requires that the .jar has also been registered with the 'add-runtimelib' command, which is one of the available commands for the Config API."
},
"startup": {
"type": "string",
"description": "Allows the request handler to only start when requested. The only option is 'lazy'.",
"enum": [
"lazy"
]
}
},
"additionalProperties": true
}

View File

@ -0,0 +1,19 @@
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of this configuration item. This name will be used to update or remove this later if necessary."
},
"class": {
"type": "string",
"description": "The configuration item class. Class names do not need to be fully qualified if they are included with Solr, so you can abbreviate the name as 'solr.SearchHandler'. Custom or third-party class names may need to be fully qualified, however."
},
"runtimeLib": {
"type": "boolean",
"description": "An optional parameter to use a custom .jar file that has been uploaded to Solr's blobstore. This additionally requires that the .jar has also been registered with the 'add-runtimelib' command, which is one of the available commands for the Config API."
}
},
"required": [ "name", "class"],
"additionalProperties": true
}

View File

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

View File

@ -0,0 +1,23 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Adding+Custom+Plugins+in+SolrCloud+Mode",
"description": "Allows you to register .jars that have been uploaded to the .system collection in Solr. Note that uploading the .jar must occur before using this API.",
"type": "object",
"properties": {
"name": {
"description": "The name of the .jar blob in .system collection. This is the name you provided when you uploaded it.",
"type": "string"
},
"version": {
"type": "integer",
"description": "The version of the blob in .system collection. Be sure to use the correct version if you have multiple versions of the same .jar uploaded."
},
"sig": {
"type": "string",
"description": "The sha1 signature of the .jar, if it was signed before uploading. If you signed the sha1 digest of your .jar file prior to uploading it to the .system collection, this is where you need to provide the signature."
}
},
"required": [
"name",
"version"
]
}

View File

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

View File

@ -0,0 +1,13 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Request+Parameters+API",
"description": "List all parameter sets (paramsets). Individual paramsets can be requested by paramset name.",
"methods": [
"GET"
],
"url": {
"paths": [
"/config/params",
"/config/params/{params_set}"
]
}
}

View File

@ -0,0 +1,18 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Config+API",
"description": "Gets the Solr configuration for a collection.",
"methods": [
"GET"
],
"url": {
"paths": [
"/config",
"/config/overlay",
"/config/query",
"/config/jmx",
"/config/requestDispatcher",
"/config/znodeVersion",
"/config/{plugin}"
]
}
}

View File

@ -0,0 +1,20 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Blob+Store+API",
"description": "Lists blobs in the blob store (the .system collection). The list can be limited by name or name and version.",
"methods": [
"GET"
],
"url": {
"paths": [
"/blob",
"/blob/{name}",
"/blob/{name}/{version}"
],
"params": {
"wt": {
"type":"string",
"description": "Use the value 'filestream' to get the file content. Use other response writers (such as xml, or json) to fetch only the metadata."
}
}
}
}

View File

@ -0,0 +1,12 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/Blob+Store+API",
"description": "Uploads a blob to the blob store. Note that the blob store is a specially named collection (which must be '.system') which must be created before uploading a blob to it.",
"methods": [
"POST"
],
"url": {
"paths": [
"/blob/{name}"
]
}
}

View File

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

View File

@ -0,0 +1,20 @@
{
"documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-STATUS",
"description": "Provides status and other information about the status of each core. Individual cores can be requested by core name.",
"methods": [
"GET"
],
"url": {
"paths": [
"/cores",
"/cores/{core}"
],
"params": {
"indexInfo": {
"type": "boolean",
"description": "If true, index information will be returned, such as information about number of documents, deletions, segments, etc. In a large cluster with more than hundreds of cores, this can take a long time to retrieve. If you have a large cluster, consider setting this to false.",
"default": true
}
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,11 @@
{
"methods": [
"GET",
"POST"
],
"url": {
"paths": [
"$handlerName"
]
}
}

View File

@ -0,0 +1,24 @@
{
"methods": [
"POST"
],
"url": {
"paths": [
"/node"
]
},
"commands": {
"overseer-op": {
"type": "object",
"additionalProperties": true
},
"rejoin-leader-election": {
"type": "object",
"additionalProperties": true
},
"invoke":{
"type": "object",
"additionalProperties": true
}
}
}

View File

@ -0,0 +1,11 @@
{
"description": "Provides information about system properties, threads, logging settings, and system details for a node.",
"methods": ["GET"],
"url": {
"paths": [
"/node/properties",
"/node/threads",
"/node/logging",
"/node/system"]
}
}

View File

@ -0,0 +1,16 @@
{
"methods": [
"GET"
],
"url": {
"paths": [
"/node/invoke"
],
"params": {
"class": {
"type": "string",
"description": "Name of the class that must be invoked. "
}
}
}
}

View File

@ -40,7 +40,7 @@
</requestHandler>
<requestHandler name="/dump" class="DumpRequestHandler" initParams="a">
<requestHandler name="/dump" class="DumpRequestHandler" initParams="a" registerPath="/,/v2">
<lst name="defaults">
<str name="a">${my.custom.variable.a:A}</str>
<str name="b">${my.custom.variable.b:B}</str>

Some files were not shown because too many files have changed in this diff Show More