From 8bfd8a55c9207a84e18eb2512d7a2057ff802671 Mon Sep 17 00:00:00 2001 From: noble Date: Sat, 12 Oct 2019 00:11:59 +1100 Subject: [PATCH] SOLR-13827: Added support for PayLoad as 3rd param --- .../org/apache/solr/api/AnnotatedApi.java | 59 ++++++++++++++----- .../src/java/org/apache/solr/api/Command.java | 2 - .../java/org/apache/solr/api/PayloadObj.java | 35 +++++++++++ .../apache/solr/util/ReflectMapWriter.java | 58 ++++++++++++++++++ .../solr/handler/admin/TestApiFramework.java | 4 +- 5 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/api/PayloadObj.java create mode 100644 solr/core/src/java/org/apache/solr/util/ReflectMapWriter.java diff --git a/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java b/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java index b1be4614ca3..964af8587a3 100644 --- a/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java +++ b/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java @@ -45,14 +45,14 @@ import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuthorizationContext; import org.apache.solr.security.PermissionNameProvider; -/**This class implements an Api just from an annotated java class +/** + * This class implements an Api just from an annotated java class * The class must have an annotation {@link EndPoint} * Each method must have an annotation {@link Command} * The methods that implement a command should have the first 2 parameters * {@link SolrQueryRequest} and {@link SolrQueryResponse} or it may optionally * have a third parameter which could be a java class annotated with jackson annotations. * The third parameter is only valid if it is using a json command payload - * */ public class AnnotatedApi extends Api implements PermissionNameProvider { @@ -62,7 +62,6 @@ public class AnnotatedApi extends Api implements PermissionNameProvider { public AnnotatedApi(Object obj) { this(obj, null); - } public AnnotatedApi(Object obj, Api fallback) { @@ -94,21 +93,21 @@ public class AnnotatedApi extends Api implements PermissionNameProvider { private static SpecProvider readSpec(Class klas) { EndPoint endPoint = (EndPoint) klas.getAnnotation(EndPoint.class); - if (endPoint == null) throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Invalid class : "+ klas.getName()); - EndPoint endPoint1 = (EndPoint) klas.getAnnotation(EndPoint.class); + if (endPoint == null) + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Invalid class : " + klas.getName()); return () -> { Map map = new LinkedHashMap(); List methods = new ArrayList<>(); - for (SolrRequest.METHOD method : endPoint1.method()) { + for (SolrRequest.METHOD method : endPoint.method()) { methods.add(method.name()); } map.put("methods", methods); - map.put("url", new ValidatingJsonMap(Collections.singletonMap("paths", Arrays.asList(endPoint1.path())))); + map.put("url", new ValidatingJsonMap(Collections.singletonMap("paths", Arrays.asList(endPoint.path())))); Map cmds = new HashMap<>(); for (Method method : klas.getMethods()) { Command command = method.getAnnotation(Command.class); - if (command != null && !command.name().isBlank()) { + if (command != null && !command.name().isEmpty()) { cmds.put(command.name(), AnnotatedApi.createSchema(method)); } } @@ -132,7 +131,7 @@ public class AnnotatedApi extends Api implements PermissionNameProvider { } } - List cmds = req.getCommands(true); + List cmds = req.getCommands(false); boolean allExists = true; for (CommandOperation cmd : cmds) { if (!commands.containsKey(cmd.name)) { @@ -168,6 +167,7 @@ public class AnnotatedApi extends Api implements PermissionNameProvider { ObjectMapper mapper = new ObjectMapper(); int paramsCount; Class c; + boolean isWrappedInPayloadObj = false; Cmd(Command command, Object obj, Method method) { @@ -181,7 +181,23 @@ public class AnnotatedApi extends Api implements PermissionNameProvider { throw new RuntimeException("Invalid params for method " + method); } if (parameterTypes.length == 3) { - c = parameterTypes[2]; + Type t = method.getGenericParameterTypes()[2]; + if (t instanceof ParameterizedType) { + ParameterizedType typ = (ParameterizedType) t; + if (typ.getRawType() == PayloadObj.class) { + isWrappedInPayloadObj = true; + Type t1 = typ.getActualTypeArguments()[0]; + if (t1 instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) t1; + c = (Class) parameterizedType.getRawType(); + } else { + c = (Class) typ.getActualTypeArguments()[0]; + } + } + } else { + c = (Class) t; + } + } if (parameterTypes.length > 3) { throw new RuntimeException("Invalid params count for method " + method); @@ -195,7 +211,6 @@ public class AnnotatedApi extends Api implements PermissionNameProvider { void invoke(SolrQueryRequest req, SolrQueryResponse rsp, CommandOperation cmd) { try { - if (paramsCount == 2) { method.invoke(obj, req, rsp); } else { @@ -203,14 +218,26 @@ public class AnnotatedApi extends Api implements PermissionNameProvider { if (o instanceof Map && c != null) { o = mapper.readValue(Utils.toJSONString(o), c); } - method.invoke(obj, req, rsp, o); + if (isWrappedInPayloadObj) { + PayloadObj payloadObj = new PayloadObj<>(cmd.name, cmd.getCommandData(), o); + cmd = payloadObj; + method.invoke(obj, req, rsp, payloadObj); + } else { + method.invoke(obj, req, rsp, o); + } + if (cmd.hasError()) { + throw new ApiBag.ExceptionWithErrObject(SolrException.ErrorCode.BAD_REQUEST, "Error executing command", + CommandOperation.captureErrors(Collections.singletonList(cmd))); + } } + } catch (SolrException se) { throw se; } catch (InvocationTargetException ite) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, ite.getCause()); } catch (Exception e) { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e); } } @@ -243,11 +270,15 @@ public class AnnotatedApi extends Api implements PermissionNameProvider { private static Map createSchemaFromType(Type t) { Map map = new LinkedHashMap<>(); + if (t instanceof ParameterizedType) { + ParameterizedType typ = (ParameterizedType) t; + if (typ.getRawType() == PayloadObj.class) { + t = typ.getActualTypeArguments()[0]; + } + } if (primitives.containsKey(t)) { map.put("type", primitives.get(t)); - } else if (t == List.class) { - } else if (t instanceof ParameterizedType && ((ParameterizedType) t).getRawType() == List.class) { Type typ = ((ParameterizedType) t).getActualTypeArguments()[0]; map.put("type", "array"); diff --git a/solr/core/src/java/org/apache/solr/api/Command.java b/solr/core/src/java/org/apache/solr/api/Command.java index d18d0647eb3..25de0773fcc 100644 --- a/solr/core/src/java/org/apache/solr/api/Command.java +++ b/solr/core/src/java/org/apache/solr/api/Command.java @@ -32,6 +32,4 @@ public @interface Command { */ String name() default ""; - String jsonSchema() default ""; - } diff --git a/solr/core/src/java/org/apache/solr/api/PayloadObj.java b/solr/core/src/java/org/apache/solr/api/PayloadObj.java new file mode 100644 index 00000000000..c09c4422a92 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/api/PayloadObj.java @@ -0,0 +1,35 @@ +/* + * 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; + +public class PayloadObj extends CommandOperation { + + private T obj; + + + public PayloadObj(String operationName, Object metaData, T obj) { + super(operationName, metaData); + this.obj = obj; + } + + public T get(){ + return obj; + } +} diff --git a/solr/core/src/java/org/apache/solr/util/ReflectMapWriter.java b/solr/core/src/java/org/apache/solr/util/ReflectMapWriter.java new file mode 100644 index 00000000000..955574049ce --- /dev/null +++ b/solr/core/src/java/org/apache/solr/util/ReflectMapWriter.java @@ -0,0 +1,58 @@ +/* + * 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.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.solr.common.MapWriter; + +public interface ReflectMapWriter extends MapWriter { + + @Override + default void writeMap(EntryWriter ew) throws IOException { + for (Field field : this.getClass().getDeclaredFields()) { + JsonProperty prop = field.getAnnotation(JsonProperty.class); + if (prop == null) continue; + int modifiers = field.getModifiers(); + if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) { + String fname = prop.value().isEmpty() ? field.getName() : prop.value(); + try { + if (field.getType() == int.class) { + ew.put(fname, field.getInt(this)); + } else if (field.getType() == float.class) { + ew.put(fname, field.getFloat(this)); + } else if (field.getType() == double.class) { + ew.put(fname, field.getDouble(this)); + } else if (field.getType() == boolean.class) { + ew.put(fname, field.getBoolean(this)); + } else if (field.getType() == long.class) { + ew.put(fname, field.getLong(this)); + } else { + ew.putIfNotNull(fname, field.get(this)); + } + } catch (IllegalAccessException e) { + //it should not happen + } + } + } + } + +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java index 933b8622739..fde0335b9cf 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java @@ -168,7 +168,7 @@ public class TestApiFramework extends SolrTestCaseJ4 { } - public void testPayload() throws IOException { + public void testPayload() { String json = "{package:pkg1, version: '0.1', files :[a.jar, b.jar]}"; Utils.fromJSONString(json); @@ -213,8 +213,6 @@ public class TestApiFramework extends SolrTestCaseJ4 { } - - } public static class AddVersion {