From 90b5c3a8dd8902e1ab418083001a3d16138447ad Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 15 Feb 2009 21:31:20 +0000 Subject: [PATCH] @RequestMapping type-level param constraints taken into account consistently --- ...ssingPortletRequestParameterException.java | 12 +-- ...ssingServletRequestParameterException.java | 10 +-- ...sfiedServletRequestParameterException.java | 88 +++++++++++++++++++ .../DefaultAnnotationHandlerMapping.java | 16 ++-- .../ServletAnnotationControllerTests.java | 78 +++++++++++++++- 5 files changed, 183 insertions(+), 21 deletions(-) create mode 100644 org.springframework.web.servlet/src/main/java/org/springframework/web/bind/UnsatisfiedServletRequestParameterException.java diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/MissingPortletRequestParameterException.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/MissingPortletRequestParameterException.java index 05f7e7a557..34a1510dd1 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/MissingPortletRequestParameterException.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/MissingPortletRequestParameterException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * 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. @@ -17,16 +17,16 @@ package org.springframework.web.portlet.bind; /** - * PortletRequestBindingException subclass that indicates a missing parameter. + * {@link PortletRequestBindingException} subclass that indicates a missing parameter. * * @author Juergen Hoeller * @since 2.0.2 */ public class MissingPortletRequestParameterException extends PortletRequestBindingException { - private String parameterName; + private final String parameterName; - private String parameterType; + private final String parameterType; /** @@ -49,14 +49,14 @@ public class MissingPortletRequestParameterException extends PortletRequestBindi /** * Return the name of the offending parameter. */ - public String getParameterName() { + public final String getParameterName() { return this.parameterName; } /** * Return the expected type of the offending parameter. */ - public String getParameterType() { + public final String getParameterType() { return this.parameterType; } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java index ea723d2364..fdd22f9754 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * 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. @@ -24,9 +24,9 @@ package org.springframework.web.bind; */ public class MissingServletRequestParameterException extends ServletRequestBindingException { - private String parameterName; + private final String parameterName; - private String parameterType; + private final String parameterType; /** @@ -49,14 +49,14 @@ public class MissingServletRequestParameterException extends ServletRequestBindi /** * Return the name of the offending parameter. */ - public String getParameterName() { + public final String getParameterName() { return this.parameterName; } /** * Return the expected type of the offending parameter. */ - public String getParameterType() { + public final String getParameterType() { return this.parameterType; } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/UnsatisfiedServletRequestParameterException.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/UnsatisfiedServletRequestParameterException.java new file mode 100644 index 0000000000..8cd7d019bc --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/UnsatisfiedServletRequestParameterException.java @@ -0,0 +1,88 @@ +/* + * 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.bind; + +import java.util.Iterator; +import java.util.Map; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * {@link ServletRequestBindingException} subclass that indicates an unsatisfied + * parameter condition, as typically expressed using an @RequestMapping + * annotation at the @Controller type level. + * + * @author Juergen Hoeller + * @since 3.0 + * @see org.springframework.web.bind.annotation.RequestMapping#params() + */ +public class UnsatisfiedServletRequestParameterException extends ServletRequestBindingException { + + private final String[] paramConditions; + + private final Map actualParams; + + + /** + * Create a new UnsatisfiedServletRequestParameterException. + * @param paramConditions the parameter conditions that have been violated + * @param actualParams the actual parameter Map associated with the ServletRequest + */ + @SuppressWarnings("unchecked") + public UnsatisfiedServletRequestParameterException(String[] paramConditions, Map actualParams) { + super(""); + this.paramConditions = paramConditions; + this.actualParams = (Map) actualParams; + } + + + @Override + public String getMessage() { + return "Parameter conditions \"" + StringUtils.arrayToDelimitedString(this.paramConditions, ", ") + + "\" not met for actual request parameters: " + requestParameterMapToString(this.actualParams); + } + + private static String requestParameterMapToString(Map actualParams) { + StringBuilder result = new StringBuilder(); + for (Iterator> it = actualParams.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + result.append(entry.getKey()).append('=').append(ObjectUtils.nullSafeToString(entry.getValue())); + if (it.hasNext()) { + result.append(", "); + } + } + return result.toString(); + } + + /** + * Return the parameter conditions that have been violated. + * @see org.springframework.web.bind.annotation.RequestMapping#params() + */ + public final String[] getParamConditions() { + return this.paramConditions; + } + + /** + * Return the actual parameter Map associated with the ServletRequest. + * @see javax.servlet.ServletRequest#getParameterMap() + */ + public final Map getActualParams() { + return this.actualParams; + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java index 2ed0bfa1c1..3137d73204 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * 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. @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import org.springframework.context.ApplicationContext; @@ -30,6 +29,7 @@ import org.springframework.stereotype.Controller; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.UnsatisfiedServletRequestParameterException; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping; @@ -172,9 +172,11 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler */ @Override protected void validateHandler(Object handler, HttpServletRequest request) throws Exception { - RequestMapping mapping = this.cachedMappings.get(handler.getClass()); + Class handlerClass = (handler instanceof String ? + getApplicationContext().getType((String) handler) : handler.getClass()); + RequestMapping mapping = this.cachedMappings.get(handlerClass); if (mapping == null) { - mapping = AnnotationUtils.findAnnotation(handler.getClass(), RequestMapping.class); + mapping = AnnotationUtils.findAnnotation(handlerClass, RequestMapping.class); } if (mapping != null) { validateMapping(mapping, request); @@ -200,12 +202,8 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler String[] mappedParams = mapping.params(); if (!ServletAnnotationMappingUtils.checkParameters(mappedParams, request)) { - throw new ServletException("Parameter conditions {" + - StringUtils.arrayToDelimitedString(mappedParams, ", ") + - "} not met for request parameters: " + request.getParameterMap()); + throw new UnsatisfiedServletRequestParameterException(mappedParams, request.getParameterMap()); } } - - } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java index 2d184e3d90..0740744aef 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * 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. @@ -584,6 +584,66 @@ public class ServletAnnotationControllerTests { assertEquals("mySurpriseView", response.getContentAsString()); } + @Test + public void constrainedParameterDispatchingController() throws Exception { + final MockServletContext servletContext = new MockServletContext(); + final MockServletConfig servletConfig = new MockServletConfig(servletContext); + + @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { + @Override + protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { + GenericWebApplicationContext wac = new GenericWebApplicationContext(); + wac.setServletContext(servletContext); + RootBeanDefinition bd = new RootBeanDefinition(MyConstrainedParameterDispatchingController.class); + bd.setScope(WebApplicationContext.SCOPE_REQUEST); + wac.registerBeanDefinition("controller", bd); + wac.refresh(); + return wac; + } + }; + servlet.init(servletConfig); + + MockHttpServletRequest request = new MockHttpServletRequest(servletContext, "GET", "/myPath.do"); + request.addParameter("view", "other"); + MockHttpServletResponse response = new MockHttpServletResponse(); + try { + servlet.service(request, response); + fail("Should have failed because of type-level parameter constraint not met"); + } + catch (ServletException ex) { + // expected + ex.printStackTrace(); + } + + request = new MockHttpServletRequest(servletContext, "GET", "/myPath.do"); + request.addParameter("active", "true"); + request.addParameter("view", "other"); + response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("myOtherView", response.getContentAsString()); + + request = new MockHttpServletRequest(servletContext, "GET", "/myPath.do"); + request.addParameter("view", "my"); + request.addParameter("lang", "de"); + response = new MockHttpServletResponse(); + try { + servlet.service(request, response); + fail("Should have failed because of type-level parameter constraint not met"); + } + catch (ServletException ex) { + // expected + ex.printStackTrace(); + } + + request = new MockHttpServletRequest(servletContext, "GET", "/myPath.do"); + request.addParameter("view", "my"); + request.addParameter("lang", "de"); + request.addParameter("active", "true"); + response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("myLangView", response.getContentAsString()); + } + @Test public void methodNameDispatchingController() throws Exception { @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { @@ -1189,6 +1249,22 @@ public class ServletAnnotationControllerTests { } + @Controller + @RequestMapping(value = "/myPath.do", params = {"active"}) + private static class MyConstrainedParameterDispatchingController { + + @RequestMapping(params = {"view", "!lang"}) + public void myOtherHandle(HttpServletResponse response) throws IOException { + response.getWriter().write("myOtherView"); + } + + @RequestMapping(method = RequestMethod.GET, params = {"view=my", "lang=de"}) + public void myLangHandle(HttpServletResponse response) throws IOException { + response.getWriter().write("myLangView"); + } + } + + @Controller @RequestMapping(value = "/*.do", method = RequestMethod.POST, params = "myParam=myValue") private static class MyPostMethodNameDispatchingController extends MethodNameDispatchingController {