1 package net.avcompris.commons3.web;
2
3 import static com.google.common.base.Preconditions.checkNotNull;
4 import static net.avcompris.commons3.web.AbstractController.CORRELATION_ID_ATTRIBUTE_NAME;
5 import static net.avcompris.commons3.web.AbstractController.USER_SESSION_ID_ATTRIBUTE_NAME;
6
7 import javax.annotation.Nullable;
8 import javax.servlet.http.Cookie;
9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11
12 import org.springframework.boot.web.servlet.error.ErrorController;
13 import org.springframework.http.HttpHeaders;
14 import org.springframework.http.ResponseEntity;
15 import org.springframework.web.bind.annotation.RequestMapping;
16 import org.springframework.web.bind.annotation.ResponseBody;
17 import org.springframework.web.util.NestedServletException;
18
19 import net.avcompris.commons3.api.exception.ServiceException;
20 import net.avcompris.commons3.api.exception.UnauthenticatedException;
21 import net.avcompris.commons3.notifier.ErrorNotifier;
22
23 public abstract class AbstractErrorController implements ErrorController {
24
25 private static final String ERROR_PATH = "/error";
26
27 private final ErrorNotifier notifier;
28
29 private final boolean debug;
30
31 public AbstractErrorController(final ErrorNotifier notifier) {
32
33 this.notifier = checkNotNull(notifier, "notifier");
34
35 debug = System.getProperty("debug") != null;
36 }
37
38
39
40
41
42
43
44 @RequestMapping(ERROR_PATH)
45 @ResponseBody
46 public final ResponseEntity<ApplicationError> handleError(final HttpServletRequest request,
47 final HttpServletResponse response) {
48
49 Throwable exception = (Exception) request.getAttribute("javax.servlet.error.exception");
50
51 final Integer servletStatusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
52
53 final String correlationId = (String) request.getAttribute(CORRELATION_ID_ATTRIBUTE_NAME);
54 final String userSessionId = (String) request.getAttribute(USER_SESSION_ID_ATTRIBUTE_NAME);
55
56 if (userSessionId != null) {
57
58 setUserSessionCookie(response, userSessionId);
59 }
60
61 if (exception != null) {
62
63 exception.printStackTrace();
64
65 notifier.notifyError(correlationId, null, exception);
66
67 if (exception instanceof NestedServletException) {
68
69 exception = exception.getCause();
70 }
71 }
72
73 final int httpErrorCode;
74 final String description;
75 final String type;
76
77 if (exception == null) {
78
79 exception = (Exception) request.getAttribute("org.springframework.web.servlet.DispatcherServlet.EXCEPTION");
80
81 if (exception != null) {
82
83 if (debug) {
84
85 exception.printStackTrace();
86 }
87
88 notifier.notifyError(correlationId, null, exception);
89 }
90
91 type = (exception != null) ? exception.getClass().getName() : null;
92
93 if (servletStatusCode != null) {
94
95 httpErrorCode = servletStatusCode;
96
97 if (servletStatusCode == 404) {
98
99 final String requestURI = (String) request.getAttribute("javax.servlet.forward.request_uri");
100
101 description = requestURI;
102
103 } else {
104
105 description = (exception != null) ? exception.getMessage() : null;
106 }
107
108 } else {
109
110 httpErrorCode = 500;
111
112 description = (exception != null) ? exception.getMessage() : "Internal server error";
113 }
114
115 } else if (exception instanceof ServiceException) {
116
117 final ServiceException serviceException = (ServiceException) exception;
118
119 httpErrorCode = serviceException.getHttpErrorCode();
120 description = serviceException.getDescription();
121 type = exception.getClass().getSimpleName();
122
123 if (exception instanceof UnauthenticatedException) {
124
125 return handleError(correlationId, httpErrorCode, description, type,
126
127 HttpHeaders.WWW_AUTHENTICATE,
128
129 "Bearer realm=\"Avantage Compris\"");
130 }
131
132 } else {
133
134 httpErrorCode = 500;
135 description = "Internal server error: " + exception;
136 type = exception.getClass().getSimpleName();
137 }
138
139 return handleError(correlationId, httpErrorCode, description, type);
140 }
141
142 private static ResponseEntity<ApplicationError> handleError(@Nullable final String correlationId,
143 final int httpErrorCode, @Nullable final String description, @Nullable final String type,
144 final String... headerNameValuePairs) {
145
146 final ApplicationError error = new ApplicationError() {
147
148 @Override
149 public String getCorrelationId() {
150 return correlationId;
151 }
152
153 @Override
154 public int getStatusCode() {
155 return httpErrorCode;
156 }
157
158 @Override
159 public String getType() {
160 return type;
161 }
162
163 @Override
164 public String getDescription() {
165 return description;
166 }
167 };
168
169 final HttpHeaders headers = new HttpHeaders();
170
171 for (int i = 0; i < headerNameValuePairs.length / 2; ++i) {
172
173 final String headerName = headerNameValuePairs[i * 2];
174 final String headerValue = headerNameValuePairs[i * 2 + 1];
175
176 headers.add(headerName, headerValue);
177 }
178
179 headers.add("Correlation-ID", correlationId);
180
181 return ResponseEntity
182 .status(httpErrorCode)
183 .headers(headers)
184 .body(error);
185 }
186
187 protected abstract boolean isApplicationSecure();
188
189 protected abstract boolean isApplicationHttpOnly();
190
191 private void setUserSessionCookie(final HttpServletResponse response, final String userSessionId) {
192
193 checkNotNull(userSessionId, "userSessionId");
194
195 final Cookie cookie = new Cookie(USER_SESSION_ID_ATTRIBUTE_NAME, userSessionId);
196
197
198 cookie.setSecure(isApplicationSecure());
199 cookie.setHttpOnly(isApplicationHttpOnly());
200 cookie.setMaxAge(3600);
201 cookie.setPath("/");
202
203 response.addCookie(cookie);
204 }
205 }