/* * Copyright 2014 Jakub Jirutka <jakub@jirutka.cz>. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cz.jirutka.spring.exhandler; import cz.jirutka.spring.exhandler.handlers.RestExceptionHandler; import cz.jirutka.spring.exhandler.interpolators.MessageInterpolator; import lombok.Setter; import org.springframework.beans.factory.FactoryBean; import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.Assert; import org.springframework.web.accept.ContentNegotiationManager; import java.util.List; import java.util.Map; import static java.util.Collections.emptyMap; @Setter public class RestHandlerExceptionResolverFactoryBean implements FactoryBean<RestHandlerExceptionResolver> { /** * The {@link ContentNegotiationManager} to use to resolve acceptable media types. * If not provided, the default instance of {@code ContentNegotiationManager} with * {@link org.springframework.web.accept.HeaderContentNegotiationStrategy HeaderContentNegotiationStrategy} * and {@link org.springframework.web.accept.FixedContentNegotiationStrategy FixedContentNegotiationStrategy} * (with {@link #setDefaultContentType(String) defaultContentType}) will be used. */ private ContentNegotiationManager contentNegotiationManager; /** * The default content type that will be used as a fallback when the requested content type is * not supported. */ private String defaultContentType; /** * Mapping of exception handlers where the key is an exception type to handle and the value is * either a HTTP status (this will register * {@link cz.jirutka.spring.exhandler.handlers.ErrorMessageRestExceptionHandler * ErrorMessageRestExceptionHandler}) and/or an instance of the {@link RestExceptionHandler}. * * <p>Each handler is also used for all the exception subtypes, when no more specific mapping * is found.</p> * * <p><b>Example:</b> * <pre>{@code * <property name="exceptionHandlers"> * <map> * <entry key="org.springframework.dao.EmptyResultDataAccessException" value="404" /> * <entry key="org.example.MyException"> * <bean class="org.example.MyExceptionHandler" /> * </entry> * </map> * </property> * }</pre> */ private Map<Class<? extends Exception>, ?> exceptionHandlers = emptyMap(); /** * The message body converters to use for converting an error message into HTTP response body. * If not provided, the default converters will be used (see * {@link cz.jirutka.spring.exhandler.support.HttpMessageConverterUtils#getDefaultHttpMessageConverters() * getDefaultHttpMessageConverters()}). */ private List<HttpMessageConverter<?>> httpMessageConverters; /** * The message interpolator to set into all exception handlers implementing * {@link cz.jirutka.spring.exhandler.interpolators.MessageInterpolatorAware} * interface, e.g. {@link cz.jirutka.spring.exhandler.handlers.ErrorMessageRestExceptionHandler}. * Built-in exception handlers uses {@link cz.jirutka.spring.exhandler.interpolators.SpelMessageInterpolator * SpelMessageInterpolator} by default. */ private MessageInterpolator messageInterpolator; /** * The message source to set into all exception handlers implementing * {@link org.springframework.context.MessageSourceAware MessageSourceAware} interface, e.g. * {@link cz.jirutka.spring.exhandler.handlers.ErrorMessageRestExceptionHandler}. * Required for built-in exception handlers. */ private MessageSource messageSource; /** * Whether to register default exception handlers for Spring exceptions. These are registered * <i>before</i> the provided exception handlers, so you can overwrite any of the default * mappings. Default is <tt>true</tt>. */ private boolean withDefaultHandlers = true; /** * Whether to use the default (built-in) message source as a fallback to resolve messages that * the provided message source can't resolve. In other words, it sets the default message * source as a <i>parent</i> of the provided message source. Default is <tt>true</tt>. */ private boolean withDefaultMessageSource = true; @SuppressWarnings("unchecked") public RestHandlerExceptionResolver getObject() { RestHandlerExceptionResolverBuilder builder = createBuilder() .messageSource(messageSource) .messageInterpolator(messageInterpolator) .httpMessageConverters(httpMessageConverters) .contentNegotiationManager(contentNegotiationManager) .defaultContentType(defaultContentType) .withDefaultHandlers(withDefaultHandlers) .withDefaultMessageSource(withDefaultMessageSource); for (Map.Entry<Class<? extends Exception>, ?> entry : exceptionHandlers.entrySet()) { Class<? extends Exception> exceptionClass = entry.getKey(); Object value = entry.getValue(); if (value instanceof RestExceptionHandler) { builder.addHandler(exceptionClass, (RestExceptionHandler) value); } else { builder.addErrorMessageHandler(exceptionClass, parseHttpStatus(value)); } } return builder.build(); } public Class<?> getObjectType() { return RestHandlerExceptionResolver.class; } public boolean isSingleton() { return false; } RestHandlerExceptionResolverBuilder createBuilder() { return RestHandlerExceptionResolver.builder(); } HttpStatus parseHttpStatus(Object value) { Assert.notNull(value, "Values of the exceptionHandlers map must not be null"); if (value instanceof HttpStatus) { return (HttpStatus) value; } else if (value instanceof Integer) { return HttpStatus.valueOf((int) value); } else if (value instanceof String) { return HttpStatus.valueOf(Integer.valueOf((String) value)); } else { throw new IllegalArgumentException(String.format( "Values of the exceptionHandlers maps must be instance of ErrorResponseFactory, HttpStatus, " + "String, or int, but %s given", value.getClass())); } } }