Simplify RestModifyView interface

The inputType can be determined by looking at the declaration
of the interface. We don't need to implement a method in each
view implementation to supply the correct data for reflection
in RestApiServlet.

Change-Id: I1afae6ddef661722efe39ccc917c028e7e993595
This commit is contained in:
Shawn Pearce
2013-01-26 11:55:39 -08:00
committed by Edwin Kempin
parent 63cc8ec1b6
commit 5ab78ad7c2
24 changed files with 78 additions and 162 deletions

View File

@@ -25,13 +25,6 @@ package com.google.gerrit.extensions.restapi;
* @param <I> type of input the JSON parser will parse the input into. * @param <I> type of input the JSON parser will parse the input into.
*/ */
public interface RestModifyView<R extends RestResource, I> extends RestView<R> { public interface RestModifyView<R extends RestResource, I> extends RestView<R> {
/**
* @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<I> inputType();
/** /**
* Process the view operation by altering the resource. * Process the view operation by altering the resource.
* *

View File

@@ -93,6 +93,8 @@ import java.io.Writer;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -264,7 +266,7 @@ public class RestApiServlet extends HttpServlet {
RestModifyView<RestResource, Object> m = RestModifyView<RestResource, Object> m =
(RestModifyView<RestResource, Object>) view; (RestModifyView<RestResource, Object>) view;
result = m.apply(rsrc, parseRequest(req, m.inputType())); result = m.apply(rsrc, parseRequest(req, inputType(m)));
} else if (view instanceof RestReadView<?>) { } else if (view instanceof RestReadView<?>) {
result = ((RestReadView<RestResource>) view).apply(rsrc); result = ((RestReadView<RestResource>) view).apply(rsrc);
} else { } else {
@@ -308,7 +310,43 @@ public class RestApiServlet extends HttpServlet {
} }
} }
private Object parseRequest(HttpServletRequest req, Class<Object> type) private static Type inputType(RestModifyView<RestResource, Object> 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, throws IOException, BadRequestException, SecurityException,
IllegalArgumentException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
InstantiationException, InvocationTargetException, MethodNotAllowedException { InstantiationException, InvocationTargetException, MethodNotAllowedException {
@@ -333,7 +371,7 @@ public class RestApiServlet extends HttpServlet {
return parsePutInput(req, type); return parsePutInput(req, type);
} else if ("DELETE".equals(req.getMethod()) && hasNoBody(req)) { } else if ("DELETE".equals(req.getMethod()) && hasNoBody(req)) {
return null; return null;
} else if (type.getDeclaredFields().length == 0 && hasNoBody(req)) { } else if (isEmptyType(type) && hasNoBody(req)) {
return createInstance(type); return createInstance(type);
} else if (isType("text/plain", req.getContentType())) { } else if (isType("text/plain", req.getContentType())) {
BufferedReader br = req.getReader(); 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) { private static boolean hasNoBody(HttpServletRequest req) {
int len = req.getContentLength(); int len = req.getContentLength();
String type = req.getContentType(); String type = req.getContentType();
@@ -365,21 +416,24 @@ public class RestApiServlet extends HttpServlet {
|| (len == 0 && isType(FORM_TYPE, type)); || (len == 0 && isType(FORM_TYPE, type));
} }
private static boolean acceptsPutInput(Class<Object> type) { @SuppressWarnings("rawtypes")
for (Field f : type.getDeclaredFields()) { private static boolean acceptsPutInput(Type type) {
if (f.getType() == PutInput.class) { if (type instanceof Class) {
return true; for (Field f : ((Class) type).getDeclaredFields()) {
if (f.getType() == PutInput.class) {
return true;
}
} }
} }
return false; return false;
} }
private Object parsePutInput(final HttpServletRequest req, Class<Object> type) private Object parsePutInput(final HttpServletRequest req, Type type)
throws SecurityException, NoSuchMethodException, throws SecurityException, NoSuchMethodException,
IllegalArgumentException, InstantiationException, IllegalAccessException, IllegalArgumentException, InstantiationException, IllegalAccessException,
InvocationTargetException, MethodNotAllowedException { InvocationTargetException, MethodNotAllowedException {
Object obj = createInstance(type); Object obj = createInstance(type);
for (Field f : type.getDeclaredFields()) { for (Field f : obj.getClass().getDeclaredFields()) {
if (f.getType() == PutInput.class) { if (f.getType() == PutInput.class) {
f.setAccessible(true); f.setAccessible(true);
f.set(obj, new PutInput() { f.set(obj, new PutInput() {
@@ -404,12 +458,16 @@ public class RestApiServlet extends HttpServlet {
throw new MethodNotAllowedException(); throw new MethodNotAllowedException();
} }
private Object parseString(String value, Class<Object> type) private Object parseString(String value, Type type)
throws BadRequestException, SecurityException, NoSuchMethodException, throws BadRequestException, SecurityException, NoSuchMethodException,
IllegalArgumentException, IllegalAccessException, InstantiationException, IllegalArgumentException, IllegalAccessException, InstantiationException,
InvocationTargetException { InvocationTargetException {
if (type == String.class) {
return value;
}
Object obj = createInstance(type); Object obj = createInstance(type);
Field[] fields = type.getDeclaredFields(); Field[] fields = obj.getClass().getDeclaredFields();
if (fields.length == 0 && Strings.isNullOrEmpty(value)) { if (fields.length == 0 && Strings.isNullOrEmpty(value)) {
return obj; return obj;
} }
@@ -424,12 +482,17 @@ public class RestApiServlet extends HttpServlet {
throw new BadRequestException("Expected JSON object"); throw new BadRequestException("Expected JSON object");
} }
private static Object createInstance(Class<Object> type) private static Object createInstance(Type type)
throws NoSuchMethodException, InstantiationException, throws NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException { IllegalAccessException, InvocationTargetException {
Constructor<Object> c = type.getDeclaredConstructor(); if (type instanceof Class) {
c.setAccessible(true); @SuppressWarnings("unchecked")
return c.newInstance(); Class<Object> clazz = (Class<Object>) type;
Constructor<Object> c = clazz.getDeclaredConstructor();
c.setAccessible(true);
return c.newInstance();
}
throw new InstantiationException("Cannot make " + type);
} }
private static void replyJson(@Nullable HttpServletRequest req, private static void replyJson(@Nullable HttpServletRequest req,

View File

@@ -65,11 +65,6 @@ public class Abandon implements RestModifyView<ChangeResource, Input> {
this.json = json; this.json = json;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(ChangeResource req, Input input) public Object apply(ChangeResource req, Input input)
throws BadRequestException, AuthException, throws BadRequestException, AuthException,

View File

@@ -41,11 +41,6 @@ class CreateDraft implements RestModifyView<RevisionResource, Input> {
this.db = db; this.db = db;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Response<GetDraft.Comment> apply(RevisionResource rsrc, Input in) public Response<GetDraft.Comment> apply(RevisionResource rsrc, Input in)
throws AuthException, BadRequestException, ResourceConflictException, OrmException { throws AuthException, BadRequestException, ResourceConflictException, OrmException {

View File

@@ -35,11 +35,6 @@ class DeleteDraft implements RestModifyView<DraftResource, Input> {
this.db = db; this.db = db;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(DraftResource rsrc, Input input) throws OrmException { public Object apply(DraftResource rsrc, Input input) throws OrmException {
db.get().patchComments().delete(Collections.singleton(rsrc.getComment())); db.get().patchComments().delete(Collections.singleton(rsrc.getComment()));

View File

@@ -43,11 +43,6 @@ class DeleteReviewer implements RestModifyView<ReviewerResource, Input> {
this.dbProvider = dbProvider; this.dbProvider = dbProvider;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(ReviewerResource rsrc, Input input) public Object apply(ReviewerResource rsrc, Input input)
throws AuthException, ResourceNotFoundException, OrmException { throws AuthException, ResourceNotFoundException, OrmException {

View File

@@ -125,11 +125,6 @@ public class PostReview implements RestModifyView<RevisionResource, Input> {
this.hooks = hooks; this.hooks = hooks;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(RevisionResource revision, Input input) public Object apply(RevisionResource revision, Input input)
throws AuthException, BadRequestException, OrmException { throws AuthException, BadRequestException, OrmException {

View File

@@ -55,11 +55,6 @@ class PutDraft implements RestModifyView<DraftResource, Input> {
this.delete = delete; this.delete = delete;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(DraftResource rsrc, Input in) throws AuthException, public Object apply(DraftResource rsrc, Input in) throws AuthException,
BadRequestException, ResourceConflictException, OrmException { BadRequestException, ResourceConflictException, OrmException {

View File

@@ -48,11 +48,6 @@ class PutTopic implements RestModifyView<ChangeResource, Input> {
this.dbProvider = dbProvider; this.dbProvider = dbProvider;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(ChangeResource req, Input input) public Object apply(ChangeResource req, Input input)
throws BadRequestException, AuthException, throws BadRequestException, AuthException,

View File

@@ -65,11 +65,6 @@ public class Restore implements RestModifyView<ChangeResource, Input> {
this.json = json; this.json = json;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(ChangeResource req, Input input) public Object apply(ChangeResource req, Input input)
throws Exception { throws Exception {

View File

@@ -83,11 +83,6 @@ public class Revert implements RestModifyView<ChangeResource, Input> {
this.canonicalWebUrl = canonicalWebUrl; this.canonicalWebUrl = canonicalWebUrl;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(ChangeResource req, Input input) throws Exception { public Object apply(ChangeResource req, Input input) throws Exception {
ChangeControl control = req.getControl(); ChangeControl control = req.getControl();

View File

@@ -82,11 +82,6 @@ public class Submit implements RestModifyView<RevisionResource, Input> {
this.mergeQueue = mergeQueue; this.mergeQueue = mergeQueue;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Output apply(RevisionResource rsrc, Input input) throws AuthException, public Output apply(RevisionResource rsrc, Input input) throws AuthException,
ResourceConflictException, RepositoryNotFoundException, IOException, ResourceConflictException, RepositoryNotFoundException, IOException,
@@ -305,11 +300,6 @@ public class Submit implements RestModifyView<RevisionResource, Input> {
this.json = json; this.json = json;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(ChangeResource rsrc, Input input) throws AuthException, public Object apply(ChangeResource rsrc, Input input) throws AuthException,
ResourceConflictException, RepositoryNotFoundException, IOException, ResourceConflictException, RepositoryNotFoundException, IOException,

View File

@@ -95,11 +95,6 @@ class AddMembers implements RestModifyView<GroupResource, Input> {
this.self = self; this.self = self;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public List<MemberInfo> apply(GroupResource resource, Input input) public List<MemberInfo> apply(GroupResource resource, Input input)
throws AuthException, MethodNotAllowedException, BadRequestException, throws AuthException, MethodNotAllowedException, BadRequestException,
@@ -201,11 +196,6 @@ class AddMembers implements RestModifyView<GroupResource, Input> {
this.id = id; this.id = id;
} }
@Override
public Class<PutMember.Input> inputType() {
return PutMember.Input.class;
}
@Override @Override
public Object apply(GroupResource resource, PutMember.Input input) public Object apply(GroupResource resource, PutMember.Input input)
throws AuthException, MethodNotAllowedException, BadRequestException, throws AuthException, MethodNotAllowedException, BadRequestException,
@@ -232,11 +222,6 @@ class AddMembers implements RestModifyView<GroupResource, Input> {
this.get = get; this.get = get;
} }
@Override
public Class<PutMember.Input> inputType() {
return PutMember.Input.class;
}
@Override @Override
public Object apply(MemberResource resource, PutMember.Input input) { public Object apply(MemberResource resource, PutMember.Input input) {
// Do nothing, the user is already a member. // Do nothing, the user is already a member.

View File

@@ -56,11 +56,6 @@ public class CreateGroup implements RestModifyView<TopLevelResource, Input> {
this.visibleToAll = cfg.getBoolean("groups", "newGroupsVisibleToAll", false); this.visibleToAll = cfg.getBoolean("groups", "newGroupsVisibleToAll", false);
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public GroupInfo apply(TopLevelResource resource, Input input) public GroupInfo apply(TopLevelResource resource, Input input)
throws AuthException, BadRequestException, OrmException, throws AuthException, BadRequestException, OrmException,

View File

@@ -61,11 +61,6 @@ public class DeleteMembers implements RestModifyView<GroupResource, Input> {
this.self = self; this.self = self;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(GroupResource resource, Input input) public Object apply(GroupResource resource, Input input)
throws AuthException, MethodNotAllowedException, BadRequestException, throws AuthException, MethodNotAllowedException, BadRequestException,
@@ -159,11 +154,6 @@ public class DeleteMembers implements RestModifyView<GroupResource, Input> {
this.delete = delete; this.delete = delete;
} }
@Override
public Class<Input> inputType() {
return DeleteMember.Input.class;
}
@Override @Override
public Object apply(MemberResource resource, Input input) public Object apply(MemberResource resource, Input input)
throws AuthException, MethodNotAllowedException, BadRequestException, throws AuthException, MethodNotAllowedException, BadRequestException,

View File

@@ -33,11 +33,6 @@ class DisablePlugin implements RestModifyView<PluginResource, Input> {
this.loader = loader; this.loader = loader;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(PluginResource resource, Input input) { public Object apply(PluginResource resource, Input input) {
String name = resource.getName(); String name = resource.getName();

View File

@@ -37,11 +37,6 @@ class EnablePlugin implements RestModifyView<PluginResource, Input> {
this.loader = loader; this.loader = loader;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(PluginResource resource, Input input) public Object apply(PluginResource resource, Input input)
throws ResourceConflictException { throws ResourceConflictException {

View File

@@ -51,11 +51,6 @@ class InstallPlugin implements RestModifyView<TopLevelResource, Input> {
this.created = created; this.created = created;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Response<ListPlugins.PluginInfo> apply(TopLevelResource resource, public Response<ListPlugins.PluginInfo> apply(TopLevelResource resource,
Input input) throws BadRequestException, IOException { Input input) throws BadRequestException, IOException {
@@ -105,11 +100,6 @@ class InstallPlugin implements RestModifyView<TopLevelResource, Input> {
this.loader = loader; this.loader = loader;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Response<ListPlugins.PluginInfo> apply(PluginResource resource, public Response<ListPlugins.PluginInfo> apply(PluginResource resource,
Input input) throws BadRequestException, IOException { Input input) throws BadRequestException, IOException {

View File

@@ -37,11 +37,6 @@ class ReloadPlugin implements RestModifyView<PluginResource, Input> {
this.loader = loader; this.loader = loader;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(PluginResource resource, Input input) throws ResourceConflictException { public Object apply(PluginResource resource, Input input) throws ResourceConflictException {
String name = resource.getName(); String name = resource.getName();

View File

@@ -35,11 +35,6 @@ class DeleteDashboard implements RestModifyView<DashboardResource, Input> {
this.defaultSetter = defaultSetter; this.defaultSetter = defaultSetter;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(DashboardResource resource, Input input) public Object apply(DashboardResource resource, Input input)
throws AuthException, BadRequestException, ResourceConflictException, throws AuthException, BadRequestException, ResourceConflictException,

View File

@@ -38,11 +38,6 @@ class SetDashboard implements RestModifyView<DashboardResource, Input> {
this.defaultSetter = defaultSetter; this.defaultSetter = defaultSetter;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(DashboardResource resource, Input input) public Object apply(DashboardResource resource, Input input)
throws AuthException, BadRequestException, ResourceConflictException, throws AuthException, BadRequestException, ResourceConflictException,

View File

@@ -55,11 +55,6 @@ class SetDefaultDashboard implements RestModifyView<DashboardResource, Input> {
this.get = get; this.get = get;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(DashboardResource resource, Input input) public Object apply(DashboardResource resource, Input input)
throws AuthException, BadRequestException, ResourceConflictException, throws AuthException, BadRequestException, ResourceConflictException,
@@ -137,11 +132,6 @@ class SetDefaultDashboard implements RestModifyView<DashboardResource, Input> {
this.setDefault = setDefault; this.setDefault = setDefault;
} }
@Override
public Class<Input> inputType() {
return SetDashboard.Input.class;
}
@Override @Override
public Object apply(ProjectResource resource, Input input) public Object apply(ProjectResource resource, Input input)
throws AuthException, BadRequestException, ResourceConflictException, throws AuthException, BadRequestException, ResourceConflictException,

View File

@@ -53,11 +53,6 @@ class SetDescription implements RestModifyView<ProjectResource, Input> {
this.gitMgr = gitMgr; this.gitMgr = gitMgr;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(ProjectResource resource, Input input) public Object apply(ProjectResource resource, Input input)
throws AuthException, BadRequestException, ResourceConflictException, throws AuthException, BadRequestException, ResourceConflictException,

View File

@@ -53,11 +53,6 @@ class SetParent implements RestModifyView<ProjectResource, Input> {
this.allProjects = allProjects; this.allProjects = allProjects;
} }
@Override
public Class<Input> inputType() {
return Input.class;
}
@Override @Override
public Object apply(ProjectResource resource, Input input) public Object apply(ProjectResource resource, Input input)
throws AuthException, BadRequestException, ResourceConflictException, throws AuthException, BadRequestException, ResourceConflictException,