You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2019/09/25 10:30:23 UTC
[lucene-solr] branch jira/SOLR-13722 updated: Use annotated
framework for V2 APIs
This is an automated email from the ASF dual-hosted git repository.
noble pushed a commit to branch jira/SOLR-13722
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git
The following commit(s) were added to refs/heads/jira/SOLR-13722 by this push:
new bf5e8a1 Use annotated framework for V2 APIs
bf5e8a1 is described below
commit bf5e8a1e389870776315d01c6dfe1c8f775c605f
Author: noble <no...@apache.org>
AuthorDate: Wed Sep 25 20:30:00 2019 +1000
Use annotated framework for V2 APIs
---
.../src/java/org/apache/solr/api/AnnotatedApi.java | 138 ++++++
.../src/java/org/apache/solr/api/CallInfo.java | 34 ++
.../core/src/java/org/apache/solr/api/Command.java | 34 ++
.../src/java/org/apache/solr/api/EndPoint.java | 38 ++
.../apache/solr/core/ContainerRequestHandlers.java | 75 +--
.../java/org/apache/solr/core/CoreContainer.java | 5 +-
.../src/java/org/apache/solr/core/FsBlobStore.java | 87 ++--
.../src/java/org/apache/solr/core/PluginBag.java | 3 +
.../solr/handler/admin/BaseHandlerApiSupport.java | 57 ++-
.../org/apache/solr/handler/admin/ClusterAPI.java | 510 ++++++++-------------
.../solr/handler/admin/CollectionHandlerApi.java | 22 +-
.../solr/security/PermissionNameProvider.java | 2 +
.../test/org/apache/solr/handler/TestPackages.java | 45 +-
.../client/solrj/request/CollectionApiMapping.java | 3 -
...package.json => cluster.packages.Commands.json} | 2 +-
.../resources/apispec/cluster.packages.GET.json | 10 +
16 files changed, 550 insertions(+), 515 deletions(-)
diff --git a/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java b/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java
new file mode 100644
index 0000000..45b3cba
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.api;
+
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.CommandOperation;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.security.PermissionNameProvider;
+
+public class AnnotatedApi extends Api implements PermissionNameProvider {
+ private EndPoint endPoint;
+ private Map<String, Cmd> commands = new HashMap<>();
+
+
+ public AnnotatedApi(Object obj) {
+ super(Utils.getSpec(readSpec(obj.getClass())));
+ Class<?> klas = obj.getClass();
+ if (!Modifier.isPublic(klas.getModifiers())) {
+ throw new RuntimeException(obj.getClass().getName() + " is not public");
+ }
+
+ endPoint = klas.getAnnotation(EndPoint.class);
+
+ for (Method m : klas.getDeclaredMethods()) {
+ Command command = m.getAnnotation(Command.class);
+ if (command == null) continue;
+
+ if (commands.containsKey(command.name())) {
+ throw new RuntimeException("Duplicate commands " + command.name());
+ }
+ commands.put(command.name(), new Cmd(command, obj, m));
+ }
+
+ }
+
+ @Override
+ public Name getPermissionName(AuthorizationContext request) {
+ return endPoint.permission();
+ }
+
+ private static String readSpec(Class klas) {
+ EndPoint endPoint = (EndPoint) klas.getAnnotation(EndPoint.class);
+ return endPoint.spec();
+
+ }
+
+ @Override
+ public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+ if (commands.size() == 1) {
+ Cmd cmd = commands.get("");
+ if (cmd != null) {
+ cmd.invoke(req, rsp, null);
+ return;
+ }
+ }
+
+ List<CommandOperation> cmds = req.getCommands(true);
+ boolean allExists = true;
+ for (CommandOperation cmd : cmds) {
+ if (!commands.containsKey(cmd.name)) {
+ cmd.addError("No such command supported :" + cmd.name);
+ allExists = false;
+ }
+ }
+ if (!allExists) {
+ throw new ApiBag.ExceptionWithErrObject(SolrException.ErrorCode.BAD_REQUEST, "error processing commands",
+ CommandOperation.captureErrors(cmds));
+ }
+
+ for (CommandOperation cmd : cmds) {
+ commands.get(cmd.name).invoke(req, rsp, cmd);
+ }
+
+ List<Map> errs = CommandOperation.captureErrors(cmds);
+ if (!errs.isEmpty()) {
+ throw new ApiBag.ExceptionWithErrObject(SolrException.ErrorCode.BAD_REQUEST, "Error in executing commands", errs);
+ }
+
+ }
+
+ class Cmd {
+ final Command command;
+ final Method method;
+ final Object obj;
+
+ Cmd(Command command, Object obj, Method method) {
+ if (Modifier.isPublic(method.getModifiers())) {
+ this.command = command;
+ this.obj = obj;
+ this.method = method;
+ Class<?>[] parameterTypes = method.getParameterTypes();
+ if (parameterTypes.length > 1 || parameterTypes[0] != CallInfo.class) {
+ throw new RuntimeException("Invalid params for method " + method);
+ }
+ } else {
+ throw new RuntimeException(method.toString() + " is not a public static method");
+ }
+
+ }
+
+ void invoke(SolrQueryRequest req, SolrQueryResponse rsp, CommandOperation cmd) {
+ try {
+ method.invoke(obj, new CallInfo(req, rsp, cmd));
+ } catch (SolrException se) {
+ throw se;
+ } catch (Exception e) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+ }
+
+ }
+ }
+
+}
diff --git a/solr/core/src/java/org/apache/solr/api/CallInfo.java b/solr/core/src/java/org/apache/solr/api/CallInfo.java
new file mode 100644
index 0000000..f5ae198
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/CallInfo.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.api;
+
+import org.apache.solr.common.util.CommandOperation;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+public class CallInfo {
+ public final SolrQueryRequest req;
+ public final SolrQueryResponse rsp;
+ public final CommandOperation command;
+
+ public CallInfo(SolrQueryRequest req, SolrQueryResponse rsp, CommandOperation command) {
+ this.req = req;
+ this.rsp = rsp;
+ this.command = command;
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/api/Command.java b/solr/core/src/java/org/apache/solr/api/Command.java
new file mode 100644
index 0000000..45303d6
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/Command.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Command {
+ /**if this is not a json command , leave it empty.
+ * Keep in mind that you cannot have duplicates.
+ * Only one method per name
+ *
+ */
+ String name() default "";
+}
diff --git a/solr/core/src/java/org/apache/solr/api/EndPoint.java b/solr/core/src/java/org/apache/solr/api/EndPoint.java
new file mode 100644
index 0000000..12f4897
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/api/EndPoint.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.apache.solr.security.PermissionNameProvider;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface EndPoint {
+ /**name of the API spec file without the '.json' suffix
+ */
+ String spec();
+
+
+ /**The well known persmission name if any
+ */
+ PermissionNameProvider.Name permission();
+}
diff --git a/solr/core/src/java/org/apache/solr/core/ContainerRequestHandlers.java b/solr/core/src/java/org/apache/solr/core/ContainerRequestHandlers.java
index a1321e5..86ec41d 100644
--- a/solr/core/src/java/org/apache/solr/core/ContainerRequestHandlers.java
+++ b/solr/core/src/java/org/apache/solr/core/ContainerRequestHandlers.java
@@ -20,25 +20,24 @@ package org.apache.solr.core;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.apache.solr.api.Api;
+import org.apache.solr.api.CallInfo;
+import org.apache.solr.api.Command;
+import org.apache.solr.api.EndPoint;
import org.apache.solr.api.V2HttpCall;
import org.apache.solr.common.MapWriter;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
-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.schema.FieldType;
-import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider;
import org.apache.solr.util.plugin.PluginInfoInitialized;
import org.slf4j.Logger;
@@ -49,7 +48,8 @@ import static org.apache.solr.common.params.CommonParams.PACKAGE;
import static org.apache.solr.common.params.CommonParams.VERSION;
import static org.apache.solr.core.PluginBag.closeQuietly;
-class ContainerRequestHandlers extends RequestHandlerBase implements PermissionNameProvider {
+@EndPoint(spec = "node.ext", permission = PermissionNameProvider.Name.CUSTOM_PERM)
+public class ContainerRequestHandlers {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
final CoreContainer coreContainer;
@@ -60,7 +60,6 @@ class ContainerRequestHandlers extends RequestHandlerBase implements PermissionN
this.coreContainer = coreContainer;
}
- @Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) {
int v = req.getParams().getInt(ConfigOverlay.ZNODEVER, -1);
if (v >= 0) {
@@ -79,27 +78,24 @@ class ContainerRequestHandlers extends RequestHandlerBase implements PermissionN
}
- @Override
- public Collection<Api> getApis() {
- return Collections.singleton(new Api(Utils.getSpec("node.ext")) {
- @Override
- public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
- String name = ((V2HttpCall) req.getHttpSolrCall()).getUrlParts().get("handlerName");
- if (name == null) {
- handleRequestBody(req, rsp);
- return;
- }
- Handler wrapper = customHandlers.get(name);
- if (wrapper == null) {
- String err = StrUtils.formatString(" No such handler: {0}, available handlers : {1}", name, customHandlers.keySet());
- log.error(err);
- throw new SolrException(SolrException.ErrorCode.NOT_FOUND, err);
- }
- wrapper.handler.handleRequest(req, rsp);
- }
- });
+ @Command
+ public void call(CallInfo info) {
+ String name = ((V2HttpCall) info.req.getHttpSolrCall()).getUrlParts().get("handlerName");
+ if (name == null) {
+ handleRequestBody(info.req, info.rsp);
+ return;
+ }
+ Handler wrapper = customHandlers.get(name);
+ if (wrapper == null) {
+ String err = StrUtils.formatString(" No such handler: {0}, available handlers : {1}", name, customHandlers.keySet());
+ log.error(err);
+ throw new SolrException(SolrException.ErrorCode.NOT_FOUND, err);
+ }
+ wrapper.handler.handleRequest(info.req, info.rsp);
+
}
+
void updateReqHandlers(Map<String, Object> properties) {
Map m = (Map) properties.getOrDefault(SolrRequestHandler.TYPE, Collections.emptyMap());
if (m.isEmpty() && customHandlers.isEmpty()) return;
@@ -109,18 +105,18 @@ class ContainerRequestHandlers extends RequestHandlerBase implements PermissionN
List<Handler> toBeClosed = new ArrayList<>();
for (Object o : m.entrySet()) {
Object v = ((Map.Entry) o).getValue();
- String name = (String) ((Map.Entry) o).getKey();
+ String name = (String) ((Map.Entry) o).getKey();
if (v instanceof Map) {
Map metaData = (Map) v;
Handler existing = customHandlers.get(name);
if (existing == null || !existing.meta.equals(metaData)) {
String klas = (String) metaData.get(FieldType.CLASS_NAME);
if (klas != null) {
- newCustomHandlers.put(name, new Handler(metaData));
+ newCustomHandlers.put(name, new Handler(metaData));
} else {
log.error("Invalid requestHandler {}", Utils.toJSONString(v));
}
- if(existing != null){
+ if (existing != null) {
toBeClosed.add(existing);
}
@@ -156,27 +152,6 @@ class ContainerRequestHandlers extends RequestHandlerBase implements PermissionN
return inst;
}
- @Override
- public String getDescription() {
- return "Custom Handlers";
- }
-
-
- @Override
- public Boolean registerV1() {
- return Boolean.FALSE;
- }
-
- @Override
- public Boolean registerV2() {
- return Boolean.TRUE;
- }
-
- @Override
- public Name getPermissionName(AuthorizationContext request) {
- if (request.getResource().endsWith("/node/ext")) return Name.COLL_READ_PERM;
- return Name.CUSTOM_PERM;
- }
class Handler implements MapWriter, PackageListeners.Listener, AutoCloseable {
SolrRequestHandler handler;
@@ -217,7 +192,7 @@ class ContainerRequestHandlers extends RequestHandlerBase implements PermissionN
@Override
public void changed(PackageBag.PackageInfo info) {
- if(this.packageInfo.znodeVersion < info.znodeVersion){
+ if (this.packageInfo.znodeVersion < info.znodeVersion) {
this.handler = createHandler(meta.attributes);
this.packageInfo = info;
}
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 0fd6bfb..db453df 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -47,6 +47,7 @@ import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
+import org.apache.solr.api.AnnotatedApi;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
@@ -645,8 +646,8 @@ public class CoreContainer {
reloadSecurityProperties();
this.backupRepoFactory = new BackupRepositoryFactory(cfg.getBackupRepositoryPlugins());
- containerHandlers.put("/ext", containerRequestHandlers);
- containerHandlers.put("/blob-get", blobStore.blobRead);
+ containerHandlers.put(new AnnotatedApi(containerRequestHandlers));
+ containerHandlers.put(new AnnotatedApi(blobStore.blobRead));
createHandler(ZK_PATH, ZookeeperInfoHandler.class.getName(), ZookeeperInfoHandler.class);
createHandler(ZK_STATUS_PATH, ZookeeperStatusHandler.class.getName(), ZookeeperStatusHandler.class);
collectionsHandler = createHandler(COLLECTIONS_HANDLER_PATH, cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
diff --git a/solr/core/src/java/org/apache/solr/core/FsBlobStore.java b/solr/core/src/java/org/apache/solr/core/FsBlobStore.java
index e3e1074..6796a98 100644
--- a/solr/core/src/java/org/apache/solr/core/FsBlobStore.java
+++ b/solr/core/src/java/org/apache/solr/core/FsBlobStore.java
@@ -28,7 +28,6 @@ import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -40,7 +39,9 @@ import java.util.function.Consumer;
import org.apache.http.client.HttpClient;
import org.apache.lucene.util.IOUtils;
-import org.apache.solr.api.Api;
+import org.apache.solr.api.CallInfo;
+import org.apache.solr.api.Command;
+import org.apache.solr.api.EndPoint;
import org.apache.solr.api.V2HttpCall;
import org.apache.solr.common.MapWriter;
import org.apache.solr.common.SolrException;
@@ -49,10 +50,8 @@ import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.Utils;
-import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -284,64 +283,38 @@ public class FsBlobStore {
}
- class BlobRead extends RequestHandlerBase implements PermissionNameProvider {
-
- @Override
- public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) {
-
- }
-
- @Override
- public String getDescription() {
- return "List fetch blobs";
- }
-
- @Override
- public Name getPermissionName(AuthorizationContext request) {
- return Name.BLOB_READ;
- }
+ @EndPoint(spec = "node.blob.GET",
+ permission = PermissionNameProvider.Name.BLOB_READ)
+ public class BlobRead {
+
+ @Command
+ public void get(CallInfo info){
+ SolrQueryRequest req = info.req;
+ SolrQueryResponse rsp = info.rsp;
+ String sha256 = ((V2HttpCall) req.getHttpSolrCall()).getUrlParts().get("sha256");
+ if (sha256 == null) {
+ rsp.add("blob", fileList(req.getParams()));
+ } else {
+ if (!fetchBlobToFS(sha256)) {
+ throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "No such blob");
+ }
- @Override
- public Collection<Api> getApis() {
- return Collections.singleton(new Api(Utils.getSpec("node.blob.GET")) {
- @Override
- public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
- String sha256 = ((V2HttpCall) req.getHttpSolrCall()).getUrlParts().get("sha256");
- if (sha256 == null) {
- rsp.add("blob", fileList(req.getParams()));
+ ModifiableSolrParams solrParams = new ModifiableSolrParams();
+ solrParams.add(CommonParams.WT, FILE_STREAM);
+ req.setParams(SolrParams.wrapDefaults(solrParams, req.getParams()));
+ rsp.add(FILE_STREAM, (SolrCore.RawWriter) os -> {
+ ByteBuffer b = tmpBlobs.get(sha256);
+ if (b != null) {
+ os.write(b.array(), b.arrayOffset(), b.limit());
} else {
- if (!fetchBlobToFS(sha256)) {
- throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "No such blob");
+ File file = new File(getBlobsPath().toFile(), sha256);
+ try (FileInputStream is = new FileInputStream(file)) {
+ org.apache.commons.io.IOUtils.copy(is, os);
}
-
- ModifiableSolrParams solrParams = new ModifiableSolrParams();
- solrParams.add(CommonParams.WT, FILE_STREAM);
- req.setParams(SolrParams.wrapDefaults(solrParams, req.getParams()));
- rsp.add(FILE_STREAM, (SolrCore.RawWriter) os -> {
- ByteBuffer b = tmpBlobs.get(sha256);
- if (b != null) {
- os.write(b.array(), b.arrayOffset(), b.limit());
- } else {
- File file = new File(getBlobsPath().toFile(), sha256);
- try (FileInputStream is = new FileInputStream(file)) {
- org.apache.commons.io.IOUtils.copy(is, os);
- }
- }
- });
}
- }
- });
- }
-
-
- @Override
- public Boolean registerV1() {
- return Boolean.FALSE;
+ });
+ }
}
- @Override
- public Boolean registerV2() {
- return Boolean.TRUE;
- }
}
}
diff --git a/solr/core/src/java/org/apache/solr/core/PluginBag.java b/solr/core/src/java/org/apache/solr/core/PluginBag.java
index 3c2c37b..433aad5 100644
--- a/solr/core/src/java/org/apache/solr/core/PluginBag.java
+++ b/solr/core/src/java/org/apache/solr/core/PluginBag.java
@@ -199,6 +199,9 @@ public class PluginBag<T> implements AutoCloseable {
if(old != null) closeQuietly(old);
return old == null ? null : old.get();
}
+ public void put(Api api){
+ apiBag.register(api,Collections.emptyMap());
+ }
public PluginHolder<T> put(String name, PluginHolder<T> plugin) {
Boolean registerApi = null;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java b/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
index a88c123..07928a9 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/BaseHandlerApiSupport.java
@@ -54,28 +54,34 @@ import static org.apache.solr.common.util.StrUtils.splitSmart;
public abstract class BaseHandlerApiSupport implements ApiSupport {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- protected final Map<SolrRequest.METHOD, Map<V2EndPoint, List<ApiCommand>>> commandsMapping;
-
- protected BaseHandlerApiSupport() {
- commandsMapping = new HashMap<>();
- for (ApiCommand cmd : getCommands()) {
- Map<V2EndPoint, List<ApiCommand>> m = commandsMapping.get(cmd.meta().getHttpMethod());
- if (m == null) commandsMapping.put(cmd.meta().getHttpMethod(), m = new HashMap<>());
- List<ApiCommand> list = m.get(cmd.meta().getEndPoint());
- if (list == null) m.put(cmd.meta().getEndPoint(), list = new ArrayList<>());
- list.add(cmd);
- }
- }
+ List<Api> apis;
@Override
public synchronized Collection<Api> getApis() {
- ImmutableList.Builder<Api> l = ImmutableList.builder();
- for (V2EndPoint op : getEndPoints()) l.add(getApi(op));
- return l.build();
+ if (apis == null) {
+ Map<SolrRequest.METHOD, Map<V2EndPoint, List<ApiCommand>>> commandsMapping = new HashMap<>();
+ for (ApiCommand cmd : getCommands()) {
+ Map<V2EndPoint, List<ApiCommand>> m = commandsMapping.get(cmd.meta().getHttpMethod());
+ if (m == null) commandsMapping.put(cmd.meta().getHttpMethod(), m = new HashMap<>());
+ List<ApiCommand> list = m.get(cmd.meta().getEndPoint());
+ if (list == null) m.put(cmd.meta().getEndPoint(), list = new ArrayList<>());
+ list.add(cmd);
+ }
+ ImmutableList.Builder<Api> l = ImmutableList.builder();
+ for (V2EndPoint op : getEndPoints()) l.add(getApi(commandsMapping, op));
+ l.addAll(getV2OnlyApis());
+ apis = l.build();
+ }
+ return apis;
+
+ }
+
+ protected Collection<Api> getV2OnlyApis() {
+ return Collections.EMPTY_LIST;
}
- private Api getApi(final V2EndPoint op) {
+ private Api getApi(Map<SolrRequest.METHOD, Map<V2EndPoint, List<ApiCommand>>> commandsMapping, final V2EndPoint op) {
final BaseHandlerApiSupport apiHandler = this;
return new Api(Utils.getSpec(op.getSpecName())) {
@Override
@@ -84,8 +90,8 @@ public abstract class BaseHandlerApiSupport implements ApiSupport {
SolrRequest.METHOD method = SolrRequest.METHOD.valueOf(req.getHttpMethod());
List<ApiCommand> commands = commandsMapping.get(method).get(op);
try {
- if(commands!=null && commands.size() == 1 && commands.get(0).isRaw()){
- commands.get(0).invoke(req,rsp, apiHandler);
+ if (commands != null && commands.size() == 1 && commands.get(0).isRaw()) {
+ commands.get(0).invoke(req, rsp, apiHandler);
return;
}
if (method == POST) {
@@ -191,7 +197,7 @@ public abstract class BaseHandlerApiSupport implements ApiSupport {
@Override
public Map toMap(Map<String, Object> suppliedMap) {
- for(Iterator<String> it=getParameterNamesIterator(); it.hasNext(); ) {
+ for (Iterator<String> it = getParameterNamesIterator(); it.hasNext(); ) {
final String param = it.next();
String key = cmd.meta().getParamSubstitute(param);
Object o = key.indexOf('.') > 0 ?
@@ -206,10 +212,10 @@ public abstract class BaseHandlerApiSupport implements ApiSupport {
Number.class.isAssignableFrom(oClass) ||
Character.class.isAssignableFrom(oClass) ||
Boolean.class.isAssignableFrom(oClass)) {
- suppliedMap.put(param,String.valueOf(o));
- } else if (List.class.isAssignableFrom(oClass) && ((List)o).get(0) instanceof String ) {
+ suppliedMap.put(param, String.valueOf(o));
+ } else if (List.class.isAssignableFrom(oClass) && ((List) o).get(0) instanceof String) {
List<String> l = (List<String>) o;
- suppliedMap.put( param, l.toArray(new String[0]));
+ suppliedMap.put(param, l.toArray(new String[0]));
} else {
// Lists pass through but will require special handling downstream
// if they contain non-string elements.
@@ -227,15 +233,16 @@ public abstract class BaseHandlerApiSupport implements ApiSupport {
protected abstract Collection<V2EndPoint> getEndPoints();
- public interface ApiCommand {
+ public interface ApiCommand {
CommandMeta meta();
- /** If true, do not do anything with the payload. The command implementation will do everything
+ /**
+ * If true, do not do anything with the payload. The command implementation will do everything
*/
- default boolean isRaw(){
+ default boolean isRaw() {
return false;
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/ClusterAPI.java
index c26503e..8826665 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ClusterAPI.java
@@ -30,13 +30,14 @@ import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.http.client.HttpClient;
-import org.apache.solr.api.ApiBag;
-import org.apache.solr.client.solrj.SolrRequest;
-import org.apache.solr.client.solrj.request.CollectionApiMapping;
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.CallInfo;
+import org.apache.solr.api.Command;
+import org.apache.solr.api.EndPoint;
import org.apache.solr.common.MapWriter;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterProperties;
-import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.CommandOperation;
@@ -46,56 +47,54 @@ import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.PackageBag;
import org.apache.solr.handler.SolrConfigHandler;
-import org.apache.solr.handler.admin.BaseHandlerApiSupport.ApiCommand;
-import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
-import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.util.RTimer;
import org.apache.solr.util.SimplePostTool;
-import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-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.client.solrj.request.CollectionApiMapping.EndPoint.CLUSTER_CMD;
-import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.CLUSTER_NODES;
-import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.CLUSTER_PKG;
-import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.CLUSTER_REPO;
import static org.apache.solr.common.params.CommonParams.PACKAGES;
-import static org.apache.solr.common.util.CommandOperation.captureErrors;
import static org.apache.solr.common.util.StrUtils.formatString;
import static org.apache.solr.core.BlobRepository.sha256Digest;
import static org.apache.solr.core.ConfigOverlay.ZNODEVER;
import static org.apache.solr.core.RuntimeLib.SHA256;
+import static org.apache.solr.security.PermissionNameProvider.Name.BLOB_WRITE;
+import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
+import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
+import static org.apache.solr.security.PermissionNameProvider.Name.PKG_EDIT;
+import static org.apache.solr.security.PermissionNameProvider.Name.PKG_READ;
//implements v2 only APIs at /cluster/* end point
class ClusterAPI {
+ private final CoreContainer coreContainer;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ ClusterAPI(CoreContainer coreContainer) {
+ this.coreContainer = coreContainer;
+ }
+
+
//sync the cluster props in every node
- static void syncClusterProps(ApiInfo info) throws IOException {
- CoreContainer cc = info.coreContainer;
+ void syncClusterProps(CallInfo info) throws IOException {
Stat stat = new Stat();
- Map<String, Object> clusterProperties = new ClusterProperties(cc.getZkController().getZkClient()).getClusterProperties(stat);
+ Map<String, Object> clusterProperties = new ClusterProperties(coreContainer.getZkController().getZkClient()).getClusterProperties(stat);
try {
- cc.getPackageBag().onChange(clusterProperties);
+ coreContainer.getPackageBag().onChange(clusterProperties);
} catch (SolrException e) {
- log.error("error executing command : " + info.op.jsonStr(), e);
+ log.error("error executing command : " + info.command.jsonStr(), e);
throw e;
} catch (Exception e) {
- log.error("error executing command : " + info.op.jsonStr(), e);
+ log.error("error executing command : " + info.command.jsonStr(), e);
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "error executing command : ", e);
}
log.info("current version of clusterprops.json is {} , trying to get every node to update ", stat.getVersion());
log.debug("The current clusterprops.json: {}", clusterProperties);
- waitForStateSync(stat.getVersion(), cc);
+ waitForStateSync(stat.getVersion(), coreContainer);
}
- private static void waitForStateSync(int expectedVersion, CoreContainer coreContainer) {
+ void waitForStateSync(int expectedVersion, CoreContainer coreContainer) {
final RTimer timer = new RTimer();
int waitTimeSecs = 30;
// get a list of active replica cores to query for the schema zk version (skipping this core of course)
@@ -133,347 +132,227 @@ class ClusterAPI {
}
+ public List<Api> getAllApis() {
+ List<Api> result = new ArrayList<>();
+ result.add(new AnnotatedApi( new ClusterAPI.ListNodes()));
+ result.add(new AnnotatedApi(new ClusterAPI.BlobWrite()));
+ result.add(new AnnotatedApi(new ClusterAPI.PkgRead()));
+ result.add(new AnnotatedApi(new ClusterAPI.PkgEdit()));
+ result.add(new AnnotatedApi(new ClusterAPI.ClusterCommands()));
+ return result;
+ }
- enum Commands implements ApiCommand {
+ @EndPoint(
+ spec = "cluster.packages.Commands",
+ permission = PKG_EDIT
+ )
+ public class PkgEdit {
- ADD_REPO(CLUSTER_REPO, POST, "add") {
- @Override
- void call(ApiInfo info) throws Exception {
- repositoryCRUD(info);
+ @Command(name = "add")
+ public void add(CallInfo callInfo) throws Exception {
+ if (addUpdatePackage(callInfo)) {
+ syncClusterProps(callInfo);
}
- },
- UPDATE_REPO(CLUSTER_REPO, POST, "update") {
- @Override
- void call(ApiInfo info) throws Exception {
- repositoryCRUD(info);
- }
- },
- DELETE_REPO(CLUSTER_REPO, POST, "delete") {
- @Override
- void call(ApiInfo info) throws Exception {
- repositoryCRUD(info);
- }
- },
+ }
- GET_NODES(CLUSTER_NODES, GET, null) {
- @Override
- void call(ApiInfo info) throws Exception {
- info.rsp.add("nodes", info.coreContainer.getZkController().getClusterState().getLiveNodes());
+ @Command(name = "update")
+ public void update(CallInfo callInfo) throws Exception {
+ if (addUpdatePackage(callInfo)) {
+ syncClusterProps(callInfo);
}
+ }
- },
- POST_BLOB(CollectionApiMapping.EndPoint.CLUSTER_BLOB, POST, null) {
- @Override
- void call(ApiInfo info) throws Exception {
- CoreContainer coreContainer = ((CollectionHandlerApi) info.apiHandler).handler.coreContainer;
- Iterable<ContentStream> streams = info.req.getContentStreams();
- if (streams == null) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "no payload");
- String sha256 = null;
- ContentStream stream = streams.iterator().next();
- try {
- ByteBuffer buf = SimplePostTool.inputStreamToByteArray(stream.getStream());
- sha256 = sha256Digest(buf);
- coreContainer.getBlobStore().distributeBlob(buf, sha256);
- info.rsp.add(SHA256, sha256);
+ @Command(name = "delete")
+ public void delPkg(CallInfo info) throws Exception {
+ if (deletePackage(info)) {
+ syncClusterProps(info);
+ }
+ }
- } catch (IOException e) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
- }
+ boolean deletePackage(CallInfo params) throws Exception {
+ if (checkEnabled(params)) return false;
+ String name = params.command.getStr(CommandOperation.ROOT_OBJ);
+ ClusterProperties clusterProperties = new ClusterProperties(coreContainer.getZkController().getZkClient());
+ Map<String, Object> props = clusterProperties.getClusterProperties();
+ List<String> pathToLib = asList(PACKAGES, name);
+ Map existing = (Map) Utils.getObjectByPath(props, false, pathToLib);
+ if (existing == null) {
+ params.command.addError("No such package : " + name);
+ return false;
}
+ Map delta = new LinkedHashMap();
+ Utils.setObjectByPath(delta, pathToLib, null, true);
+ clusterProperties.setClusterProperties(delta);
+ return true;
+ }
- @Override
- public boolean isRaw() {
- return true;
- }
- },
-
- SET_CLUSTER_PROPERTY_OBJ(CLUSTER_CMD,
- POST,
- "set-obj-property") {
- @Override
- void call(ApiInfo info) throws Exception {
- ClusterProperties clusterProperties = new ClusterProperties(info.coreContainer.getZkController().getZkClient());
- try {
- clusterProperties.setClusterProperties(info.op.getDataMap());
- } catch (Exception e) {
- throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in API", e);
- }
- }
- },
- LIST_PKG(CLUSTER_PKG, GET, null){
- @Override
- void call(ApiInfo info) throws Exception {
- ClusterProperties clusterProperties = new ClusterProperties(info.coreContainer.getZkController().getZkClient());
- info.rsp.add(PACKAGES, clusterProperties.getClusterProperty(PACKAGES, MapWriter.EMPTY));
- }
- },
- ADD_PACKAGE(CLUSTER_PKG,
- POST,
- "add") {
- @Override
- void call(ApiInfo info) throws Exception {
- if (addUpdatePackage(info)) syncClusterProps(info);
- }
- },
- UPDATE_PACKAGE(CLUSTER_PKG,
- POST,
- "update") {
- @Override
- void call(ApiInfo info) throws Exception {
- if (addUpdatePackage(info)) syncClusterProps(info);
- }
- },
- DELETE_PKG(CLUSTER_PKG,
- POST,
- "delete") {
- @Override
- void call(ApiInfo info) throws Exception {
- if (deletePackage(info)) syncClusterProps(info);
+ boolean checkEnabled(CallInfo info) {
+ if (!PackageBag.enablePackage) {
+ info.command.addError("node not started with enable.package=true");
+ return true;
}
+ return false;
+ }
- boolean deletePackage(ApiInfo params) throws Exception {
- if (checkEnabled(params)) return false;
- String name = params.op.getStr(CommandOperation.ROOT_OBJ);
- ClusterProperties clusterProperties = new ClusterProperties(params.coreContainer.getZkController().getZkClient());
- Map<String, Object> props = clusterProperties.getClusterProperties();
- List<String> pathToLib = asList(PACKAGES, name);
- Map existing = (Map) Utils.getObjectByPath(props, false, pathToLib);
- if (existing == null) {
- params.op.addError("No such runtimeLib : " + name);
+ boolean addUpdatePackage(CallInfo params) throws Exception {
+ if (checkEnabled(params)) return false;
+ CommandOperation op = params.command;
+ String name = op.getStr("name");
+ ClusterProperties clusterProperties = new ClusterProperties(coreContainer.getZkController().getZkClient());
+ Map<String, Object> props = clusterProperties.getClusterProperties();
+ List<String> pathToLib = asList(PACKAGES, name);
+ Map existing = (Map) Utils.getObjectByPath(props, false, pathToLib);
+ Map<String, Object> dataMap = Utils.getDeepCopy(op.getDataMap(), 3);
+ PackageBag.PackageInfo packageInfo = new PackageBag.PackageInfo(dataMap, 0);
+
+ if ("add".equals(op.name)) {
+ if (existing != null) {
+ op.addError(StrUtils.formatString("The package with a name ''{0}'' already exists ", name));
return false;
}
- Map delta = new LinkedHashMap();
- Utils.setObjectByPath(delta, pathToLib, null, true);
- clusterProperties.setClusterProperties(delta);
- return true;
- }
- },
- ADD_REQ_HANDLER(CLUSTER_CMD,
- POST,
- "add-requesthandler") {
- @Override
- void call(ApiInfo info) throws Exception {
- if (addRequestHandler(info)) syncClusterProps(info);
- }
-
- boolean addRequestHandler(ApiInfo info) throws Exception {
- Map data = info.op.getDataMap();
- String name = (String) data.get("name");
- CoreContainer coreContainer = info.coreContainer;
- ClusterProperties clusterProperties = new ClusterProperties(coreContainer.getZkController().getZkClient());
- Map<String, Object> map = clusterProperties.getClusterProperties();
- if (Utils.getObjectByPath(map, false, asList(SolrRequestHandler.TYPE, name)) != null) {
- info.op.addError("A requestHandler already exists with the said name");
+ } else {// this is an update command
+ if (existing == null) {
+ op.addError(StrUtils.formatString("The package with a name ''{0}'' does not exist", name));
return false;
}
- Map m = new LinkedHashMap();
- Utils.setObjectByPath(m, asList(SolrRequestHandler.TYPE, name), data, true);
- clusterProperties.setClusterProperties(m);
- return true;
- }
- },
- DELETE_REQ_HANDLER(CLUSTER_CMD,
- POST,
- "delete-requesthandler") {
- @Override
- void call(ApiInfo info) throws Exception {
- if (deleteReqHandler(info)) syncClusterProps(info);
- }
-
- boolean deleteReqHandler(ApiInfo params) throws Exception {
- String name = params.op.getStr("");
- ClusterProperties clusterProperties = new ClusterProperties(params.coreContainer.getZkController().getZkClient());
- Map<String, Object> map = clusterProperties.getClusterProperties();
- if (Utils.getObjectByPath(map, false, asList(SolrRequestHandler.TYPE, name)) == null) {
- params.op.addError("NO such requestHandler with name :");
+ PackageBag.PackageInfo oldInfo = new PackageBag.PackageInfo(existing, 1);
+ if (Objects.equals(oldInfo, packageInfo)) {
+ op.addError("Trying to update a package with the same data");
return false;
}
- Map m = new LinkedHashMap();
- Utils.setObjectByPath(m, asList(SolrRequestHandler.TYPE, name), null, true);
- clusterProperties.setClusterProperties(m);
- return true;
+ packageInfo.oldBlob = oldInfo.blobs.stream()
+ .map(it -> it.sha256)
+ .collect(Collectors.toList());
}
- }
-
- ;
-
- private CollectionApiMapping.CommandMeta meta;
-
- Commands(CollectionApiMapping.V2EndPoint endPoint, SolrRequest.METHOD method, String cmdName) {
- meta = new CollectionApiMapping.CommandMeta() {
- @Override
- public String getName() {
- return cmdName;
+ try {
+ List<String> errs = packageInfo.validate(coreContainer);
+ if (!errs.isEmpty()) {
+ for (String err : errs) op.addError(err);
+ return false;
}
+ } catch (FileNotFoundException fnfe) {
+ op.addError(fnfe.getMessage());
+ return false;
- @Override
- public SolrRequest.METHOD getHttpMethod() {
- return method;
- }
+ } catch (SolrException e) {
+ log.error("Error loading package ", e);
+ op.addError(e.getMessage());
+ return false;
+ }
- @Override
- public CollectionApiMapping.V2EndPoint getEndPoint() {
- return endPoint;
- }
- };
+ Map delta = new LinkedHashMap();
+ Utils.setObjectByPath(delta, pathToLib, packageInfo, true);
+ clusterProperties.setClusterProperties(delta);
+ return true;
}
- @Override
- public CollectionApiMapping.CommandMeta meta() {
- return meta;
+ }
+ @EndPoint(
+ spec = "cluster.packages.GET",
+ permission = PKG_READ
+ )
+ public class PkgRead {
+ @Command
+ public void list(CallInfo info) throws IOException {
+ ClusterProperties clusterProperties = new ClusterProperties(coreContainer. getZkController().getZkClient());
+ info.rsp.add(PACKAGES, clusterProperties.getClusterProperty(PACKAGES, MapWriter.EMPTY));
}
+ }
- @Override
- public void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception {
- CommandOperation op = null;
- if (meta().getHttpMethod() == SolrRequest.METHOD.POST) {
- if (meta.getName() != null) {
- List<CommandOperation> commands = req.getCommands(true);
- if (commands == null || commands.size() != 1)
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "should have exactly one command");
- op = commands.get(0);
- }
- }
+ @EndPoint(spec = "cluster.blob",
+ permission = BLOB_WRITE)
+ public class BlobWrite {
+ @Command
+ public void add(CallInfo info) {
+ Iterable<ContentStream> streams = info.req.getContentStreams();
+ if (streams == null) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "no payload");
+ String sha256 = null;
+ ContentStream stream = streams.iterator().next();
+ try {
+ ByteBuffer buf = SimplePostTool.inputStreamToByteArray(stream.getStream());
+ sha256 = sha256Digest(buf);
+ coreContainer.getBlobStore().distributeBlob(buf, sha256);
+ info.rsp.add(SHA256, sha256);
- call(new ApiInfo(req, rsp, apiHandler, op,
- ((CollectionHandlerApi) apiHandler).handler.coreContainer));
- if (op != null && op.hasError()) {
- throw new ApiBag.ExceptionWithErrObject(SolrException.ErrorCode.BAD_REQUEST, "error processing commands", captureErrors(singletonList(op)));
+ } catch (IOException e) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
}
+
}
- abstract void call(ApiInfo info) throws Exception;
+ }
+ @EndPoint(spec = "cluster.nodes",
+ permission = COLL_READ_PERM)
+ public class ListNodes {
+ @Command
+ public void list(CallInfo info) {
+ info.rsp.add("nodes", coreContainer.getZkController().getClusterState().getLiveNodes());
+ }
}
- private static void repositoryCRUD(ApiInfo info) {
- try {
- SolrZkClient zkClient = info.coreContainer.getZkController().getZkClient();
- Map data = Utils.getDeepCopy(Utils.getJson(zkClient, ZkStateReader.PACKAGE_REPO, true),3);
- Map<String, Object> dataMap = null;
- String name = null;
- name = info.op.getCommandData() instanceof String ?
- (String) info.op.getCommandData() :
- info.op.getStr("name");
-
- List<String> path = asList("repository", name);
- boolean contains = Utils.getObjectByPath(data, false, path) != null;
-
- if(info.op.name.equals(Commands.ADD_REPO.meta.getName())){
- if(contains){
- info.op.addError("repository "+ name + " already exists");
- return;
- }
- dataMap = info.op.getDataMap();
+ @EndPoint(spec = "cluster.Commands",
+ permission = COLL_EDIT_PERM)
+ public class ClusterCommands {
+ @Command(name = "add-requesthandler")
+ public void addHandler(CallInfo info) throws Exception {
+ if (addRequestHandler(info)) syncClusterProps(info);
+ }
- } else if(info.op.name.equals(Commands.UPDATE_REPO.meta.getName())) {
- if(!contains){
- info.op.addError("repository "+ name + " does not exist");
- return;
- }
- dataMap = info.op.getDataMap();
+ @Command(name = "delete-requesthandler")
+ public void delHandler(CallInfo info) throws Exception {
+ if (deleteReqHandler(info)) syncClusterProps(info);
+ }
- } else if(info.op.name.equals(Commands.DELETE_REPO.meta.getName())){
- if(!contains){
- info.op.addError("repository "+ name + " does not exist");
- return;
- }
+ @Command(name = "set-obj-property")
+ public void setObj(CallInfo info) {
+ ClusterProperties clusterProperties = new ClusterProperties(coreContainer.getZkController().getZkClient());
+ try {
+ clusterProperties.setClusterProperties(info.command.getDataMap());
+ } catch (Exception e) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in API", e);
}
- Map delta = new LinkedHashMap();
- Utils.setObjectByPath(delta, path, dataMap, true);
- updateRepository(zkClient, delta);
- } catch (SolrException se){
- throw se;
- } catch (Exception e) {
- throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
}
- }
- static boolean addUpdatePackage(ApiInfo params) throws Exception {
- if (checkEnabled(params)) return false;
- CommandOperation op = params.op;
- String name = op.getStr("name");
- ClusterProperties clusterProperties = new ClusterProperties(params.coreContainer.getZkController().getZkClient());
- Map<String, Object> props = clusterProperties.getClusterProperties();
- List<String> pathToLib = asList(PACKAGES, name);
- Map existing = (Map) Utils.getObjectByPath(props, false, pathToLib);
- Map<String, Object> dataMap = Utils.getDeepCopy(op.getDataMap(), 3);
- PackageBag.PackageInfo packageInfo = new PackageBag.PackageInfo(dataMap, 0);
-
- if (ClusterAPI.Commands.ADD_PACKAGE.meta().getName().equals(op.name)) {
- if (existing != null) {
- op.addError(StrUtils.formatString("The package with a name ''{0}'' already exists ", name));
- return false;
- }
- } else {// this is an update command
- if (existing == null) {
- op.addError(StrUtils.formatString("The package with a name ''{0}'' does not exist", name));
- return false;
- }
- PackageBag.PackageInfo oldInfo = new PackageBag.PackageInfo(existing, 1);
- if (Objects.equals(oldInfo, packageInfo)) {
- op.addError("Trying to update a package with the same data");
+
+ boolean addRequestHandler(CallInfo info) throws Exception {
+ Map data = info.command.getDataMap();
+ String name = (String) data.get("name");
+ ClusterProperties clusterProperties = new ClusterProperties(coreContainer.getZkController().getZkClient());
+ Map<String, Object> map = clusterProperties.getClusterProperties();
+ if (Utils.getObjectByPath(map, false, asList(SolrRequestHandler.TYPE, name)) != null) {
+ info.command.addError("A requestHandler already exists with the said name");
return false;
}
- packageInfo.oldBlob = oldInfo.blobs.stream()
- .map(it -> it.sha256)
- .collect(Collectors.toList());
+ Map m = new LinkedHashMap();
+ Utils.setObjectByPath(m, asList(SolrRequestHandler.TYPE, name), data, true);
+ clusterProperties.setClusterProperties(m);
+ return true;
}
- try {
- List<String> errs = packageInfo.validate(params.coreContainer);
- if(!errs.isEmpty()){
- for (String err : errs) op.addError(err);
+
+ boolean deleteReqHandler(CallInfo info) throws Exception {
+ String name = info.command.getStr("");
+ ClusterProperties clusterProperties = new ClusterProperties(coreContainer.getZkController().getZkClient());
+ Map<String, Object> map = clusterProperties.getClusterProperties();
+ if (Utils.getObjectByPath(map, false, asList(SolrRequestHandler.TYPE, name)) == null) {
+ info.command.addError("NO such requestHandler with name :");
return false;
}
- } catch (FileNotFoundException fnfe) {
- op.addError(fnfe.getMessage());
- return false;
-
- } catch (SolrException e) {
- log.error("Error loading package ", e);
- op.addError(e.getMessage());
- return false;
- }
-
- Map delta = new LinkedHashMap();
- Utils.setObjectByPath(delta, pathToLib, packageInfo, true);
- clusterProperties.setClusterProperties(delta);
- return true;
-
- }
-
- private static boolean checkEnabled(ApiInfo params) {
- if (!PackageBag.enablePackage) {
- params.op.addError("node not started with enable.package=true");
+ Map m = new LinkedHashMap();
+ Utils.setObjectByPath(m, asList(SolrRequestHandler.TYPE, name), null, true);
+ clusterProperties.setClusterProperties(m);
return true;
}
- return false;
- }
- static class ApiInfo {
- final SolrQueryRequest req;
- final SolrQueryResponse rsp;
- final BaseHandlerApiSupport apiHandler;
- final CommandOperation op;
- final CoreContainer coreContainer;
-
- ApiInfo(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler, CommandOperation op, CoreContainer coreContainer) {
- this.req = req;
- this.rsp = rsp;
- this.apiHandler = apiHandler;
- this.op = op;
- this.coreContainer = coreContainer;
- }
}
-
static class PerNodeCallable extends SolrConfigHandler.PerReplicaCallable {
private final HttpClient httpClient;
final String v2Url;
@@ -529,13 +408,4 @@ class ClusterAPI {
}
- static void updateRepository(SolrZkClient client, Map delta) throws KeeperException, InterruptedException {
- client.atomicUpdate(ZkStateReader.PACKAGE_REPO, zkData -> {
- if (zkData == null) return Utils.toJSON(delta);
- Map<String, Object> zkJson = (Map<String, Object>) Utils.fromJSON(zkData);
- boolean modified = Utils.mergeJson(zkJson, delta);
- return modified ? Utils.toJSON(zkJson) : null;
- });
-
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
index 95f30bb..2fb3a9f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.apache.solr.api.Api;
import org.apache.solr.client.solrj.request.CollectionApiMapping;
import org.apache.solr.client.solrj.request.CollectionApiMapping.CommandMeta;
import org.apache.solr.client.solrj.request.CollectionApiMapping.Meta;
@@ -39,13 +40,13 @@ public class CollectionHandlerApi extends BaseHandlerApiSupport {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
final CollectionsHandler handler;
- static Collection<ApiCommand> apiCommands = createApiMapping();
-
+ final Collection<ApiCommand> apiCommands;
public CollectionHandlerApi(CollectionsHandler handler) {
this.handler = handler;
+ apiCommands = createApiMapping();
}
- private static Collection<ApiCommand> createApiMapping() {
+ private Collection<ApiCommand> createApiMapping() {
//there
Map<CommandMeta, ApiCommand> apiMapping = new HashMap<>();
@@ -67,17 +68,6 @@ public class CollectionHandlerApi extends BaseHandlerApiSupport {
}
}
}
- //The following APIs have only V2 implementations
-
- for (ClusterAPI.Commands api : ClusterAPI.Commands.values()) {
- apiMapping.put(api.meta(), api );
- }
-
- for (Meta meta : Meta.values()) {
- if (apiMapping.get(meta) == null) {
- log.error("ERROR_INIT. No corresponding API implementation for : " + meta.commandName);
- }
- }
return apiMapping.values();
}
@@ -95,5 +85,9 @@ public class CollectionHandlerApi extends BaseHandlerApiSupport {
return apiCommands;
}
+ @Override
+ protected Collection<Api> getV2OnlyApis() {
+ return new ClusterAPI(handler.getCoreContainer()).getAllApis();
+ }
}
diff --git a/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java b/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java
index 16b39a4..d2ca0cc 100644
--- a/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java
+++ b/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java
@@ -54,6 +54,8 @@ public interface PermissionNameProvider {
METRICS_HISTORY_READ_PERM("metrics-history-read", null),
BLOB_READ("blob-read", null),
BLOB_WRITE("blob-write", null),
+ PKG_EDIT("package-write", null),
+ PKG_READ("package-read", null),
ALL("all", unmodifiableSet(new HashSet<>(asList("*", null))))
;
final String name;
diff --git a/solr/core/src/test/org/apache/solr/handler/TestPackages.java b/solr/core/src/test/org/apache/solr/handler/TestPackages.java
index c9be737..c377426 100644
--- a/solr/core/src/test/org/apache/solr/handler/TestPackages.java
+++ b/solr/core/src/test/org/apache/solr/handler/TestPackages.java
@@ -52,7 +52,6 @@ import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.NavigableObject;
import org.apache.solr.common.cloud.ClusterProperties;
import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
@@ -172,7 +171,7 @@ public class TestPackages extends SolrCloudTestCase {
.withPayload(payload)
.withMethod(SolrRequest.METHOD.POST)
.build().process(cluster.getSolrClient());
- fail("Expected error");
+ fail("Error expected");
} catch (BaseHttpSolrClient.RemoteExecutionException e) {
assertTrue("actual output : " + Utils.toJSONString(e.getMetaData()), e.getMetaData()._getStr("error/details[0]/errorMessages[0]", "").contains("No such blob: "));
}
@@ -412,6 +411,7 @@ public class TestPackages extends SolrCloudTestCase {
}
+ @Test
public void testPluginFrompackage() throws Exception {
String COLLECTION_NAME = "globalLoaderColl";
@@ -783,47 +783,6 @@ public class TestPackages extends SolrCloudTestCase {
}
- public void testRepoCRUD() throws Exception{
- MiniSolrCloudCluster cluster = configureCluster(4)
- .withJettyConfig(jetty -> jetty.enableV2(true))
- .addConfig("conf", configset("cloud-minimal"))
- .configure();
- try {
- String payload = "{add : {name : myrepo, url: 'http://localhost/abc' , version : '1.1'}}";
- new V2Request.Builder("/cluster/repository")
- .withPayload(payload)
- .withMethod(SolrRequest.METHOD.POST)
- .build().process(cluster.getSolrClient());
- Map repojson = Utils.getJson(cluster.getZkClient(), ZkStateReader.PACKAGE_REPO, true);
-
- assertEquals("http://localhost/abc", Utils.getObjectByPath(repojson, true, "/repository/myrepo/url"));
- assertEquals("1.1", Utils.getObjectByPath(repojson, true, "/repository/myrepo/version"));
- payload = "{update : {name : myrepo, url: 'http://localhost/abc' , version : '1.2'}}";
- new V2Request.Builder("/cluster/repository")
- .withPayload(payload)
- .withMethod(SolrRequest.METHOD.POST)
- .build().process(cluster.getSolrClient());
- repojson = Utils.getJson(cluster.getZkClient(), ZkStateReader.PACKAGE_REPO, true);
-
- assertEquals("http://localhost/abc", Utils.getObjectByPath(repojson, true, "/repository/myrepo/url"));
- assertEquals("1.2", Utils.getObjectByPath(repojson, true, "/repository/myrepo/version"));
-
- payload = "{delete : myrepo}";
- new V2Request.Builder("/cluster/repository")
- .withPayload(payload)
- .withMethod(SolrRequest.METHOD.POST)
- .build().process(cluster.getSolrClient());
- repojson = Utils.getJson(cluster.getZkClient(), ZkStateReader.PACKAGE_REPO, true);
-
- assertNull( Utils.getObjectByPath(repojson, true, "/repository/myrepo"));
-
-
- }finally {
- cluster.shutdown();
- }
-
- }
-
private void postBlob(SolrClient client, ByteBuffer blob, String sh256) throws SolrServerException, IOException {
V2Response rsp = new V2Request.Builder("/cluster/blob")
.withMethod(SolrRequest.METHOD.POST)
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
index a532c15..4f0fa2d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
@@ -339,11 +339,8 @@ public class CollectionApiMapping {
public enum EndPoint implements V2EndPoint {
CLUSTER("cluster"),
- CLUSTER_BLOB("cluster.blob"),
CLUSTER_ALIASES("cluster.aliases"),
CLUSTER_CMD("cluster.Commands"),
- CLUSTER_PKG("cluster.package"),
- CLUSTER_REPO("cluster.repository"),
CLUSTER_NODES("cluster.nodes"),
CLUSTER_CMD_STATUS("cluster.commandstatus"),
CLUSTER_CMD_STATUS_DELETE("cluster.commandstatus.delete"),
diff --git a/solr/solrj/src/resources/apispec/cluster.package.json b/solr/solrj/src/resources/apispec/cluster.packages.Commands.json
similarity index 96%
rename from solr/solrj/src/resources/apispec/cluster.package.json
rename to solr/solrj/src/resources/apispec/cluster.packages.Commands.json
index d58b50c..b52851e 100644
--- a/solr/solrj/src/resources/apispec/cluster.package.json
+++ b/solr/solrj/src/resources/apispec/cluster.packages.Commands.json
@@ -1,6 +1,6 @@
{
"methods": [
- "GET", "POST"
+ "POST"
],
"url": {
"paths": [
diff --git a/solr/solrj/src/resources/apispec/cluster.packages.GET.json b/solr/solrj/src/resources/apispec/cluster.packages.GET.json
new file mode 100644
index 0000000..ba11bd2
--- /dev/null
+++ b/solr/solrj/src/resources/apispec/cluster.packages.GET.json
@@ -0,0 +1,10 @@
+{
+ "methods": [
+ "GET"
+ ],
+ "url": {
+ "paths": [
+ "/cluster/package"
+ ]
+ }
+}
\ No newline at end of file