/* * Copyright (c) 2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.svcs.errorhandling.mappers; import static com.emc.storageos.svcs.errorhandling.resources.ServiceCode.API_BAD_REQUEST; import static com.emc.storageos.svcs.errorhandling.resources.ServiceCode.API_PARAMETER_INVALID; import static com.emc.storageos.svcs.errorhandling.resources.ServiceCode.UNFORSEEN_ERROR; import static com.emc.storageos.svcs.errorhandling.resources.ServiceErrorFactory.DEFAULT_LOCALE; import static com.emc.storageos.svcs.errorhandling.resources.ServiceErrorFactory.toServiceErrorRestRep; import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; import static javax.ws.rs.core.Response.Status.SERVICE_UNAVAILABLE; import java.lang.reflect.Field; import java.util.List; import java.util.Locale; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.StatusType; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.JsonMappingException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.model.errorhandling.ServiceErrorRestRep; import com.emc.storageos.svcs.errorhandling.model.ServiceCoded; import com.emc.storageos.svcs.errorhandling.model.StatusCoded; import com.emc.storageos.svcs.errorhandling.resources.ServiceCode; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.core.provider.jaxb.AbstractRootElementProvider; /** * Class ControllerExceptionMapper will map * */ @Provider public class ServiceCodeExceptionMapper implements ExceptionMapper<Exception> { private static final Logger _log = LoggerFactory.getLogger(ServiceCodeExceptionMapper.class); @Context protected UriInfo info; @Context protected HttpHeaders headers; @Override public Response toResponse(final Exception t) { final Throwable e = getException(t); final Locale preferedLocale = getPreferedLocale(headers); final ServiceErrorRestRep serviceError = toServiceError(e, preferedLocale); final StatusType status = getHTTPStatus(e, info); // Just get the ServiceCode from the ServiceError final int code = serviceError.getCode(); // CQs 603808, 603811 // Check for those WebApplicationExceptions which result from the XML parser // correctly processing its input. Stack traces are not necessary and should // be filtered out in order not to raise undue concerns String prefix = "Responding to internal " + code + " with HTTP " + status; if (isStackTracePrinted(e)) { _log.warn(prefix + "; Caused by", e); } else { _log.warn(prefix + "; Caused by " + e.getMessage()); } final ResponseBuilder builder = Response.status(status); if (status.getStatusCode() == SERVICE_UNAVAILABLE.getStatusCode()) { // recommend how many seconds to wait before retrying builder.header("Retry-After", "30"); } builder.type(getPreferredMediaType(headers)); builder.entity(serviceError); return builder.build(); } /** * Test if the stack trace should be suppressed for the exception. Currently, * exceptions thrown by Jersey from class AbstractRootElementProvider are * selected for suppression. * * @param e the exception to be tested * @returns true if trace if to be printed, false otherwise. */ public static boolean isStackTracePrinted(Throwable e) { if (e instanceof WebApplicationException) { Class<?> cl2check = AbstractRootElementProvider.class; StackTraceElement[] ste = e.getStackTrace(); for (int i = 0; i < ste.length; i++) { // use classes so process is not brittle if (ste[i].getClassName().equals(cl2check.getName())) { return false; } } } return true; } /** * Get the HTTP Status for the specified Exception * * @param e * @param info * @return */ public static StatusType getHTTPStatus(final Throwable e, final UriInfo info) { if (e instanceof ServiceCoded) { // e is ServiceCoded so defer to next method return getHTTPStatus((ServiceCoded) e); } else if (e instanceof WebApplicationException) { // use the status from the WebApplicationException return getHTTPStatus((WebApplicationException) e); } else if (e instanceof IllegalArgumentException) { return API_PARAMETER_INVALID.getHTTPStatus(); } else if (e instanceof JsonMappingException || e instanceof JsonParseException) { return API_BAD_REQUEST.getHTTPStatus(); } // Others will be ServiceCodeExceptions return UNFORSEEN_ERROR.getHTTPStatus(); } public static StatusType getHTTPStatus(final WebApplicationException e) { return ClientResponse.Status.fromStatusCode(e.getResponse().getStatus()); } public static StatusType getHTTPStatus(final ServiceCoded e) { if (e instanceof StatusCoded) { // If e is StatusCoded then just get the status from it return ((StatusCoded) e).getStatus(); } // e is ServiceCoded so use the retryable field for the status return e.isRetryable() ? SERVICE_UNAVAILABLE : INTERNAL_SERVER_ERROR; } /** * Convert the Exception to a ServiceError * * @param e * @return */ public static ServiceErrorRestRep toServiceError(final Throwable e, final Locale locale) { if (e instanceof ServiceCoded) { // e should now be ServiceCoded return toServiceErrorRestRep((ServiceCoded) e, locale); } else if (e instanceof WebApplicationException) { // convert WebApplicationExceptions to ServiceErrors return toServiceErrorRestRep((WebApplicationException) e, locale); } else if (e instanceof IllegalArgumentException) { return toServiceErrorRestRep(API_PARAMETER_INVALID, e.getMessage(), locale); } else if (e instanceof JsonMappingException || e instanceof JsonParseException) { return toServiceErrorRestRep(API_BAD_REQUEST, e.getMessage(), locale); } // Others will be ServiceCodeExceptions return toServiceErrorRestRep(UNFORSEEN_ERROR, e.getMessage(), locale); } /** * If the Exception wraps a {@link ServiceCoded} then return the cause * otherwise return the exception * * @param e * @return */ private Throwable getException(final Exception e) { if (e.getCause() instanceof ServiceCoded) { // Catch some runtime exceptions that wrap ServiceCoded Exceptions _log.info("Unwrapping the Exception from:", e); return e.getCause(); } // return all others return e; } public static Locale getPreferedLocale(final HttpHeaders headers) { try { List<Locale> locales = headers.getAcceptableLanguages(); if (headers.getAcceptableLanguages() != null && !locales.isEmpty()) { return locales.get(0); } } catch (Exception e) { _log.debug("Unable to determine prefered locale. " + e.getMessage()); } return DEFAULT_LOCALE; } /** * Iterate over the acceptable media type from the request and returns * XML or JSON type on first occurrence. If not found, return XML type. * * @param headers request headers * @return preferred media type, XML by default. */ private MediaType getPreferredMediaType(final HttpHeaders headers) { try { List<MediaType> mediaTypes = headers.getAcceptableMediaTypes(); if (mediaTypes != null && !mediaTypes.isEmpty()) { for (MediaType mediaType : mediaTypes) { // ServiceErrorRestRep can only be serialized in JSON or XML (by default) if (mediaType.equals(MediaType.APPLICATION_JSON_TYPE) || mediaType.equals(MediaType.APPLICATION_XML_TYPE)) { return mediaType; } } } } catch (Exception e) { _log.debug("Unable to determine prefered media types. ", e); } return MediaType.APPLICATION_XML_TYPE; } public static void main(final String[] args) throws SecurityException, NoSuchFieldException { for (final ServiceCode code : ServiceCode.values()) { System.out.println("Service Code: " + code.getCode()); System.out.println("Name: " + code.name()); System.out.println("Description: " + code.getSummary(Locale.ENGLISH)); System.out.println("Retryable: " + code.isRetryable()); System.out.println("Fatal: " + code.isFatal()); final Field field = ServiceCode.class.getField(code.name()); final boolean deprecated = field.isAnnotationPresent(Deprecated.class); System.out.println("Deprecated: " + deprecated); final StatusType status = code.getHTTPStatus(); System.out.println("HTTP Status: " + status.getStatusCode() + " " + status.getReasonPhrase()); System.out.println(); } } }