From e0d7c6be00ed855e473f19f405ddbbcdb6cbb885 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 17 Feb 2016 22:59:12 +0100 Subject: [PATCH] Name attributes in method argument annotations allow for placeholders and expressions Issue: SPR-13952 --- ...tractNamedValueMethodArgumentResolver.java | 43 +++++++++------ .../HeaderMethodArgumentResolverTests.java | 44 +++++++++------ ...tractNamedValueMethodArgumentResolver.java | 43 +++++++++------ ...uestHeaderMethodArgumentResolverTests.java | 55 +++++++++++++------ 4 files changed, 115 insertions(+), 70 deletions(-) diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java index eca61cd403..dcf20f8381 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java @@ -87,10 +87,16 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); - Object arg = resolveArgumentInternal(nestedParameter, message, namedValueInfo.name); + Object resolvedName = resolveStringValue(namedValueInfo.name); + if (resolvedName == null) { + throw new IllegalArgumentException( + "Specified name must not resolve to null: [" + namedValueInfo.name + "]"); + } + + Object arg = resolveArgumentInternal(nestedParameter, message, resolvedName.toString()); if (arg == null) { if (namedValueInfo.defaultValue != null) { - arg = resolveDefaultValue(namedValueInfo.defaultValue); + arg = resolveStringValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required && !nestedParameter.isOptional()) { handleMissingValue(namedValueInfo.name, nestedParameter, message); @@ -98,7 +104,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType()); } else if ("".equals(arg) && namedValueInfo.defaultValue != null) { - arg = resolveDefaultValue(namedValueInfo.defaultValue); + arg = resolveStringValue(namedValueInfo.defaultValue); } if (!ClassUtils.isAssignableValue(parameter.getParameterType(), arg)) { @@ -148,6 +154,22 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle return new NamedValueInfo(name, info.required, defaultValue); } + /** + * Resolve the given annotation-specified value, + * potentially containing placeholders and expressions. + */ + private Object resolveStringValue(String value) { + if (this.configurableBeanFactory == null) { + return value; + } + String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value); + BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver(); + if (exprResolver == null) { + return value; + } + return exprResolver.evaluate(placeholdersResolved, this.expressionContext); + } + /** * Resolves the given parameter type and value name into an argument value. * @param parameter the method parameter to resolve to an argument value @@ -159,21 +181,6 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle protected abstract Object resolveArgumentInternal(MethodParameter parameter, Message message, String name) throws Exception; - /** - * Resolves the given default value into an argument value. - */ - private Object resolveDefaultValue(String defaultValue) { - if (this.configurableBeanFactory == null) { - return defaultValue; - } - String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(defaultValue); - BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver(); - if (exprResolver == null) { - return defaultValue; - } - return exprResolver.evaluate(placeholdersResolved, this.expressionContext); - } - /** * Invoked when a named value is required, but * {@link #resolveArgumentInternal(MethodParameter, Message, String)} returned {@code null} and diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java index 429b5ce8ac..52656e44b3 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -34,6 +34,7 @@ import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.support.MessageBuilder; import org.springframework.messaging.support.NativeMessageHeaderAccessor; +import org.springframework.util.ReflectionUtils; import static org.junit.Assert.*; @@ -49,7 +50,8 @@ public class HeaderMethodArgumentResolverTests { private MethodParameter paramRequired; private MethodParameter paramNamedDefaultValueStringHeader; - private MethodParameter paramSystemProperty; + private MethodParameter paramSystemPropertyDefaultValue; + private MethodParameter paramSystemPropertyName; private MethodParameter paramNotAnnotated; private MethodParameter paramNativeHeader; @@ -61,13 +63,13 @@ public class HeaderMethodArgumentResolverTests { cxt.refresh(); this.resolver = new HeaderMethodArgumentResolver(new DefaultConversionService(), cxt.getBeanFactory()); - Method method = getClass().getDeclaredMethod("handleMessage", - String.class, String.class, String.class, String.class, String.class); + Method method = ReflectionUtils.findMethod(getClass(), "handleMessage", (Class[]) null); this.paramRequired = new SynthesizingMethodParameter(method, 0); this.paramNamedDefaultValueStringHeader = new SynthesizingMethodParameter(method, 1); - this.paramSystemProperty = new SynthesizingMethodParameter(method, 2); - this.paramNotAnnotated = new SynthesizingMethodParameter(method, 3); - this.paramNativeHeader = new SynthesizingMethodParameter(method, 4); + this.paramSystemPropertyDefaultValue = new SynthesizingMethodParameter(method, 2); + this.paramSystemPropertyName = new SynthesizingMethodParameter(method, 3); + this.paramNotAnnotated = new SynthesizingMethodParameter(method, 4); + this.paramNativeHeader = new SynthesizingMethodParameter(method, 5); this.paramRequired.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); GenericTypeResolver.resolveParameterType(this.paramRequired, HeaderMethodArgumentResolver.class); @@ -84,18 +86,14 @@ public class HeaderMethodArgumentResolverTests { public void resolveArgument() throws Exception { Message message = MessageBuilder.withPayload(new byte[0]).setHeader("param1", "foo").build(); Object result = this.resolver.resolveArgument(this.paramRequired, message); - assertEquals("foo", result); } - // SPR-11326 - - @Test + @Test // SPR-11326 public void resolveArgumentNativeHeader() throws Exception { TestMessageHeaderAccessor headers = new TestMessageHeaderAccessor(); headers.setNativeHeader("param1", "foo"); Message message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build(); - assertEquals("foo", this.resolver.resolveArgument(this.paramRequired, message)); } @@ -120,7 +118,6 @@ public class HeaderMethodArgumentResolverTests { public void resolveArgumentDefaultValue() throws Exception { Message message = MessageBuilder.withPayload(new byte[0]).build(); Object result = this.resolver.resolveArgument(this.paramNamedDefaultValueStringHeader, message); - assertEquals("bar", result); } @@ -129,7 +126,7 @@ public class HeaderMethodArgumentResolverTests { System.setProperty("systemProperty", "sysbar"); try { Message message = MessageBuilder.withPayload(new byte[0]).build(); - Object result = resolver.resolveArgument(paramSystemProperty, message); + Object result = resolver.resolveArgument(paramSystemPropertyDefaultValue, message); assertEquals("sysbar", result); } finally { @@ -137,13 +134,26 @@ public class HeaderMethodArgumentResolverTests { } } + @Test + public void resolveNameFromSystemProperty() throws Exception { + System.setProperty("systemProperty", "sysbar"); + try { + Message message = MessageBuilder.withPayload(new byte[0]).setHeader("sysbar", "foo").build(); + Object result = resolver.resolveArgument(paramSystemPropertyName, message); + assertEquals("foo", result); + } + finally { + System.clearProperty("systemProperty"); + } + } - @SuppressWarnings("unused") - private void handleMessage( + + public void handleMessage( @Header String param1, @Header(name = "name", defaultValue = "bar") String param2, @Header(name = "name", defaultValue = "#{systemProperties.systemProperty}") String param3, - String param4, + @Header(name = "#{systemProperties.systemProperty}") String param4, + String param5, @Header("nativeHeaders.param1") String nativeHeaderParam1) { } diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java index 909cdad292..e2e92d6439 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java @@ -89,10 +89,16 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); - Object arg = resolveName(namedValueInfo.name, nestedParameter, webRequest); + Object resolvedName = resolveStringValue(namedValueInfo.name); + if (resolvedName == null) { + throw new IllegalArgumentException( + "Specified name must not resolve to null: [" + namedValueInfo.name + "]"); + } + + Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); if (arg == null) { if (namedValueInfo.defaultValue != null) { - arg = resolveDefaultValue(namedValueInfo.defaultValue); + arg = resolveStringValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required && !nestedParameter.isOptional()) { handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); @@ -100,7 +106,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType()); } else if ("".equals(arg) && namedValueInfo.defaultValue != null) { - arg = resolveDefaultValue(namedValueInfo.defaultValue); + arg = resolveStringValue(namedValueInfo.defaultValue); } if (binderFactory != null) { @@ -162,6 +168,22 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle return new NamedValueInfo(name, info.required, defaultValue); } + /** + * Resolve the given annotation-specified value, + * potentially containing placeholders and expressions. + */ + private Object resolveStringValue(String value) { + if (this.configurableBeanFactory == null) { + return value; + } + String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value); + BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver(); + if (exprResolver == null) { + return value; + } + return exprResolver.evaluate(placeholdersResolved, this.expressionContext); + } + /** * Resolve the given parameter type and value name into an argument value. * @param name the name of the value being resolved @@ -174,21 +196,6 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception; - /** - * Resolve the given default value into an argument value. - */ - private Object resolveDefaultValue(String defaultValue) { - if (this.configurableBeanFactory == null) { - return defaultValue; - } - String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(defaultValue); - BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver(); - if (exprResolver == null) { - return defaultValue; - } - return exprResolver.evaluate(placeholdersResolved, this.expressionContext); - } - /** * Invoked when a named value is required, but {@link #resolveName(String, MethodParameter, NativeWebRequest)} * returned {@code null} and there is no default value. Subclasses typically throw an exception in this case. diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java index b484e2ff0d..c8807b61d7 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -27,6 +27,7 @@ import org.springframework.core.MethodParameter; import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; +import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.context.request.NativeWebRequest; @@ -50,6 +51,7 @@ public class RequestHeaderMethodArgumentResolverTests { private MethodParameter paramNamedValueStringArray; private MethodParameter paramSystemProperty; private MethodParameter paramContextPath; + private MethodParameter paramResolvedName; private MethodParameter paramNamedValueMap; private MockHttpServletRequest servletRequest; @@ -64,12 +66,13 @@ public class RequestHeaderMethodArgumentResolverTests { context.refresh(); resolver = new RequestHeaderMethodArgumentResolver(context.getBeanFactory()); - Method method = getClass().getMethod("params", String.class, String[].class, String.class, String.class, Map.class); + Method method = ReflectionUtils.findMethod(getClass(), "params", (Class[]) null); paramNamedDefaultValueStringHeader = new SynthesizingMethodParameter(method, 0); paramNamedValueStringArray = new SynthesizingMethodParameter(method, 1); paramSystemProperty = new SynthesizingMethodParameter(method, 2); paramContextPath = new SynthesizingMethodParameter(method, 3); - paramNamedValueMap = new SynthesizingMethodParameter(method, 4); + paramResolvedName = new SynthesizingMethodParameter(method, 4); + paramNamedValueMap = new SynthesizingMethodParameter(method, 5); servletRequest = new MockHttpServletRequest(); webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse()); @@ -97,18 +100,16 @@ public class RequestHeaderMethodArgumentResolverTests { servletRequest.addHeader("name", expected); Object result = resolver.resolveArgument(paramNamedDefaultValueStringHeader, null, webRequest, null); - assertTrue(result instanceof String); assertEquals("Invalid result", expected, result); } @Test public void resolveStringArrayArgument() throws Exception { - String[] expected = new String[]{"foo", "bar"}; + String[] expected = new String[] {"foo", "bar"}; servletRequest.addHeader("name", expected); Object result = resolver.resolveArgument(paramNamedValueStringArray, null, webRequest, null); - assertTrue(result instanceof String[]); assertArrayEquals("Invalid result", expected, (String[]) result); } @@ -116,7 +117,6 @@ public class RequestHeaderMethodArgumentResolverTests { @Test public void resolveDefaultValue() throws Exception { Object result = resolver.resolveArgument(paramNamedDefaultValueStringHeader, null, webRequest, null); - assertTrue(result instanceof String); assertEquals("Invalid result", "bar", result); } @@ -124,18 +124,37 @@ public class RequestHeaderMethodArgumentResolverTests { @Test public void resolveDefaultValueFromSystemProperty() throws Exception { System.setProperty("systemProperty", "bar"); - Object result = resolver.resolveArgument(paramSystemProperty, null, webRequest, null); - System.clearProperty("systemProperty"); + try { + Object result = resolver.resolveArgument(paramSystemProperty, null, webRequest, null); + assertTrue(result instanceof String); + assertEquals("bar", result); + } + finally { + System.clearProperty("systemProperty"); + } + } - assertTrue(result instanceof String); - assertEquals("bar", result); + @Test + public void resolveNameFromSystemProperty() throws Exception { + String expected = "foo"; + servletRequest.addHeader("bar", expected); + + System.setProperty("systemProperty", "bar"); + try { + Object result = resolver.resolveArgument(paramResolvedName, null, webRequest, null); + assertTrue(result instanceof String); + assertEquals("Invalid result", expected, result); + } + finally { + System.clearProperty("systemProperty"); + } } @Test public void resolveDefaultValueFromRequest() throws Exception { servletRequest.setContextPath("/bar"); - Object result = resolver.resolveArgument(paramContextPath, null, webRequest, null); + Object result = resolver.resolveArgument(paramContextPath, null, webRequest, null); assertTrue(result instanceof String); assertEquals("/bar", result); } @@ -146,11 +165,13 @@ public class RequestHeaderMethodArgumentResolverTests { } - public void params(@RequestHeader(name = "name", defaultValue = "bar") String param1, - @RequestHeader("name") String[] param2, - @RequestHeader(name = "name", defaultValue="#{systemProperties.systemProperty}") String param3, - @RequestHeader(name = "name", defaultValue="#{request.contextPath}") String param4, - @RequestHeader("name") Map unsupported) { + public void params( + @RequestHeader(name = "name", defaultValue = "bar") String param1, + @RequestHeader("name") String[] param2, + @RequestHeader(name = "name", defaultValue="#{systemProperties.systemProperty}") String param3, + @RequestHeader(name = "name", defaultValue="#{request.contextPath}") String param4, + @RequestHeader("#{systemProperties.systemProperty}") String param5, + @RequestHeader("name") Map unsupported) { } }