View Javadoc
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  	// @Override
39  	// public final String getErrorPath() {
40  	// 
41  	// 	return ERROR_PATH;
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 		// cookie.setDomain("toptal.com");
198 		cookie.setSecure(isApplicationSecure());
199 		cookie.setHttpOnly(isApplicationHttpOnly());
200 		cookie.setMaxAge(3600);
201 		cookie.setPath("/");
202 
203 		response.addCookie(cookie);
204 	}
205 }