/*
* Copyright (C) 2014 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.util;
import com.intel.mtwilson.My;
import com.intel.mtwilson.jaxrs2.server.Util;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.apache.commons.beanutils.PropertyUtils;
import org.stringtemplate.v4.*;
/**
* If the throwable is localizable, it sets the locale and uses the localized
* message directly. Otherwise, it attempts to use the throwable's class name as
* a localization key for a localized message with no parameters. If that
* doesn't work either, a localized "internal server error" message is returned.
*
* @author jbuhacoff
*/
@Provider
public class ThrowableMapper implements ExceptionMapper<Throwable> {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ThrowableMapper.class);
@Context
protected HttpHeaders headers;
@Override
public Response toResponse(Throwable exception) {
log.debug("ThrowableMapper toResponse", exception);
Locale locale = Util.getAcceptableLocale(headers.getAcceptableLanguages(), My.configuration().getAvailableLocales());
String localizedMessage;
if( exception instanceof MWException ) {
MWException mwe = (MWException)exception;
mwe.setLocale(locale); // localizes output of getErrorMessage() below
localizedMessage = mwe.getErrorMessage();
}
else {
localizedMessage = getLocalizedErrorMessage(exception, locale);
}
int status = 400; // assume bad request unless we find out otherwise
if( exception instanceof WebApplicationException ) {
status = ((WebApplicationException)exception).getResponse().getStatus();
}
// ResponseBuilder responseBuilder = Response.status(status).header("Error", localizedMessage);
// setting empty entity to prevent web container from providing its own html error page wrapping our status message.
// for example, if our localized message is "Bad argument" with status 400, Tomcat would wrap it with this html message:
// <html><head><title>Apache Tomcat/7.0.34 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 400 - Bad argument</h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u>Bad argument</u></p><p><b>description</b> <u>The request sent by the client was syntactically incorrect.</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/7.0.34</h3></body></html>
// ResponseBuilder responseBuilder = Response.status(new CustomStatus(status, localizedMessage)).type("text/plain").entity(""); //.header("Error", localizedMessage).entity(Entity.text("").); // entity(Entity.text("")
ResponseBuilder responseBuilder = Response.status(status).type("text/plain").entity(localizedMessage); //.header("Error", localizedMessage).entity(Entity.text("").); // entity(Entity.text("")
/*
if( exception instanceof MWException ) {
ErrorCode code = ((MWException)exception).getErrorCode();
responseBuilder.header("Error-Code", code.getErrorCode());
responseBuilder.header("Error-Name", code.name());
}
else {
responseBuilder.header("Error-Code", ErrorCode.SYSTEM_ERROR.getErrorCode());
responseBuilder.header("Error-Name", ErrorCode.SYSTEM_ERROR.name());
}
*/
Response response = responseBuilder.build();
return response;
}
public static class CustomStatus implements Response.StatusType {
private int statusCode;
private Response.Status.Family family;
private String reasonPhrase;
public CustomStatus(int statusCode, String reasonPhrase) {
this.statusCode = statusCode;
this.family = Response.Status.Family.familyOf(statusCode);
this.reasonPhrase = reasonPhrase;
}
public CustomStatus(int statusCode, Response.Status.Family family, String reasonPhrase) {
this.statusCode = statusCode;
this.family = family;
this.reasonPhrase = reasonPhrase;
}
@Override
public int getStatusCode() {
return statusCode;
}
@Override
public Response.Status.Family getFamily() {
return family;
}
@Override
public String getReasonPhrase() {
return reasonPhrase;
}
}
protected String getLocalizedErrorMessage(Throwable exception, Locale locale) {
ResourceBundle bundle = ResourceBundle.getBundle("MtWilsonErrors", locale);
log.debug("Message toString with locale: {}", locale.toString());
log.debug("Message toString loaded resource bundle: {}", bundle.getLocale().toString());
String key = exception.getClass().getName(); // for example "java.lang.IllegalArgumentException"
try {
String pattern = bundle.getString(key); // for example "Illegal argument: <message>" ; throws MissingResourceException
ST template = new ST(pattern);
Map<String, Object> sourceAttrs = PropertyUtils.describe(exception);// throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
for (Map.Entry<String, Object> attr : sourceAttrs.entrySet()) {
// there are attributes we skip, like "class" from getClass()
// if( attr.getKey().equals("class") ) { continue; } // let the template see the exception class so an author could write <class.name> to get the exception class name into the message
Object value = PropertyUtils.getSimpleProperty(exception, attr.getKey());
template.add(attr.getKey(), value);
}
String result = template.render();
log.debug("Rendered template: {}", result);
return result;
} catch (MissingResourceException e) {
log.error("No translation for key {} in bundle {}: {}", e.getKey(), e.getClassName(), e.getLocalizedMessage());
return key; // return just the message name with no parameters since we weren't able to find a localized translation, at least this will allow the recipient to maintain a local translation table for such untranslated constants
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
log.error("Cannot describe exception object", e);
return key;
}
}
}