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.linecorp.centraldogma.server.internal.api.ContentServiceV1.IS_WATCH_REQUEST;
20 import static com.linecorp.centraldogma.server.internal.api.HttpApiUtil.newResponse;
21
22 import java.util.Map;
23 import java.util.function.BiFunction;
24
25 import com.google.common.collect.ImmutableMap;
26
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.ServerErrorHandler;
33 import com.linecorp.armeria.server.ServiceRequestContext;
34 import com.linecorp.armeria.server.annotation.ExceptionHandlerFunction;
35 import com.linecorp.centraldogma.common.AuthorizationException;
36 import com.linecorp.centraldogma.common.ChangeConflictException;
37 import com.linecorp.centraldogma.common.EntryNoContentException;
38 import com.linecorp.centraldogma.common.EntryNotFoundException;
39 import com.linecorp.centraldogma.common.InvalidPushException;
40 import com.linecorp.centraldogma.common.MirrorAccessException;
41 import com.linecorp.centraldogma.common.MirrorException;
42 import com.linecorp.centraldogma.common.PermissionException;
43 import com.linecorp.centraldogma.common.ProjectExistsException;
44 import com.linecorp.centraldogma.common.ProjectNotFoundException;
45 import com.linecorp.centraldogma.common.QueryExecutionException;
46 import com.linecorp.centraldogma.common.ReadOnlyException;
47 import com.linecorp.centraldogma.common.RedundantChangeException;
48 import com.linecorp.centraldogma.common.RepositoryExistsException;
49 import com.linecorp.centraldogma.common.RepositoryNotFoundException;
50 import com.linecorp.centraldogma.common.RevisionNotFoundException;
51 import com.linecorp.centraldogma.common.ShuttingDownException;
52 import com.linecorp.centraldogma.common.TextPatchConflictException;
53 import com.linecorp.centraldogma.common.jsonpatch.JsonPatchConflictException;
54 import com.linecorp.centraldogma.server.internal.storage.RequestAlreadyTimedOutException;
55 import com.linecorp.centraldogma.server.internal.storage.repository.RepositoryMetadataException;
56 import com.linecorp.centraldogma.server.metadata.MemberNotFoundException;
57 import com.linecorp.centraldogma.server.metadata.TokenNotFoundException;
58
59
60
61
62 public final class HttpApiExceptionHandler implements ServerErrorHandler {
63
64
65
66
67 private static final Map<Class<?>, BiFunction<ServiceRequestContext, Throwable, HttpResponse>>
68 exceptionHandlers;
69
70 static {
71 final ImmutableMap.Builder<Class<?>,
72 BiFunction<ServiceRequestContext, Throwable, HttpResponse>> builder = ImmutableMap.builder();
73
74 builder.put(ChangeConflictException.class,
75 (ctx, cause) -> newResponse(ctx, HttpStatus.CONFLICT, cause,
76 "The given changeset or revision has a conflict."))
77 .put(EntryNotFoundException.class,
78 (ctx, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause,
79 "Entry '%s' does not exist.", cause.getMessage()))
80 .put(EntryNoContentException.class,
81 (ctx, cause) -> HttpResponse.of(HttpStatus.NO_CONTENT))
82 .put(ProjectExistsException.class,
83 (ctx, cause) -> newResponse(ctx, HttpStatus.CONFLICT, cause,
84 "Project '%s' exists already.", cause.getMessage()))
85 .put(ProjectNotFoundException.class,
86 (ctx, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause,
87 "Project '%s' does not exist.", cause.getMessage()))
88 .put(RedundantChangeException.class,
89 (ctx, cause) -> newResponse(ctx, HttpStatus.CONFLICT, cause,
90 "The given changeset does not change anything."))
91 .put(RepositoryExistsException.class,
92 (ctx, cause) -> newResponse(ctx, HttpStatus.CONFLICT, cause,
93 "Repository '%s' exists already.", cause.getMessage()))
94 .put(JsonPatchConflictException.class,
95 (ctx, cause) -> newResponse(ctx, HttpStatus.CONFLICT, cause))
96 .put(TextPatchConflictException.class,
97 (ctx, cause) -> newResponse(ctx, HttpStatus.CONFLICT, cause))
98 .put(RepositoryMetadataException.class,
99 (ctx, cause) -> newResponse(ctx, HttpStatus.INTERNAL_SERVER_ERROR, cause))
100 .put(RepositoryNotFoundException.class,
101 (ctx, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause,
102 "Repository '%s' does not exist.", cause.getMessage()))
103 .put(RevisionNotFoundException.class,
104 (ctx, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause,
105 "Revision %s does not exist.", cause.getMessage()))
106 .put(TokenNotFoundException.class,
107 (ctx, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause, cause.getMessage()))
108 .put(MemberNotFoundException.class,
109 (ctx, cause) -> newResponse(ctx, HttpStatus.NOT_FOUND, cause, cause.getMessage()))
110 .put(QueryExecutionException.class,
111 (ctx, cause) -> newResponse(ctx, HttpStatus.BAD_REQUEST, cause))
112 .put(UnsupportedOperationException.class,
113 (ctx, cause) -> newResponse(ctx, HttpStatus.BAD_REQUEST, cause))
114 .put(InvalidPushException.class,
115 (ctx, cause) -> newResponse(ctx, HttpStatus.BAD_REQUEST, cause))
116 .put(ReadOnlyException.class,
117 (ctx, cause) -> newResponse(ctx, HttpStatus.SERVICE_UNAVAILABLE, cause))
118 .put(MirrorException.class,
119 (ctx, cause) -> newResponse(ctx, HttpStatus.INTERNAL_SERVER_ERROR, cause))
120 .put(MirrorAccessException.class,
121 (ctx, cause) -> newResponse(ctx, HttpStatus.FORBIDDEN, cause))
122 .put(AuthorizationException.class,
123 (ctx, cause) -> newResponse(ctx, HttpStatus.UNAUTHORIZED, cause))
124 .put(PermissionException.class,
125 (ctx, cause) -> newResponse(ctx, HttpStatus.FORBIDDEN, cause));
126
127 exceptionHandlers = builder.build();
128 }
129
130 @Override
131 public HttpResponse onServiceException(ServiceRequestContext ctx, Throwable cause) {
132 final Throwable peeledCause = Exceptions.peel(cause);
133
134 if (peeledCause instanceof HttpStatusException ||
135 peeledCause instanceof HttpResponseException) {
136 return null;
137 }
138
139
140 final BiFunction<ServiceRequestContext, Throwable, HttpResponse> func =
141 exceptionHandlers.get(peeledCause.getClass());
142 if (func != null) {
143 ctx.setShouldReportUnloggedExceptions(false);
144 return func.apply(ctx, peeledCause);
145 }
146
147 if (peeledCause instanceof ShuttingDownException) {
148 if (Boolean.TRUE.equals(ctx.attr(IS_WATCH_REQUEST))) {
149 ctx.setShouldReportUnloggedExceptions(false);
150
151 return HttpResponse.of(HttpStatus.NOT_MODIFIED);
152 }
153 }
154
155 if (peeledCause instanceof IllegalArgumentException) {
156 ctx.setShouldReportUnloggedExceptions(false);
157 return newResponse(ctx, HttpStatus.BAD_REQUEST, peeledCause);
158 }
159
160 if (peeledCause instanceof RequestAlreadyTimedOutException) {
161 ctx.setShouldReportUnloggedExceptions(false);
162 return newResponse(ctx, HttpStatus.SERVICE_UNAVAILABLE, peeledCause);
163 }
164
165 return newResponse(ctx, HttpStatus.INTERNAL_SERVER_ERROR, peeledCause);
166 }
167 }