use of javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST in project gerrit by GerritCodeReview.
the class PostReview method apply.
public Response<ReviewResult> apply(BatchUpdate.Factory updateFactory, RevisionResource revision, ReviewInput input, Timestamp ts) throws RestApiException, UpdateException, OrmException, IOException, PermissionBackendException {
// Respect timestamp, but truncate at change created-on time.
ts = Ordering.natural().max(ts, revision.getChange().getCreatedOn());
if (revision.getEdit().isPresent()) {
throw new ResourceConflictException("cannot post review on edit");
}
if (input.onBehalfOf != null) {
revision = onBehalfOf(revision, input);
} else if (input.drafts == null) {
input.drafts = DraftHandling.DELETE;
}
if (input.labels != null) {
checkLabels(revision, input.strictLabels, input.labels);
}
if (input.comments != null) {
cleanUpComments(input.comments);
checkComments(revision, input.comments);
}
if (input.robotComments != null) {
if (!migration.readChanges()) {
throw new MethodNotAllowedException("robot comments not supported");
}
checkRobotComments(revision, input.robotComments);
}
if (input.notify == null) {
log.warn("notify = null; assuming notify = NONE");
input.notify = NotifyHandling.NONE;
}
ListMultimap<RecipientType, Account.Id> accountsToNotify = notifyUtil.resolveAccounts(input.notifyDetails);
Map<String, AddReviewerResult> reviewerJsonResults = null;
List<PostReviewers.Addition> reviewerResults = Lists.newArrayList();
boolean hasError = false;
boolean confirm = false;
if (input.reviewers != null) {
reviewerJsonResults = Maps.newHashMap();
for (AddReviewerInput reviewerInput : input.reviewers) {
// Prevent notifications because setting reviewers is batched.
reviewerInput.notify = NotifyHandling.NONE;
PostReviewers.Addition result = postReviewers.prepareApplication(revision.getChangeResource(), reviewerInput, true);
reviewerJsonResults.put(reviewerInput.reviewer, result.result);
if (result.result.error != null) {
hasError = true;
continue;
}
if (result.result.confirm != null) {
confirm = true;
continue;
}
reviewerResults.add(result);
}
}
ReviewResult output = new ReviewResult();
output.reviewers = reviewerJsonResults;
if (hasError || confirm) {
return Response.withStatusCode(SC_BAD_REQUEST, output);
}
output.labels = input.labels;
try (BatchUpdate bu = updateFactory.create(db.get(), revision.getChange().getProject(), revision.getUser(), ts)) {
Account.Id id = revision.getUser().getAccountId();
boolean ccOrReviewer = false;
if (input.labels != null && !input.labels.isEmpty()) {
ccOrReviewer = input.labels.values().stream().filter(v -> v != 0).findFirst().isPresent();
}
if (!ccOrReviewer) {
// Check if user was already CCed or reviewing prior to this review.
ReviewerSet currentReviewers = approvalsUtil.getReviewers(db.get(), revision.getChangeResource().getNotes());
ccOrReviewer = currentReviewers.all().contains(id);
}
// themselves as a reviewer or to the CC list.
for (PostReviewers.Addition reviewerResult : reviewerResults) {
bu.addOp(revision.getChange().getId(), reviewerResult.op);
if (!ccOrReviewer && reviewerResult.result.reviewers != null) {
for (ReviewerInfo reviewerInfo : reviewerResult.result.reviewers) {
if (Objects.equals(id.get(), reviewerInfo._accountId)) {
ccOrReviewer = true;
break;
}
}
}
if (!ccOrReviewer && reviewerResult.result.ccs != null) {
for (AccountInfo accountInfo : reviewerResult.result.ccs) {
if (Objects.equals(id.get(), accountInfo._accountId)) {
ccOrReviewer = true;
break;
}
}
}
}
if (!ccOrReviewer) {
// User posting this review isn't currently in the reviewer or CC list,
// isn't being explicitly added, and isn't voting on any label.
// Automatically CC them on this change so they receive replies.
PostReviewers.Addition selfAddition = postReviewers.ccCurrentUser(revision.getUser(), revision);
bu.addOp(revision.getChange().getId(), selfAddition.op);
}
bu.addOp(revision.getChange().getId(), new Op(revision.getPatchSet().getId(), input, accountsToNotify));
bu.execute();
for (PostReviewers.Addition reviewerResult : reviewerResults) {
reviewerResult.gatherResults();
}
emailReviewers(revision.getChange(), reviewerResults, input.notify, accountsToNotify);
}
return Response.ok(output);
}
use of javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST in project cruise-control by linkedin.
the class KafkaCruiseControlServlet method getPartitionLoad.
private boolean getPartitionLoad(HttpServletRequest request, HttpServletResponse response) throws Exception {
Resource resource;
Long startMs;
Long endMs;
boolean json = wantJSON(request);
try {
String resourceString = request.getParameter(RESOURCE_PARAM);
try {
if (resourceString == null) {
resourceString = DEFAULT_PARTITION_LOAD_RESOURCE;
}
resource = Resource.valueOf(resourceString.toUpperCase());
} catch (IllegalArgumentException iae) {
String errorMsg = String.format("Invalid resource type %s. The resource type must be one of the following: " + "CPU, DISK, NW_IN, NW_OUT", resourceString);
StringWriter sw = new StringWriter();
iae.printStackTrace(new PrintWriter(sw));
setErrorResponse(response, sw.toString(), errorMsg, SC_BAD_REQUEST, json);
// Close session
return true;
}
String startMsString = request.getParameter(START_MS_PARAM);
String endMsString = request.getParameter(END_MS_PARAM);
startMs = startMsString == null ? -1L : Long.parseLong(startMsString);
endMs = endMsString == null ? System.currentTimeMillis() : Long.parseLong(endMsString);
} catch (Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
setErrorResponse(response, sw.toString(), e.getMessage(), SC_BAD_REQUEST, json);
// Close session
return true;
}
ModelCompletenessRequirements requirements = new ModelCompletenessRequirements(1, 0.0, false);
// Get cluster model asynchronously.
ClusterModel clusterModel = getAndMaybeReturnProgress(request, response, () -> _asyncKafkaCruiseControl.clusterModel(startMs, endMs, requirements));
if (clusterModel == null) {
return false;
}
List<Partition> sortedPartitions = clusterModel.replicasSortedByUtilization(resource);
OutputStream out = response.getOutputStream();
String entriesString = request.getParameter(ENTRIES);
Integer entries = entriesString == null ? Integer.MAX_VALUE : Integer.parseInt(entriesString);
int numEntries = 0;
if (!json) {
int topicNameLength = clusterModel.topics().stream().mapToInt(String::length).max().orElse(20) + 5;
setResponseCode(response, SC_OK);
out.write(String.format("%" + topicNameLength + "s%10s%30s%20s%20s%20s%20s%n", "PARTITION", "LEADER", "FOLLOWERS", "CPU (%)", "DISK (MB)", "NW_IN (KB/s)", "NW_OUT (KB/s)").getBytes(StandardCharsets.UTF_8));
for (Partition p : sortedPartitions) {
if (++numEntries > entries) {
break;
}
List<Integer> followers = p.followers().stream().map((replica) -> replica.broker().id()).collect(Collectors.toList());
out.write(String.format("%" + topicNameLength + "s%10s%30s%19.6f%19.3f%19.3f%19.3f%n", p.leader().topicPartition(), p.leader().broker().id(), followers, p.leader().load().expectedUtilizationFor(Resource.CPU), p.leader().load().expectedUtilizationFor(Resource.DISK), p.leader().load().expectedUtilizationFor(Resource.NW_IN), p.leader().load().expectedUtilizationFor(Resource.NW_OUT)).getBytes(StandardCharsets.UTF_8));
}
} else {
Map<String, Object> partitionMap = new HashMap<>();
List<Object> partitionList = new ArrayList<>();
List<String> header = new ArrayList<>(Arrays.asList("topic", "partition", "leader", "followers", "CPU", "DISK", "NW_IN", "NW_OUT"));
partitionMap.put("version", JSON_VERSION);
partitionMap.put("header", header);
for (Partition p : sortedPartitions) {
if (++numEntries > entries) {
break;
}
List<Integer> followers = p.followers().stream().map((replica) -> replica.broker().id()).collect(Collectors.toList());
List<Object> record = new ArrayList<>();
record.add(p.leader().topicPartition().topic());
record.add(p.leader().topicPartition().partition());
record.add(p.leader().broker().id());
record.add(followers);
record.add(p.leader().load().expectedUtilizationFor(Resource.CPU));
record.add(p.leader().load().expectedUtilizationFor(Resource.DISK));
record.add(p.leader().load().expectedUtilizationFor(Resource.NW_IN));
record.add(p.leader().load().expectedUtilizationFor(Resource.NW_OUT));
partitionList.add(record);
}
partitionMap.put("records", partitionList);
Gson gson = new Gson();
String g = gson.toJson(partitionMap);
setJSONResponseCode(response, SC_OK);
response.setContentLength(g.length());
out.write(g.getBytes(StandardCharsets.UTF_8));
}
out.flush();
return true;
}
use of javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST in project gerrit by GerritCodeReview.
the class RestApiServlet method service.
@Override
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));
// 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));
}
}
}
use of javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST in project gerrit by GerritCodeReview.
the class PostReview method apply.
public Response<ReviewResult> apply(RevisionResource revision, ReviewInput input, Instant ts) throws RestApiException, UpdateException, IOException, PermissionBackendException, ConfigInvalidException, PatchListNotAvailableException {
// Respect timestamp, but truncate at change created-on time.
ts = Ordering.natural().max(ts, revision.getChange().getCreatedOn());
if (revision.getEdit().isPresent()) {
throw new ResourceConflictException("cannot post review on edit");
}
ProjectState projectState = projectCache.get(revision.getProject()).orElseThrow(illegalState(revision.getProject()));
LabelTypes labelTypes = projectState.getLabelTypes(revision.getNotes());
logger.atFine().log("strict label checking is %s", (strictLabels ? "enabled" : "disabled"));
metrics.draftHandling.increment(input.drafts == null ? "N/A" : input.drafts.name());
input.drafts = firstNonNull(input.drafts, DraftHandling.KEEP);
logger.atFine().log("draft handling = %s", input.drafts);
if (input.onBehalfOf != null) {
revision = onBehalfOf(revision, labelTypes, input);
}
if (input.labels != null) {
checkLabels(revision, labelTypes, input.labels);
}
if (input.comments != null) {
input.comments = cleanUpComments(input.comments);
checkComments(revision, input.comments);
}
if (input.draftIdsToPublish != null) {
checkDraftIds(revision, input.draftIdsToPublish, input.drafts);
}
if (input.robotComments != null) {
input.robotComments = cleanUpComments(input.robotComments);
checkRobotComments(revision, input.robotComments);
}
if (input.notify == null) {
input.notify = defaultNotify(revision.getChange(), input);
}
logger.atFine().log("notify handling = %s", input.notify);
Map<String, ReviewerResult> reviewerJsonResults = null;
List<ReviewerModification> reviewerResults = Lists.newArrayList();
boolean hasError = false;
boolean confirm = false;
if (input.reviewers != null) {
reviewerJsonResults = Maps.newHashMap();
for (ReviewerInput reviewerInput : input.reviewers) {
ReviewerModification result = reviewerModifier.prepare(revision.getNotes(), revision.getUser(), reviewerInput, true);
reviewerJsonResults.put(reviewerInput.reviewer, result.result);
if (result.result.error != null) {
logger.atFine().log("Adding %s as reviewer failed: %s", reviewerInput.reviewer, result.result.error);
hasError = true;
continue;
}
if (result.result.confirm != null) {
logger.atFine().log("Adding %s as reviewer requires confirmation", reviewerInput.reviewer);
confirm = true;
continue;
}
logger.atFine().log("Adding %s as reviewer was prepared", reviewerInput.reviewer);
reviewerResults.add(result);
}
}
ReviewResult output = new ReviewResult();
output.reviewers = reviewerJsonResults;
if (hasError || confirm) {
output.error = ERROR_ADDING_REVIEWER;
return Response.withStatusCode(SC_BAD_REQUEST, output);
}
output.labels = input.labels;
try (BatchUpdate bu = updateFactory.create(revision.getChange().getProject(), revision.getUser(), ts)) {
Account account = revision.getUser().asIdentifiedUser().getAccount();
boolean ccOrReviewer = false;
if (input.labels != null && !input.labels.isEmpty()) {
ccOrReviewer = input.labels.values().stream().anyMatch(v -> v != 0);
if (ccOrReviewer) {
logger.atFine().log("calling user is cc/reviewer on the change due to voting on a label");
}
}
if (!ccOrReviewer) {
// Check if user was already CCed or reviewing prior to this review.
ReviewerSet currentReviewers = approvalsUtil.getReviewers(revision.getChangeResource().getNotes());
ccOrReviewer = currentReviewers.all().contains(account.id());
if (ccOrReviewer) {
logger.atFine().log("calling user is already cc/reviewer on the change");
}
}
// Apply reviewer changes first. Revision emails should be sent to the
// updated set of reviewers. Also keep track of whether the user added
// themselves as a reviewer or to the CC list.
logger.atFine().log("adding reviewer additions");
for (ReviewerModification reviewerResult : reviewerResults) {
// Send a single batch email below.
reviewerResult.op.suppressEmail();
// Send events below, if possible as batch.
reviewerResult.op.suppressEvent();
bu.addOp(revision.getChange().getId(), reviewerResult.op);
if (!ccOrReviewer && reviewerResult.reviewers.contains(account)) {
logger.atFine().log("calling user is explicitly added as reviewer or CC");
ccOrReviewer = true;
}
}
if (!ccOrReviewer) {
// User posting this review isn't currently in the reviewer or CC list,
// isn't being explicitly added, and isn't voting on any label.
// Automatically CC them on this change so they receive replies.
logger.atFine().log("CCing calling user");
ReviewerModification selfAddition = reviewerModifier.ccCurrentUser(revision.getUser(), revision);
selfAddition.op.suppressEmail();
selfAddition.op.suppressEvent();
bu.addOp(revision.getChange().getId(), selfAddition.op);
}
// Add WorkInProgressOp if requested.
if ((input.ready || input.workInProgress) && didWorkInProgressChange(revision.getChange().isWorkInProgress(), input)) {
if (input.ready && input.workInProgress) {
output.error = ERROR_WIP_READY_MUTUALLY_EXCLUSIVE;
return Response.withStatusCode(SC_BAD_REQUEST, output);
}
revision.getChangeResource().permissions().check(ChangePermission.TOGGLE_WORK_IN_PROGRESS_STATE);
if (input.ready) {
output.ready = true;
}
logger.atFine().log("setting work-in-progress to %s", input.workInProgress);
WorkInProgressOp wipOp = workInProgressOpFactory.create(input.workInProgress, new WorkInProgressOp.Input());
wipOp.suppressEmail();
bu.addOp(revision.getChange().getId(), wipOp);
}
// Add the review op.
logger.atFine().log("posting review");
bu.addOp(revision.getChange().getId(), new Op(projectState, revision.getPatchSet().id(), input));
// Notify based on ReviewInput, ignoring the notify settings from any ReviewerInputs.
NotifyResolver.Result notify = notifyResolver.resolve(input.notify, input.notifyDetails);
bu.setNotify(notify);
// Adjust the attention set based on the input
replyAttentionSetUpdates.updateAttentionSet(bu, revision.getNotes(), input, revision.getUser());
bu.execute();
// Re-read change to take into account results of the update.
ChangeData cd = changeDataFactory.create(revision.getProject(), revision.getChange().getId());
for (ReviewerModification reviewerResult : reviewerResults) {
reviewerResult.gatherResults(cd);
}
// Sending emails and events from ReviewersOps was suppressed so we can send a single batch
// email/event here.
batchEmailReviewers(revision.getUser(), revision.getChange(), reviewerResults, notify);
batchReviewerEvents(revision.getUser(), cd, revision.getPatchSet(), reviewerResults, ts);
}
return Response.ok(output);
}
Aggregations