/*
* 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.
* 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.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.AbstractView;
import static org.junit.Assert.*;
/**
* @author Rossen Stoyanchev
* @since 3.1
*/
public class UriTemplateServletAnnotationControllerHandlerMethodTests extends AbstractServletHandlerMethodTests {
@Test
public void simple() throws Exception {
initServletWithControllers(SimpleUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("test-42-7", response.getContentAsString());
}
@Test
public void multiple() throws Exception {
initServletWithControllers(MultipleUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42;q=24/bookings/21-other;q=12");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(200, response.getStatus());
assertEquals("test-42-q24-21-other-q12", response.getContentAsString());
}
@Test
public void pathVarsInModel() throws Exception {
final Map<String, Object> pathVars = new HashMap<>();
pathVars.put("hotel", "42");
pathVars.put("booking", 21);
pathVars.put("other", "other");
WebApplicationContext wac =
initServlet(new ApplicationContextInitializer<GenericWebApplicationContext>() {
@Override
public void initialize(GenericWebApplicationContext context) {
RootBeanDefinition beanDef = new RootBeanDefinition(ModelValidatingViewResolver.class);
beanDef.getConstructorArgumentValues().addGenericArgumentValue(pathVars);
context.registerBeanDefinition("viewResolver", beanDef);
}
}, ViewRenderingController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42;q=1,2/bookings/21-other;q=3;r=R");
getServlet().service(request, new MockHttpServletResponse());
ModelValidatingViewResolver resolver = wac.getBean(ModelValidatingViewResolver.class);
assertEquals(3, resolver.validatedAttrCount);
}
@Test
public void binding() throws Exception {
initServletWithControllers(BindingUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/dates/2008-11-18");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(200, response.getStatus());
request = new MockHttpServletRequest("GET", "/hotels/42/dates/2008-foo-bar");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(400, response.getStatus());
initServletWithControllers(NonBindingUriTemplateController.class);
request = new MockHttpServletRequest("GET", "/hotels/42/dates/2008-foo-bar");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(500, response.getStatus());
}
@Test
public void ambiguous() throws Exception {
initServletWithControllers(AmbiguousUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/new");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("specific", response.getContentAsString());
}
@Test
public void relative() throws Exception {
initServletWithControllers(RelativePathUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("test-42-21", response.getContentAsString());
request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21.html");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("test-42-21", response.getContentAsString());
}
@Test
public void extension() throws Exception {
initServletWithControllers(SimpleUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42;jsessionid=c0o7fszeb1;q=24.xml");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("test-42-24", response.getContentAsString());
}
@Test
public void typeConversionError() throws Exception {
initServletWithControllers(SimpleUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.xml");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
@Test
public void explicitSubPath() throws Exception {
initServletWithControllers(ExplicitSubPathController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("test-42", response.getContentAsString());
}
@Test
public void implicitSubPath() throws Exception {
initServletWithControllers(ImplicitSubPathController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("test-42", response.getContentAsString());
}
@Test
public void crud() throws Exception {
initServletWithControllers(CrudController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("list", response.getContentAsString());
request = new MockHttpServletRequest("GET", "/hotels/");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("list", response.getContentAsString());
request = new MockHttpServletRequest("POST", "/hotels");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("create", response.getContentAsString());
request = new MockHttpServletRequest("GET", "/hotels/42");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("show-42", response.getContentAsString());
request = new MockHttpServletRequest("GET", "/hotels/42/");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("show-42", response.getContentAsString());
request = new MockHttpServletRequest("PUT", "/hotels/42");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("createOrUpdate-42", response.getContentAsString());
request = new MockHttpServletRequest("DELETE", "/hotels/42");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("remove-42", response.getContentAsString());
}
@Test
public void methodNotSupported() throws Exception {
initServletWithControllers(MethodNotAllowedController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/1");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(200, response.getStatus());
request = new MockHttpServletRequest("POST", "/hotels/1");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(405, response.getStatus());
request = new MockHttpServletRequest("GET", "/hotels");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(200, response.getStatus());
request = new MockHttpServletRequest("POST", "/hotels");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(405, response.getStatus());
}
@Test
public void multiPaths() throws Exception {
initServletWithControllers(MultiPathController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/category/page/5");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("handle4-page-5", response.getContentAsString());
request = new MockHttpServletRequest("GET", "/category/page/5.html");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("handle4-page-5", response.getContentAsString());
}
@Test
public void customRegex() throws Exception {
initServletWithControllers(CustomRegexController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42;q=1;q=2");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(200, response.getStatus());
assertEquals("test-42-;q=1;q=2-[1, 2]", response.getContentAsString());
}
/*
* See SPR-6640
*/
@Test
public void menuTree() throws Exception {
initServletWithControllers(MenuTreeController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/book/menu/type/M5");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("M5", response.getContentAsString());
}
/*
* See SPR-6876
*/
@Test
public void variableNames() throws Exception {
initServletWithControllers(VariableNamesController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/foo");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("foo-foo", response.getContentAsString());
request = new MockHttpServletRequest("DELETE", "/test/bar");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("bar-bar", response.getContentAsString());
}
/*
* See SPR-8543
*/
@Test
public void variableNamesWithUrlExtension() throws Exception {
initServletWithControllers(VariableNamesController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/foo.json");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("foo-foo", response.getContentAsString());
}
/*
* See SPR-6978
*/
@Test
public void doIt() throws Exception {
initServletWithControllers(Spr6978Controller.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/100");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("loadEntity:foo:100", response.getContentAsString());
request = new MockHttpServletRequest("POST", "/foo/100");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("publish:foo:100", response.getContentAsString());
request = new MockHttpServletRequest("GET", "/module/100");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("loadModule:100", response.getContentAsString());
request = new MockHttpServletRequest("POST", "/module/100");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("publish:module:100", response.getContentAsString());
}
/*
* Controllers
*/
@Controller
public static class SimpleUriTemplateController {
@RequestMapping("/{root}")
public void handle(@PathVariable("root") int root, @MatrixVariable(required=false, defaultValue="7") int q,
Writer writer) throws IOException {
assertEquals("Invalid path variable value", 42, root);
writer.write("test-" + root + "-" + q);
}
}
@Controller
public static class MultipleUriTemplateController {
@RequestMapping("/hotels/{hotel}/bookings/{booking}-{other}")
public void handle(@PathVariable("hotel") String hotel,
@PathVariable int booking,
@PathVariable String other,
@MatrixVariable(name = "q", pathVar = "hotel") int qHotel,
@MatrixVariable(name = "q", pathVar = "other") int qOther,
Writer writer) throws IOException {
assertEquals("Invalid path variable value", "42", hotel);
assertEquals("Invalid path variable value", 21, booking);
writer.write("test-" + hotel + "-q" + qHotel + "-" + booking + "-" + other + "-q" + qOther);
}
}
@Controller
public static class ViewRenderingController {
@RequestMapping("/hotels/{hotel}/bookings/{booking}-{other}")
public void handle(@PathVariable("hotel") String hotel, @PathVariable int booking,
@PathVariable String other, @MatrixVariable MultiValueMap<String, String> params) {
assertEquals("Invalid path variable value", "42", hotel);
assertEquals("Invalid path variable value", 21, booking);
assertEquals(Arrays.asList("1", "2", "3"), params.get("q"));
assertEquals("R", params.getFirst("r"));
}
}
@Controller
public static class BindingUriTemplateController {
@InitBinder
public void initBinder(WebDataBinder binder, @PathVariable("hotel") String hotel) {
assertEquals("Invalid path variable value", "42", hotel);
binder.initBeanPropertyAccess();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
@RequestMapping("/hotels/{hotel}/dates/{date}")
public void handle(@PathVariable("hotel") String hotel, @PathVariable Date date, Writer writer)
throws IOException {
assertEquals("Invalid path variable value", "42", hotel);
assertEquals("Invalid path variable value", new GregorianCalendar(2008, 10, 18).getTime(), date);
writer.write("test-" + hotel);
}
}
@Controller
public static class NonBindingUriTemplateController {
@RequestMapping("/hotels/{hotel}/dates/{date}")
public void handle(@PathVariable("hotel") String hotel, @PathVariable Date date, Writer writer)
throws IOException {
}
}
@Controller
@RequestMapping("/hotels/{hotel}")
public static class RelativePathUriTemplateController {
@RequestMapping("bookings/{booking}")
public void handle(@PathVariable("hotel") String hotel, @PathVariable int booking, Writer writer)
throws IOException {
assertEquals("Invalid path variable value", "42", hotel);
assertEquals("Invalid path variable value", 21, booking);
writer.write("test-" + hotel + "-" + booking);
}
}
@Controller
@RequestMapping("/hotels")
public static class AmbiguousUriTemplateController {
@RequestMapping("/{hotel}")
public void handleVars(@PathVariable("hotel") String hotel, Writer writer) throws IOException {
assertEquals("Invalid path variable value", "42", hotel);
writer.write("variables");
}
@RequestMapping("/new")
public void handleSpecific(Writer writer) throws IOException {
writer.write("specific");
}
@RequestMapping("/*")
public void handleWildCard(Writer writer) throws IOException {
writer.write("wildcard");
}
}
@Controller
@RequestMapping("/hotels/*")
public static class ExplicitSubPathController {
@RequestMapping("{hotel}")
public void handleHotel(@PathVariable String hotel, Writer writer) throws IOException {
writer.write("test-" + hotel);
}
}
@Controller
@RequestMapping("hotels")
public static class ImplicitSubPathController {
@RequestMapping("{hotel}")
public void handleHotel(@PathVariable String hotel, Writer writer) throws IOException {
writer.write("test-" + hotel);
}
}
@Controller
public static class CustomRegexController {
@RequestMapping("/{root:\\d+}{params}")
public void handle(@PathVariable("root") int root, @PathVariable("params") String paramString,
@MatrixVariable List<Integer> q, Writer writer) throws IOException {
assertEquals("Invalid path variable value", 42, root);
writer.write("test-" + root + "-" + paramString + "-" + q);
}
}
@Controller
public static class DoubleController {
@RequestMapping("/lat/{latitude}/long/{longitude}")
public void testLatLong(@PathVariable Double latitude, @PathVariable Double longitude, Writer writer)
throws IOException {
writer.write("latitude-" + latitude + "-longitude-" + longitude);
}
}
@Controller
@RequestMapping("hotels")
public static class CrudController {
@RequestMapping(method = RequestMethod.GET)
public void list(Writer writer) throws IOException {
writer.write("list");
}
@RequestMapping(method = RequestMethod.POST)
public void create(Writer writer) throws IOException {
writer.write("create");
}
@RequestMapping(value = "/{hotel}", method = RequestMethod.GET)
public void show(@PathVariable String hotel, Writer writer) throws IOException {
writer.write("show-" + hotel);
}
@RequestMapping(value = "{hotel}", method = RequestMethod.PUT)
public void createOrUpdate(@PathVariable String hotel, Writer writer) throws IOException {
writer.write("createOrUpdate-" + hotel);
}
@RequestMapping(value = "{hotel}", method = RequestMethod.DELETE)
public void remove(@PathVariable String hotel, Writer writer) throws IOException {
writer.write("remove-" + hotel);
}
}
@Controller
@RequestMapping("/hotels")
public static class MethodNotAllowedController {
@RequestMapping(method = RequestMethod.GET)
public void list(Writer writer) {
}
@RequestMapping(method = RequestMethod.GET, value = "{hotelId}")
public void show(@PathVariable long hotelId, Writer writer) {
}
@RequestMapping(method = RequestMethod.PUT, value = "{hotelId}")
public void createOrUpdate(@PathVariable long hotelId, Writer writer) {
}
@RequestMapping(method = RequestMethod.DELETE, value = "/{hotelId}")
public void remove(@PathVariable long hotelId, Writer writer) {
}
}
@Controller
@RequestMapping("/category")
public static class MultiPathController {
@RequestMapping(value = {"/{category}/page/{page}", "/*/{category}/page/{page}"})
public void category(@PathVariable String category, @PathVariable int page, Writer writer) throws IOException {
writer.write("handle1-");
writer.write("category-" + category);
writer.write("page-" + page);
}
@RequestMapping(value = {"/{category}", "/*/{category}"})
public void category(@PathVariable String category, Writer writer) throws IOException {
writer.write("handle2-");
writer.write("category-" + category);
}
@RequestMapping(value = {""})
public void category(Writer writer) throws IOException {
writer.write("handle3");
}
@RequestMapping(value = {"/page/{page}"})
public void category(@PathVariable int page, Writer writer) throws IOException {
writer.write("handle4-");
writer.write("page-" + page);
}
}
@Controller
@RequestMapping("/*/menu/") // was /*/menu/**
public static class MenuTreeController {
@RequestMapping("type/{var}")
public void getFirstLevelFunctionNodes(@PathVariable("var") String var, Writer writer) throws IOException {
writer.write(var);
}
}
@Controller
@RequestMapping("/test")
public static class VariableNamesController {
@RequestMapping(value = "/{foo}", method=RequestMethod.GET)
public void foo(@PathVariable String foo, Writer writer) throws IOException {
writer.write("foo-" + foo);
}
@RequestMapping(value = "/{bar}", method=RequestMethod.DELETE)
public void bar(@PathVariable String bar, Writer writer) throws IOException {
writer.write("bar-" + bar);
}
}
@Controller
public static class Spr6978Controller {
@RequestMapping(value = "/{type}/{id}", method = RequestMethod.GET)
public void loadEntity(@PathVariable final String type, @PathVariable final long id, Writer writer)
throws IOException {
writer.write("loadEntity:" + type + ":" + id);
}
@RequestMapping(value = "/module/{id}", method = RequestMethod.GET)
public void loadModule(@PathVariable final long id, Writer writer) throws IOException {
writer.write("loadModule:" + id);
}
@RequestMapping(value = "/{type}/{id}", method = RequestMethod.POST)
public void publish(@PathVariable final String type, @PathVariable final long id, Writer writer)
throws IOException {
writer.write("publish:" + type + ":" + id);
}
}
public static class ModelValidatingViewResolver implements ViewResolver {
private final Map<String, Object> attrsToValidate;
int validatedAttrCount;
public ModelValidatingViewResolver(Map<String, Object> attrsToValidate) {
this.attrsToValidate = attrsToValidate;
}
@Override
public View resolveViewName(final String viewName, Locale locale) throws Exception {
return new AbstractView () {
@Override
public String getContentType() {
return null;
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
for (String key : attrsToValidate.keySet()) {
assertTrue("Model should contain attribute named " + key, model.containsKey(key));
assertEquals(attrsToValidate.get(key), model.get(key));
validatedAttrCount++;
}
}
};
}
}
// @Ignore("ControllerClassNameHandlerMapping")
// public void controllerClassName() throws Exception {
// @Ignore("useDefaultSuffixPattern property not supported")
// public void doubles() throws Exception {
// @Ignore("useDefaultSuffixPattern property not supported")
// public void noDefaultSuffixPattern() throws Exception {
}