protected final void service()

in java/com/google/gerrit/httpd/restapi/RestApiServlet.java [346:806]


  protected final void service(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException {
    final long startNanos = System.nanoTime();
    long auditStartTs = TimeUtil.nowMs();
    res.setHeader("Content-Disposition", "attachment");
    res.setHeader("X-Content-Type-Options", "nosniff");
    int statusCode = SC_OK;
    long responseBytes = -1;
    Optional<Exception> cause = Optional.empty();
    Response<?> response = null;
    QueryParams qp = null;
    Object inputRequestBody = null;
    RestResource rsrc = TopLevelResource.INSTANCE;
    ViewData viewData = null;

    try (TraceContext traceContext = enableTracing(req, res)) {
      String requestUri = requestUri(req);

      try (PerThreadCache ignored = PerThreadCache.create()) {
        List<IdString> path = splitPath(req);
        RequestInfo requestInfo = createRequestInfo(traceContext, requestUri, path);
        globals.requestListeners.runEach(l -> l.onRequest(requestInfo));

        // It's important that the PerformanceLogContext is closed before the response is sent to
        // the client. Only this way it is ensured that the invocation of the PerformanceLogger
        // plugins happens before the client sees the response. This is needed for being able to
        // test performance logging from an acceptance test (see
        // TraceIT#performanceLoggingForRestCall()).
        try (RequestStateContext requestStateContext =
                RequestStateContext.open()
                    .addRequestStateProvider(
                        globals.deadlineCheckerFactory.create(
                            requestInfo, req.getHeader(X_GERRIT_DEADLINE)));
            PerformanceLogContext performanceLogContext =
                new PerformanceLogContext(globals.config, globals.performanceLoggers)) {
          traceRequestData(req);

          if (isCorsPreflight(req)) {
            doCorsPreflight(req, res);
            return;
          }

          qp = ParameterParser.getQueryParams(req);
          checkCors(req, res, qp.hasXdOverride());
          if (qp.hasXdOverride()) {
            req = applyXdOverrides(req, qp);
          }
          checkUserSession(req);

          RestCollection<RestResource, RestResource> rc = members.get();
          globals
              .permissionBackend
              .currentUser()
              .checkAny(GlobalPermission.fromAnnotation(rc.getClass()));

          viewData = new ViewData(null, null);

          if (path.isEmpty()) {
            globals.quotaChecker.enforce(req);
            if (rc instanceof NeedsParams) {
              ((NeedsParams) rc).setParams(qp.params());
            }

            if (isRead(req)) {
              viewData = new ViewData(null, rc.list());
            } else if (isPost(req)) {
              RestView<RestResource> restCollectionView =
                  rc.views().get(PluginName.GERRIT, "POST_ON_COLLECTION./");
              if (restCollectionView != null) {
                viewData = new ViewData(null, restCollectionView);
              } else {
                throw methodNotAllowed(req);
              }
            } else {
              // DELETE on root collections is not supported
              throw methodNotAllowed(req);
            }
          } else {
            IdString id = path.remove(0);
            try {
              rsrc = parseResourceWithRetry(req, traceContext, viewData.pluginName, rc, rsrc, id);
              globals.quotaChecker.enforce(rsrc, req);
              if (path.isEmpty()) {
                checkPreconditions(req);
              }
            } catch (ResourceNotFoundException e) {
              if (!path.isEmpty()) {
                throw e;
              }
              globals.quotaChecker.enforce(req);

              if (isPost(req) || isPut(req)) {
                RestView<RestResource> createView = rc.views().get(PluginName.GERRIT, "CREATE./");
                if (createView != null) {
                  viewData = new ViewData(null, createView);
                  path.add(id);
                } else {
                  throw e;
                }
              } else if (isDelete(req)) {
                RestView<RestResource> deleteView =
                    rc.views().get(PluginName.GERRIT, "DELETE_MISSING./");
                if (deleteView != null) {
                  viewData = new ViewData(null, deleteView);
                  path.add(id);
                } else {
                  throw e;
                }
              } else {
                throw e;
              }
            }
            if (viewData.view == null) {
              viewData = view(rc, req.getMethod(), path);
            }
          }
          checkRequiresCapability(viewData);

          while (viewData.view instanceof RestCollection<?, ?>) {
            @SuppressWarnings("unchecked")
            RestCollection<RestResource, RestResource> c =
                (RestCollection<RestResource, RestResource>) viewData.view;

            if (path.isEmpty()) {
              if (isRead(req)) {
                viewData = new ViewData(null, c.list());
              } else if (isPost(req)) {
                // TODO: Here and on other collection methods: There is a bug that binds child views
                // with pluginName="gerrit" instead of the real plugin name. This has never worked
                // correctly and should be fixed where the binding gets created (DynamicMapProvider)
                // and here.
                RestView<RestResource> restCollectionView =
                    c.views().get(PluginName.GERRIT, "POST_ON_COLLECTION./");
                if (restCollectionView != null) {
                  viewData = new ViewData(null, restCollectionView);
                } else {
                  throw methodNotAllowed(req);
                }
              } else if (isDelete(req)) {
                RestView<RestResource> restCollectionView =
                    c.views().get(PluginName.GERRIT, "DELETE_ON_COLLECTION./");
                if (restCollectionView != null) {
                  viewData = new ViewData(null, restCollectionView);
                } else {
                  throw methodNotAllowed(req);
                }
              } else {
                throw methodNotAllowed(req);
              }
              break;
            }
            IdString id = path.remove(0);
            try {
              rsrc = parseResourceWithRetry(req, traceContext, viewData.pluginName, c, rsrc, id);
              checkPreconditions(req);
              viewData = new ViewData(null, null);
            } catch (ResourceNotFoundException e) {
              if (!path.isEmpty()) {
                throw e;
              }

              if (isPost(req) || isPut(req)) {
                RestView<RestResource> createView = c.views().get(PluginName.GERRIT, "CREATE./");
                if (createView != null) {
                  viewData = new ViewData(viewData.pluginName, createView);
                  path.add(id);
                } else {
                  throw e;
                }
              } else if (isDelete(req)) {
                RestView<RestResource> deleteView =
                    c.views().get(PluginName.GERRIT, "DELETE_MISSING./");
                if (deleteView != null) {
                  viewData = new ViewData(viewData.pluginName, deleteView);
                  path.add(id);
                } else {
                  throw e;
                }
              } else {
                throw e;
              }
            }
            if (viewData.view == null) {
              viewData = view(c, req.getMethod(), path);
            }
            checkRequiresCapability(viewData);
          }

          if (notModified(req, traceContext, viewData, rsrc)) {
            logger.atFinest().log("REST call succeeded: %d", SC_NOT_MODIFIED);
            res.sendError(SC_NOT_MODIFIED);
            return;
          }

          try (DynamicOptions pluginOptions =
              new DynamicOptions(globals.injector, globals.dynamicBeans)) {
            if (!globals
                .paramParser
                .get()
                .parse(viewData.view, pluginOptions, qp.params(), req, res)) {
              return;
            }

            if (viewData.view instanceof RestReadView<?> && isRead(req)) {
              response =
                  invokeRestReadViewWithRetry(
                      req,
                      traceContext,
                      viewData,
                      (RestReadView<RestResource>) viewData.view,
                      rsrc);
            } else if (viewData.view instanceof RestModifyView<?, ?>) {
              @SuppressWarnings("unchecked")
              RestModifyView<RestResource, Object> m =
                  (RestModifyView<RestResource, Object>) viewData.view;

              Type type = inputType(m);
              inputRequestBody = parseRequest(req, type);
              response =
                  invokeRestModifyViewWithRetry(
                      req, traceContext, viewData, m, rsrc, inputRequestBody);

              if (inputRequestBody instanceof RawInput) {
                try (InputStream is = req.getInputStream()) {
                  ServletUtils.consumeRequestBody(is);
                }
              }
            } else if (viewData.view instanceof RestCollectionCreateView<?, ?, ?>) {
              @SuppressWarnings("unchecked")
              RestCollectionCreateView<RestResource, RestResource, Object> m =
                  (RestCollectionCreateView<RestResource, RestResource, Object>) viewData.view;

              Type type = inputType(m);
              inputRequestBody = parseRequest(req, type);
              response =
                  invokeRestCollectionCreateViewWithRetry(
                      req, traceContext, viewData, m, rsrc, path.get(0), inputRequestBody);
              if (inputRequestBody instanceof RawInput) {
                try (InputStream is = req.getInputStream()) {
                  ServletUtils.consumeRequestBody(is);
                }
              }
            } else if (viewData.view instanceof RestCollectionDeleteMissingView<?, ?, ?>) {
              @SuppressWarnings("unchecked")
              RestCollectionDeleteMissingView<RestResource, RestResource, Object> m =
                  (RestCollectionDeleteMissingView<RestResource, RestResource, Object>)
                      viewData.view;

              Type type = inputType(m);
              inputRequestBody = parseRequest(req, type);
              response =
                  invokeRestCollectionDeleteMissingViewWithRetry(
                      req, traceContext, viewData, m, rsrc, path.get(0), inputRequestBody);
              if (inputRequestBody instanceof RawInput) {
                try (InputStream is = req.getInputStream()) {
                  ServletUtils.consumeRequestBody(is);
                }
              }
            } else if (viewData.view instanceof RestCollectionModifyView<?, ?, ?>) {
              @SuppressWarnings("unchecked")
              RestCollectionModifyView<RestResource, RestResource, Object> m =
                  (RestCollectionModifyView<RestResource, RestResource, Object>) viewData.view;

              Type type = inputType(m);
              inputRequestBody = parseRequest(req, type);
              response =
                  invokeRestCollectionModifyViewWithRetry(
                      req, traceContext, viewData, m, rsrc, inputRequestBody);
              if (inputRequestBody instanceof RawInput) {
                try (InputStream is = req.getInputStream()) {
                  ServletUtils.consumeRequestBody(is);
                }
              }
            } else {
              throw new ResourceNotFoundException();
            }
            String isUpdatedRefEnabled = req.getHeader(X_GERRIT_UPDATED_REF_ENABLED);
            if (!Strings.isNullOrEmpty(isUpdatedRefEnabled)
                && Boolean.valueOf(isUpdatedRefEnabled)) {
              setXGerritUpdatedRefResponseHeaders(req, res);
            }

            if (response instanceof Response.Redirect) {
              CacheHeaders.setNotCacheable(res);
              String location = ((Response.Redirect) response).location();
              res.sendRedirect(location);
              logger.atFinest().log("REST call redirected to: %s", location);
              return;
            } else if (response instanceof Response.Accepted) {
              CacheHeaders.setNotCacheable(res);
              res.setStatus(response.statusCode());
              res.setHeader(HttpHeaders.LOCATION, ((Response.Accepted) response).location());
              logger.atFinest().log("REST call succeeded: %d", response.statusCode());
              return;
            }

            statusCode = response.statusCode();
            configureCaching(req, res, traceContext, rsrc, viewData, response.caching());
            res.setStatus(statusCode);
            logger.atFinest().log("REST call succeeded: %d", statusCode);
          }

          if (response != Response.none()) {
            Object value = Response.unwrap(response);
            if (value instanceof BinaryResult) {
              responseBytes = replyBinaryResult(req, res, (BinaryResult) value);
            } else {
              responseBytes = replyJson(req, res, false, qp.config(), value);
            }
          }
        }
      } catch (MalformedJsonException | JsonParseException e) {
        cause = Optional.of(e);
        logger.atFine().withCause(e).log("REST call failed on JSON parsing");
        responseBytes =
            replyError(
                req, res, statusCode = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request", e);
      } catch (BadRequestException e) {
        cause = Optional.of(e);
        responseBytes =
            replyError(
                req, res, statusCode = SC_BAD_REQUEST, messageOr(e, "Bad Request"), e.caching(), e);
      } catch (AuthException e) {
        cause = Optional.of(e);
        responseBytes =
            replyError(
                req, res, statusCode = SC_FORBIDDEN, messageOr(e, "Forbidden"), e.caching(), e);
      } catch (AmbiguousViewException e) {
        cause = Optional.of(e);
        responseBytes =
            replyError(req, res, statusCode = SC_NOT_FOUND, messageOr(e, "Ambiguous"), e);
      } catch (ResourceNotFoundException e) {
        cause = Optional.of(e);
        responseBytes =
            replyError(
                req, res, statusCode = SC_NOT_FOUND, messageOr(e, "Not Found"), e.caching(), e);
      } catch (MethodNotAllowedException e) {
        cause = Optional.of(e);
        responseBytes =
            replyError(
                req,
                res,
                statusCode = SC_METHOD_NOT_ALLOWED,
                messageOr(e, "Method Not Allowed"),
                e.caching(),
                e);
      } catch (ResourceConflictException e) {
        cause = Optional.of(e);
        responseBytes =
            replyError(
                req, res, statusCode = SC_CONFLICT, messageOr(e, "Conflict"), e.caching(), e);
      } catch (PreconditionFailedException e) {
        cause = Optional.of(e);
        responseBytes =
            replyError(
                req,
                res,
                statusCode = SC_PRECONDITION_FAILED,
                messageOr(e, "Precondition Failed"),
                e.caching(),
                e);
      } catch (UnprocessableEntityException e) {
        cause = Optional.of(e);
        responseBytes =
            replyError(
                req,
                res,
                statusCode = SC_UNPROCESSABLE_ENTITY,
                messageOr(e, "Unprocessable Entity"),
                e.caching(),
                e);
      } catch (NotImplementedException e) {
        cause = Optional.of(e);
        logger.atSevere().withCause(e).log("Error in %s %s", req.getMethod(), uriForLogging(req));
        responseBytes =
            replyError(
                req, res, statusCode = SC_NOT_IMPLEMENTED, messageOr(e, "Not Implemented"), e);
      } catch (QuotaException e) {
        cause = Optional.of(e);
        responseBytes =
            replyError(
                req,
                res,
                statusCode = SC_TOO_MANY_REQUESTS,
                messageOr(e, "Quota limit reached"),
                e.caching(),
                e);
      } catch (InvalidDeadlineException e) {
        cause = Optional.of(e);
        responseBytes =
            replyError(req, res, statusCode = SC_BAD_REQUEST, messageOr(e, "Bad Request"), e);
      } catch (Exception e) {
        cause = Optional.of(e);

        Optional<RequestCancelledException> requestCancelledException =
            RequestCancelledException.getFromCausalChain(e);
        if (requestCancelledException.isPresent()) {
          RequestStateProvider.Reason cancellationReason =
              requestCancelledException.get().getCancellationReason();
          globals.cancellationMetrics.countCancelledRequest(
              RequestInfo.RequestType.REST, requestUri, cancellationReason);
          statusCode = getCancellationStatusCode(cancellationReason);
          responseBytes =
              replyError(
                  req, res, statusCode, getCancellationMessage(requestCancelledException.get()), e);
        } else {
          statusCode = SC_INTERNAL_SERVER_ERROR;

          Optional<ExceptionHook.Status> status = getStatus(e);
          statusCode =
              status.map(ExceptionHook.Status::statusCode).orElse(SC_INTERNAL_SERVER_ERROR);

          if (res.isCommitted()) {
            responseBytes = 0;
            if (statusCode == SC_INTERNAL_SERVER_ERROR) {
              logger.atSevere().withCause(e).log(
                  "Error in %s %s, response already committed",
                  req.getMethod(), uriForLogging(req));
            } else {
              logger.atWarning().log(
                  "Response for %s %s already committed, wanted to set status %d",
                  req.getMethod(), uriForLogging(req), statusCode);
            }
          } else {
            res.reset();
            TraceContext.getTraceId().ifPresent(traceId -> res.addHeader(X_GERRIT_TRACE, traceId));

            if (status.isPresent()) {
              responseBytes = reply(req, res, e, status.get(), getUserMessages(e));
            } else {
              responseBytes = replyInternalServerError(req, res, e, getUserMessages(e));
            }
          }
        }
      } finally {
        String metric = getViewName(viewData);
        String formattedCause = cause.map(globals.retryHelper::formatCause).orElse("_none");
        globals.metrics.count.increment(metric);
        if (statusCode >= SC_BAD_REQUEST) {
          globals.metrics.errorCount.increment(metric, statusCode, formattedCause);
        }
        if (responseBytes != -1) {
          globals.metrics.responseBytes.record(metric, responseBytes);
        }
        globals.metrics.serverLatency.record(
            metric, System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
        globals.auditService.dispatch(
            new ExtendedHttpAuditEvent(
                globals.webSession.get().getSessionId(),
                globals.currentUser.get(),
                req,
                auditStartTs,
                qp != null ? qp.params() : ImmutableListMultimap.of(),
                inputRequestBody,
                statusCode,
                response,
                rsrc,
                viewData == null ? null : viewData.view));
      }
    }
  }