From 20c1fdbf7e4573948434cfa82e1f7b0fc4d54b31 Mon Sep 17 00:00:00 2001 From: Noble Paul Date: Tue, 30 Jun 2020 22:40:26 +1000 Subject: [PATCH] SOLR-14404: use MethodHandles in AnnotatedAPI (#1624) --- .../org/apache/solr/api/AnnotatedApi.java | 118 +++++++++--------- .../solr/handler/admin/TestApiFramework.java | 15 ++- 2 files changed, 74 insertions(+), 59 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 index b3d65d05cb7..d9548c91fa7 100644 --- a/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java +++ b/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java @@ -20,10 +20,9 @@ package org.apache.solr.api; import java.io.Closeable; import java.io.IOException; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; @@ -38,10 +37,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.common.SolrException; import org.apache.solr.common.SpecProvider; -import org.apache.solr.common.util.CommandOperation; -import org.apache.solr.common.util.JsonSchemaCreator; -import org.apache.solr.common.util.Utils; -import org.apache.solr.common.util.ValidatingJsonMap; +import org.apache.solr.common.util.*; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuthorizationContext; @@ -87,16 +83,18 @@ public class AnnotatedApi extends Api implements PermissionNameProvider , Closea public static List getApis(Object obj) { return getApis(obj.getClass(), obj); } - public static List getApis(Class klas , Object obj) { - if (!Modifier.isPublic(klas.getModifiers())) { - throw new RuntimeException(klas.getName() + " is not public"); + public static List getApis(Class theClass , Object obj) { + Class klas = null; + try { + klas = MethodHandles.publicLookup().accessClass(theClass); + } catch (IllegalAccessException e) { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Method may be non-public/inaccessible", e); } - if (klas.getAnnotation(EndPoint.class) != null) { EndPoint endPoint = klas.getAnnotation(EndPoint.class); List methods = new ArrayList<>(); Map commands = new HashMap<>(); - for (Method m : klas.getDeclaredMethods()) { + for (Method m : klas.getMethods()) { Command command = m.getAnnotation(Command.class); if (command != null) { methods.add(m); @@ -113,12 +111,9 @@ public class AnnotatedApi extends Api implements PermissionNameProvider , Closea return Collections.singletonList(new AnnotatedApi(specProvider, endPoint, commands, null)); } else { List apis = new ArrayList<>(); - for (Method m : klas.getDeclaredMethods()) { + for (Method m : klas.getMethods()) { EndPoint endPoint = m.getAnnotation(EndPoint.class); if (endPoint == null) continue; - if (!Modifier.isPublic(m.getModifiers())) { - throw new RuntimeException("Non public method " + m.toGenericString()); - } Cmd cmd = new Cmd("", obj, m); SpecProvider specProvider = readSpec(endPoint, Collections.singletonList(m)); apis.add(new AnnotatedApi(specProvider, endPoint, Collections.singletonMap("", cmd), null)); @@ -212,40 +207,40 @@ public class AnnotatedApi extends Api implements PermissionNameProvider , Closea static class Cmd { final String command; - final Method method; + final MethodHandle method; final Object obj; ObjectMapper mapper = SolrJacksonAnnotationInspector.createObjectMapper(); int paramsCount; @SuppressWarnings({"rawtypes"}) - Class c; + Class parameterClass; boolean isWrappedInPayloadObj = false; Cmd(String command, Object obj, Method method) { - if (Modifier.isPublic(method.getModifiers())) { - this.command = command; - this.obj = obj; - this.method = method; - Class[] parameterTypes = method.getParameterTypes(); - paramsCount = parameterTypes.length; - if (parameterTypes.length == 1) { - readPayloadType(method.getGenericParameterTypes()[0]); - } else if (parameterTypes.length == 3) { - if (parameterTypes[0] != SolrQueryRequest.class || parameterTypes[1] != SolrQueryResponse.class) { - throw new RuntimeException("Invalid params for method " + method); - } - Type t = method.getGenericParameterTypes()[2]; - readPayloadType(t); - } - if (parameterTypes.length > 3) { - throw new RuntimeException("Invalid params count for method " + method); - } - } else { - throw new RuntimeException(method.toString() + " is not a public static method"); + this.command = command; + this.obj = obj; + try { + this.method = MethodHandles.publicLookup().unreflect(method); + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to access method, may be not public or accessible ", e); + } + Class[] parameterTypes = method.getParameterTypes(); + paramsCount = parameterTypes.length; + if (parameterTypes.length == 1) { + readPayloadType(method.getGenericParameterTypes()[0]); + } else if (parameterTypes.length == 3) { + if (parameterTypes[0] != SolrQueryRequest.class || parameterTypes[1] != SolrQueryResponse.class) { + throw new RuntimeException("Invalid params for method " + method); + } + Type t = method.getGenericParameterTypes()[2]; + readPayloadType(t); + } + if (parameterTypes.length > 3) { + throw new RuntimeException("Invalid params count for method " + method); } - } + @SuppressWarnings("rawtypes") private void readPayloadType(Type t) { if (t instanceof ParameterizedType) { ParameterizedType typ = (ParameterizedType) t; @@ -253,19 +248,19 @@ public class AnnotatedApi extends Api implements PermissionNameProvider , Closea isWrappedInPayloadObj = true; if(typ.getActualTypeArguments().length == 0){ //this is a raw type - c = Map.class; + parameterClass = Map.class; return; } Type t1 = typ.getActualTypeArguments()[0]; if (t1 instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) t1; - c = (Class) parameterizedType.getRawType(); + parameterClass = (Class) parameterizedType.getRawType(); } else { - c = (Class) typ.getActualTypeArguments()[0]; + parameterClass = (Class) typ.getActualTypeArguments()[0]; } } } else { - c = (Class) t; + parameterClass = (Class) t; } } @@ -273,21 +268,35 @@ public class AnnotatedApi extends Api implements PermissionNameProvider , Closea @SuppressWarnings({"unchecked"}) void invoke(SolrQueryRequest req, SolrQueryResponse rsp, CommandOperation cmd) { try { - if(paramsCount ==1) { - Object o = cmd.getCommandData(); - if (o instanceof Map && c != null && c != Map.class) { - o = mapper.readValue(Utils.toJSONString(o), c); + Object o = null; + String commandName = null; + if(paramsCount == 1) { + if(cmd == null) { + if(parameterClass != null) { + try { + ContentStream stream = req.getContentStreams().iterator().next(); + o = mapper.readValue(stream.getStream(), parameterClass); + } catch (IOException e) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "invalid payload", e); + } + } + } else { + commandName = cmd.name; + o = cmd.getCommandData(); + if (o instanceof Map && parameterClass != null && parameterClass != Map.class) { + o = mapper.readValue(Utils.toJSONString(o), parameterClass); + } } - PayloadObj payloadObj = new PayloadObj<>(cmd.name, cmd.getCommandData(), o, req, rsp); + PayloadObj payloadObj = new PayloadObj<>(commandName, o, o, req, rsp); cmd = payloadObj; method.invoke(obj, payloadObj); checkForErrorInPayload(cmd); } else if (paramsCount == 2) { method.invoke(obj, req, rsp); } else { - Object o = cmd.getCommandData(); - if (o instanceof Map && c != null) { - o = mapper.readValue(Utils.toJSONString(o), c); + o = cmd.getCommandData(); + if (o instanceof Map && parameterClass != null) { + o = mapper.readValue(Utils.toJSONString(o), parameterClass); } if (isWrappedInPayloadObj) { PayloadObj payloadObj = new PayloadObj<>(cmd.name, cmd.getCommandData(), o, req, rsp); @@ -298,15 +307,10 @@ public class AnnotatedApi extends Api implements PermissionNameProvider , Closea } checkForErrorInPayload(cmd); } - - - } catch (SolrException se) { + } catch (RuntimeException se) { log.error("Error executing command ", se); throw se; - } catch (InvocationTargetException ite) { - log.error("Error executing command ", ite); - throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, ite.getCause()); - } catch (Exception e) { + } catch (Throwable e) { log.error("Error executing command : ", e); throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e); } 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 5f0a57a4296..55ce63b73e7 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 @@ -149,7 +149,7 @@ public class TestApiFramework extends SolrTestCaseJ4 { } - public void testPayload() { + public void testPayload() throws IOException { String json = "{package:pkg1, version: '0.1', files :[a.jar, b.jar]}"; Utils.fromJSONString(json); @@ -176,7 +176,18 @@ public class TestApiFramework extends SolrTestCaseJ4 { assertEquals("b.jar", addversion.files.get(1)); + apiBag.registerObject(new C()); + rsp = v2ApiInvoke(apiBag, "/path1", "POST", new ModifiableSolrParams(), + new ByteArrayInputStream("{\"package\":\"mypkg\", \"version\": \"1.0\", \"files\" : [\"a.jar\", \"b.jar\"]}".getBytes(UTF_8))); + assertEquals("mypkg", rsp.getValues()._getStr("payload/package", null)); + assertEquals("1.0", rsp.getValues()._getStr("payload/version", null)); + } + public static class C { + @EndPoint(path = "/path1", method = POST, permission = PermissionNameProvider.Name.ALL) + public void m1(PayloadObj add) { + add.getResponse().add("payload",add.get()); + } } @EndPoint(method = POST, path = "/cluster/package", permission = PermissionNameProvider.Name.ALL) @@ -195,7 +206,7 @@ public class TestApiFramework extends SolrTestCaseJ4 { } - public static class AddVersion { + public static class AddVersion implements ReflectMapWriter { @JsonProperty(value = "package", required = true) public String pkg; @JsonProperty(value = "version", required = true)