From 0dabdb8207920e875a9020c1bcd8a9220c3c1d88 Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Wed, 7 Oct 2015 12:15:39 +0200 Subject: [PATCH] Add request method based mapping Closes #22 --- .../RequestMappingHandlerMapping.java | 82 +++++++++- .../RequestMappingHandlerMappingTests.java | 143 ++++++++++++++++++ 2 files changed, 218 insertions(+), 7 deletions(-) create mode 100644 spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerMappingTests.java diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerMapping.java b/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerMapping.java index 76e175742cf..17df6ff8192 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerMapping.java @@ -15,8 +15,14 @@ */ package org.springframework.reactive.web.dispatch.method.annotation; -import java.util.LinkedHashMap; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -25,10 +31,12 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.http.HttpMethod; import org.springframework.reactive.web.dispatch.HandlerMapping; import org.springframework.reactive.web.http.ServerHttpRequest; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethodSelector; @@ -42,7 +50,7 @@ public class RequestMappingHandlerMapping implements HandlerMapping, private static final Log logger = LogFactory.getLog(RequestMappingHandlerMapping.class); - private final Map methodMap = new LinkedHashMap<>(); + private final Map methodMap = new TreeMap<>(); private ApplicationContext applicationContext; @@ -67,11 +75,16 @@ public class RequestMappingHandlerMapping implements HandlerMapping, RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); if (annotation != null && annotation.value().length > 0) { String path = annotation.value()[0]; + RequestMethod[] methods = annotation.method(); HandlerMethod handlerMethod = new HandlerMethod(bean, method); if (logger.isInfoEnabled()) { logger.info("Mapped \"" + path + "\" onto " + handlerMethod); } - methodMap.put(path, handlerMethod); + RequestMappingInfo info = new RequestMappingInfo(path, methods); + if (this.methodMap.containsKey(info)) { + throw new IllegalStateException("Duplicate mapping found for " + info); + } + methodMap.put(info, handlerMethod); } return false; }); @@ -81,11 +94,66 @@ public class RequestMappingHandlerMapping implements HandlerMapping, @Override public Object getHandler(ServerHttpRequest request) { String path = request.getURI().getPath(); - HandlerMethod handlerMethod = this.methodMap.get(path); - if (logger.isDebugEnabled()) { - logger.debug("Mapped " + path + " to [" + handlerMethod + "]"); + HttpMethod method = request.getMethod(); + for (Map.Entry entry : this.methodMap.entrySet()) { + RequestMappingInfo info = entry.getKey(); + if (path.equals(info.getPath()) && (info.getMethods().isEmpty() || info.getMethods().contains(RequestMethod.valueOf(method.name())))) { + if (logger.isDebugEnabled()) { + logger.debug("Mapped " + method + " " + path + " to [" + entry.getValue() + "]"); + } + return entry.getValue(); + } + } + return null; + } + + + private static class RequestMappingInfo implements Comparable { + + private String path; + + private Set methods; + + + public RequestMappingInfo(String path, RequestMethod... methods) { + this(path, asList(methods)); + } + + public RequestMappingInfo(String path, Collection methods) { + this.path = path; + this.methods = new TreeSet<>(methods); + } + + + public String getPath() { + return path; + } + + public Set getMethods() { + return methods; + } + + private static List asList(RequestMethod... requestMethods) { + return (requestMethods != null ? Arrays.asList(requestMethods) : Collections.emptyList()); + } + + @Override + public int compareTo(Object o) { + RequestMappingInfo other = (RequestMappingInfo)o; + if (!this.path.equals(other.getPath())) { + return -1; + } + if (this.methods.isEmpty() && !other.methods.isEmpty()) { + return 1; + } + if (!this.methods.isEmpty() && other.methods.isEmpty()) { + return -1; + } + if (this.methods.equals(other.methods)) { + return 0; + } + return -1; } - return handlerMethod; } } diff --git a/spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerMappingTests.java b/spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerMappingTests.java new file mode 100644 index 00000000000..9bb4a02aede --- /dev/null +++ b/spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerMappingTests.java @@ -0,0 +1,143 @@ +/* + * Copyright 2002-2015 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.reactive.web.dispatch.method.annotation; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; + +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.reactivestreams.Publisher; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.reactive.web.http.ServerHttpRequest; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.method.HandlerMethod; + +/** + * @author Sebastien Deleuze + */ +public class RequestMappingHandlerMappingTests { + + private RequestMappingHandlerMapping mapping; + + @Before + public void setup() { + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.registerSingleton("handlerMapping", RequestMappingHandlerMapping.class); + wac.registerSingleton("controller", TestController.class); + wac.refresh(); + this.mapping = (RequestMappingHandlerMapping)wac.getBean("handlerMapping"); + } + + @Test + public void path() throws NoSuchMethodException { + ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "boo"); + HandlerMethod handler = (HandlerMethod) this.mapping.getHandler(request); + assertEquals(TestController.class.getMethod("boo"), handler.getMethod()); + } + + @Test + public void method() throws NoSuchMethodException { + ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.POST, "foo"); + HandlerMethod handler = (HandlerMethod) this.mapping.getHandler(request); + assertEquals(TestController.class.getMethod("postFoo"), handler.getMethod()); + + request = new MockServerHttpRequest(HttpMethod.GET, "foo"); + handler = (HandlerMethod) this.mapping.getHandler(request); + assertEquals(TestController.class.getMethod("getFoo"), handler.getMethod()); + + request = new MockServerHttpRequest(HttpMethod.PUT, "foo"); + handler = (HandlerMethod) this.mapping.getHandler(request); + assertEquals(TestController.class.getMethod("foo"), handler.getMethod()); + } + + + @Controller + @SuppressWarnings("unused") + private static class TestController { + + @RequestMapping("foo") + public String foo() { + return "foo"; + } + + @RequestMapping(path = "foo", method = RequestMethod.POST) + public String postFoo() { + return "postFoo"; + } + + @RequestMapping(path = "foo", method = RequestMethod.GET) + public String getFoo() { + return "getFoo"; + } + + @RequestMapping("bar") + public String bar() { + return "bar"; + } + + @RequestMapping("boo") + public String boo() { + return "boo"; + } + + } + + private static class MockServerHttpRequest implements ServerHttpRequest{ + + private HttpMethod method; + + private URI uri; + + public MockServerHttpRequest(HttpMethod method, String path) { + this.method = method; + try { + this.uri = new URI(path); + } catch (URISyntaxException ex) { + throw new IllegalStateException("Could not get URI: " + ex.getMessage(), ex); + } + } + + @Override + public Publisher getBody() { + return null; + } + + @Override + public HttpMethod getMethod() { + return this.method; + } + + @Override + public URI getURI() { + return this.uri; + } + + @Override + public HttpHeaders getHeaders() { + return null; + } + } + +}