diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestModifyView.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestModifyView.java index d561b67b17..2fa0278b7f 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestModifyView.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestModifyView.java @@ -25,13 +25,6 @@ package com.google.gerrit.extensions.restapi; * @param type of input the JSON parser will parse the input into. */ public interface RestModifyView extends RestView { - /** - * @return Java class object defining the input type. The JSON parser will - * parse the supplied request body into a new instance of this class - * before passing it to apply. - */ - Class inputType(); - /** * Process the view operation by altering the resource. * diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java index 2142b7ecfe..9a81bd6946 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java @@ -93,6 +93,8 @@ import java.io.Writer; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.Collections; import java.util.List; import java.util.Map; @@ -264,7 +266,7 @@ public class RestApiServlet extends HttpServlet { RestModifyView m = (RestModifyView) view; - result = m.apply(rsrc, parseRequest(req, m.inputType())); + result = m.apply(rsrc, parseRequest(req, inputType(m))); } else if (view instanceof RestReadView) { result = ((RestReadView) view).apply(rsrc); } else { @@ -308,7 +310,43 @@ public class RestApiServlet extends HttpServlet { } } - private Object parseRequest(HttpServletRequest req, Class type) + private static Type inputType(RestModifyView m) { + Type inputType = extractInputType(m.getClass()); + if (inputType == null) { + throw new IllegalStateException(String.format( + "View %s does not correctly implement %s", + m.getClass(), RestModifyView.class.getSimpleName())); + } + return inputType; + } + + @SuppressWarnings("rawtypes") + private static Type extractInputType(Class clazz) { + for (Type t : clazz.getGenericInterfaces()) { + if (t instanceof ParameterizedType + && ((ParameterizedType) t).getRawType() == RestModifyView.class) { + return ((ParameterizedType) t).getActualTypeArguments()[1]; + } + } + + if (clazz.getSuperclass() != null) { + Type i = extractInputType(clazz.getSuperclass()); + if (i != null) { + return i; + } + } + + for (Class t : clazz.getInterfaces()) { + Type i = extractInputType(t); + if (i != null) { + return i; + } + } + + return null; + } + + private Object parseRequest(HttpServletRequest req, Type type) throws IOException, BadRequestException, SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, MethodNotAllowedException { @@ -333,7 +371,7 @@ public class RestApiServlet extends HttpServlet { return parsePutInput(req, type); } else if ("DELETE".equals(req.getMethod()) && hasNoBody(req)) { return null; - } else if (type.getDeclaredFields().length == 0 && hasNoBody(req)) { + } else if (isEmptyType(type) && hasNoBody(req)) { return createInstance(type); } else if (isType("text/plain", req.getContentType())) { BufferedReader br = req.getReader(); @@ -358,6 +396,19 @@ public class RestApiServlet extends HttpServlet { } } + @SuppressWarnings("rawtypes") + private static boolean isEmptyType(Type type) { + if (type == String.class) { + return false; + } else if (type instanceof Class) { + Class clazz = (Class) type; + Class base = clazz.getSuperclass(); + return clazz.getDeclaredFields().length == 0 + && (base == null || isEmptyType(base)); + } + return false; + } + private static boolean hasNoBody(HttpServletRequest req) { int len = req.getContentLength(); String type = req.getContentType(); @@ -365,21 +416,24 @@ public class RestApiServlet extends HttpServlet { || (len == 0 && isType(FORM_TYPE, type)); } - private static boolean acceptsPutInput(Class type) { - for (Field f : type.getDeclaredFields()) { - if (f.getType() == PutInput.class) { - return true; + @SuppressWarnings("rawtypes") + private static boolean acceptsPutInput(Type type) { + if (type instanceof Class) { + for (Field f : ((Class) type).getDeclaredFields()) { + if (f.getType() == PutInput.class) { + return true; + } } } return false; } - private Object parsePutInput(final HttpServletRequest req, Class type) + private Object parsePutInput(final HttpServletRequest req, Type type) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, MethodNotAllowedException { Object obj = createInstance(type); - for (Field f : type.getDeclaredFields()) { + for (Field f : obj.getClass().getDeclaredFields()) { if (f.getType() == PutInput.class) { f.setAccessible(true); f.set(obj, new PutInput() { @@ -404,12 +458,16 @@ public class RestApiServlet extends HttpServlet { throw new MethodNotAllowedException(); } - private Object parseString(String value, Class type) + private Object parseString(String value, Type type) throws BadRequestException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException { + if (type == String.class) { + return value; + } + Object obj = createInstance(type); - Field[] fields = type.getDeclaredFields(); + Field[] fields = obj.getClass().getDeclaredFields(); if (fields.length == 0 && Strings.isNullOrEmpty(value)) { return obj; } @@ -424,12 +482,17 @@ public class RestApiServlet extends HttpServlet { throw new BadRequestException("Expected JSON object"); } - private static Object createInstance(Class type) + private static Object createInstance(Type type) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { - Constructor c = type.getDeclaredConstructor(); - c.setAccessible(true); - return c.newInstance(); + if (type instanceof Class) { + @SuppressWarnings("unchecked") + Class clazz = (Class) type; + Constructor c = clazz.getDeclaredConstructor(); + c.setAccessible(true); + return c.newInstance(); + } + throw new InstantiationException("Cannot make " + type); } private static void replyJson(@Nullable HttpServletRequest req, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java index 3dc003b9fc..79083912a7 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java @@ -65,11 +65,6 @@ public class Abandon implements RestModifyView { this.json = json; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(ChangeResource req, Input input) throws BadRequestException, AuthException, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java index cb56627582..b1299095b4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java @@ -41,11 +41,6 @@ class CreateDraft implements RestModifyView { this.db = db; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Response apply(RevisionResource rsrc, Input in) throws AuthException, BadRequestException, ResourceConflictException, OrmException { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java index 6573204237..0d5898ea89 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java @@ -35,11 +35,6 @@ class DeleteDraft implements RestModifyView { this.db = db; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(DraftResource rsrc, Input input) throws OrmException { db.get().patchComments().delete(Collections.singleton(rsrc.getComment())); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java index 9921388c58..a7e525460a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java @@ -43,11 +43,6 @@ class DeleteReviewer implements RestModifyView { this.dbProvider = dbProvider; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(ReviewerResource rsrc, Input input) throws AuthException, ResourceNotFoundException, OrmException { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java index 1a9d1f22b3..1dc9c30fb2 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java @@ -125,11 +125,6 @@ public class PostReview implements RestModifyView { this.hooks = hooks; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(RevisionResource revision, Input input) throws AuthException, BadRequestException, OrmException { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java index cd55405d0f..6f76bdf805 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java @@ -55,11 +55,6 @@ class PutDraft implements RestModifyView { this.delete = delete; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(DraftResource rsrc, Input in) throws AuthException, BadRequestException, ResourceConflictException, OrmException { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java index 3747f9f0b2..b96b4805bd 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java @@ -48,11 +48,6 @@ class PutTopic implements RestModifyView { this.dbProvider = dbProvider; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(ChangeResource req, Input input) throws BadRequestException, AuthException, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java index 5b78b5bd55..7bae29379d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java @@ -65,11 +65,6 @@ public class Restore implements RestModifyView { this.json = json; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(ChangeResource req, Input input) throws Exception { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java index 3614b37ba4..e365a8a6cc 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java @@ -83,11 +83,6 @@ public class Revert implements RestModifyView { this.canonicalWebUrl = canonicalWebUrl; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(ChangeResource req, Input input) throws Exception { ChangeControl control = req.getControl(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java index 542d5165ed..c730aab8ac 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java @@ -82,11 +82,6 @@ public class Submit implements RestModifyView { this.mergeQueue = mergeQueue; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Output apply(RevisionResource rsrc, Input input) throws AuthException, ResourceConflictException, RepositoryNotFoundException, IOException, @@ -305,11 +300,6 @@ public class Submit implements RestModifyView { this.json = json; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(ChangeResource rsrc, Input input) throws AuthException, ResourceConflictException, RepositoryNotFoundException, IOException, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java index 1342cca309..d623f9da53 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java @@ -95,11 +95,6 @@ class AddMembers implements RestModifyView { this.self = self; } - @Override - public Class inputType() { - return Input.class; - } - @Override public List apply(GroupResource resource, Input input) throws AuthException, MethodNotAllowedException, BadRequestException, @@ -201,11 +196,6 @@ class AddMembers implements RestModifyView { this.id = id; } - @Override - public Class inputType() { - return PutMember.Input.class; - } - @Override public Object apply(GroupResource resource, PutMember.Input input) throws AuthException, MethodNotAllowedException, BadRequestException, @@ -232,11 +222,6 @@ class AddMembers implements RestModifyView { this.get = get; } - @Override - public Class inputType() { - return PutMember.Input.class; - } - @Override public Object apply(MemberResource resource, PutMember.Input input) { // Do nothing, the user is already a member. diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java index c270b79cd7..eb21a3acc2 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java @@ -56,11 +56,6 @@ public class CreateGroup implements RestModifyView { this.visibleToAll = cfg.getBoolean("groups", "newGroupsVisibleToAll", false); } - @Override - public Class inputType() { - return Input.class; - } - @Override public GroupInfo apply(TopLevelResource resource, Input input) throws AuthException, BadRequestException, OrmException, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java index bb20b89915..cbf6876b12 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java @@ -61,11 +61,6 @@ public class DeleteMembers implements RestModifyView { this.self = self; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(GroupResource resource, Input input) throws AuthException, MethodNotAllowedException, BadRequestException, @@ -159,11 +154,6 @@ public class DeleteMembers implements RestModifyView { this.delete = delete; } - @Override - public Class inputType() { - return DeleteMember.Input.class; - } - @Override public Object apply(MemberResource resource, Input input) throws AuthException, MethodNotAllowedException, BadRequestException, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java index 1dc77118ee..cae5ce679c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java @@ -33,11 +33,6 @@ class DisablePlugin implements RestModifyView { this.loader = loader; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(PluginResource resource, Input input) { String name = resource.getName(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java index 492271f3ab..f33d814920 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java @@ -37,11 +37,6 @@ class EnablePlugin implements RestModifyView { this.loader = loader; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(PluginResource resource, Input input) throws ResourceConflictException { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java index cd64dfdae5..e2cac6ec29 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java @@ -51,11 +51,6 @@ class InstallPlugin implements RestModifyView { this.created = created; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Response apply(TopLevelResource resource, Input input) throws BadRequestException, IOException { @@ -105,11 +100,6 @@ class InstallPlugin implements RestModifyView { this.loader = loader; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Response apply(PluginResource resource, Input input) throws BadRequestException, IOException { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java index a3c301ba4e..9f9ef2db83 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java @@ -37,11 +37,6 @@ class ReloadPlugin implements RestModifyView { this.loader = loader; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(PluginResource resource, Input input) throws ResourceConflictException { String name = resource.getName(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteDashboard.java index 84473f4fdb..da1e46ba19 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteDashboard.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteDashboard.java @@ -35,11 +35,6 @@ class DeleteDashboard implements RestModifyView { this.defaultSetter = defaultSetter; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(DashboardResource resource, Input input) throws AuthException, BadRequestException, ResourceConflictException, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java index 7c4c886662..930da12223 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java @@ -38,11 +38,6 @@ class SetDashboard implements RestModifyView { this.defaultSetter = defaultSetter; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(DashboardResource resource, Input input) throws AuthException, BadRequestException, ResourceConflictException, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java index 7fb298677a..4050f4cfcc 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java @@ -55,11 +55,6 @@ class SetDefaultDashboard implements RestModifyView { this.get = get; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(DashboardResource resource, Input input) throws AuthException, BadRequestException, ResourceConflictException, @@ -137,11 +132,6 @@ class SetDefaultDashboard implements RestModifyView { this.setDefault = setDefault; } - @Override - public Class inputType() { - return SetDashboard.Input.class; - } - @Override public Object apply(ProjectResource resource, Input input) throws AuthException, BadRequestException, ResourceConflictException, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDescription.java index 253ffa1d64..dd489e7baf 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDescription.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDescription.java @@ -53,11 +53,6 @@ class SetDescription implements RestModifyView { this.gitMgr = gitMgr; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(ProjectResource resource, Input input) throws AuthException, BadRequestException, ResourceConflictException, diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java index c7959f918f..e84cced4a8 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java @@ -53,11 +53,6 @@ class SetParent implements RestModifyView { this.allProjects = allProjects; } - @Override - public Class inputType() { - return Input.class; - } - @Override public Object apply(ProjectResource resource, Input input) throws AuthException, BadRequestException, ResourceConflictException,