DATAREST-276 - Improve base URI handling.

Introduced BaseUri value object being able to extract the lookup path within the Spring Data REST URI namespace from requests and plain lookup paths. This encapsulates the logic we previously had in RepositoryRestHandlerMapping but it is needed in the HandlerMethodArgumentResolver implementations as well to make sure they extract RepositoryMetadata correctly.
This commit is contained in:
Oliver Gierke
2014-04-30 14:51:49 +02:00
parent e5dbc30d8a
commit addaea62ef
6 changed files with 161 additions and 73 deletions

View File

@@ -0,0 +1,132 @@
/*
* 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.net.URI;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.hateoas.UriTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UrlPathHelper;
/**
* Value object to be able to extract the lookup path within a configured base URi that forms a URI namespace.
*
* @author Oliver Gierke
*/
public class BaseUri {
public static final BaseUri NONE = new BaseUri(URI.create(""));
private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper();
private final URI baseUri;
/**
* Creates a new {@link BaseUri} with the given URI as base.
*
* @param uri must not be {@literal null}.
*/
public BaseUri(URI uri) {
Assert.notNull(uri, "Base URI must not be null!");
this.baseUri = uri;
}
/**
* Returns the base URI.
*
* @return
*/
public URI getUri() {
return baseUri;
}
/**
* Extracts the actual lookup path within the Spring Data REST managed URI space. This includes stripping the
* necessary parts of the base URI from the source lookup path.
*
* @param request must not be {@literal null}.
* @return the stripped lookup path with then the repository URI space or {@literal null} in case the lookup path is
* not pointing into the repository URI space.
*/
public String getRepositoryLookupPath(NativeWebRequest request) {
return getRepositoryLookupPath(request.getNativeRequest(HttpServletRequest.class));
}
/**
* Extracts the actual lookup path within the Spring Data REST managed URI space. This includes stripping the
* necessary parts of the base URI from the source lookup path.
*
* @param request must not be {@literal null}.
* @return the stripped lookup path with then the repository URI space or {@literal null} in case the lookup path is
* not pointing into the repository URI space.
*/
private String getRepositoryLookupPath(HttpServletRequest request) {
String lookupPath = URL_PATH_HELPER.getLookupPathForRequest(request);
lookupPath = new UriTemplate(lookupPath).expand().toString();
return getRepositoryLookupPath(lookupPath);
}
/**
* Extracts the actual lookup path within the Spring Data REST managed URI space. This includes stripping the
* necessary parts of the base URI from the source lookup path.
*
* @param lookupPath must not be {@literal null}.
* @return the stripped lookup path with then the repository URI space or {@literal null} in case the lookup path is
* not pointing into the repository URI space.
*/
public String getRepositoryLookupPath(String lookupPath) {
Assert.notNull(lookupPath, "Lookup path must not be null!");
lookupPath = StringUtils.trimTrailingCharacter(lookupPath, '/');
if (!baseUri.isAbsolute()) {
String uri = baseUri.toString();
if (!StringUtils.hasText(uri)) {
return lookupPath;
}
uri = uri.startsWith("/") ? uri : "/".concat(uri);
return lookupPath.startsWith(uri) ? lookupPath.substring(uri.length(), lookupPath.length()) : null;
}
List<String> baseUriSegments = UriComponentsBuilder.fromUri(baseUri).build().getPathSegments();
Collections.reverse(baseUriSegments);
String tail = "";
for (String tailSegment : baseUriSegments) {
tail = "/".concat(tailSegment).concat(tail);
if (lookupPath.startsWith(tail)) {
return lookupPath.substring(tail.length(), lookupPath.length());
}
}
return null;
}
}

View File

