Browse Source
Prior to this commit, @SubscribeEvent @UnsubscribeEvent and @MessageMapping annotated message handling methods could only match a strict message destination. This commit adds a @PathVariable annotation and updates the message matching/handling process, since message handling methods can now match PathMatcher-like destinations and get path variables injected in parameters. Issue: SPR-10949pull/386/merge
5 changed files with 392 additions and 5 deletions
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
/* |
||||
* Copyright 2002-2013 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.messaging.handler.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 a path template |
||||
* variable. Supported for {@link org.springframework.messaging.simp.annotation.SubscribeEvent}, |
||||
* {@link org.springframework.messaging.simp.annotation.UnsubscribeEvent}, |
||||
* {@link org.springframework.messaging.handler.annotation.MessageMapping} |
||||
* annotated handler methods. |
||||
* |
||||
* <p>A {@code @PathVariable} template variable is always required and does not have |
||||
* a default value to fall back on. |
||||
* |
||||
* @author Brian Clozel |
||||
* @see org.springframework.messaging.simp.annotation.SubscribeEvent |
||||
* @see org.springframework.messaging.simp.annotation.UnsubscribeEvent |
||||
* @see org.springframework.messaging.handler.annotation.MessageMapping |
||||
* @since 4.0 |
||||
*/ |
||||
@Target(ElementType.PARAMETER) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Documented |
||||
public @interface PathVariable { |
||||
|
||||
/** The path template variable to bind to. */ |
||||
String value() default ""; |
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
/* |
||||
* Copyright 2002-2013 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.messaging.handler.annotation.support; |
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.core.convert.ConversionService; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.handler.annotation.PathVariable; |
||||
import org.springframework.messaging.handler.annotation.ValueConstants; |
||||
import org.springframework.messaging.simp.handler.AnnotationMethodMessageHandler; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Resolves method parameters annotated with {@link PathVariable @PathVariable}. |
||||
* |
||||
* <p>A @{@link PathVariable} is a named value that gets resolved from a path |
||||
* template variable that matches the Message destination header. |
||||
* It is always required and does not have a default value to fall back on. |
||||
* |
||||
* @author Brian Clozel |
||||
* @see org.springframework.messaging.handler.annotation.PathVariable |
||||
* @see org.springframework.messaging.MessageHeaders |
||||
* @since 4.0 |
||||
*/ |
||||
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { |
||||
|
||||
public PathVariableMethodArgumentResolver(ConversionService cs, ConfigurableBeanFactory beanFactory) { |
||||
super(cs, beanFactory); |
||||
} |
||||
|
||||
@Override |
||||
public boolean supportsParameter(MethodParameter parameter) { |
||||
return parameter.hasParameterAnnotation(PathVariable.class); |
||||
} |
||||
|
||||
@Override |
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { |
||||
PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class); |
||||
return new PathVariableNamedValueInfo(annotation); |
||||
} |
||||
|
||||
@Override |
||||
protected Object resolveArgumentInternal(MethodParameter parameter, Message<?> message, String name) throws Exception { |
||||
Map<String, String> pathTemplateVars = |
||||
(Map<String, String>) message.getHeaders().get(AnnotationMethodMessageHandler.PATH_TEMPLATE_VARIABLES_HEADER); |
||||
return (pathTemplateVars != null) ? pathTemplateVars.get(name) : null; |
||||
} |
||||
|
||||
@Override |
||||
protected void handleMissingValue(String name, MethodParameter parameter, Message<?> message) { |
||||
throw new MessageHandlingException(message, "Missing path template variable '" + name + |
||||
"' for method parameter type [" + parameter.getParameterType() + "]"); |
||||
} |
||||
|
||||
private static class PathVariableNamedValueInfo extends NamedValueInfo { |
||||
|
||||
private PathVariableNamedValueInfo(PathVariable annotation) { |
||||
super(annotation.value(), true, ValueConstants.DEFAULT_NONE); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
/* |
||||
* Copyright 2002-2013 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.messaging.handler.annotation.support; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.context.support.GenericApplicationContext; |
||||
import org.springframework.core.DefaultParameterNameDiscoverer; |
||||
import org.springframework.core.GenericTypeResolver; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.core.convert.support.DefaultConversionService; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.handler.annotation.PathVariable; |
||||
import org.springframework.messaging.simp.handler.AnnotationMethodMessageHandler; |
||||
import org.springframework.messaging.support.MessageBuilder; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertFalse; |
||||
import static org.junit.Assert.assertTrue; |
||||
|
||||
/** |
||||
* Test fixture for {@link PathVariableMethodArgumentResolver} tests. |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class PathVariableMethodArgumentResolverTests { |
||||
|
||||
private PathVariableMethodArgumentResolver resolver; |
||||
|
||||
private MethodParameter paramAnnotated; |
||||
private MethodParameter paramAnnotatedValue; |
||||
private MethodParameter paramNotAnnotated; |
||||
|
||||
@Before |
||||
public void setup() throws Exception { |
||||
GenericApplicationContext cxt = new GenericApplicationContext(); |
||||
cxt.refresh(); |
||||
this.resolver = new PathVariableMethodArgumentResolver(new DefaultConversionService(), cxt.getBeanFactory()); |
||||
|
||||
Method method = getClass().getDeclaredMethod("handleMessage", |
||||
String.class, String.class, String.class); |
||||
this.paramAnnotated = new MethodParameter(method, 0); |
||||
this.paramAnnotatedValue = new MethodParameter(method, 1); |
||||
this.paramNotAnnotated = new MethodParameter(method, 2); |
||||
|
||||
this.paramAnnotated.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); |
||||
GenericTypeResolver.resolveParameterType(this.paramAnnotated, PathVariableMethodArgumentResolver.class); |
||||
this.paramAnnotatedValue.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); |
||||
GenericTypeResolver.resolveParameterType(this.paramAnnotatedValue, PathVariableMethodArgumentResolver.class); |
||||
} |
||||
|
||||
@Test |
||||
public void supportsParameter() { |
||||
assertTrue(resolver.supportsParameter(paramAnnotated)); |
||||
assertTrue(resolver.supportsParameter(paramAnnotatedValue)); |
||||
assertFalse(resolver.supportsParameter(paramNotAnnotated)); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveArgument() throws Exception { |
||||
Map<String,Object> pathParams = new HashMap<String,Object>(); |
||||
pathParams.put("foo","bar"); |
||||
pathParams.put("name","value"); |
||||
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]) |
||||
.setHeader(AnnotationMethodMessageHandler.PATH_TEMPLATE_VARIABLES_HEADER, pathParams).build(); |
||||
Object result = this.resolver.resolveArgument(this.paramAnnotated, message); |
||||
assertEquals("bar",result); |
||||
result = this.resolver.resolveArgument(this.paramAnnotatedValue, message); |
||||
assertEquals("value",result); |
||||
} |
||||
|
||||
@Test(expected = MessageHandlingException.class) |
||||
public void resolveArgumentNotFound() throws Exception { |
||||
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build(); |
||||
this.resolver.resolveArgument(this.paramAnnotated, message); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
private void handleMessage( |
||||
@PathVariable String foo, |
||||
@PathVariable(value = "name") String param1, |
||||
String param3) { |
||||
} |
||||
} |
||||
Loading…
Reference in new issue