From 49ba677b64a92a2678461791738e20decd98a6c4 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 20 Jan 2009 18:32:24 +0000 Subject: [PATCH] introduced @CookieValue annotation --- .../AnnotationMethodHandlerAdapter.java | 15 +++ .../web/portlet/util/PortletUtils.java | 25 ++++- .../Portlet20AnnotationControllerTests.java | 73 ++++++++---- .../web/bind/annotation/CookieValue.java | 67 +++++++++++ .../web/bind/annotation/RequestHeader.java | 6 +- .../web/bind/annotation/RequestParam.java | 6 +- .../support/HandlerMethodInvoker.java | 106 ++++++++++++++---- .../AnnotationMethodHandlerAdapter.java | 33 +++--- .../ServletAnnotationControllerTests.java | 22 ++-- .../springframework/web/util/WebUtils.java | 3 +- 10 files changed, 276 insertions(+), 80 deletions(-) create mode 100644 org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/CookieValue.java diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java index 94ce3556200..c121470875d 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java @@ -50,6 +50,7 @@ import javax.portlet.ResourceRequest; import javax.portlet.ResourceResponse; import javax.portlet.UnavailableException; import javax.portlet.WindowState; +import javax.servlet.http.Cookie; import org.springframework.beans.BeanUtils; import org.springframework.core.Conventions; @@ -578,6 +579,20 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator impl } } + @Override + protected Object resolveCookieValue(String cookieName, Class paramType, NativeWebRequest webRequest) + throws Exception { + + PortletRequest portletRequest = (PortletRequest) webRequest.getNativeRequest(); + Cookie cookieValue = PortletUtils.getCookie(portletRequest, cookieName); + if (Cookie.class.isAssignableFrom(paramType)) { + return cookieValue; + } + else { + return cookieValue.getValue(); + } + } + @Override protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest) throws Exception { diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/util/PortletUtils.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/util/PortletUtils.java index 2e0dd8d8476..5ed7ec5a89b 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/util/PortletUtils.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/util/PortletUtils.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. @@ -20,15 +20,14 @@ import java.io.File; import java.io.FileNotFoundException; import java.util.Enumeration; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.TreeMap; - import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.PortletContext; import javax.portlet.PortletRequest; import javax.portlet.PortletSession; +import javax.servlet.http.Cookie; import org.springframework.util.Assert; import org.springframework.web.util.WebUtils; @@ -270,6 +269,7 @@ public abstract class PortletUtils { return mutex; } + /** * Expose the given Map as request attributes, using the keys as attribute names * and the values as corresponding attribute values. Keys must be Strings. @@ -284,6 +284,25 @@ public abstract class PortletUtils { } } + /** + * Retrieve the first cookie with the given name. Note that multiple + * cookies can have the same name but different paths or domains. + * @param request current servlet request + * @param name cookie name + * @return the first cookie with the given name, or null if none is found + */ + public static Cookie getCookie(PortletRequest request, String name) { + Assert.notNull(request, "Request must not be null"); + Cookie cookies[] = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (name.equals(cookie.getName())) { + return cookie; + } + } + } + return null; + } /** * Check if a specific input type="submit" parameter was sent in the request, diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/mvc/annotation/Portlet20AnnotationControllerTests.java b/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/mvc/annotation/Portlet20AnnotationControllerTests.java index fae16d091b6..306529ceca9 100644 --- a/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/mvc/annotation/Portlet20AnnotationControllerTests.java +++ b/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/mvc/annotation/Portlet20AnnotationControllerTests.java @@ -36,8 +36,10 @@ import javax.portlet.RenderResponse; import javax.portlet.StateAwareResponse; import javax.portlet.UnavailableException; import javax.portlet.WindowState; +import javax.servlet.http.Cookie; -import junit.framework.TestCase; +import static org.junit.Assert.*; +import org.junit.Test; import org.springframework.beans.BeansException; import org.springframework.beans.DerivedTestBean; @@ -67,8 +69,10 @@ import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.support.WebArgumentResolver; @@ -90,9 +94,10 @@ import org.springframework.web.portlet.mvc.AbstractController; * @author Juergen Hoeller * @since 3.0 */ -public class Portlet20AnnotationControllerTests extends TestCase { +public class Portlet20AnnotationControllerTests { - public void testStandardHandleMethod() throws Exception { + @Test + public void standardHandleMethod() throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { GenericWebApplicationContext wac = new GenericWebApplicationContext(); @@ -109,19 +114,22 @@ public class Portlet20AnnotationControllerTests extends TestCase { assertEquals("test", response.getContentAsString()); } - public void testAdaptedHandleMethods() throws Exception { + @Test + public void adaptedHandleMethods() throws Exception { doTestAdaptedHandleMethods(MyAdaptedController.class); } - public void testAdaptedHandleMethods2() throws Exception { + @Test + public void adaptedHandleMethods2() throws Exception { doTestAdaptedHandleMethods(MyAdaptedController2.class); } - public void testAdaptedHandleMethods3() throws Exception { + @Test + public void adaptedHandleMethods3() throws Exception { doTestAdaptedHandleMethods(MyAdaptedController3.class); } - public void doTestAdaptedHandleMethods(final Class controllerClass) throws Exception { + private void doTestAdaptedHandleMethods(final Class controllerClass) throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { GenericWebApplicationContext wac = new GenericWebApplicationContext(); @@ -140,9 +148,11 @@ public class Portlet20AnnotationControllerTests extends TestCase { MockRenderRequest request = new MockRenderRequest(PortletMode.EDIT); request.addParameter("param1", "value1"); request.addParameter("param2", "2"); + request.addProperty("header1", "10"); + request.setCookies(new Cookie("cookie1", "3")); MockRenderResponse response = new MockRenderResponse(); portlet.render(request, response); - assertEquals("test-value1-2", response.getContentAsString()); + assertEquals("test-value1-2-10-3", response.getContentAsString()); request = new MockRenderRequest(PortletMode.HELP); request.addParameter("name", "name1"); @@ -159,7 +169,8 @@ public class Portlet20AnnotationControllerTests extends TestCase { assertEquals("test-name1-typeMismatch", response.getContentAsString()); } - public void testFormController() throws Exception { + @Test + public void formController() throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { GenericWebApplicationContext wac = new GenericWebApplicationContext(); @@ -181,7 +192,8 @@ public class Portlet20AnnotationControllerTests extends TestCase { assertEquals("myView-name1-typeMismatch-tb1-myValue", response.getContentAsString()); } - public void testModelFormController() throws Exception { + @Test + public void modelFormController() throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { GenericWebApplicationContext wac = new GenericWebApplicationContext(); @@ -203,7 +215,8 @@ public class Portlet20AnnotationControllerTests extends TestCase { assertEquals("myView-name1-typeMismatch-tb1-myValue", response.getContentAsString()); } - public void testCommandProvidingFormController() throws Exception { + @Test + public void commandProvidingFormController() throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { GenericWebApplicationContext wac = new GenericWebApplicationContext(); @@ -229,7 +242,8 @@ public class Portlet20AnnotationControllerTests extends TestCase { assertEquals("myView-String:myDefaultName-typeMismatch-tb1-myOriginalValue", response.getContentAsString()); } - public void testTypedCommandProvidingFormController() throws Exception { + @Test + public void typedCommandProvidingFormController() throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { GenericWebApplicationContext wac = new GenericWebApplicationContext(); @@ -276,7 +290,8 @@ public class Portlet20AnnotationControllerTests extends TestCase { assertEquals("myView-myName-typeMismatch-tb1-myOriginalValue", response.getContentAsString()); } - public void testBinderInitializingCommandProvidingFormController() throws Exception { + @Test + public void binderInitializingCommandProvidingFormController() throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { GenericWebApplicationContext wac = new GenericWebApplicationContext(); @@ -299,7 +314,8 @@ public class Portlet20AnnotationControllerTests extends TestCase { assertEquals("myView-String:myDefaultName-typeMismatch-tb1-myOriginalValue", response.getContentAsString()); } - public void testSpecificBinderInitializingCommandProvidingFormController() throws Exception { + @Test + public void specificBinderInitializingCommandProvidingFormController() throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { StaticPortletApplicationContext wac = new StaticPortletApplicationContext(); @@ -322,7 +338,8 @@ public class Portlet20AnnotationControllerTests extends TestCase { assertEquals("myView-String:myDefaultName-typeMismatch-tb1-myOriginalValue", response.getContentAsString()); } - public void testParameterDispatchingController() throws Exception { + @Test + public void parameterDispatchingController() throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { StaticPortletApplicationContext wac = new StaticPortletApplicationContext(); @@ -362,7 +379,8 @@ public class Portlet20AnnotationControllerTests extends TestCase { assertEquals("mySurpriseView", response.getContentAsString()); } - public void testTypeLevelParameterDispatchingController() throws Exception { + @Test + public void typeLevelParameterDispatchingController() throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { StaticPortletApplicationContext wac = new StaticPortletApplicationContext(); @@ -447,7 +465,8 @@ public class Portlet20AnnotationControllerTests extends TestCase { assertEquals("mySurpriseView", response.getContentAsString()); } - public void testPortlet20DispatchingController() throws Exception { + @Test + public void portlet20DispatchingController() throws Exception { DispatcherPortlet portlet = new DispatcherPortlet() { protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) throws BeansException { StaticPortletApplicationContext wac = new StaticPortletApplicationContext(); @@ -525,6 +544,10 @@ public class Portlet20AnnotationControllerTests extends TestCase { } + /* + * Controllers + */ + @RequestMapping("VIEW") private static class MyController extends AbstractController { @@ -546,8 +569,10 @@ public class Portlet20AnnotationControllerTests extends TestCase { @RequestMapping("EDIT") @RenderMapping - public void myHandle(@RequestParam("param1")String p1, @RequestParam("param2")int p2, RenderResponse response) throws IOException { - response.getWriter().write("test-" + p1 + "-" + p2); + public void myHandle(@RequestParam("param1") String p1, @RequestParam("param2") int p2, + @RequestHeader("header1") long h1, @CookieValue("cookie1") Cookie c1, + RenderResponse response) throws IOException { + response.getWriter().write("test-" + p1 + "-" + p2 + "-" + h1 + "-" + c1.getValue()); } @RequestMapping("HELP") @@ -575,8 +600,9 @@ public class Portlet20AnnotationControllerTests extends TestCase { @RequestMapping("EDIT") @RenderMapping - public void myHandle(@RequestParam("param1")String p1, int param2, RenderResponse response) throws IOException { - response.getWriter().write("test-" + p1 + "-" + param2); + public void myHandle(@RequestParam("param1") String p1, int param2, RenderResponse response, + @RequestHeader("header1") String h1, @CookieValue("cookie1") String c1) throws IOException { + response.getWriter().write("test-" + p1 + "-" + param2 + "-" + h1 + "-" + c1); } @RequestMapping("HELP") @@ -604,8 +630,9 @@ public class Portlet20AnnotationControllerTests extends TestCase { @RequestMapping("EDIT") @RenderMapping - public void myHandle(@RequestParam("param1")String p1, @RequestParam("param2")int p2, RenderResponse response) throws IOException { - response.getWriter().write("test-" + p1 + "-" + p2); + public void myHandle(@RequestParam("param1") String p1, int param2, @RequestHeader Integer header1, + @CookieValue int cookie1, RenderResponse response) throws IOException { + response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1); } @RequestMapping("HELP") diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/CookieValue.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/CookieValue.java new file mode 100644 index 00000000000..281c5c2072c --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/CookieValue.java @@ -0,0 +1,67 @@ +/* + * 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation which indicates that a method parameter should be bound to an HTTP cookie. + * Supported for annotated handler methods in Servlet and Portlet environments. + * + *

The method parameter may be declared as type {@link javax.servlet.http.Cookie} + * or as cookie value type (String, int, etc). + * + * @author Juergen Hoeller + * @since 3.0 + * @see RequestMapping + * @see RequestParam + * @see RequestHeader + * @see org.springframework.web.bind.annotation.RequestMapping + * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter + * @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CookieValue { + + /** + * The name of the cookie to bind to. + */ + String value() default ""; + + /** + * Whether the header is required. + *

Default is true, leading to an exception thrown in case + * of the header missing in the request. Switch this to false + * if you prefer a null in case of the header missing. + *

Alternatively, provide a {@link #defaultValue() defaultValue}, + * which implicitely sets this flag to false. + */ + boolean required() default true; + + /** + * The default value to use as a fallback. Supplying a default value implicitely + * sets {@link #required()} to false. + */ + String defaultValue() default ""; + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java index 4918166643c..e9214a2de43 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestHeader.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. @@ -24,11 +24,13 @@ import java.lang.annotation.Target; /** * Annotation which indicates that a method parameter should be bound to a web request header. - * Supported for {@link RequestMapping} annotated handler methods in Servlet and Portlet environments. + * Supported for annotated handler methods in Servlet and Portlet environments. * * @author Juergen Hoeller * @since 3.0 * @see RequestMapping + * @see RequestParam + * @see CookieValue * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter * @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter */ diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestParam.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestParam.java index 69ba92bd56b..acfb1939527 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestParam.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestParam.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. @@ -24,12 +24,14 @@ import java.lang.annotation.Target; /** * Annotation which indicates that a method parameter should be bound to a web request parameter. - * Supported for {@link RequestMapping} annotated handler methods in Servlet and Portlet environments. + * Supported for annotated handler methods in Servlet and Portlet environments. * * @author Arjen Poutsma * @author Juergen Hoeller * @since 2.5 * @see RequestMapping + * @see RequestHeader + * @see CookieValue * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter * @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter */ diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java index 3a4c4a962bc..3bea9a84162 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.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. @@ -43,6 +43,7 @@ import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; @@ -158,10 +159,11 @@ public class HandlerMethodInvoker { GenericTypeResolver.resolveParameterType(methodParam, handler.getClass()); String paramName = null; String headerName = null; - boolean required = false; - String defaultValue = null; + String cookieName = null; String pathVarName = null; String attrName = null; + boolean required = false; + String defaultValue = null; int found = 0; Annotation[] paramAnns = methodParam.getParameterAnnotations(); @@ -180,9 +182,11 @@ public class HandlerMethodInvoker { defaultValue = requestHeader.defaultValue(); found++; } - else if (ModelAttribute.class.isInstance(paramAnn)) { - ModelAttribute attr = (ModelAttribute) paramAnn; - attrName = attr.value(); + else if (CookieValue.class.isInstance(paramAnn)) { + CookieValue cookieValue = (CookieValue) paramAnn; + cookieName = cookieValue.value(); + required = cookieValue.required(); + defaultValue = cookieValue.defaultValue(); found++; } else if (PathVariable.class.isInstance(paramAnn)) { @@ -190,6 +194,11 @@ public class HandlerMethodInvoker { pathVarName = pathVar.value(); found++; } + else if (ModelAttribute.class.isInstance(paramAnn)) { + ModelAttribute attr = (ModelAttribute) paramAnn; + attrName = attr.value(); + found++; + } } if (found > 1) { @@ -229,6 +238,12 @@ public class HandlerMethodInvoker { else if (headerName != null) { args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler); } + else if (cookieName != null) { + args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler); + } + else if (pathVarName != null) { + args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler); + } else if (attrName != null) { WebDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler); boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1])); @@ -242,9 +257,6 @@ public class HandlerMethodInvoker { } implicitModel.putAll(binder.getBindingResult().getModel()); } - else if (pathVarName != null) { - args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler); - } } return args; @@ -406,6 +418,62 @@ public class HandlerMethodInvoker { return binder.convertIfNecessary(headerValue, paramType, methodParam); } + private Object resolveCookieValue(String cookieName, boolean required, String defaultValue, + MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) + throws Exception { + + Class paramType = methodParam.getParameterType(); + if (cookieName.length() == 0) { + cookieName = getRequiredParameterName(methodParam); + } + Object cookieValue = resolveCookieValue(cookieName, paramType, webRequest); + if (cookieValue == null) { + if (StringUtils.hasText(defaultValue)) { + cookieValue = defaultValue; + } + else if (required) { + raiseMissingCookieException(cookieName, paramType); + } + checkValue(cookieName, cookieValue, paramType); + } + WebDataBinder binder = createBinder(webRequest, null, cookieName); + initBinder(handlerForInitBinderCall, cookieName, binder, webRequest); + return binder.convertIfNecessary(cookieValue, paramType, methodParam); + } + + /** + * Resolves the given {@link CookieValue @CookieValue} annotation. + * Throws an UnsupportedOperationException by default. + */ + protected Object resolveCookieValue(String cookieName, Class paramType, NativeWebRequest webRequest) + throws Exception { + + throw new UnsupportedOperationException("@CookieValue not supported"); + } + + private Object resolvePathVariable(String pathVarName, MethodParameter methodParam, + NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception { + + Class paramType = methodParam.getParameterType(); + if (pathVarName.length() == 0) { + pathVarName = getRequiredParameterName(methodParam); + } + String pathVarValue = resolvePathVariable(pathVarName, paramType, webRequest); + WebDataBinder binder = createBinder(webRequest, null, pathVarName); + initBinder(handlerForInitBinderCall, pathVarName, binder, webRequest); + return binder.convertIfNecessary(pathVarValue, paramType, methodParam); + } + + /** + * Resolves the given {@link PathVariable @PathVariable} annotation. + * Throws an UnsupportedOperationException by default. + */ + protected String resolvePathVariable(String pathVarName, Class paramType, NativeWebRequest webRequest) + throws Exception { + + throw new UnsupportedOperationException("@PathVariable not supported"); + } + private String getRequiredParameterName(MethodParameter methodParam) { String name = methodParam.getParameterName(); if (name == null) { @@ -452,17 +520,6 @@ public class HandlerMethodInvoker { return binder; } - /** - * Resolves the given {@link org.springframework.web.bind.annotation.PathVariable @PathVariable} - * variable. Throws an UnsupportedOperationException by default. Overridden in - * {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.ServletHandlerMethodInvoker}. - */ - protected Object resolvePathVariable(String pathVarName, MethodParameter methodParam, - NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception { - - throw new UnsupportedOperationException("@PathVariable not supported"); - } - @SuppressWarnings("unchecked") public final void updateModelAttributes(Object handler, Map mavModel, @@ -525,18 +582,19 @@ public class HandlerMethodInvoker { throw new IllegalStateException("Missing header '" + headerName + "' of type [" + paramType.getName() + "]"); } + protected void raiseMissingCookieException(String cookieName, Class paramType) throws Exception { + throw new IllegalStateException("Missing cookie value '" + cookieName + "' of type [" + paramType.getName() + "]"); + } + protected void raiseSessionRequiredException(String message) throws Exception { throw new IllegalStateException(message); } - protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) - throws Exception { - + protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception { return new WebRequestDataBinder(target, objectName); } protected void doBind(NativeWebRequest webRequest, WebDataBinder binder, boolean failOnErrors) throws Exception { - WebRequestDataBinder requestBinder = (WebRequestDataBinder) binder; requestBinder.bind(webRequest); if (failOnErrors) { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java index fb39deef9d9..86f189fa6eb 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java @@ -32,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @@ -653,29 +654,31 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen } @Override - @SuppressWarnings({"unchecked"}) - protected Object resolvePathVariable(String pathVarName, MethodParameter methodParam, - NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception { + protected Object resolveCookieValue(String cookieName, Class paramType, NativeWebRequest webRequest) + throws Exception { - Class paramType = methodParam.getParameterType(); - if (pathVarName.length() == 0) { - pathVarName = methodParam.getParameterName(); - if (pathVarName == null) { - throw new IllegalStateException("No variable name specified for @PathVariable argument of type [" + - paramType.getName() + "], and no parameter name information found in class file either."); - } + HttpServletRequest servletRequest = (HttpServletRequest) webRequest.getNativeRequest(); + Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName); + if (Cookie.class.isAssignableFrom(paramType)) { + return cookieValue; } + else { + return cookieValue.getValue(); + } + } + + @Override + @SuppressWarnings({"unchecked"}) + protected String resolvePathVariable(String pathVarName, Class paramType, NativeWebRequest webRequest) + throws Exception { + HttpServletRequest servletRequest = (HttpServletRequest) webRequest.getNativeRequest(); Map uriTemplateVariables = (Map) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); if (uriTemplateVariables == null || !uriTemplateVariables.containsKey(pathVarName)) { throw new IllegalStateException("Could not find @PathVariable [" + pathVarName + "] in @RequestMapping"); } - String pathVarValue = uriTemplateVariables.get(pathVarName); - - WebDataBinder binder = createBinder(webRequest, null, pathVarName); - initBinder(handlerForInitBinderCall, pathVarName, binder, webRequest); - return binder.convertIfNecessary(pathVarValue, paramType, methodParam); + return uriTemplateVariables.get(pathVarName); } @Override 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 d0f532d5b37..76526cb1275 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 @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Set; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @@ -61,6 +62,7 @@ import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestHeader; @@ -283,9 +285,10 @@ public class ServletAnnotationControllerTests { request.addParameter("param1", "value1"); request.addParameter("param2", "2"); request.addHeader("header1", "10"); + request.setCookies(new Cookie("cookie1", "3")); response = new MockHttpServletResponse(); servlet.service(request, response); - assertEquals("test-value1-2-10", response.getContentAsString()); + assertEquals("test-value1-2-10-3", response.getContentAsString()); request = new MockHttpServletRequest("GET", "/myPath3.do"); request.addParameter("param1", "value1"); @@ -835,8 +838,9 @@ public class ServletAnnotationControllerTests { @RequestMapping("/myPath2.do") public void myHandle(@RequestParam("param1") String p1, @RequestParam("param2") int p2, - @RequestHeader("header1") long h1, HttpServletResponse response) throws IOException { - response.getWriter().write("test-" + p1 + "-" + p2 + "-" + h1); + @RequestHeader("header1") long h1, @CookieValue("cookie1") Cookie c1, + HttpServletResponse response) throws IOException { + response.getWriter().write("test-" + p1 + "-" + p2 + "-" + h1 + "-" + c1.getValue()); } @RequestMapping("/myPath3") @@ -862,8 +866,8 @@ public class ServletAnnotationControllerTests { @RequestMapping("/myPath2.do") public void myHandle(@RequestParam("param1") String p1, int param2, HttpServletResponse response, - @RequestHeader("header1") String h1) throws IOException { - response.getWriter().write("test-" + p1 + "-" + param2 + "-" + h1); + @RequestHeader("header1") String h1, @CookieValue("cookie1") String c1) throws IOException { + response.getWriter().write("test-" + p1 + "-" + param2 + "-" + h1 + "-" + c1); } @RequestMapping("/myPath3") @@ -883,8 +887,8 @@ public class ServletAnnotationControllerTests { @RequestMapping("/myPath2.do") public void myHandle(@RequestParam("param1") T p1, int param2, @RequestHeader Integer header1, - HttpServletResponse response) throws IOException { - response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1); + @CookieValue int cookie1, HttpServletResponse response) throws IOException { + response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1); } @InitBinder @@ -907,8 +911,8 @@ public class ServletAnnotationControllerTests { @Override public void myHandle(@RequestParam("param1") String p1, int param2, @RequestHeader Integer header1, - HttpServletResponse response) throws IOException { - response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1); + @CookieValue int cookie1, HttpServletResponse response) throws IOException { + response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1); } @RequestMapping("/myPath3") diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java b/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java index 40b226124d3..af858775572 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java +++ b/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.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. @@ -493,7 +493,6 @@ public abstract class WebUtils { return null; } - /** * Check if a specific input type="submit" parameter was sent in the request, * either via a button (directly with name) or via an image (name + ".x" or