Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id C3E86200C0E for ; Wed, 1 Feb 2017 18:00:35 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id C2892160B6B; Wed, 1 Feb 2017 17:00:35 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 65335160B67 for ; Wed, 1 Feb 2017 18:00:32 +0100 (CET) Received: (qmail 86354 invoked by uid 500); 1 Feb 2017 17:00:30 -0000 Mailing-List: contact commits-help@lucene.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@lucene.apache.org Delivered-To: mailing list commits@lucene.apache.org Received: (qmail 85690 invoked by uid 99); 1 Feb 2017 17:00:29 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 01 Feb 2017 17:00:29 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 7ABE3F29DC; Wed, 1 Feb 2017 17:00:29 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: krisden@apache.org To: commits@lucene.apache.org Date: Wed, 01 Feb 2017 17:00:46 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [18/35] lucene-solr:jira/solr-8593: SOLR-8029: Added new style APIs and a framework for creating new APIs and mapping old APIs to new archived-at: Wed, 01 Feb 2017 17:00:35 -0000 http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java index 148d73c..d7759ca 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java @@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit; import com.google.common.collect.ImmutableSet; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; +import org.apache.solr.api.Api; import org.apache.solr.client.solrj.SolrResponse; import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.apache.solr.client.solrj.impl.HttpSolrClient.Builder; @@ -106,6 +107,7 @@ import static org.apache.solr.cloud.OverseerCollectionMessageHandler.ONLY_IF_DOW import static org.apache.solr.cloud.OverseerCollectionMessageHandler.REQUESTID; import static org.apache.solr.cloud.OverseerCollectionMessageHandler.SHARDS_PROP; import static org.apache.solr.cloud.OverseerCollectionMessageHandler.SHARD_UNIQUE; +import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST; import static org.apache.solr.common.cloud.DocCollection.DOC_ROUTER; import static org.apache.solr.common.cloud.DocCollection.RULE; import static org.apache.solr.common.cloud.DocCollection.SNITCH; @@ -135,12 +137,14 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); protected final CoreContainer coreContainer; + private final CollectionHandlerApi v2Handler ; public CollectionsHandler() { super(); // Unlike most request handlers, CoreContainer initialization // should happen in the constructor... this.coreContainer = null; + v2Handler = new CollectionHandlerApi(this); } @@ -151,6 +155,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission */ public CollectionsHandler(final CoreContainer coreContainer) { this.coreContainer = coreContainer; + v2Handler = new CollectionHandlerApi(this); } @Override @@ -205,33 +210,39 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission CollectionOperation operation = CollectionOperation.get(action); log.info("Invoked Collection Action :{} with params {} and sendToOCPQueue={}", action.toLower(), req.getParamString(), operation.sendToOCPQueue); - SolrResponse response = null; - Map 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 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 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 execute(SolrQueryRequest req, SolrQueryResponse rsp, CollectionsHandler h) throws Exception; - + } public static final List 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 getApis() { + return v2Handler.getApis(); + } + + @Override + public Boolean registerV2() { + return Boolean.TRUE; + } } http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java index f3a8dd2..5d6f02c 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java @@ -18,11 +18,13 @@ package org.apache.solr.handler.admin; import java.lang.invoke.MethodHandles; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import org.apache.solr.api.Api; import org.apache.solr.client.solrj.SolrResponse; import org.apache.solr.cloud.OverseerSolrResponse; import org.apache.solr.cloud.OverseerTaskQueue.QueueEvent; @@ -61,6 +63,7 @@ public class ConfigSetsHandler extends RequestHandlerBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); protected final CoreContainer coreContainer; public static long DEFAULT_ZK_TIMEOUT = 300*1000; + private final ConfigSetsHandlerApi configSetsHandlerApi = new ConfigSetsHandlerApi(this); /** * Overloaded ctor to inject CoreContainer into the handler. @@ -71,10 +74,6 @@ public class ConfigSetsHandler extends RequestHandlerBase { this.coreContainer = coreContainer; } - @Override - final public void init(NamedList args) { - - } @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { @@ -96,16 +95,7 @@ public class ConfigSetsHandler extends RequestHandlerBase { ConfigSetAction action = ConfigSetAction.get(a); if (action == null) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown action: " + a); - ConfigSetOperation operation = ConfigSetOperation.get(action); - log.info("Invoked ConfigSet Action :{} with params {} ", action.toLower(), req.getParamString()); - Map 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 result = operation.call(req, rsp, this); + sendToZk(rsp, operation, result); + } + + protected void sendToZk(SolrQueryResponse rsp, ConfigSetOperation operation, Map 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 getApis() { + return configSetsHandlerApi.getApis(); + } + + @Override + public Boolean registerV2() { + return Boolean.TRUE; + } } http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandlerApi.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandlerApi.java new file mode 100644 index 0000000..6037bcd --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandlerApi.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.solr.handler.admin; + +import java.util.Arrays; +import java.util.List; + +import org.apache.solr.client.solrj.SolrRequest; +import org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE; +import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET; +import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; +import static org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation.CREATE_OP; +import static org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation.DELETE_OP; +import static org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation.LIST_OP; + +public class ConfigSetsHandlerApi extends BaseHandlerApiSupport { + + final ConfigSetsHandler configSetHandler; + + public ConfigSetsHandlerApi(ConfigSetsHandler configSetHandler) { + this.configSetHandler = configSetHandler; + } + + + @Override + protected List getCommands() { + return Arrays.asList(Cmd.values()); + } + + @Override + protected List getEndPoints() { + return Arrays.asList(EndPoint.values()); + } + + enum Cmd implements ApiCommand { + LIST(EndPoint.LIST_CONFIG, LIST_OP, GET), + CREATE(EndPoint.CONFIG_COMMANDS, CREATE_OP, POST, "create"), + DEL(EndPoint.CONFIG_DEL, DELETE_OP, DELETE) + ; + private final EndPoint endPoint; + private final ConfigSetOperation op; + private final SolrRequest.METHOD method; + private final String cmdName; + + Cmd(EndPoint endPoint, ConfigSetOperation op, SolrRequest.METHOD method) { + this(endPoint, op, method, null); + } + + Cmd(EndPoint endPoint, ConfigSetOperation op, SolrRequest.METHOD method, String cmdName) { + this.cmdName = cmdName; + this.endPoint = endPoint; + this.op = op; + this.method = method; + } + + @Override + public String getName() { + return cmdName; + } + + @Override + public SolrRequest.METHOD getHttpMethod() { + return method; + } + + @Override + public V2EndPoint getEndPoint() { + return endPoint; + } + + @Override + public void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception { + ((ConfigSetsHandlerApi) apiHandler).configSetHandler.invokeAction(req, rsp, op.action); + } + + } + enum EndPoint implements V2EndPoint { + LIST_CONFIG("cluster.configs"), + CONFIG_COMMANDS("cluster.configs.Commands"), + CONFIG_DEL("cluster.configs.delete"); + + public final String spec; + + EndPoint(String spec) { + this.spec = spec; + } + + @Override + public String getSpecName() { + return spec; + } + } +} http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java index a415d8a..275ec18 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java @@ -18,6 +18,7 @@ package org.apache.solr.handler.admin; import java.io.File; import java.lang.invoke.MethodHandles; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -28,6 +29,7 @@ import java.util.concurrent.ExecutorService; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang.StringUtils; +import org.apache.solr.api.Api; import org.apache.solr.cloud.CloudDescriptor; import org.apache.solr.cloud.ZkController; import org.apache.solr.common.SolrException; @@ -66,6 +68,7 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); protected final CoreContainer coreContainer; protected final Map> 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())); map.put(FAILED, Collections.synchronizedMap(new LinkedHashMap())); 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())); map.put(FAILED, Collections.synchronizedMap(new LinkedHashMap())); 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 getApis() { + return coreAdminHandlerApi.getApis(); + } + static { for (CoreAdminOperation op : CoreAdminOperation.values()) opMap.put(op.action.toString().toLowerCase(Locale.ROOT), op); http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandlerApi.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandlerApi.java new file mode 100644 index 0000000..9d256e6 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandlerApi.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.solr.handler.admin; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import org.apache.solr.client.solrj.SolrRequest; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET; +import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; +import static org.apache.solr.handler.admin.CoreAdminHandlerApi.EndPoint.CORES_COMMANDS; +import static org.apache.solr.handler.admin.CoreAdminHandlerApi.EndPoint.CORES_STATUS; +import static org.apache.solr.handler.admin.CoreAdminHandlerApi.EndPoint.NODEAPIS; +import static org.apache.solr.handler.admin.CoreAdminHandlerApi.EndPoint.NODEINVOKE; +import static org.apache.solr.handler.admin.CoreAdminHandlerApi.EndPoint.PER_CORE_COMMANDS; +import static org.apache.solr.handler.admin.CoreAdminOperation.CREATE_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.FORCEPREPAREFORLEADERSHIP_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.INVOKE_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.MERGEINDEXES_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.OVERSEEROP_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.PREPRECOVERY_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.REJOINLEADERELECTION_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.RELOAD_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.RENAME_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.REQUESTAPPLYUPDATES_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.REQUESTBUFFERUPDATES_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.REQUESTRECOVERY_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.REQUESTSTATUS_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.REQUESTSYNCSHARD_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.SPLIT_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.STATUS_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.SWAP_OP; +import static org.apache.solr.handler.admin.CoreAdminOperation.UNLOAD_OP; + +public class CoreAdminHandlerApi extends BaseHandlerApiSupport { + private final CoreAdminHandler handler; + + public CoreAdminHandlerApi(CoreAdminHandler handler) { + this.handler = handler; + } + + enum Cmd implements ApiCommand { + CREATE(CORES_COMMANDS, POST, CREATE_OP, null, ImmutableMap.of("config", "configSet")), + UNLOAD(PER_CORE_COMMANDS, POST, UNLOAD_OP, null, null), + RELOAD(PER_CORE_COMMANDS, POST, RELOAD_OP, null, null), + STATUS(CORES_STATUS, GET, STATUS_OP), + SWAP(PER_CORE_COMMANDS, POST, SWAP_OP, null, ImmutableMap.of("other", "with")), + RENAME(PER_CORE_COMMANDS, POST, RENAME_OP, null, null), + MERGEINDEXES(PER_CORE_COMMANDS, POST, MERGEINDEXES_OP, "merge-indexes", null), + SPLIT(PER_CORE_COMMANDS, POST, SPLIT_OP, null, ImmutableMap.of("split.key", "splitKey")), + PREPRECOVERY(PER_CORE_COMMANDS, POST, PREPRECOVERY_OP, "prep-recovery", null), + REQUESTRECOVERY(PER_CORE_COMMANDS, POST, REQUESTRECOVERY_OP, null, null), + REQUESTSYNCSHARD(PER_CORE_COMMANDS, POST, REQUESTSYNCSHARD_OP, "request-sync-shard", null), + REQUESTBUFFERUPDATES(PER_CORE_COMMANDS, POST, REQUESTBUFFERUPDATES_OP, "request-buffer-updates", null), + REQUESTAPPLYUPDATES(PER_CORE_COMMANDS, POST, REQUESTAPPLYUPDATES_OP, "request-apply-updates", null), + REQUESTSTATUS(PER_CORE_COMMANDS, POST, REQUESTSTATUS_OP, null, null), + OVERSEEROP(NODEAPIS, POST, OVERSEEROP_OP, "overseer-op", null), + REJOINLEADERELECTION(NODEAPIS, POST, REJOINLEADERELECTION_OP, "rejoin-leader-election", null), + INVOKE(NODEINVOKE, GET, INVOKE_OP, null, null), + FORCEPREPAREFORLEADERSHIP(PER_CORE_COMMANDS, POST, FORCEPREPAREFORLEADERSHIP_OP, "force-prepare-for-leadership", null); + + public final String commandName; + public final BaseHandlerApiSupport.V2EndPoint endPoint; + public final SolrRequest.METHOD method; + public final Map 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 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 getCommands() { + return Arrays.asList(Cmd.values()); + } + + @Override + protected List getEndPoints() { + return Arrays.asList(EndPoint.values()); + } + + +} http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java index 8fdac21..c7cd052 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java @@ -16,24 +16,28 @@ */ package org.apache.solr.handler.admin; +import java.util.Collection; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.solr.api.ApiBag.ReqHandlerToApi; import org.apache.solr.common.SolrException; -import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.util.NamedList; import org.apache.solr.core.CoreContainer; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.response.SolrQueryResponse; +import org.apache.solr.api.Api; +import static java.util.Collections.singletonList; +import static org.apache.solr.api.ApiBag.getSpec; import static org.apache.solr.common.params.CommonParams.PATH; -public class InfoHandler extends RequestHandlerBase { +public class InfoHandler extends RequestHandlerBase { + protected final CoreContainer coreContainer; - - private ThreadDumpHandler threadDumpHandler = new ThreadDumpHandler(); - private PropertiesRequestHandler propertiesHandler = new PropertiesRequestHandler(); - private LoggingHandler loggingHandler; - private SystemInfoHandler systemInfoHandler; /** * Overloaded ctor to inject CoreContainer into the handler. @@ -42,9 +46,10 @@ public class InfoHandler extends RequestHandlerBase { */ public InfoHandler(final CoreContainer coreContainer) { this.coreContainer = coreContainer; - systemInfoHandler = new SystemInfoHandler(coreContainer); - loggingHandler = new LoggingHandler(coreContainer); - + handlers.put("threads", new ThreadDumpHandler()); + handlers.put("properties", new PropertiesRequestHandler()); + handlers.put("logging", new LoggingHandler(coreContainer)); + handlers.put("system", new SystemInfoHandler(coreContainer)); } @@ -73,27 +78,19 @@ public class InfoHandler extends RequestHandlerBase { } String path = (String) req.getContext().get(PATH); + handle(req, rsp, path); + } + + private void handle(SolrQueryRequest req, SolrQueryResponse rsp, String path) { int i = path.lastIndexOf('/'); String name = path.substring(i + 1, path.length()); - - if (name.equalsIgnoreCase("properties")) { - propertiesHandler.handleRequest(req, rsp); - } else if (name.equalsIgnoreCase("threads")) { - threadDumpHandler.handleRequest(req, rsp); - } else if (name.equalsIgnoreCase("logging")) { - loggingHandler.handleRequest(req, rsp); - } else if (name.equalsIgnoreCase("system")) { - systemInfoHandler.handleRequest(req, rsp); - } else { - if (name.equalsIgnoreCase("info")) name = ""; - throw new SolrException(ErrorCode.NOT_FOUND, "Info Handler not found: " + name); + RequestHandlerBase handler = handlers.get(name.toLowerCase(Locale.ROOT)); + if(handler == null) { + throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "No handler by name "+name + " available names are "+ handlers.keySet()); } - + handler.handleRequest(req, rsp); rsp.setHttpCaching(false); } - - - //////////////////////// SolrInfoMBeans methods ////////////////////// @@ -109,39 +106,52 @@ public class InfoHandler extends RequestHandlerBase { } protected PropertiesRequestHandler getPropertiesHandler() { - return propertiesHandler; + return (PropertiesRequestHandler) handlers.get("properties"); + } protected ThreadDumpHandler getThreadDumpHandler() { - return threadDumpHandler; + return (ThreadDumpHandler) handlers.get("threads"); } protected LoggingHandler getLoggingHandler() { - return loggingHandler; + return (LoggingHandler) handlers.get("logging"); } protected SystemInfoHandler getSystemInfoHandler() { - return systemInfoHandler; + return (SystemInfoHandler) handlers.get("system"); } protected void setPropertiesHandler(PropertiesRequestHandler propertiesHandler) { - this.propertiesHandler = propertiesHandler; + handlers.put("properties", propertiesHandler); } protected void setThreadDumpHandler(ThreadDumpHandler threadDumpHandler) { - this.threadDumpHandler = threadDumpHandler; + handlers.put("threads", threadDumpHandler); } protected void setLoggingHandler(LoggingHandler loggingHandler) { - this.loggingHandler = loggingHandler; + handlers.put("logging", loggingHandler); } protected void setSystemInfoHandler(SystemInfoHandler systemInfoHandler) { - this.systemInfoHandler = systemInfoHandler; + handlers.put("system", systemInfoHandler); } @Override public SolrRequestHandler getSubHandler(String subPath) { return this; } + + private Map handlers = new ConcurrentHashMap<>(); + + @Override + public Collection getApis() { + return singletonList(new ReqHandlerToApi(this, getSpec("node.Info"))); + } + + @Override + public Boolean registerV2() { + return Boolean.TRUE; + } } http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java index 1b81722..eceb4b7 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java @@ -20,12 +20,15 @@ import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import com.google.common.collect.ImmutableList; +import org.apache.solr.api.ApiBag; import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.util.Utils; @@ -34,10 +37,16 @@ import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.SolrConfigHandler; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; +import org.apache.solr.security.AuthenticationPlugin; import org.apache.solr.security.AuthorizationContext; +import org.apache.solr.security.AuthorizationPlugin; import org.apache.solr.security.ConfigEditablePlugin; import org.apache.solr.security.PermissionNameProvider; import org.apache.solr.util.CommandOperation; +import org.apache.solr.api.Api; +import org.apache.solr.api.ApiBag.ReqHandlerToApi; +import org.apache.solr.api.SpecProvider; +import org.apache.solr.util.JsonSchemaValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -244,5 +253,66 @@ public abstract class SecurityConfHandler extends RequestHandlerBase implements return "SecurityConfig: version=" + version + ", data=" + Utils.toJSONString(data); } } + + private Collection apis; + private AuthenticationPlugin authcPlugin; + private AuthorizationPlugin authzPlugin; + + @Override + public Collection getApis() { + if (apis == null) { + synchronized (this) { + if (apis == null) { + Collection 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 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 getCommandSchema() { + //it is possible that the Authorization plugin is modified since the last call. invalidate the + // the cached commandSchema + if(SecurityConfHandler.this.authzPlugin != cores.getAuthorizationPlugin()) commandSchema = null; + SecurityConfHandler.this.authzPlugin = cores.getAuthorizationPlugin(); + return super.getCommandSchema(); + } + }); + + this.apis = ImmutableList.copyOf(apis); + } + } + } + return this.apis; + } + + @Override + public Boolean registerV2() { + return Boolean.TRUE; + } } http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java index 35d04f6..a494ec4 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -21,9 +21,13 @@ import org.apache.solr.schema.IndexSchema; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.ContentStream; import org.apache.solr.core.SolrCore; +import org.apache.solr.servlet.HttpSolrCall; +import org.apache.solr.util.CommandOperation; import org.apache.solr.util.RTimerTree; import java.security.Principal; +import java.util.Collections; +import java.util.List; import java.util.Map; /** @@ -98,6 +102,26 @@ public interface SolrQueryRequest extends AutoCloseable { public void setJSON(Map json); public Principal getUserPrincipal(); + + default String getPath() { + return (String) getContext().get("path"); + } + + default Map getPathTemplateValues() { + return Collections.emptyMap(); + } + + default List getCommands(boolean validateInput) { + return Collections.emptyList(); + } + + default String getHttpMethod() { + return (String) getContext().get("httpMethod"); + } + + default HttpSolrCall getHttpSolrCall() { + return null; + } } http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java b/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java index ebee2fc..4b0e4d6 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java @@ -16,8 +16,13 @@ */ package org.apache.solr.request; +import org.apache.solr.api.ApiBag; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.util.ValidatingJsonMap; import org.apache.solr.common.util.SuppressForbidden; import org.apache.solr.search.SolrIndexSearcher; +import org.apache.solr.util.CommandOperation; +import org.apache.solr.util.JsonSchemaValidator; import org.apache.solr.util.RTimerTree; import org.apache.solr.util.RefCounted; import org.apache.solr.schema.IndexSchema; @@ -26,10 +31,16 @@ import org.apache.solr.common.util.ContentStream; import org.apache.solr.core.SolrCore; import java.io.Closeable; +import java.io.InputStream; +import java.io.InputStreamReader; import java.security.Principal; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.HashMap; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * Base implementation of SolrQueryRequest 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 parsedCommands; + + public List getCommands(boolean validateInput) { + if (parsedCommands == null) { + Iterable 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 getValidators(){ + return Collections.EMPTY_MAP; + } } http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/schema/SchemaManager.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/schema/SchemaManager.java b/solr/core/src/java/org/apache/solr/schema/SchemaManager.java index 8c3b5f0..7092c09 100644 --- a/solr/core/src/java/org/apache/solr/schema/SchemaManager.java +++ b/solr/core/src/java/org/apache/solr/schema/SchemaManager.java @@ -18,7 +18,6 @@ package org.apache.solr.schema; import java.io.IOException; import java.io.InputStream; -import java.io.Reader; import java.io.StringWriter; import java.lang.invoke.MethodHandles; import java.nio.charset.StandardCharsets; @@ -71,18 +70,10 @@ public class SchemaManager { /** * Take in a JSON command set and execute them. It tries to capture as many errors * as possible instead of failing at the first error it encounters - * @param reader The input as a Reader * @return List of errors. If the List is empty then the operation was successful. */ - public List performOperations(Reader reader) throws Exception { - List 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 ops = req.getCommands(false); List errs = CommandOperation.captureErrors(ops); if (!errs.isEmpty()) return errs; http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java index 5c0717b..f1665c7 100644 --- a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java +++ b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java @@ -37,11 +37,13 @@ import org.apache.http.Header; import org.apache.http.auth.BasicUserPrincipal; import org.apache.http.message.BasicHeader; import org.apache.solr.common.SolrException; +import org.apache.solr.common.util.ValidatingJsonMap; import org.apache.solr.util.CommandOperation; +import org.apache.solr.api.SpecProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEditablePlugin { +public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEditablePlugin , SpecProvider { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private AuthenticationProvider authenticationProvider; private final static ThreadLocal
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 pluginConfig); boolean authenticate(String user, String pwd); @@ -170,6 +172,10 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita Map getPromptHeaders(); } + @Override + public ValidatingJsonMap getSpec() { + return authenticationProvider.getSpec(); + } public boolean getBlockUnknown(){ return blockUnknown; } http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java index ed5a05c..a8a97ed 100644 --- a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java +++ b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java @@ -27,6 +27,9 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; +import org.apache.solr.api.ApiBag; +import org.apache.solr.api.SpecProvider; +import org.apache.solr.common.util.ValidatingJsonMap; import org.apache.solr.util.CommandOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +42,7 @@ import static org.apache.solr.handler.admin.SecurityConfHandler.getListValue; import static org.apache.solr.handler.admin.SecurityConfHandler.getMapValue; -public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, ConfigEditablePlugin { +public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, ConfigEditablePlugin, SpecProvider { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private final Map> usersVsRoles = new HashMap<>(); @@ -232,4 +235,10 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config private static final Map ops = unmodifiableMap(asList(AutorizationEditOperation.values()).stream().collect(toMap(AutorizationEditOperation::getOperationName, identity()))); + + @Override + public ValidatingJsonMap getSpec() { + return ApiBag.getSpec("cluster.security.RuleBasedAuthorization").getSpec(); + + } } http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java index 69664fd..0cc58cd 100644 --- a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java +++ b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java @@ -30,7 +30,10 @@ import java.util.Set; import com.google.common.collect.ImmutableSet; import org.apache.commons.codec.binary.Base64; +import org.apache.solr.common.util.ValidatingJsonMap; + import org.apache.solr.util.CommandOperation; +import org.apache.solr.api.ApiBag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -152,5 +155,10 @@ public class Sha256AuthenticationProvider implements ConfigEditablePlugin, Basi return latestConf; } + @Override + public ValidatingJsonMap getSpec() { + return ApiBag.getSpec("cluster.security.BasicAuth.Commands").getSpec(); + } + static final Set supported_ops = ImmutableSet.of("set-user", "delete-user"); } http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java index 1f98da9..b244015 100644 --- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java +++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java @@ -56,6 +56,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.InputStreamEntity; +import org.apache.solr.api.ApiBag; import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.client.solrj.impl.HttpClientUtil; import org.apache.solr.common.SolrException; @@ -71,6 +72,8 @@ import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; +import org.apache.solr.common.util.ContentStream; +import org.apache.solr.common.util.ValidatingJsonMap; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.common.util.StrUtils; @@ -97,6 +100,8 @@ import org.apache.solr.servlet.SolrDispatchFilter.Action; import org.apache.solr.servlet.cache.HttpCacheHeaderUtil; import org.apache.solr.servlet.cache.Method; import org.apache.solr.update.processor.DistributingUpdateProcessorFactory; +import org.apache.solr.util.CommandOperation; +import org.apache.solr.util.JsonSchemaValidator; import org.apache.solr.util.RTimerTree; import org.apache.zookeeper.KeeperException; import org.slf4j.Logger; @@ -149,6 +154,13 @@ public class HttpSolrCall { protected String coreUrl; protected SolrConfig config; protected Map 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 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 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 parsedCommands; + + public List getCommands(boolean validateInput) { + if (parsedCommands == null) { + Iterable 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 getValidators(){ + return Collections.EMPTY_MAP; + } /** * A faster method for randomly picking items when you do not need to http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java b/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java index a74fa8a..00733f5 100644 --- a/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java +++ b/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java @@ -15,8 +15,10 @@ * limitations under the License. */ package org.apache.solr.servlet; +import org.apache.solr.api.ApiBag; import org.apache.solr.common.SolrException; import org.apache.solr.common.util.NamedList; +import org.apache.solr.util.CommandOperation; import org.slf4j.Logger; import java.io.PrintWriter; @@ -48,6 +50,10 @@ public class ResponseUtils { errorMetadata.add(SolrException.ERROR_CLASS, ex.getClass().getName()); errorMetadata.add(SolrException.ROOT_ERROR_CLASS, SolrException.getRootCause(ex).getClass().getName()); info.add("metadata", errorMetadata); + if (ex instanceof ApiBag.ExceptionWithErrObject) { + ApiBag.ExceptionWithErrObject exception = (ApiBag.ExceptionWithErrObject) ex; + info.add(CommandOperation.ERR_MSGS, exception.getErrs() ); + } } for (Throwable th = ex; th != null; th = th.getCause()) { http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java index a411bb3..ce65069 100644 --- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java +++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java @@ -58,6 +58,7 @@ import org.apache.commons.io.output.CloseShieldOutputStream; import org.apache.commons.lang.StringUtils; import org.apache.http.client.HttpClient; import org.apache.lucene.util.Version; +import org.apache.solr.api.V2HttpCall; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.cloud.SolrZkClient; @@ -93,6 +94,7 @@ public class SolrDispatchFilter extends BaseSolrFilter { // Effectively immutable private Boolean testMode = null; + private boolean isV2Enabled = !"true".equals(System.getProperty("disable.v2.api", "false")); /** * Enum to define action that needs to be processed. @@ -102,7 +104,7 @@ public class SolrDispatchFilter extends BaseSolrFilter { * This is generally when an error is set and returned. * RETRY:Retry the request. In cases when a core isn't found to work with, this is set. */ - enum Action { + public enum Action { PASSTHROUGH, FORWARD, RETURN, RETRY, ADMIN, REMOTEQUERY, PROCESS } @@ -136,7 +138,7 @@ public class SolrDispatchFilter extends BaseSolrFilter { log.trace("SolrDispatchFilter.init(): {}", this.getClass().getClassLoader()); SolrRequestParsers.fileCleaningTracker = new SolrFileCleaningTracker(); - + StartupLoggingUtils.checkLogDir(); logWelcomeBanner(); String muteConsole = System.getProperty(SOLR_LOG_MUTECONSOLE); @@ -380,7 +382,17 @@ public class SolrDispatchFilter extends BaseSolrFilter { * want to add attributes to the request and send errors differently */ protected HttpSolrCall getHttpSolrCall(HttpServletRequest request, HttpServletResponse response, boolean retry) { - return new HttpSolrCall(this, cores, request, response, retry); + String path = request.getServletPath(); + if (request.getPathInfo() != null) { + // this lets you handle /update/commit when /update is a servlet + path += request.getPathInfo(); + } + + if (isV2Enabled && (path.startsWith("/v2/") || path.equals("/v2"))) { + return new V2HttpCall(this, cores, request, response, false); + } else { + return new HttpSolrCall(this, cores, request, response, retry); + } } private boolean authenticateRequest(ServletRequest request, ServletResponse response, final AtomicReference wrappedRequest) throws IOException { http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java index 968320e..c311d4a 100644 --- a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java +++ b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java @@ -33,6 +33,7 @@ import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -45,6 +46,7 @@ import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.FileCleaningTracker; import org.apache.commons.io.input.CloseShieldInputStream; import org.apache.lucene.util.IOUtils; +import org.apache.solr.api.V2HttpCall; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.params.CommonParams; @@ -58,6 +60,7 @@ import org.apache.solr.core.SolrConfig; import org.apache.solr.core.SolrCore; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.util.CommandOperation; import org.apache.solr.util.RTimerTree; import org.apache.solr.util.SolrFileCleaningTracker; @@ -224,11 +227,33 @@ public class SolrRequestParsers } } + final HttpSolrCall httpSolrCall = req == null ? null : (HttpSolrCall) req.getAttribute(HttpSolrCall.class.getName()); SolrQueryRequestBase q = new SolrQueryRequestBase(core, params, requestTimer) { @Override public Principal getUserPrincipal() { return req == null ? null : req.getUserPrincipal(); } + + @Override + public List getCommands(boolean validateInput) { + if (httpSolrCall != null) { + return httpSolrCall.getCommands(validateInput); + } + return Collections.emptyList(); + } + + @Override + public Map getPathTemplateValues() { + if (httpSolrCall != null && httpSolrCall instanceof V2HttpCall) { + return ((V2HttpCall) httpSolrCall).getUrlParts(); + } + return Collections.EMPTY_MAP; + } + + @Override + public HttpSolrCall getHttpSolrCall() { + return httpSolrCall; + } }; if( streams != null && streams.size() > 0 ) { q.setContentStreams( streams ); @@ -848,4 +873,4 @@ public class SolrRequestParsers -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/util/CommandOperation.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/util/CommandOperation.java b/solr/core/src/java/org/apache/solr/util/CommandOperation.java index 6b8f14f..88dfbb9 100644 --- a/solr/core/src/java/org/apache/solr/util/CommandOperation.java +++ b/solr/core/src/java/org/apache/solr/util/CommandOperation.java @@ -50,6 +50,10 @@ public class CommandOperation { this.name = operationName; } + public Object getCommandData() { + return commandData; + } + public String getStr(String key, String def) { if (ROOT_OBJ.equals(key)) { Object obj = getRootPrimitive(); http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/util/JsonSchemaValidator.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/util/JsonSchemaValidator.java b/solr/core/src/java/org/apache/solr/util/JsonSchemaValidator.java new file mode 100644 index 0000000..1074ed8 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/util/JsonSchemaValidator.java @@ -0,0 +1,370 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.solr.util; + +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.solr.common.util.StrUtils; +import org.apache.solr.common.util.Utils; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableMap; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + +/**A very basic and lightweight json schema parsing and data validation tool. This custom tool is created + * because a) we need to support non json inputs b) to avoiding double parsing (this accepts an already parsed json as a map) + * It validates most aspects of json schema but it is NOT A FULLY COMPLIANT JSON schema parser or validator. + * What is supported ? + * a) all types and their validation (string, boolean, array, enum,object, integer, number) + * b) 'required' properties, 'additionalProperties' + * + * + */ + +public class JsonSchemaValidator { + private final SchemaNode root; + + public JsonSchemaValidator(String jsonString) { + this((Map) Utils.fromJSONString(jsonString)); + } + public JsonSchemaValidator(Map jsonSchema) { + root = new SchemaNode(null); + root.isRequired = true; + List 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 children; + + private SchemaNode(SchemaNode parent) { + this.parent = parent; + } + + private void validateSchema(Map jsonSchema, List 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 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 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 validateJson(Object data) { + List 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 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 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 errors) { + if (attrSchema.get(Type.ENUM._name) != null) { + schemaNode.elementType = schemaNode.type; + schemaNode.type = Type.ENUM; + } + } + + @Override + void postValidateSchema(Map attrSchema, SchemaNode schemaNode, List 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 errors) { + Object val = attrSchema.get(key); + if (val instanceof List) { + List list = (List) val; + if (attr.children != null) { + for (Map.Entry 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 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 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 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 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 knownAttributes = unmodifiableMap(asList(SchemaAttribute.values()).stream().collect(toMap(SchemaAttribute::getKey, identity()))); + +} http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/java/org/apache/solr/util/PathTrie.java ---------------------------------------------------------------------- diff --git a/solr/core/src/java/org/apache/solr/util/PathTrie.java b/solr/core/src/java/org/apache/solr/util/PathTrie.java new file mode 100644 index 0000000..ceaa5de --- /dev/null +++ b/solr/core/src/java/org/apache/solr/util/PathTrie.java @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.solr.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.solr.common.util.StrUtils; + +import static java.util.Collections.emptyList; + +/**A utility class to efficiently parse/store/lookup hierarchical paths which are templatized + * like /collections/{collection}/shards/{shard}/{replica} + */ +public class PathTrie { + private final Set reserved = new HashSet<>(); + Node root = new Node(emptyList(), null); + + public PathTrie() { } + + public PathTrie(Set reserved) { + this.reserved.addAll(reserved); + } + + + + public void insert(String path, Map replacements, T o) { + List parts = getPathSegments(path); + insert(parts,replacements, o); + } + + public void insert(List parts, Map 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 getPathSegments(String path) { + if (path == null || path.isEmpty()) return emptyList(); + List parts = new ArrayList() { + @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 templateValues) { + return root.lookup(getPathSegments(path), 0, templateValues); + } + + public T lookup(List path, Map templateValues) { + return root.lookup(path, 0, templateValues); + } + + public T lookup(String path, Map templateValues, Set 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 children; + T obj; + String templateName; + + Node(List 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 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 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 pieces, int i, Map 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 pathSegments, int index, Map templateVariables, Set availableSubPaths) { + if (templateName != null) templateVariables.put(templateName, pathSegments.get(index - 1)); + if (pathSegments.size() < index + 1) { + findAvailableChildren("", availableSubPaths); + return obj; + } + String piece = pathSegments.get(index); + if (children == null) return null; + Node n = children.get(piece); + if (n == null && !reserved.contains(piece)) n = children.get(""); + if (n == null) return null; + return n.lookup(pathSegments, index + 1, templateVariables, availableSubPaths); + } + } + +} http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/ImplicitPlugins.json ---------------------------------------------------------------------- diff --git a/solr/core/src/resources/ImplicitPlugins.json b/solr/core/src/resources/ImplicitPlugins.json index 34e5c07..a1ddbe7 100644 --- a/solr/core/src/resources/ImplicitPlugins.json +++ b/solr/core/src/resources/ImplicitPlugins.json @@ -26,6 +26,10 @@ "json.command": "false" } }, + "update":{ + "class":"solr.UpdateRequestHandlerApi", + "useParams": "_UPDATE_JSON_DOCS" + }, "/config": { "useParams":"_CONFIG", "class": "solr.SolrConfigHandler" @@ -159,4 +163,4 @@ } } } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/cluster.Commands.json ---------------------------------------------------------------------- diff --git a/solr/core/src/resources/apispec/cluster.Commands.json b/solr/core/src/resources/apispec/cluster.Commands.json new file mode 100644 index 0000000..8983964 --- /dev/null +++ b/solr/core/src/resources/apispec/cluster.Commands.json @@ -0,0 +1,74 @@ +{ + "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API", + "description": "Cluster-wide commands to assign roles to nodes, remove role assignments, or add, edit or remove a cluster-wide property.", + "methods": [ + "POST" + ], + "url": { + "paths": [ + "/cluster" + ] + }, + "commands": { + "add-role":{ + "type":"object", + "documentation":"https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api15", + "description":"Assign a specific role to a node in the cluster.", + "properties": { + "role": { + "type": "string", + "description": "The name of the role. The only supported role is 'overseer'." + + }, + "node": { + "type": "string", + "description": "The name of the node. It is possible to assign a role even before that node is started." + + } + }, + "required": [ + "role", + "node" + ] + }, + "remove-role":{ + "type":"object", + "documentation":"https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api16", + "description":"Unassign a role from a node in the cluster.", + "properties": { + "role": { + "type": "string", + "description": "The name of the role. The only supported role as of now is 'overseer'." + + }, + "node": { + "type": "string", + "description": "The name of the node where the role should be removed." + } + }, + "required": [ + "role", + "node" + ] + }, + "set-property": { + "type": "object", + "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API#CollectionsAPI-api11", + "description": "Add, edit, or delete a cluster-wide property.", + "properties": { + "name": { + "type": "string", + "description": "The name of the property" + }, + "val": { + "type": "string", + "description": "The value of the property. If the value is empty or null, the property is unset." + } + }, + "required": [ + "name", + "val" + ] + } + } +}