SPR-5633 - Portlet-version of @ExceptionHandler
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1658 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
595dca1d33
commit
8b37f28f2b
|
|
@ -7,4 +7,6 @@ org.springframework.web.portlet.HandlerMapping=org.springframework.web.portlet.m
|
|||
org.springframework.web.portlet.HandlerAdapter=org.springframework.web.portlet.mvc.SimpleControllerHandlerAdapter,\
|
||||
org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter
|
||||
|
||||
org.springframework.web.portlet.HandlerExceptionResolver=org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver
|
||||
|
||||
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.web.portlet.handler;
|
||||
|
||||
import java.util.Set;
|
||||
import javax.portlet.PortletRequest;
|
||||
import javax.portlet.WindowState;
|
||||
import javax.portlet.RenderRequest;
|
||||
import javax.portlet.RenderResponse;
|
||||
import javax.portlet.ResourceRequest;
|
||||
import javax.portlet.ResourceResponse;
|
||||
import javax.portlet.MimeResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.web.portlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.portlet.ModelAndView;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link HandlerExceptionResolver} implementations. <p>Provides a set of mapped handlers that
|
||||
* the resolver should map to, and the {@link Ordered} implementation.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
|
||||
|
||||
/** Logger available to subclasses */
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private int order = Ordered.LOWEST_PRECEDENCE;
|
||||
|
||||
private Set mappedHandlers;
|
||||
|
||||
private Class[] mappedHandlerClasses;
|
||||
|
||||
private Log warnLogger;
|
||||
|
||||
private boolean renderWhenMinimized = false;
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the set of handlers that this exception resolver should map.
|
||||
* The exception mappings and the default error view will only apply
|
||||
* to the specified handlers.
|
||||
* <p>If no handlers set, both the exception mappings and the default error
|
||||
* view will apply to all handlers. This means that a specified default
|
||||
* error view will be used as fallback for all exceptions; any further
|
||||
* HandlerExceptionResolvers in the chain will be ignored in this case.
|
||||
*/
|
||||
public void setMappedHandlers(Set mappedHandlers) {
|
||||
this.mappedHandlers = mappedHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the set of classes that this exception resolver should apply to.
|
||||
* The exception mappings and the default error view will only apply
|
||||
* to handlers of the specified type; the specified types may be interfaces
|
||||
* and superclasses of handlers as well.
|
||||
* <p>If no handlers and handler classes are set, the exception mappings
|
||||
* and the default error view will apply to all handlers. This means that
|
||||
* a specified default error view will be used as fallback for all exceptions;
|
||||
* any further HandlerExceptionResolvers in the chain will be ignored in
|
||||
* this case.
|
||||
*/
|
||||
public void setMappedHandlerClasses(Class[] mappedHandlerClasses) {
|
||||
this.mappedHandlerClasses = mappedHandlerClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the log category for warn logging. The name will be passed to the
|
||||
* underlying logger implementation through Commons Logging, getting
|
||||
* interpreted as log category according to the logger's configuration.
|
||||
* <p>Default is no warn logging. Specify this setting to activate
|
||||
* warn logging into a specific category. Alternatively, override
|
||||
* the {@link #logException} method for custom logging.
|
||||
* @see org.apache.commons.logging.LogFactory#getLog(String)
|
||||
* @see org.apache.log4j.Logger#getLogger(String)
|
||||
* @see java.util.logging.Logger#getLogger(String)
|
||||
*/
|
||||
public void setWarnLogCategory(String loggerName) {
|
||||
this.warnLogger = LogFactory.getLog(loggerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the resolver should render a view when the portlet is in
|
||||
* a minimized window. The default is "false".
|
||||
* @see javax.portlet.RenderRequest#getWindowState()
|
||||
* @see javax.portlet.WindowState#MINIMIZED
|
||||
*/
|
||||
public void setRenderWhenMinimized(boolean renderWhenMinimized) {
|
||||
this.renderWhenMinimized = renderWhenMinimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this resolver is supposed to apply (i.e. the handler
|
||||
* matches in case of "mappedHandlers" having been specified), then
|
||||
* delegates to the {@link #doResolveException} template method.
|
||||
*/
|
||||
public ModelAndView resolveException(
|
||||
RenderRequest request, RenderResponse response, Object handler, Exception ex) {
|
||||
|
||||
if (shouldApplyTo(request, handler)) {
|
||||
return doResolveException(request, response, handler, ex);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ModelAndView resolveException(
|
||||
ResourceRequest request, ResourceResponse response, Object handler, Exception ex) {
|
||||
|
||||
if (shouldApplyTo(request, handler)) {
|
||||
return doResolveException(request, response, handler, ex);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this resolver is supposed to apply to the given handler.
|
||||
* <p>The default implementation checks against the specified mapped handlers
|
||||
* and handler classes, if any, and alspo checks the window state (according
|
||||
* to the "renderWhenMinimize" property).
|
||||
* @param request current portlet request
|
||||
* @param handler the executed handler, or <code>null</code> if none chosen at the
|
||||
* time of the exception (for example, if multipart resolution failed)
|
||||
* @return whether this resolved should proceed with resolving the exception
|
||||
* for the given request and handler
|
||||
* @see #setMappedHandlers
|
||||
* @see #setMappedHandlerClasses
|
||||
*/
|
||||
protected boolean shouldApplyTo(PortletRequest request, Object handler) {
|
||||
// If the portlet is minimized and we don't want to render then return null.
|
||||
if (WindowState.MINIMIZED.equals(request.getWindowState()) && !this.renderWhenMinimized) {
|
||||
return false;
|
||||
}
|
||||
// Check mapped handlers...
|
||||
if (handler != null) {
|
||||
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
|
||||
return true;
|
||||
}
|
||||
if (this.mappedHandlerClasses != null) {
|
||||
for (Class mappedClass : this.mappedHandlerClasses) {
|
||||
if (mappedClass.isInstance(handler)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Else only apply if there are no explicit handler mappings.
|
||||
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given exception at warn level, provided that warn logging has been
|
||||
* activated through the {@link #setWarnLogCategory "warnLogCategory"} property.
|
||||
* <p>Calls {@link #buildLogMessage} in order to determine the concrete message
|
||||
* to log. Always passes the full exception to the logger.
|
||||
* @param ex the exception that got thrown during handler execution
|
||||
* @param request current portlet request (useful for obtaining metadata)
|
||||
* @see #setWarnLogCategory
|
||||
* @see #buildLogMessage
|
||||
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
|
||||
*/
|
||||
protected void logException(Exception ex, PortletRequest request) {
|
||||
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
|
||||
this.warnLogger.warn(buildLogMessage(ex, request), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a log message for the given exception, occured during processing
|
||||
* the given request.
|
||||
* @param ex the exception that got thrown during handler execution
|
||||
* @param request current portlet request (useful for obtaining metadata)
|
||||
* @return the log message to use
|
||||
*/
|
||||
protected String buildLogMessage(Exception ex, PortletRequest request) {
|
||||
return "Handler execution resulted in exception";
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually resolve the given exception that got thrown during on handler execution,
|
||||
* returning a ModelAndView that represents a specific error page if appropriate.
|
||||
*
|
||||
* <p>Must be overridden in subclasses, in order to apply specific exception checks. Note that this template method
|
||||
* will be invoked <i>after</i> checking whether this resolved applies ("mappedHandlers" etc), so an implementation
|
||||
* may simply proceed with its actual exception handling.
|
||||
|
||||
* @param request current portlet request
|
||||
* @param response current portlet response
|
||||
* @param handler the executed handler, or null if none chosen at the time of
|
||||
* the exception (for example, if multipart resolution failed)
|
||||
* @param ex the exception that got thrown during handler execution
|
||||
* @return a corresponding ModelAndView to forward to, or null for default processing
|
||||
*/
|
||||
protected abstract ModelAndView doResolveException(PortletRequest request,
|
||||
MimeResponse response,
|
||||
Object handler,
|
||||
Exception ex);
|
||||
|
||||
}
|
||||
|
|
@ -18,20 +18,9 @@ package org.springframework.web.portlet.handler;
|
|||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import javax.portlet.MimeResponse;
|
||||
import javax.portlet.PortletRequest;
|
||||
import javax.portlet.RenderRequest;
|
||||
import javax.portlet.RenderResponse;
|
||||
import javax.portlet.ResourceRequest;
|
||||
import javax.portlet.ResourceResponse;
|
||||
import javax.portlet.WindowState;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.portlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.portlet.ModelAndView;
|
||||
|
||||
/**
|
||||
|
|
@ -45,97 +34,22 @@ import org.springframework.web.portlet.ModelAndView;
|
|||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author John A. Lewis
|
||||
* @author Arjen Poutsma
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SimpleMappingExceptionResolver implements HandlerExceptionResolver, Ordered {
|
||||
public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {
|
||||
|
||||
/**
|
||||
* The default name of the exception attribute: "exception".
|
||||
*/
|
||||
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
|
||||
|
||||
|
||||
/** Logger available to subclasses */
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
|
||||
|
||||
private Set mappedHandlers;
|
||||
|
||||
private Class[] mappedHandlerClasses;
|
||||
|
||||
private boolean renderWhenMinimized = false;
|
||||
|
||||
private Log warnLogger;
|
||||
|
||||
private Properties exceptionMappings;
|
||||
|
||||
private String defaultErrorView;
|
||||
|
||||
private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;
|
||||
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the set of handlers that this exception resolver should map.
|
||||
* The exception mappings and the default error view will only apply
|
||||
* to the specified handlers.
|
||||
* <p>If no handlers set, both the exception mappings and the default error
|
||||
* view will apply to all handlers. This means that a specified default
|
||||
* error view will be used as fallback for all exceptions; any further
|
||||
* HandlerExceptionResolvers in the chain will be ignored in this case.
|
||||
*/
|
||||
public void setMappedHandlers(Set mappedHandlers) {
|
||||
this.mappedHandlers = mappedHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the set of classes that this exception resolver should apply to.
|
||||
* The exception mappings and the default error view will only apply
|
||||
* to handlers of the specified type; the specified types may be interfaces
|
||||
* and superclasses of handlers as well.
|
||||
* <p>If no handlers and handler classes are set, the exception mappings
|
||||
* and the default error view will apply to all handlers. This means that
|
||||
* a specified default error view will be used as fallback for all exceptions;
|
||||
* any further HandlerExceptionResolvers in the chain will be ignored in
|
||||
* this case.
|
||||
*/
|
||||
public void setMappedHandlerClasses(Class[] mappedHandlerClasses) {
|
||||
this.mappedHandlerClasses = mappedHandlerClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the resolver should render a view when the portlet is in
|
||||
* a minimized window. The default is "false".
|
||||
* @see javax.portlet.RenderRequest#getWindowState()
|
||||
* @see javax.portlet.WindowState#MINIMIZED
|
||||
*/
|
||||
public void setRenderWhenMinimized(boolean renderWhenMinimized) {
|
||||
this.renderWhenMinimized = renderWhenMinimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the log category for warn logging. The name will be passed to the
|
||||
* underlying logger implementation through Commons Logging, getting
|
||||
* interpreted as log category according to the logger's configuration.
|
||||
* <p>Default is no warn logging. Specify this setting to activate
|
||||
* warn logging into a specific category. Alternatively, override
|
||||
* the {@link #logException} method for custom logging.
|
||||
* @see org.apache.commons.logging.LogFactory#getLog(String)
|
||||
* @see org.apache.log4j.Logger#getLogger(String)
|
||||
* @see java.util.logging.Logger#getLogger(String)
|
||||
*/
|
||||
public void setWarnLogCategory(String loggerName) {
|
||||
this.warnLogger = LogFactory.getLog(loggerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mappings between exception class names and error view names.
|
||||
* The exception class name can be a substring, with no wildcard support
|
||||
|
|
@ -176,70 +90,6 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
|
|||
this.exceptionAttribute = exceptionAttribute;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether this resolver is supposed to apply (i.e. the handler
|
||||
* matches in case of "mappedHandlers" having been specified), then
|
||||
* delegates to the {@link #doResolveException} template method.
|
||||
*/
|
||||
public ModelAndView resolveException(
|
||||
RenderRequest request, RenderResponse response, Object handler, Exception ex) {
|
||||
|
||||
if (shouldApplyTo(request, handler)) {
|
||||
return doResolveException(request, response, handler, ex);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ModelAndView resolveException(
|
||||
ResourceRequest request, ResourceResponse response, Object handler, Exception ex) {
|
||||
|
||||
if (shouldApplyTo(request, handler)) {
|
||||
return doResolveException(request, response, handler, ex);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether this resolver is supposed to apply to the given handler.
|
||||
* <p>The default implementation checks against the specified mapped handlers
|
||||
* and handler classes, if any, and alspo checks the window state (according
|
||||
* to the "renderWhenMinimize" property).
|
||||
* @param request current portlet request
|
||||
* @param handler the executed handler, or <code>null</code> if none chosen at the
|
||||
* time of the exception (for example, if multipart resolution failed)
|
||||
* @return whether this resolved should proceed with resolving the exception
|
||||
* for the given request and handler
|
||||
* @see #setMappedHandlers
|
||||
* @see #setMappedHandlerClasses
|
||||
*/
|
||||
protected boolean shouldApplyTo(PortletRequest request, Object handler) {
|
||||
// If the portlet is minimized and we don't want to render then return null.
|
||||
if (WindowState.MINIMIZED.equals(request.getWindowState()) && !this.renderWhenMinimized) {
|
||||
return false;
|
||||
}
|
||||
// Check mapped handlers...
|
||||
if (handler != null) {
|
||||
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
|
||||
return true;
|
||||
}
|
||||
if (this.mappedHandlerClasses != null) {
|
||||
for (Class mappedClass : this.mappedHandlerClasses) {
|
||||
if (mappedClass.isInstance(handler)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Else only apply if there are no explicit handler mappings.
|
||||
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually resolve the given exception that got thrown during on handler execution,
|
||||
* returning a ModelAndView that represents a specific error page if appropriate.
|
||||
|
|
@ -250,6 +100,7 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
|
|||
* @param ex the exception that got thrown during handler execution
|
||||
* @return a corresponding ModelAndView to forward to, or null for default processing
|
||||
*/
|
||||
@Override
|
||||
protected ModelAndView doResolveException(
|
||||
PortletRequest request, MimeResponse response, Object handler, Exception ex) {
|
||||
|
||||
|
|
@ -269,36 +120,6 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log the given exception at warn level, provided that warn logging has been
|
||||
* activated through the {@link #setWarnLogCategory "warnLogCategory"} property.
|
||||
* <p>Calls {@link #buildLogMessage} in order to determine the concrete message
|
||||
* to log. Always passes the full exception to the logger.
|
||||
* @param ex the exception that got thrown during handler execution
|
||||
* @param request current portlet request (useful for obtaining metadata)
|
||||
* @see #setWarnLogCategory
|
||||
* @see #buildLogMessage
|
||||
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
|
||||
*/
|
||||
protected void logException(Exception ex, PortletRequest request) {
|
||||
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
|
||||
this.warnLogger.warn(buildLogMessage(ex, request), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a log message for the given exception, occured during processing
|
||||
* the given request.
|
||||
* @param ex the exception that got thrown during handler execution
|
||||
* @param request current portlet request (useful for obtaining metadata)
|
||||
* @return the log message to use
|
||||
*/
|
||||
protected String buildLogMessage(Exception ex, PortletRequest request) {
|
||||
return "Handler execution resulted in exception";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine the view name for the given exception, searching the
|
||||
* {@link #setExceptionMappings "exceptionMappings"}, using the
|
||||
|
|
|
|||
|
|
@ -0,0 +1,416 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.web.portlet.mvc.annotation;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import javax.portlet.ClientDataRequest;
|
||||
import javax.portlet.Event;
|
||||
import javax.portlet.EventRequest;
|
||||
import javax.portlet.MimeResponse;
|
||||
import javax.portlet.PortalContext;
|
||||
import javax.portlet.PortletMode;
|
||||
import javax.portlet.PortletPreferences;
|
||||
import javax.portlet.PortletRequest;
|
||||
import javax.portlet.PortletResponse;
|
||||
import javax.portlet.PortletSession;
|
||||
import javax.portlet.WindowState;
|
||||
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.support.WebArgumentResolver;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.portlet.ModelAndView;
|
||||
import org.springframework.web.portlet.context.PortletWebRequest;
|
||||
import org.springframework.web.portlet.handler.AbstractHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.View;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link org.springframework.web.portlet.HandlerExceptionResolver} interface that handles
|
||||
* exceptions through the {@link ExceptionHandler} annotation.
|
||||
* <p>This exception resolver is enabled by default in the {@link org.springframework.web.portlet.DispatcherPortlet}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public class AnnotationMethodHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
|
||||
|
||||
private WebArgumentResolver[] customArgumentResolvers;
|
||||
|
||||
/**
|
||||
* Set a custom ArgumentResolvers to use for special method parameter types. Such a custom ArgumentResolver will kick
|
||||
* in first, having a chance to resolve an argument value before the standard argument handling kicks in.
|
||||
*/
|
||||
public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
|
||||
this.customArgumentResolvers = new WebArgumentResolver[]{argumentResolver};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set one or more custom ArgumentResolvers to use for special method parameter types. Any such custom ArgumentResolver
|
||||
* will kick in first, having a chance to resolve an argument value before the standard argument handling kicks in.
|
||||
*/
|
||||
public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
|
||||
this.customArgumentResolvers = argumentResolvers;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelAndView doResolveException(PortletRequest request,
|
||||
MimeResponse response,
|
||||
Object handler,
|
||||
Exception ex) {
|
||||
if (handler != null) {
|
||||
Method handlerMethod = findBestExceptionHandlerMethod(handler, ex);
|
||||
if (handlerMethod != null) {
|
||||
NativeWebRequest webRequest = new PortletWebRequest(request, response);
|
||||
try {
|
||||
Object[] args = resolveHandlerArguments(handlerMethod, handler, webRequest, ex);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Invoking request handler method: " + handlerMethod);
|
||||
}
|
||||
Object retVal = doInvokeMethod(handlerMethod, handler, args);
|
||||
return getModelAndView(retVal);
|
||||
}
|
||||
catch (Exception invocationEx) {
|
||||
logger.error("Invoking request method resulted in exception : " + handlerMethod, invocationEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the handler method that matches the thrown exception best.
|
||||
*
|
||||
* @param handler the handler object
|
||||
* @param thrownException the exception to be handled
|
||||
* @return the best matching method; or <code>null</code> if none is found
|
||||
*/
|
||||
private Method findBestExceptionHandlerMethod(Object handler, final Exception thrownException) {
|
||||
final Class<?> handlerType = handler.getClass();
|
||||
final Class<? extends Throwable> thrownExceptionType = thrownException.getClass();
|
||||
|
||||
final Map<Class<? extends Throwable>, Method> resolverMethods =
|
||||
new LinkedHashMap<Class<? extends Throwable>, Method>();
|
||||
|
||||
ReflectionUtils.doWithMethods(handlerType, new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) {
|
||||
method = ClassUtils.getMostSpecificMethod(method, handlerType);
|
||||
List<Class<? extends Throwable>> handledExceptions = getHandledExceptions(method);
|
||||
for (Class<? extends Throwable> handledException : handledExceptions) {
|
||||
if (handledException.isAssignableFrom(thrownExceptionType)) {
|
||||
if (!resolverMethods.containsKey(handledException)) {
|
||||
resolverMethods.put(handledException, method);
|
||||
}
|
||||
else {
|
||||
Method oldMappedMethod = resolverMethods.get(handledException);
|
||||
throw new IllegalStateException(
|
||||
"Ambiguous exception handler mapped for " + handledException + "]: {" +
|
||||
oldMappedMethod + ", " + method + "}.");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return getBestMatchingMethod(thrownException, resolverMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the exception classes handled by the given method.
|
||||
*
|
||||
* <p>Default implementation looks for exceptions in the {@linkplain ExceptionHandler#value() annotation}, or -
|
||||
* if that annotation element is empty - any exceptions listed in the method parameters if the method is annotated
|
||||
* with {@code @ExceptionHandler}.
|
||||
*
|
||||
* @param method the method
|
||||
* @return the handled exceptions
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected List<Class<? extends Throwable>> getHandledExceptions(Method method) {
|
||||
List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>();
|
||||
ExceptionHandler exceptionHandler = method.getAnnotation(ExceptionHandler.class);
|
||||
if (exceptionHandler != null) {
|
||||
if (!ObjectUtils.isEmpty(exceptionHandler.value())) {
|
||||
result.addAll(Arrays.asList(exceptionHandler.value()));
|
||||
}
|
||||
else {
|
||||
for (Class<?> param : method.getParameterTypes()) {
|
||||
if (Throwable.class.isAssignableFrom(param)) {
|
||||
result.add((Class<? extends Throwable>) param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best matching method. Uses the {@link DepthComparator}.
|
||||
*/
|
||||
private Method getBestMatchingMethod(Exception thrownException,
|
||||
Map<Class<? extends Throwable>, Method> resolverMethods) {
|
||||
if (!resolverMethods.isEmpty()) {
|
||||
List<Class<? extends Throwable>> handledExceptions =
|
||||
new ArrayList<Class<? extends Throwable>>(resolverMethods.keySet());
|
||||
Collections.sort(handledExceptions, new DepthComparator(thrownException));
|
||||
Class<? extends Throwable> bestMatchMethod = handledExceptions.get(0);
|
||||
return resolverMethods.get(bestMatchMethod);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the arguments for the given method. Delegates to {@link #resolveCommonArgument}.
|
||||
*/
|
||||
private Object[] resolveHandlerArguments(Method handlerMethod,
|
||||
Object handler,
|
||||
NativeWebRequest webRequest,
|
||||
Exception thrownException) throws Exception {
|
||||
|
||||
Class[] paramTypes = handlerMethod.getParameterTypes();
|
||||
Object[] args = new Object[paramTypes.length];
|
||||
|
||||
Class<?> handlerType = handler.getClass();
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
MethodParameter methodParam = new MethodParameter(handlerMethod, i);
|
||||
GenericTypeResolver.resolveParameterType(methodParam, handlerType);
|
||||
|
||||
Class paramType = methodParam.getParameterType();
|
||||
|
||||
Object argValue = resolveCommonArgument(methodParam, webRequest, thrownException);
|
||||
if (argValue != WebArgumentResolver.UNRESOLVED) {
|
||||
args[i] = argValue;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"Unsupported argument [" + paramType.getName() + "] for @ExceptionHandler method: " +
|
||||
handlerMethod);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves common method arguments. Delegates to registered {@link #setCustomArgumentResolver(org.springframework.web.bind.support.WebArgumentResolver) argumentResolvers} first,
|
||||
* then checking {@link #resolveStandardArgument}.
|
||||
*
|
||||
* @param methodParameter the method parameter
|
||||
* @param webRequest the request
|
||||
* @param thrownException the exception thrown
|
||||
* @return the argument value, or {@link org.springframework.web.bind.support.WebArgumentResolver#UNRESOLVED}
|
||||
*/
|
||||
protected Object resolveCommonArgument(MethodParameter methodParameter,
|
||||
NativeWebRequest webRequest,
|
||||
Exception thrownException) throws Exception {
|
||||
// Invoke custom argument resolvers if present...
|
||||
if (this.customArgumentResolvers != null) {
|
||||
for (WebArgumentResolver argumentResolver : this.customArgumentResolvers) {
|
||||
Object value = argumentResolver.resolveArgument(methodParameter, webRequest);
|
||||
if (value != WebArgumentResolver.UNRESOLVED) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolution of standard parameter types...
|
||||
Class paramType = methodParameter.getParameterType();
|
||||
Object value = resolveStandardArgument(paramType, webRequest, thrownException);
|
||||
if (value != WebArgumentResolver.UNRESOLVED && !ClassUtils.isAssignableValue(paramType, value)) {
|
||||
throw new IllegalStateException(
|
||||
"Standard argument type [" + paramType.getName() + "] resolved to incompatible value of type [" +
|
||||
(value != null ? value.getClass() : null) +
|
||||
"]. Consider declaring the argument type in a less specific fashion.");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves standard method arguments. Default implementation handles {@link org.springframework.web.context.request.NativeWebRequest},
|
||||
* {@link javax.servlet.ServletRequest}, {@link javax.servlet.ServletResponse}, {@link javax.servlet.http.HttpSession}, {@link java.security.Principal}, {@link java.util.Locale},
|
||||
* request {@link java.io.InputStream}, request {@link java.io.Reader}, response {@link java.io.OutputStream}, response {@link java.io.Writer},
|
||||
* and the given {@code thrownException}.
|
||||
*
|
||||
* @param parameterType the method parameter type
|
||||
* @param webRequest the request
|
||||
* @param thrownException the exception thrown
|
||||
* @return the argument value, or {@link org.springframework.web.bind.support.WebArgumentResolver#UNRESOLVED}
|
||||
*/
|
||||
protected Object resolveStandardArgument(Class parameterType,
|
||||
NativeWebRequest webRequest,
|
||||
Exception thrownException) throws Exception {
|
||||
if (WebRequest.class.isAssignableFrom(parameterType)) {
|
||||
return webRequest;
|
||||
}
|
||||
|
||||
PortletRequest request = (PortletRequest) webRequest.getNativeRequest();
|
||||
PortletResponse response = (PortletResponse) webRequest.getNativeResponse();
|
||||
|
||||
if (PortletRequest.class.isAssignableFrom(parameterType)) {
|
||||
return request;
|
||||
}
|
||||
else if (PortletResponse.class.isAssignableFrom(parameterType)) {
|
||||
return response;
|
||||
}
|
||||
else if (PortletSession.class.isAssignableFrom(parameterType)) {
|
||||
return request.getPortletSession();
|
||||
}
|
||||
else if (PortletPreferences.class.isAssignableFrom(parameterType)) {
|
||||
return request.getPreferences();
|
||||
}
|
||||
else if (PortletMode.class.isAssignableFrom(parameterType)) {
|
||||
return request.getPortletMode();
|
||||
}
|
||||
else if (WindowState.class.isAssignableFrom(parameterType)) {
|
||||
return request.getWindowState();
|
||||
}
|
||||
else if (PortalContext.class.isAssignableFrom(parameterType)) {
|
||||
return request.getPortalContext();
|
||||
}
|
||||
else if (Principal.class.isAssignableFrom(parameterType)) {
|
||||
return request.getUserPrincipal();
|
||||
}
|
||||
else if (Locale.class.equals(parameterType)) {
|
||||
return request.getLocale();
|
||||
}
|
||||
else if (InputStream.class.isAssignableFrom(parameterType)) {
|
||||
if (!(request instanceof ClientDataRequest)) {
|
||||
throw new IllegalStateException("InputStream can only get obtained for Action/ResourceRequest");
|
||||
}
|
||||
return ((ClientDataRequest) request).getPortletInputStream();
|
||||
}
|
||||
else if (Reader.class.isAssignableFrom(parameterType)) {
|
||||
if (!(request instanceof ClientDataRequest)) {
|
||||
throw new IllegalStateException("Reader can only get obtained for Action/ResourceRequest");
|
||||
}
|
||||
return ((ClientDataRequest) request).getReader();
|
||||
}
|
||||
else if (OutputStream.class.isAssignableFrom(parameterType)) {
|
||||
if (!(response instanceof MimeResponse)) {
|
||||
throw new IllegalStateException("OutputStream can only get obtained for Render/ResourceResponse");
|
||||
}
|
||||
return ((MimeResponse) response).getPortletOutputStream();
|
||||
}
|
||||
else if (Writer.class.isAssignableFrom(parameterType)) {
|
||||
if (!(response instanceof MimeResponse)) {
|
||||
throw new IllegalStateException("Writer can only get obtained for Render/ResourceResponse");
|
||||
}
|
||||
return ((MimeResponse) response).getWriter();
|
||||
}
|
||||
else if (Event.class.equals(parameterType)) {
|
||||
if (!(request instanceof EventRequest)) {
|
||||
throw new IllegalStateException("Event can only get obtained from EventRequest");
|
||||
}
|
||||
return ((EventRequest) request).getEvent();
|
||||
}
|
||||
else if (parameterType.isInstance(thrownException)) {
|
||||
return thrownException;
|
||||
}
|
||||
else {
|
||||
return WebArgumentResolver.UNRESOLVED;
|
||||
}
|
||||
}
|
||||
|
||||
private Object doInvokeMethod(Method method, Object target, Object[] args) throws Exception {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
try {
|
||||
return method.invoke(target, args);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
ReflectionUtils.rethrowException(ex.getTargetException());
|
||||
}
|
||||
throw new IllegalStateException("Should never get here");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ModelAndView getModelAndView(Object returnValue) {
|
||||
if (returnValue instanceof ModelAndView) {
|
||||
return (ModelAndView) returnValue;
|
||||
}
|
||||
else if (returnValue instanceof Model) {
|
||||
return new ModelAndView().addAllObjects(((Model) returnValue).asMap());
|
||||
}
|
||||
else if (returnValue instanceof Map) {
|
||||
return new ModelAndView().addAllObjects((Map) returnValue);
|
||||
}
|
||||
else if (returnValue instanceof View) {
|
||||
return new ModelAndView(returnValue);
|
||||
}
|
||||
else if (returnValue instanceof String) {
|
||||
return new ModelAndView((String) returnValue);
|
||||
}
|
||||
else if (returnValue == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator capable of sorting exceptions based on their depth from the thrown exception type.
|
||||
*/
|
||||
private static class DepthComparator implements Comparator<Class<? extends Throwable>> {
|
||||
|
||||
private final Class<? extends Throwable> handlerExceptionType;
|
||||
|
||||
private DepthComparator(Exception handlerException) {
|
||||
this.handlerExceptionType = handlerException.getClass();
|
||||
}
|
||||
|
||||
public int compare(Class<? extends Throwable> o1, Class<? extends Throwable> o2) {
|
||||
int depth1 = getDepth(o1, 0);
|
||||
int depth2 = getDepth(o2, 0);
|
||||
|
||||
return depth2 - depth1;
|
||||
}
|
||||
|
||||
private int getDepth(Class exceptionType, int depth) {
|
||||
if (exceptionType.equals(handlerExceptionType)) {
|
||||
// Found it!
|
||||
return depth;
|
||||
}
|
||||
// If we've gone as far as we can go and haven't found it...
|
||||
if (Throwable.class.equals(exceptionType)) {
|
||||
return -1;
|
||||
}
|
||||
return getDepth(exceptionType.getSuperclass(), depth + 1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.web.portlet.mvc.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.BindException;
|
||||
|
||||
import javax.portlet.PortletRequest;
|
||||
import javax.portlet.PortletResponse;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.mock.web.portlet.MockPortletRequest;
|
||||
import org.springframework.mock.web.portlet.MockPortletResponse;
|
||||
import org.springframework.mock.web.portlet.MockRenderRequest;
|
||||
import org.springframework.mock.web.portlet.MockRenderResponse;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.portlet.ModelAndView;
|
||||
|
||||
/** @author Arjen Poutsma */
|
||||
public class AnnotationMethodHandlerExceptionResolverTests {
|
||||
|
||||
private AnnotationMethodHandlerExceptionResolver exceptionResolver;
|
||||
|
||||
private MockRenderRequest request;
|
||||
|
||||
private MockRenderResponse response;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
exceptionResolver = new AnnotationMethodHandlerExceptionResolver();
|
||||
request = new MockRenderRequest();
|
||||
response = new MockRenderResponse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple() {
|
||||
BindException ex = new BindException();
|
||||
SimpleController controller = new SimpleController();
|
||||
ModelAndView mav = exceptionResolver.resolveException(request, response, controller, ex);
|
||||
assertNotNull("No ModelAndView returned", mav);
|
||||
assertEquals("Invalid view name returned", "BindException", mav.getViewName());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void ambiguous() {
|
||||
IllegalArgumentException ex = new IllegalArgumentException();
|
||||
AmbiguousController controller = new AmbiguousController();
|
||||
exceptionResolver.resolveException(request, response, controller, ex);
|
||||
}
|
||||
|
||||
@Controller
|
||||
private static class SimpleController {
|
||||
|
||||
@ExceptionHandler(IOException.class)
|
||||
public String handleIOException(IOException ex, PortletRequest request) {
|
||||
return ClassUtils.getShortName(ex.getClass());
|
||||
}
|
||||
|
||||
@ExceptionHandler(BindException.class)
|
||||
public String handleBindException(Exception ex, PortletResponse response) {
|
||||
return ClassUtils.getShortName(ex.getClass());
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public String handleIllegalArgumentException(Exception ex) {
|
||||
return ClassUtils.getShortName(ex.getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Controller
|
||||
private static class AmbiguousController {
|
||||
|
||||
@ExceptionHandler({BindException.class, IllegalArgumentException.class})
|
||||
public String handle1(Exception ex, PortletRequest request, PortletResponse response)
|
||||
throws IOException {
|
||||
return ClassUtils.getShortName(ex.getClass());
|
||||
}
|
||||
|
||||
@ExceptionHandler
|
||||
public String handle2(IllegalArgumentException ex) {
|
||||
return ClassUtils.getShortName(ex.getClass());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue