diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriAwareController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriAwareController.java new file mode 100644 index 000000000..cedb6d2ee --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriAwareController.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 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.data.rest.webmvc; + +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; + +import org.springframework.stereotype.Component; + +/** + * Annotation to declare a controller that declares request mappings to be augmented with a base URI in the Spring Data + * REST configuration. + * + * @author Oliver Gierke + */ +@Documented +@Component +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +public @interface BaseUriAwareController { +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriAwareHandlerMapping.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriAwareHandlerMapping.java new file mode 100644 index 000000000..6c2303e47 --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriAwareHandlerMapping.java @@ -0,0 +1,164 @@ +/* + * Copyright 2014 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.data.rest.webmvc; + +import static org.springframework.util.StringUtils.*; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import org.springframework.core.Ordered; +import org.springframework.data.rest.core.config.RepositoryRestConfiguration; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +/** + * Special {@link RequestMappingHandlerMapping} that uses the base URI configured in the + * {@link RepositoryRestConfiguration}, strips it from incoming requests in case they start with it and hands the + * altered URI to the superclass for normal handler method lookup. + * + * @author Oliver Gierke + */ +public class BaseUriAwareHandlerMapping extends RequestMappingHandlerMapping { + + private final RepositoryRestConfiguration configuration; + + /** + * Creates a new {@link BaseUriAwareHandlerMapping} using the given {@link RepositoryRestConfiguration}. + * + * @param configuration must not be {@literal null}. + */ + public BaseUriAwareHandlerMapping(RepositoryRestConfiguration configuration) { + + Assert.notNull(configuration, "RepositoryRestConfiguration must not be null!"); + this.configuration = configuration; + setOrder(Ordered.LOWEST_PRECEDENCE - 150); + } + + /* + * (non-Javadoc) + * @see org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod(java.lang.String, javax.servlet.http.HttpServletRequest) + */ + @Override + protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { + + String acceptType = request.getHeader("Accept"); + + if (null == acceptType) { + acceptType = configuration.getDefaultMediaType().toString(); + } + + List acceptHeaderTypes = MediaType.parseMediaTypes(acceptType); + List acceptableTypes = new ArrayList(); + + for (MediaType mt : acceptHeaderTypes) { + if ("*".equals(mt.getType()) && "*".equals(mt.getSubtype()) || "application".equals(mt.getType()) + && "*".equals(mt.getSubtype())) { + mt = configuration.getDefaultMediaType(); + } + if (!acceptableTypes.contains(mt)) { + acceptableTypes.add(mt); + } + } + + if (acceptableTypes.size() > 1) { + acceptType = collectionToDelimitedString(acceptableTypes, ","); + } else if (acceptableTypes.size() == 1) { + acceptType = acceptableTypes.get(0).toString(); + } else { + acceptType = configuration.getDefaultMediaType().toString(); + } + + String uri = new BaseUri(configuration.getBaseUri()).getRepositoryLookupPath(lookupPath); + + if (uri == null) { + return null; + } + + uri = StringUtils.hasText(uri) ? uri : "/"; + + HttpServletRequest wrapper = new DefaultAcceptTypeHttpServletRequest(request, acceptType, uri); + + return supportsLookupPath(uri) ? super.lookupHandlerMethod(uri, wrapper) : null; + } + + protected boolean supportsLookupPath(String lookupPath) { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler(java.lang.Class) + */ + @Override + protected boolean isHandler(Class beanType) { + return beanType.getAnnotation(BaseUriAwareController.class) != null; + } + + private static class DefaultAcceptTypeHttpServletRequest extends HttpServletRequestWrapper { + + private final String defaultAcceptType; + private final String requestUri; + + private DefaultAcceptTypeHttpServletRequest(HttpServletRequest request, String defaultAcceptType) { + this(request, defaultAcceptType, null); + } + + private DefaultAcceptTypeHttpServletRequest(HttpServletRequest request, String defaultAcceptType, String requestUri) { + super(request); + this.defaultAcceptType = defaultAcceptType; + this.requestUri = requestUri; + } + + /* + * (non-Javadoc) + * @see javax.servlet.http.HttpServletRequestWrapper#getHeader(java.lang.String) + */ + @Override + public String getHeader(String name) { + + if ("accept".equals(name.toLowerCase())) { + return defaultAcceptType; + } else { + return super.getHeader(name); + } + } + + /* + * (non-Javadoc) + * @see javax.servlet.http.HttpServletRequestWrapper#getRequestURI() + */ + @Override + public String getRequestURI() { + return requestUri != null ? requestUri : super.getRequestURI(); + } + + /* + * (non-Javadoc) + * @see javax.servlet.http.HttpServletRequestWrapper#getServletPath() + */ + @Override + public String getServletPath() { + return requestUri != null ? requestUri : super.getServletPath(); + } + } +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryController.java index 007532ee7..22850be8e 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryController.java @@ -39,7 +39,6 @@ import org.springframework.web.bind.annotation.RequestMethod; * @author Oliver Gierke */ @RepositoryRestController -@RequestMapping("/") public class RepositoryController extends AbstractRepositoryRestController { private final Repositories repositories; @@ -76,7 +75,7 @@ public class RepositoryController extends AbstractRepositoryRestController { * @return * @since 2.2 */ - @RequestMapping(method = RequestMethod.OPTIONS) + @RequestMapping(value = "/", method = RequestMethod.OPTIONS) public HttpEntity optionsForRepositories() { HttpHeaders headers = new HttpHeaders(); @@ -91,7 +90,7 @@ public class RepositoryController extends AbstractRepositoryRestController { * @return * @since 2.2 */ - @RequestMapping(method = RequestMethod.HEAD) + @RequestMapping(value = "/", method = RequestMethod.HEAD) public ResponseEntity headForRepositories() { return new ResponseEntity(HttpStatus.NO_CONTENT); } @@ -101,7 +100,7 @@ public class RepositoryController extends AbstractRepositoryRestController { * * @return */ - @RequestMapping(method = RequestMethod.GET) + @RequestMapping(value = "/", method = RequestMethod.GET) public HttpEntity listRepositories() { RepositoryLinksResource resource = new RepositoryLinksResource(); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java index b670ad92a..7ce689902 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java @@ -168,7 +168,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem Iterable results; - if (pageable != null) { + if (pageable.getPageable() != null) { results = invoker.invokeFindAll(pageable.getPageable()); } else { results = invoker.invokeFindAll(sort); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java index f1ae4cd2b..fc7140d98 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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. @@ -33,6 +33,6 @@ import org.springframework.stereotype.Component; @Component @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +@BaseUriAwareController public @interface RepositoryRestController { - } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java index 9143fcb22..69c096a04 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java @@ -63,6 +63,6 @@ public class RepositoryRestHandlerAdapter extends ResourceProcessorInvokingHandl Class controllerType = handlerMethod.getBeanType(); - return AnnotationUtils.findAnnotation(controllerType, RepositoryRestController.class) != null; + return AnnotationUtils.findAnnotation(controllerType, BaseUriAwareController.class) != null; } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java index 20e3bfb85..26af6e8ce 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java @@ -15,24 +15,15 @@ */ package org.springframework.data.rest.webmvc; -import static org.springframework.util.StringUtils.*; - -import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; - import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.mapping.ResourceMappings; import org.springframework.data.rest.webmvc.support.JpaHelper; -import org.springframework.http.MediaType; import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; /** @@ -44,10 +35,9 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl * @author Jon Brisbin * @author Oliver Gierke */ -public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping { +public class RepositoryRestHandlerMapping extends BaseUriAwareHandlerMapping { private final ResourceMappings mappings; - private final RepositoryRestConfiguration config; private JpaHelper jpaHelper; @@ -60,11 +50,12 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping { */ public RepositoryRestHandlerMapping(ResourceMappings mappings, RepositoryRestConfiguration config) { + super(config); + Assert.notNull(mappings, "ResourceMappings must not be null!"); Assert.notNull(config, "RepositoryRestConfiguration must not be null!"); this.mappings = mappings; - this.config = config; setOrder(Ordered.LOWEST_PRECEDENCE - 100); } @@ -76,62 +67,19 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping { this.jpaHelper = jpaHelper; } - /* + /* * (non-Javadoc) - * @see org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod(java.lang.String, javax.servlet.http.HttpServletRequest) + * @see org.springframework.data.rest.webmvc.BaseUriAwareHandlerMapping#supportsLookupPath(java.lang.String) */ @Override - protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest origRequest) throws Exception { + protected boolean supportsLookupPath(String lookupPath) { - String acceptType = origRequest.getHeader("Accept"); - - if (null == acceptType) { - acceptType = config.getDefaultMediaType().toString(); + if ("/".equals(lookupPath)) { + return true; } - List acceptHeaderTypes = MediaType.parseMediaTypes(acceptType); - List acceptableTypes = new ArrayList(); - - for (MediaType mt : acceptHeaderTypes) { - if ("*".equals(mt.getType()) && "*".equals(mt.getSubtype()) || "application".equals(mt.getType()) - && "*".equals(mt.getSubtype())) { - mt = config.getDefaultMediaType(); - } - if (!acceptableTypes.contains(mt)) { - acceptableTypes.add(mt); - } - } - - if (acceptableTypes.size() > 1) { - acceptType = collectionToDelimitedString(acceptableTypes, ","); - } else if (acceptableTypes.size() == 1) { - acceptType = acceptableTypes.get(0).toString(); - } else { - acceptType = config.getDefaultMediaType().toString(); - } - - String uri = new BaseUri(config.getBaseUri()).getRepositoryLookupPath(lookupPath); - - if (uri == null) { - return null; - } - - uri = StringUtils.hasText(uri) ? uri : "/"; - - HttpServletRequest request = new DefaultAcceptTypeHttpServletRequest(origRequest, acceptType, uri); - - // Root request - if (uri.equals("/")) { - return super.lookupHandlerMethod("/", request); - } - - String[] parts = uri.split("/"); - - if (mappings.exportsTopLevelResourceFor(parts[uri.startsWith("/") ? 1 : 0])) { - return super.lookupHandlerMethod(uri, request); - } - - return null; + String[] parts = lookupPath.split("/"); + return mappings.exportsTopLevelResourceFor(parts[lookupPath.startsWith("/") ? 1 : 0]); } /* @@ -155,48 +103,4 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping { } } } - - private static class DefaultAcceptTypeHttpServletRequest extends HttpServletRequestWrapper { - - private final String defaultAcceptType; - private final String requestUri; - - private DefaultAcceptTypeHttpServletRequest(HttpServletRequest request, String defaultAcceptType) { - this(request, defaultAcceptType, null); - } - - private DefaultAcceptTypeHttpServletRequest(HttpServletRequest request, String defaultAcceptType, String requestUri) { - super(request); - this.defaultAcceptType = defaultAcceptType; - this.requestUri = requestUri; - } - - @Override - public String getHeader(String name) { - - if ("accept".equals(name.toLowerCase())) { - return defaultAcceptType; - } else { - return super.getHeader(name); - } - } - - /* - * (non-Javadoc) - * @see javax.servlet.http.HttpServletRequestWrapper#getRequestURI() - */ - @Override - public String getRequestURI() { - return requestUri != null ? requestUri : super.getRequestURI(); - } - - /* - * (non-Javadoc) - * @see javax.servlet.http.HttpServletRequestWrapper#getServletPath() - */ - @Override - public String getServletPath() { - return requestUri != null ? requestUri : super.getServletPath(); - } - } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/AlpsController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/AlpsController.java index 591ea4009..77a5c62c6 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/AlpsController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/AlpsController.java @@ -26,8 +26,8 @@ import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.mapping.ResourceMappings; import org.springframework.data.rest.core.mapping.ResourceMetadata; +import org.springframework.data.rest.webmvc.BaseUriAwareController; import org.springframework.data.rest.webmvc.BaseUri; -import org.springframework.data.rest.webmvc.RepositoryRestController; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.data.rest.webmvc.RootResourceInformation; import org.springframework.hateoas.alps.Alps; @@ -48,11 +48,11 @@ import org.springframework.web.util.UriComponentsBuilder; * @author Oliver Gierke * @see http://alps.io */ -@RepositoryRestController -@RequestMapping(AlpsController.ALPS_ROOT_MAPPING) +@BaseUriAwareController public class AlpsController { static final String ALPS_ROOT_MAPPING = "/alps"; + static final String ALPS_RESOURCE_MAPPING = ALPS_ROOT_MAPPING + "/{repository}"; private final Repositories repositories; private final ResourceMappings mappings; @@ -83,7 +83,7 @@ public class AlpsController { * * @return */ - @RequestMapping(value = { "", "/{repository}" }, method = OPTIONS) + @RequestMapping(value = { ALPS_ROOT_MAPPING, ALPS_RESOURCE_MAPPING }, method = OPTIONS) HttpEntity alpsOptions() { verifyAlpsEnabled(); @@ -99,7 +99,7 @@ public class AlpsController { * * @return */ - @RequestMapping(method = GET) + @RequestMapping(value = ALPS_ROOT_MAPPING, method = GET) HttpEntity alps() { verifyAlpsEnabled(); @@ -132,7 +132,7 @@ public class AlpsController { * @param information * @return */ - @RequestMapping(value = "/{repository}", method = GET) + @RequestMapping(value = ALPS_RESOURCE_MAPPING, method = GET) HttpEntity descriptor(RootResourceInformation information) { verifyAlpsEnabled(); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java index 0ba00fdba..4f899cf22 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java @@ -66,7 +66,9 @@ import org.springframework.data.rest.core.projection.ProxyProjectionFactory; import org.springframework.data.rest.core.support.DomainObjectMerger; import org.springframework.data.rest.core.support.RepositoryRelProvider; import org.springframework.data.rest.core.util.UUIDConverter; +import org.springframework.data.rest.webmvc.BaseUriAwareController; import org.springframework.data.rest.webmvc.BaseUri; +import org.springframework.data.rest.webmvc.BaseUriAwareHandlerMapping; import org.springframework.data.rest.webmvc.RepositoryRestController; import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter; import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping; @@ -135,7 +137,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; @Configuration @EnableHypermediaSupport(type = HypermediaType.HAL) @ComponentScan(basePackageClasses = RepositoryRestController.class, - includeFilters = @Filter(RepositoryRestController.class), useDefaultFilters = false) + includeFilters = @Filter(BaseUriAwareController.class), useDefaultFilters = false) @ImportResource("classpath*:META-INF/spring-data-rest/**/*.xml") @Import(SpringDataJacksonConfiguration.class) public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebConfiguration { @@ -500,7 +502,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon @Bean public RequestMappingHandlerMapping fallbackMapping() { - return new RequestMappingHandlerMapping(); + return new BaseUriAwareHandlerMapping(config()); } @Bean