/* * 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.handler.RequestHandlerUtils; 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(7);//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 == null ? 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()); } } } RequestHandlerUtils.addExperimentalFormatWarning(rsp); } }); 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(); } }