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 3385bf2985..05e1698b9d 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 @@ -15,6 +15,7 @@ package com.google.gerrit.httpd.restapi; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS; import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS; import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS; @@ -115,6 +116,7 @@ import com.google.gson.stream.MalformedJsonException; import com.google.gwtexpui.server.CacheHeaders; import com.google.inject.Inject; import com.google.inject.Provider; +import com.google.inject.TypeLiteral; import com.google.inject.util.Providers; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -638,40 +640,21 @@ public class RestApiServlet extends HttpServlet { } 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; - } + // MyModifyView implements RestModifyView + TypeLiteral typeLiteral = TypeLiteral.get(m.getClass()); - @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]; - } - } + // RestModifyView + // This is smart enough to resolve even when there are intervening subclasses, even if they have + // reordered type arguments. + TypeLiteral supertypeLiteral = typeLiteral.getSupertype(RestModifyView.class); - 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; + Type supertype = supertypeLiteral.getType(); + checkState( + supertype instanceof ParameterizedType, + "supertype of %s is not parameterized: %s", + typeLiteral, + supertypeLiteral); + return ((ParameterizedType) supertype).getActualTypeArguments()[1]; } private Object parseRequest(HttpServletRequest req, Type type) 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 b1e351b83c..5d9f7b996a 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 @@ -19,7 +19,6 @@ import com.google.gerrit.common.TimeUtil; import com.google.gerrit.extensions.restapi.DefaultInput; import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestApiException; -import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.extensions.webui.UiAction; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.ChangeMessage; @@ -34,6 +33,8 @@ import com.google.gerrit.server.update.BatchUpdate; import com.google.gerrit.server.update.BatchUpdateOp; import com.google.gerrit.server.update.ChangeContext; import com.google.gerrit.server.update.Context; +import com.google.gerrit.server.update.RetryHelper; +import com.google.gerrit.server.update.RetryingRestModifyView; import com.google.gerrit.server.update.UpdateException; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; @@ -41,10 +42,10 @@ import com.google.inject.Provider; import com.google.inject.Singleton; @Singleton -public class PutTopic implements RestModifyView, UiAction { +public class PutTopic extends RetryingRestModifyView> + implements UiAction { private final Provider dbProvider; private final ChangeMessagesUtil cmUtil; - private final BatchUpdate.Factory batchUpdateFactory; private final TopicEdited topicEdited; public static class Input { @@ -55,29 +56,27 @@ public class PutTopic implements RestModifyView, UiAction PutTopic( Provider dbProvider, ChangeMessagesUtil cmUtil, - BatchUpdate.Factory batchUpdateFactory, + RetryHelper retryHelper, TopicEdited topicEdited) { + super(retryHelper); this.dbProvider = dbProvider; this.cmUtil = cmUtil; - this.batchUpdateFactory = batchUpdateFactory; this.topicEdited = topicEdited; } @Override - public Response apply(ChangeResource req, Input input) + protected Response applyImpl( + BatchUpdate.Factory updateFactory, ChangeResource req, Input input) throws UpdateException, RestApiException, PermissionBackendException { req.permissions().check(ChangePermission.EDIT_TOPIC_NAME); - Op op = new Op(input != null ? input : new Input()); try (BatchUpdate u = - batchUpdateFactory.create( + updateFactory.create( dbProvider.get(), req.getChange().getProject(), req.getUser(), TimeUtil.nowTs())) { u.addOp(req.getId(), op); u.execute(); } - return Strings.isNullOrEmpty(op.newTopicName) - ? Response.none() - : Response.ok(op.newTopicName); + return Strings.isNullOrEmpty(op.newTopicName) ? Response.none() : Response.ok(op.newTopicName); } private class Op implements BatchUpdateOp { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/update/RetryingRestModifyView.java b/gerrit-server/src/main/java/com/google/gerrit/server/update/RetryingRestModifyView.java new file mode 100644 index 0000000000..e2f4a020b0 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/update/RetryingRestModifyView.java @@ -0,0 +1,35 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed 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 com.google.gerrit.server.update; + +import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.extensions.restapi.RestResource; + +public abstract class RetryingRestModifyView + implements RestModifyView { + private final RetryHelper retryHelper; + + protected RetryingRestModifyView(RetryHelper retryHelper) { + this.retryHelper = retryHelper; + } + + @Override + public final O apply(R resource, I input) throws Exception { + return retryHelper.execute((updateFactory) -> applyImpl(updateFactory, resource, input)); + } + + protected abstract O applyImpl(BatchUpdate.Factory updateFactory, R resource, I input) + throws Exception; +}