com.github.edgarespina.mwa.handler.MessageConverterHandlerExceptionResolver Maven / Gradle / Ivy
package com.github.edgarespina.mwa.handler;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
/**
*
* A {@link HandlerExceptionResolver} that is enable to return message error
* using a {@link HttpMessageConverter}.
*
* The default message contains two fields:
*
* - type: with the short exception class name.
*
- message: with the exception message.
*
*
* @author edgar.espina
* @since 0.1
*/
@SuppressWarnings({"unchecked", "rawtypes" })
public class MessageConverterHandlerExceptionResolver
extends AbstractHandlerExceptionResolver {
/**
* The list of message converters.
*/
private List> messageConverters;
/**
* The exception type.
*/
protected final Class extends Exception> exceptionClass;
/**
* The logging system.
*/
private final Logger logger = getLogger(getClass());
/**
* The HTTP response status.
*/
private final HttpStatus responseStatus;
/**
* A new {@link MessageConverterHandlerExceptionResolver}.
*
* @param exceptionClass The exception type. Required.
*/
public MessageConverterHandlerExceptionResolver(
@Nonnull final Class extends Exception> exceptionClass) {
this(exceptionClass, HttpStatus.BAD_REQUEST);
}
/**
* A new {@link MessageConverterHandlerExceptionResolver}.
*
* @param exceptionClass The exception type. Required.
* @param responseStatus The HTTP response status. Required.
*/
public MessageConverterHandlerExceptionResolver(
@Nonnull final Class extends Exception> exceptionClass,
@Nonnull final HttpStatus responseStatus) {
this.exceptionClass =
checkNotNull(exceptionClass, "The exception class is required.");
this.responseStatus =
checkNotNull(responseStatus, "The response status is required.");
setOrder(HIGHEST_PRECEDENCE);
}
/**
* Resolve the exception as a message if the ex is an instance of
* {@link #exceptionClass}. {@inheritDoc}
*/
@Override
protected ModelAndView doResolveException(final HttpServletRequest request,
final HttpServletResponse response, final Object handler,
final Exception ex) {
try {
if (canHandle(ex)) {
handle(convert(ex), request, response);
logger.warn(buildLogMessage(ex, request), ex);
return new ModelAndView();
}
} catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName()
+ "] resulted in Exception", handlerException);
}
return null;
}
/**
* True if the exception should be handled.
*
* @param ex The candidata exception.
* @return True if the exception should be handled.
*/
protected boolean canHandle(final Exception ex) {
return exceptionClass.isInstance(ex);
}
/**
* Convert the exception to a user friendly message.
*
* @param exception The exception.
* @return A user friendly message.
* @see #asMap(Throwable)
*/
protected Object convert(final Exception exception) {
Throwable cause = ExceptionUtils.getRootCause(exception);
if (cause == null) {
cause = exception;
}
return asMap(cause);
}
/**
* Convert the exception into a map a write two fields in it:
* type: exception class name; and message: exception error.
*
* @param cause The exception.
* @return A map message.
*/
protected Map asMap(final Throwable cause) {
Map error = new LinkedHashMap();
error.put("type",
StringUtils.uncapitalize(cause.getClass().getSimpleName()));
error.put("message", cause.getMessage());
return error;
}
/**
* Prepare everything for writing the response.
*
* @param error The error object.
* @param request The request.
* @param response The response.
* @throws IOException If the response cannot be writing.
*/
private void handle(final Object error,
final HttpServletRequest request, final HttpServletResponse response)
throws IOException {
MediaType contentType = contentType(request);
HttpMessageConverter converter =
messageConverter(contentType, error, request);
response.setStatus(responseStatus.value());
HttpOutputMessage output = new ServletServerHttpResponse(response);
converter.write(error, contentType, output);
}
/**
* Extract content-type from the request.
*
* @param request The request.
* @return A content-type.
*/
private MediaType contentType(final HttpServletRequest request) {
return new ServletServerHttpRequest(request).getHeaders().getContentType();
}
/**
* Detect a message converter for the given content-type.
*
* @param contentType The content-type.
* @param error The error object.
* @param request The request.
* @return A message converter.
*/
private HttpMessageConverter messageConverter(final MediaType contentType,
final Object error, final HttpServletRequest request) {
if (contentType != null) {
for (HttpMessageConverter converter : messageConverters) {
if (converter.canWrite(error.getClass(), contentType)) {
return converter;
}
}
}
throw new IllegalStateException("Message converter not found for: "
+ contentType);
}
/**
* Set the list of message converters.
*
* @param messageConverters The message converter. Required.
*/
public void setMessageConverters(
final List> messageConverters) {
this.messageConverters =
checkNotNull(messageConverters,
"The message converter list is required.");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy