1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.server.internal.api;
18
19 import static com.google.common.base.MoreObjects.firstNonNull;
20 import static com.linecorp.centraldogma.server.internal.api.HttpApiUtil.newResponse;
21
22 import java.util.Map;
23
24 import com.google.common.collect.ImmutableMap;
25
26 import com.linecorp.armeria.common.HttpRequest;
27 import com.linecorp.armeria.common.HttpResponse;
28 import com.linecorp.armeria.common.HttpStatus;
29 import com.linecorp.armeria.common.util.Exceptions;
30 import com.linecorp.armeria.server.HttpResponseException;
31 import com.linecorp.armeria.server.HttpStatusException;
32 import com.linecorp.armeria.server.ServiceRequestContext;
33 import com.linecorp.armeria.server.annotation.ExceptionHandlerFunction;
34 import com.linecorp.centraldogma.common.ChangeConflictException;
35 import com.linecorp.centraldogma.common.EntryNoContentException;
36 import com.linecorp.centraldogma.common.EntryNotFoundException;
37 import com.linecorp.centraldogma.common.InvalidPushException;
38 import com.linecorp.centraldogma.common.ProjectExistsException;
39 import com.linecorp.centraldogma.common.ProjectNotFoundException;
40 import com.linecorp.centraldogma.common.QueryExecutionException;
41 import com.linecorp.centraldogma.common.ReadOnlyException;
42 import com.linecorp.centraldogma.common.RedundantChangeException;
43 import com.linecorp.centraldogma.common.RepositoryExistsException;
44 import com.linecorp.centraldogma.common.RepositoryNotFoundException;
45 import com.linecorp.centraldogma.common.RevisionNotFoundException;
46 import com.linecorp.centraldogma.common.TooManyRequestsException;
47 import com.linecorp.centraldogma.server.internal.admin.service.TokenNotFoundException;
48 import com.linecorp.centraldogma.server.internal.storage.RequestAlreadyTimedOutException;
49 import com.linecorp.centraldogma.server.internal.storage.repository.RepositoryMetadataException;
50
51
52
53
54 public final class HttpApiExceptionHandler implements ExceptionHandlerFunction {
55
56
57
58
59 private static final Map<Class<?>, ExceptionHandlerFunction> exceptionHandlers;
60
61 static {
62 final ImmutableMap.Builder<Class<?>,
63 ExceptionHandlerFunction> builder = ImmutableMap.builder();
64
65 builder.put(ChangeConflictException.class,
66 (ctx, req, cause) -> newResponse(ctx, HttpStatus.CONFLICT, cause,
67 "The given changeset or revision has a conflict."))
68 .put(EntryNotFoundException.class,
69 (ctx, req, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause,
70 "Entry '%s' does not exist.", cause.getMessage()))
71 .put(EntryNoContentException.class,
72 (ctx, req, cause) -> HttpResponse.of(HttpStatus.NO_CONTENT))
73 .put(ProjectExistsException.class,
74 (ctx, req, cause) -> newResponse(ctx, HttpStatus.CONFLICT, cause,
75 "Project '%s' exists already.", cause.getMessage()))
76 .put(ProjectNotFoundException.class,
77 (ctx, req, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause,
78 "Project '%s' does not exist.", cause.getMessage()))
79 .put(RedundantChangeException.class,
80 (ctx, req, cause) -> newResponse(ctx, HttpStatus.CONFLICT, cause,
81 "The given changeset does not change anything."))
82 .put(RepositoryExistsException.class,
83 (ctx, req, cause) -> newResponse(ctx, HttpStatus.CONFLICT, cause,
84 "Repository '%s' exists already.", cause.getMessage()))
85 .put(RepositoryMetadataException.class,
86 (ctx, req, cause) -> newResponse(ctx, HttpStatus.INTERNAL_SERVER_ERROR, cause))
87 .put(RepositoryNotFoundException.class,
88 (ctx, req, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause,
89 "Repository '%s' does not exist.", cause.getMessage()))
90 .put(RevisionNotFoundException.class,
91 (ctx, req, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause,
92 "Revision %s does not exist.", cause.getMessage()))
93 .put(TokenNotFoundException.class,
94 (ctx, req, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause,
95 "Token '%s' does not exist.", cause.getMessage()))
96 .put(QueryExecutionException.class,
97 (ctx, req, cause) -> newResponse(ctx, HttpStatus.BAD_REQUEST, cause))
98 .put(UnsupportedOperationException.class,
99 (ctx, req, cause) -> newResponse(ctx, HttpStatus.BAD_REQUEST, cause))
100 .put(TooManyRequestsException.class,
101 (ctx, req, cause) -> {
102 final TooManyRequestsException cast = (TooManyRequestsException) cause;
103 final Object type = firstNonNull(cast.type(), "requests");
104 return newResponse(ctx, HttpStatus.TOO_MANY_REQUESTS, cast,
105 "Too many %s are sent to %s", type, cause.getMessage());
106 })
107 .put(InvalidPushException.class,
108 (ctx, req, cause) -> newResponse(ctx, HttpStatus.BAD_REQUEST, cause))
109 .put(ReadOnlyException.class,
110 (ctx, req, cause) -> newResponse(ctx, HttpStatus.SERVICE_UNAVAILABLE, cause));
111
112 exceptionHandlers = builder.build();
113 }
114
115 @Override
116 public HttpResponse handleException(ServiceRequestContext ctx, HttpRequest req, Throwable cause) {
117 final Throwable peeledCause = Exceptions.peel(cause);
118
119 if (peeledCause instanceof HttpStatusException ||
120 peeledCause instanceof HttpResponseException) {
121 return ExceptionHandlerFunction.fallthrough();
122 }
123
124
125 final ExceptionHandlerFunction func = exceptionHandlers.get(peeledCause.getClass());
126 if (func != null) {
127 return func.handleException(ctx, req, peeledCause);
128 }
129
130 if (peeledCause instanceof IllegalArgumentException) {
131 return newResponse(ctx, HttpStatus.BAD_REQUEST, peeledCause);
132 }
133
134 if (peeledCause instanceof RequestAlreadyTimedOutException) {
135 return newResponse(ctx, HttpStatus.SERVICE_UNAVAILABLE, peeledCause);
136 }
137
138 return newResponse(ctx, HttpStatus.INTERNAL_SERVER_ERROR, peeledCause);
139 }
140 }