1 package cn.home1.oss.lib.errorhandle.api;
2
3 import static cn.home1.oss.lib.errorhandle.api.ResolvedErrorException.isResolvedError;
4 import static cn.home1.oss.lib.errorhandle.api.ResolvedErrorException.isResolvedErrorWrapByOther;
5 import static com.google.common.base.Throwables.getStackTraceAsString;
6 import static com.google.common.collect.Lists.newArrayList;
7 import static cn.home1.oss.lib.common.CurlUtils.curl;
8 import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST;
9 import static org.springframework.web.servlet.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
10
11 import com.google.common.collect.ImmutableList;
12
13 import cn.home1.oss.lib.errorhandle.api.ExceptionTranslator.Location;
14
15 import cn.home1.oss.lib.common.Defaults;
16
17 import lombok.extern.slf4j.Slf4j;
18
19 import org.joda.time.DateTime;
20 import org.slf4j.Marker;
21 import org.slf4j.MarkerFactory;
22 import org.springframework.core.GenericTypeResolver;
23 import org.springframework.core.convert.ConversionService;
24 import org.springframework.http.HttpHeaders;
25 import org.springframework.http.HttpStatus;
26 import org.springframework.util.Assert;
27 import org.springframework.web.context.request.RequestAttributes;
28 import org.springframework.web.context.request.ServletRequestAttributes;
29
30 import java.util.List;
31 import java.util.Optional;
32
33 import javax.servlet.ServletException;
34 import javax.servlet.http.HttpServletRequest;
35
36
37
38
39
40
41
42 @Slf4j
43 public abstract class AbstractConcreteExceptionResolver<T extends Throwable> implements ConcreteExceptionResolver<T> {
44
45 private static final String TRACE_OFF = "trace off";
46
47 protected final Class<T> exceptionClass;
48
49 protected ConversionService conversionService;
50 protected ExceptionTranslator exceptionTranslator;
51 protected StackTraceIndicator stackTraceIndicator;
52
53
54
55
56 protected AbstractConcreteExceptionResolver() {
57 this.exceptionClass = determineTargetType();
58 }
59
60 protected AbstractConcreteExceptionResolver(final Class<T> exceptionClass) {
61 this.exceptionClass = exceptionClass;
62 }
63
64 @Deprecated
65 static HttpStatus parseHttpStatus(final Object value) {
66 Assert.notNull(value, "Values of the resolverMap map must not be null");
67
68 final HttpStatus result;
69 if (value instanceof HttpStatus) {
70 result = (HttpStatus) value;
71 } else if (value instanceof Integer) {
72 result = HttpStatus.valueOf((int) value);
73 } else if (value instanceof String) {
74 result = HttpStatus.valueOf(Integer.parseInt((String) value));
75 } else {
76 throw new IllegalArgumentException(String.format(
77 "Values of the resolverMap maps must be instance of "
78 + "ErrorResponseFactory, HttpStatus, String, or int, "
79 + "but %s given",
80 value.getClass()));
81 }
82 return result;
83 }
84
85 @Deprecated
86 @SuppressWarnings("unused")
87 private static <T extends Throwable> Throwable cause(final T exception) {
88 Throwable cause = null;
89 if (exception != null) {
90 cause = exception;
91 while (cause instanceof ServletException && cause.getCause() != null) {
92 final ServletException servletException = ((ServletException) cause);
93 cause = servletException.getCause();
94 }
95 }
96 return cause;
97 }
98
99 private static String error(final Optional<Integer> statusOptional) {
100
101 String result;
102 if (statusOptional.isPresent()) {
103 final Integer status = statusOptional.get();
104 try {
105 final HttpStatus httpStatus = HttpStatus.valueOf(status);
106 result = httpStatus.getReasonPhrase();
107 } catch (final IllegalArgumentException ignored) {
108 if (log.isDebugEnabled()) {
109 log.debug("Unable to obtain a reason, status {}", status, ignored);
110 }
111 result = "Http Status " + status;
112 }
113 } else {
114 result = "None";
115 }
116 return result;
117 }
118
119 @Override
120 public final ResolvedError resolve(
121 final HttpServletRequest request,
122 final T throwable
123 ) {
124 final DateTime now = Defaults.now();
125
126
127
128 request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
129
130 final RequestAttributes requestAttributes = new ServletRequestAttributes(request);
131
132 final Boolean stackTrace = this.stackTraceIndicator.stackTrace(request, null);
133 final String path = request.getQueryString() != null ?
134 request.getRequestURI() + request.getQueryString() :
135 request.getRequestURI();
136 final String track = stackTrace ? curl(request) : TRACE_OFF;
137
138 return this.resolve(requestAttributes, throwable, now, stackTrace, path, track);
139 }
140
141
142
143
144
145
146 @Override
147 public final ResolvedError resolve(
148 final RequestAttributes requestAttributes,
149 final T throwable
150 ) {
151 final DateTime now = Defaults.now();
152
153 final Object requestUri = getAttribute(
154 requestAttributes,
155 "javax.servlet.error.request_uri"
156 );
157 final Boolean stackTrace = this.stackTraceIndicator.stackTrace(null, null);
158 final String path = requestUri != null ? requestUri.toString() : null;
159
160 final String track = stackTrace ? path : TRACE_OFF;
161
162 return this.resolve(requestAttributes, throwable, now, stackTrace, path, track);
163 }
164
165 protected ResolvedError resolve(
166 final RequestAttributes requestAttributes,
167 final T throwable,
168 final DateTime now,
169 final Boolean stackTrace,
170 final String path,
171 final String track
172 ) {
173 final ResolvedError resolvedError;
174 if (isResolvedError(throwable)) {
175 resolvedError = ((ResolvedErrorException) throwable).getError();
176 resolvedError.trackPrepend(track);
177 } else if (isResolvedErrorWrapByOther(throwable)) {
178
179
180
181 log.warn("这里将被封装过ResolvedErrorException提取出来,原异常信息为:", throwable);
182 resolvedError = ((ResolvedErrorException) throwable.getCause()).getError();
183 resolvedError.trackPrepend(track);
184 } else {
185 final Location location = find(throwable).orElse(null);
186
187 final Optional<List<ValidationError>> validationErrorsOptional = this.validationErrors(throwable);
188 final Optional<Integer> statusOptional = this.status(requestAttributes, location, throwable);
189 final String error = error(statusOptional);
190 final List<ValidationError> validationErrors = validationErrorsOptional.orElse(null);
191 final String exception = throwable != null ? throwable.getClass().getName() : null;
192 final String message = message(requestAttributes, throwable, validationErrorsOptional);
193 final Integer status = statusOptional.orElse(500);
194 final Long timestamp = now.getMillis();
195 final String trace = stackTrace && throwable != null ? getStackTraceAsString(throwable) : null;
196
197 final String datetime = now.toString(Defaults.ISO8601);
198 final String localizedMessage = this.localizedMessage(requestAttributes, location, throwable).orElse("null");
199 final HttpHeaders headers = this.createHeaders(requestAttributes, throwable).orElse(null);
200 final List<String> tracks = ImmutableList.of(track);
201
202 resolvedError = ResolvedError.resolvedErrorBuilder()
203
204 .error(error)
205 .validationErrors(
206 validationErrors != null ? validationErrors.toArray(new ValidationError[validationErrors.size()]) : null
207 )
208 .exception(exception)
209 .message(message)
210 .path(path)
211 .status(status)
212 .timestamp(timestamp)
213 .trace(trace)
214
215 .datetime(datetime)
216 .localizedMessage(localizedMessage)
217 .headers(HttpHeader.fromHttpHeaders(headers))
218 .tracks(tracks.toArray(new String[tracks.size()]))
219 .build();
220 }
221
222 logError(requestAttributes, throwable, resolvedError);
223
224 return resolvedError;
225 }
226
227
228
229
230
231
232
233
234
235 protected void logError(
236 final RequestAttributes requestAttributes,
237 final T throwable,
238 final ResolvedError resolvedError
239 ) {
240 if (resolvedError.getStatus() >= HttpStatus.INTERNAL_SERVER_ERROR.value()) {
241 final Marker marker = MarkerFactory.getMarker("error");
242 final String msg = String.format(
243 "%s ~> %d",
244 resolvedError.getPath(),
245 resolvedError.getStatus()
246 );
247
248 if (log.isTraceEnabled()) {
249 log.trace("attributes in request scope: {}",
250 newArrayList(requestAttributes.getAttributeNames(SCOPE_REQUEST)));
251 }
252 log.warn(marker, msg, new Object[]{throwable});
253 }
254 }
255
256 @SuppressWarnings("unchecked")
257 private Class<T> determineTargetType() {
258 return (Class<T>) GenericTypeResolver.resolveTypeArguments(
259 this.getClass(),
260 AbstractConcreteExceptionResolver.class
261 )[0];
262 }
263
264 @Override
265 public Class<T> getExceptionClass() {
266 return this.exceptionClass;
267 }
268
269 @Override
270 public void setConversionService(final ConversionService conversionService) {
271 this.conversionService = conversionService;
272 }
273
274 @Override
275 public ExceptionTranslator getExceptionTranslator() {
276 return this.exceptionTranslator;
277 }
278
279 @Override
280 public void setExceptionTranslator(final ExceptionTranslator exceptionTranslator) {
281 this.exceptionTranslator = exceptionTranslator;
282 }
283
284 @Override
285 public void setStackTraceIndicator(final StackTraceIndicator stackTraceIndicator) {
286 this.stackTraceIndicator = stackTraceIndicator;
287 }
288 }