@@ -17,9 +17,7 @@ package org.springframework.data.rest.webmvc;
import static org.springframework.util.StringUtils.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@@ -36,7 +34,6 @@ 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;
import org.springframework.web.util.UriComponentsBuilder;
/**
* {@link RequestMappingHandlerMapping} implementation that will only find a handler method if a
@@ -113,7 +110,7 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
acceptType = config.getDefaultMediaType().toString();
}
String uri = extractRepositoryLookupPath(lookupPath, config.getBaseUri());
String uri = new BaseUri(config.getBaseUri()).getRepositoryLookupPath(lookupPath);
if (uri == null) {
return null;
@@ -135,50 +132,6 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
return null;
}
/**
* Extracts the actual lookup path within the Spring Data REST managed URI space. This includes stripping the
* necessary parts of the base URI from the source lookup path.
*
* @param lookupPath must not be {@literal null}.
* @param baseUri must not be {@literal null}.
* @return the stripped lookup path with then the repository URI space or {@literal null} in case the lookup path is
* not pointing into the repository URI space.
*/
private static String extractRepositoryLookupPath(String lookupPath, URI baseUri) {
Assert.notNull(lookupPath, "Lookup path must not be null!");
Assert.notNull(baseUri, "Base URI must not be null!");
lookupPath = StringUtils.trimTrailingCharacter(lookupPath, '/');
if (!baseUri.isAbsolute()) {
String uri = baseUri.toString();
if (!StringUtils.hasText(uri)) {
return lookupPath;
}
uri = uri.startsWith("/") ? uri : "/".concat(uri);
return lookupPath.startsWith(uri) ? lookupPath.substring(uri.length(), lookupPath.length()) : null;
}
List<String> baseUriSegments = UriComponentsBuilder.fromUri(baseUri).build().getPathSegments();
Collections.reverse(baseUriSegments);
String tail = "";
for (String tailSegment : baseUriSegments) {
tail = "/".concat(tailSegment).concat(tail);
if (lookupPath.startsWith(tail)) {
return lookupPath.substring(tail.length(), lookupPath.length());
}
}
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler(java.lang.Class)

View File

@@ -61,6 +61,7 @@ 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.BaseUri;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
@@ -240,6 +241,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
return config;
}
@Bean
public BaseUri baseUri() {
return new BaseUri(config().getBaseUri());
}
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor} to turn beans annotated as
* {@link org.springframework.data.rest.repository.annotation.RepositoryEventHandler}s.
@@ -286,13 +292,13 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
@Bean
public ResourceMetadataHandlerMethodArgumentResolver resourceMetadataHandlerMethodArgumentResolver() {
return new ResourceMetadataHandlerMethodArgumentResolver(repositories(), resourceMappings());
return new ResourceMetadataHandlerMethodArgumentResolver(repositories(), resourceMappings(), baseUri());
}
@Bean
public BackendIdHandlerMethodArgumentResolver backendIdHandlerMethodArgumentResolver() {
return new BackendIdHandlerMethodArgumentResolver(backendIdConverterRegistry(),
resourceMetadataHandlerMethodArgumentResolver());
resourceMetadataHandlerMethodArgumentResolver(), baseUri());
}
/**

View File

@@ -23,6 +23,7 @@ import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.BaseUri;
import org.springframework.data.rest.webmvc.util.UriUtils;
import org.springframework.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
@@ -40,6 +41,7 @@ public class ResourceMetadataHandlerMethodArgumentResolver implements HandlerMet
private final Repositories repositories;
private final ResourceMappings mappings;
private final BaseUri baseUri;
/**
* Creates a new {@link ResourceMetadataHandlerMethodArgumentResolver} for the given {@link Repositories} and
@@ -47,14 +49,18 @@ public class ResourceMetadataHandlerMethodArgumentResolver implements HandlerMet
*
* @param repositories must not be {@literal null}.
* @param mappings must not be {@literal null}.
* @param baseUri must not be {@literal null}.
*/
public ResourceMetadataHandlerMethodArgumentResolver(Repositories repositories, ResourceMappings mappings) {
public ResourceMetadataHandlerMethodArgumentResolver(Repositories repositories, ResourceMappings mappings,
BaseUri baseUri) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(mappings, "ResourceMappings must not be null!");
Assert.notNull(baseUri, "BaseUri must not be null!");
this.repositories = repositories;
this.mappings = mappings;
this.baseUri = baseUri;
}
/*
@@ -74,7 +80,8 @@ public class ResourceMetadataHandlerMethodArgumentResolver implements HandlerMet
public ResourceMetadata resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String repositoryKey = UriUtils.findMappingVariable("repository", parameter, webRequest);
String lookupPath = baseUri.getRepositoryLookupPath(webRequest);
String repositoryKey = UriUtils.findMappingVariable("repository", parameter, lookupPath);
if (!hasText(repositoryKey)) {
return null;

View File

@@ -19,6 +19,7 @@ import java.io.Serializable;
import org.springframework.core.MethodParameter;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.BaseUri;
import org.springframework.data.rest.webmvc.config.ResourceMetadataHandlerMethodArgumentResolver;
import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter;
@@ -40,22 +41,26 @@ public class BackendIdHandlerMethodArgumentResolver implements HandlerMethodArgu
private final PluginRegistry<BackendIdConverter, Class<?>> idConverters;
private final ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver;
private final BaseUri baseUri;
/**
* Creates a new {@link BackendIdHandlerMethodArgumentResolver} for the given {@link BackendIdConverter}s and
* {@link ResourceMetadataHandlerMethodArgumentResolver}.
*
* @param idConverters the {@link BackendIdConverter}s registered in the system.
* @param resourceMetadataResolver the resolver to obtain {@link ResourceMetadata} from.
* @param idConverters the {@link BackendIdConverter}s registered in the system, must not be {@literal null}..
* @param resourceMetadataResolver the resolver to obtain {@link ResourceMetadata} from, must not be {@literal null}.
* @param baseUri must not be {@literal null}.
*/
public BackendIdHandlerMethodArgumentResolver(PluginRegistry<BackendIdConverter, Class<?>> idConverters,
ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver) {
ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver, BaseUri baseUri) {
Assert.notNull(idConverters, "Id converters must not be null!");
Assert.notNull(resourceMetadataResolver, "ResourceMetadata resolver must not be null!");
Assert.notNull(baseUri, "BaseUri must not be null!");
this.idConverters = idConverters;
this.resourceMetadataResolver = resourceMetadataResolver;
this.baseUri = baseUri;
}
/*
@@ -91,6 +96,7 @@ public class BackendIdHandlerMethodArgumentResolver implements HandlerMethodArgu
}
BackendIdConverter pluginFor = idConverters.getPluginFor(metadata.getDomainType(), DefaultIdConverter.INSTANCE);
return pluginFor.fromRequestId(UriUtils.findMappingVariable("id", parameter, request), metadata.getDomainType());
String lookupPath = baseUri.getRepositoryLookupPath(request);
return pluginFor.fromRequestId(UriUtils.findMappingVariable("id", parameter, lookupPath), metadata.getDomainType());
}
}

View File

@@ -17,14 +17,9 @@ package org.springframework.data.rest.webmvc.util;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.hateoas.UriTemplate;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.util.UrlPathHelper;
/**
* Utility methods to work with requests and URIs.
@@ -33,8 +28,6 @@ import org.springframework.web.util.UrlPathHelper;
*/
public abstract class UriUtils {
private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper();
private UriUtils() {}
/**
@@ -45,13 +38,11 @@ public abstract class UriUtils {
* @param request
* @return
*/
public static String findMappingVariable(String variable, MethodParameter parameter, NativeWebRequest request) {
public static String findMappingVariable(String variable, MethodParameter parameter, String lookupPath) {
Assert.hasText(variable, "Variable name must not be null or empty!");
Assert.notNull(parameter, "Method parameter must not be null!");
Assert.notNull(request, "Request must not be null!");
String lookupPath = getCleanLookupPath(request);
RequestMapping annotation = parameter.getMethodAnnotation(RequestMapping.class);
for (String mapping : annotation.value()) {
@@ -66,11 +57,4 @@ public abstract class UriUtils {
return null;
}
private static String getCleanLookupPath(NativeWebRequest request) {
HttpServletRequest httpServletRequest = request.getNativeRequest(HttpServletRequest.class);
String lookupPath = URL_PATH_HELPER.getLookupPathForRequest(httpServletRequest);
return new UriTemplate(lookupPath).expand().toString();
}
}