package org.jboss.seam.rest.exceptions; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Observes; import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.servlet.ServletContext; 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.jboss.solder.logging.Logger; import org.jboss.seam.rest.SeamRestConfiguration; import org.jboss.seam.rest.validation.ValidationException; import org.jboss.seam.rest.validation.ValidationExceptionHandler; import org.jboss.solder.el.Expressions; /** * This {@link ExceptionMapper} implementation converts caught exceptions to HTTP responses based on exception mapping rules. * <p/> * <p> * If there is no matching rule for an exception, the exception is rethrown wrapped within {@link UnhandledException}. Note that * this implementation is replaced by CatchExceptionMapper in environments where Seam Catch is available. * </p> * * @author <a href="http://community.jboss.org/people/jharting">Jozef Hartinger</a> * @see ExceptionMappingConfiguration */ @Provider @ApplicationScoped public class SeamExceptionMapper implements ExceptionMapper<Throwable> { @Inject @RestResource private ResponseBuilder responseBuilder; @Inject @RestResource private Instance<Response> response; @Inject private Expressions expressions; @Inject private ValidationExceptionHandler validationExceptionHandler; private Map<Class<? extends Throwable>, Mapping> mappings = new HashMap<Class<? extends Throwable>, Mapping>(); private static final Logger log = Logger.getLogger(SeamExceptionMapper.class); /** * Mappings are stored in a Map so that we can find them by the exception type. */ @Inject public void init(Instance<SeamRestConfiguration> configuration, ExceptionMappingExtension extension) { log.info("Processing exception mapping configuration."); // XML-configured mappings if (!configuration.isAmbiguous() && !configuration.isUnsatisfied()) { Set<Mapping> exceptionMappings = configuration.get().getMappings(); for (Mapping mapping : exceptionMappings) { addExceptionMapping(mapping); } } // annotation-configured mappings for (Mapping mapping : extension.getExceptionMappings()) { addExceptionMapping(mapping); } } protected void addExceptionMapping(Mapping mapping) { this.mappings.put(mapping.getExceptionType(), mapping); log.infov("Registered {0}", mapping); } /** * This observer method triggers {@link #init(ExceptionMappingConfiguration)} on bootstrap. */ public void init(@Observes @RestResource ServletContext ctx) { } public Response toResponse(Throwable e) { log.debugv("Handling {0}", e.getClass()); Throwable exception = e; while (exception != null) // iterate over cause chain { Class<? extends Throwable> exceptionType = exception.getClass(); if (mappings.containsKey(exceptionType)) { produceResponse(exception, responseBuilder); return response.get(); } if (exception instanceof ValidationException) { validationExceptionHandler.handleValidationException((ValidationException) exception, responseBuilder); return response.get(); } log.debugv("Unwrapping {0}", exception.getClass()); exception = exception.getCause(); } // No ExceptionMapper/ExceptionMapping, rethrow the exception throw new UnhandledException(e); } protected void produceResponse(Throwable exception, ResponseBuilder builder) { Mapping mapping = mappings.get(exception.getClass()); log.debugv("Found exception mapping {0} for {1}", mapping, exception.getClass()); builder.status(mapping.getStatusCode()); String message = createMessage(mapping.getMessage(), mapping.isInterpolateMessageBody(), mapping.isUseExceptionMessage(), exception, expressions); if (message != null) { builder.entity(createEntityBody(mapping, message)); } } protected String createMessage(String message, boolean interpolate, boolean useExceptionMessage, Throwable e, Expressions expressions) { String msg = message; if (msg == null || msg.length() == 0) { if (useExceptionMessage) { msg = e.getMessage(); } else { return null; // empty body is acceptable } } if (interpolate && msg != null) { return expressions.evaluateValueExpression(msg, String.class); } return msg; } protected Object createEntityBody(Mapping mapping, String message) { if (mapping.isUseJaxb()) { return new ErrorMessageWrapper(message); } return message; } public Map<Class<? extends Throwable>, Mapping> getMappings() { return mappings; } }