DATAREST-429 - Improved handling of custom base paths.

Introduced a new property basePath on RepositoryRestConfiguration to allow a smooth migration to a new non-absolute base path configuration model to customize the URIs under which repository resources are exposed.

We're deprecating the current baseUri property as it previously supported absolute URIs. These are still supported but a warning is logged to ping users to move to the new base path based configuration.

We now use the base path to dynamically augment the mappings detected on our controllers with the path configured.
This commit is contained in:
Oliver Gierke
2014-10-15 11:58:26 +02:00
parent e2a35e7ce9
commit 55cf4cd9e0
12 changed files with 713 additions and 221 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-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.
@@ -20,9 +20,12 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* @author Jon Brisbin
@@ -31,7 +34,11 @@ import org.springframework.util.Assert;
@SuppressWarnings("deprecation")
public class RepositoryRestConfiguration {
private URI baseUri = URI.create("");
private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryRestConfiguration.class);
private static final URI NO_URI = URI.create("");
private URI baseUri = NO_URI;
private URI basePath = NO_URI;
private int defaultPageSize = 20;
private int maxPageSize = 1000;
private String pageParamName = "page";
@@ -76,16 +83,36 @@ public class RepositoryRestConfiguration {
* @return The base URI.
*/
public URI getBaseUri() {
return baseUri;
return basePath != NO_URI ? basePath : baseUri;
}
/**
* The base path to expose repository resources under.
*
* @return the basePath
*/
public URI getBasePath() {
return basePath;
}
/**
* The base URI against which the exporter should calculate its links.
*
* @param baseUri must not be {@literal null}.
* @deprecated use {@link #setBasePath(String)} instead.
*/
@Deprecated
public RepositoryRestConfiguration setBaseUri(URI baseUri) {
Assert.notNull(baseUri, "The base URI cannot be null.");
LOGGER.warn("Configuring a base URI has been deprecated. Use basePath property instead!");
if (baseUri.isAbsolute()) {
LOGGER
.warn("Using absolute base URIs will not be supported as of Spring Data REST 2.3! Be sure to switch to configuring a base path!");
}
this.baseUri = baseUri;
return this;
}
@@ -94,11 +121,25 @@ public class RepositoryRestConfiguration {
* The base URI against which the exporter should calculate its links.
*
* @param baseUri must not be {@literal null}.
* @deprecated use {@link #setBasePath(String)} instead.
*/
@Deprecated
public RepositoryRestConfiguration setBaseUri(String baseUri) {
Assert.notNull(baseUri, "The base URI cannot be null.");
this.baseUri = URI.create(baseUri);
return this;
return setBaseUri(URI.create(baseUri));
}
/**
* Configures the base path to be used by Spring Data REST to expose repository resources.
*
* @param basePath the basePath to set
*/
public void setBasePath(String basePath) {
basePath = StringUtils.trimTrailingCharacter(basePath, '/');
this.basePath = URI.create(basePath.startsWith("/") ? basePath : "/".concat(basePath));
Assert.isTrue(!this.basePath.isAbsolute(), "Absolute URIs are not supported as base path!");
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-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.
@@ -33,5 +33,5 @@ import org.springframework.stereotype.Component;
@Component
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
public @interface BaseUriAwareController {
public @interface BasePathAwareController {
}

View File

@@ -0,0 +1,500 @@
/*
* Copyright 2014-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.data.rest.webmvc;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.Principal;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.Part;
import org.springframework.core.Ordered;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.util.Assert;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UrlPathHelper;
/**
* A {@link RequestMappingHandlerMapping} that augments the request mappings
*
* @author Oliver Gierke
*/
public class BasePathAwareHandlerMapping extends RequestMappingHandlerMapping {
private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper();
private final RepositoryRestConfiguration configuration;
private String prefix;
/**
* Creates a new {@link BasePathAwareHandlerMapping} using the given {@link RepositoryRestConfiguration}.
*
* @param configuration must not be {@literal null}.
*/
public BasePathAwareHandlerMapping(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.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod(java.lang.reflect.Method, java.lang.Class)
*/
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
if (info == null) {
return null;
}
PatternsRequestCondition patternsCondition = info.getPatternsCondition();
Set<String> patterns = patternsCondition.getPatterns();
String[] augmentedPatterns = new String[patterns.size()];
int count = 0;
for (String pattern : patterns) {
augmentedPatterns[count++] = prefix.concat(pattern);
}
PatternsRequestCondition condition = new PatternsRequestCondition(augmentedPatterns, getUrlPathHelper(),
getPathMatcher(), useSuffixPatternMatch(), useTrailingSlashMatch(), getFileExtensions());
return new RequestMappingInfo(condition, info.getMethodsCondition(), info.getParamsCondition(),
info.getHeadersCondition(), info.getConsumesCondition(), info.getProducesCondition(), info.getCustomCondition());
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler(java.lang.Class)
*/
@Override
protected boolean isHandler(Class<?> beanType) {
return beanType.getAnnotation(BasePathAwareController.class) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
URI baseUri = configuration.getBaseUri();
if (baseUri.isAbsolute()) {
HttpServletRequest request = new UriAwareHttpServletRequest(getServletContext(), baseUri);
this.prefix = URL_PATH_HELPER.getPathWithinApplication(request);
} else {
this.prefix = baseUri.toString();
}
super.afterPropertiesSet();
}
private class UriAwareHttpServletRequest implements HttpServletRequest {
private final ServletContext context;
private final String path;
/**
* @param context
* @param uri
*/
public UriAwareHttpServletRequest(ServletContext context, URI uri) {
this.context = context;
this.path = uri.getPath();
}
/*
* (non-Javadoc)
* @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
*/
@Override
public Object getAttribute(String name) {
return null;
}
@Override
public Enumeration<String> getAttributeNames() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see javax.servlet.ServletRequest#getCharacterEncoding()
*/
@Override
public String getCharacterEncoding() {
return null;
}
@Override
public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
throw new UnsupportedOperationException();
}
@Override
public int getContentLength() {
throw new UnsupportedOperationException();
}
@Override
public String getContentType() {
throw new UnsupportedOperationException();
}
@Override
public ServletInputStream getInputStream() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public String getParameter(String name) {
throw new UnsupportedOperationException();
}
@Override
public Enumeration<String> getParameterNames() {
throw new UnsupportedOperationException();
}
@Override
public String[] getParameterValues(String name) {
throw new UnsupportedOperationException();
}
@Override
public Map<String, String[]> getParameterMap() {
throw new UnsupportedOperationException();
}
@Override
public String getProtocol() {
throw new UnsupportedOperationException();
}
@Override
public String getScheme() {
throw new UnsupportedOperationException();
}
@Override
public String getServerName() {
throw new UnsupportedOperationException();
}
@Override
public int getServerPort() {
throw new UnsupportedOperationException();
}
@Override
public BufferedReader getReader() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public String getRemoteAddr() {
throw new UnsupportedOperationException();
}
@Override
public String getRemoteHost() {
throw new UnsupportedOperationException();
}
@Override
public void setAttribute(String name, Object o) {
throw new UnsupportedOperationException();
}
@Override
public void removeAttribute(String name) {
throw new UnsupportedOperationException();
}
@Override
public Locale getLocale() {
throw new UnsupportedOperationException();
}
@Override
public Enumeration<Locale> getLocales() {
throw new UnsupportedOperationException();
}
@Override
public boolean isSecure() {
throw new UnsupportedOperationException();
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
throw new UnsupportedOperationException();
}
@Override
public String getRealPath(String path) {
throw new UnsupportedOperationException();
}
@Override
public int getRemotePort() {
throw new UnsupportedOperationException();
}
@Override
public String getLocalName() {
throw new UnsupportedOperationException();
}
@Override
public String getLocalAddr() {
throw new UnsupportedOperationException();
}
@Override
public int getLocalPort() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see javax.servlet.ServletRequest#getServletContext()
*/
@Override
public ServletContext getServletContext() {
return context;
}
@Override
public AsyncContext startAsync() throws IllegalStateException {
throw new UnsupportedOperationException();
}
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
throws IllegalStateException {
throw new UnsupportedOperationException();
}
@Override
public boolean isAsyncStarted() {
throw new UnsupportedOperationException();
}
@Override
public boolean isAsyncSupported() {
throw new UnsupportedOperationException();
}
@Override
public AsyncContext getAsyncContext() {
throw new UnsupportedOperationException();
}
@Override
public DispatcherType getDispatcherType() {
throw new UnsupportedOperationException();
}
@Override
public String getAuthType() {
throw new UnsupportedOperationException();
}
@Override
public Cookie[] getCookies() {
throw new UnsupportedOperationException();
}
@Override
public long getDateHeader(String name) {
throw new UnsupportedOperationException();
}
@Override
public String getHeader(String name) {
throw new UnsupportedOperationException();
}
@Override
public Enumeration<String> getHeaders(String name) {
throw new UnsupportedOperationException();
}
@Override
public Enumeration<String> getHeaderNames() {
throw new UnsupportedOperationException();
}
@Override
public int getIntHeader(String name) {
throw new UnsupportedOperationException();
}
@Override
public String getMethod() {
throw new UnsupportedOperationException();
}
@Override
public String getPathInfo() {
throw new UnsupportedOperationException();
}
@Override
public String getPathTranslated() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see javax.servlet.http.HttpServletRequest#getContextPath()
*/
@Override
public String getContextPath() {
return context.getContextPath();
}
@Override
public String getQueryString() {
throw new UnsupportedOperationException();
}
@Override
public String getRemoteUser() {
throw new UnsupportedOperationException();
}
@Override
public boolean isUserInRole(String role) {
throw new UnsupportedOperationException();
}
@Override
public Principal getUserPrincipal() {
throw new UnsupportedOperationException();
}
@Override
public String getRequestedSessionId() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see javax.servlet.http.HttpServletRequest#getRequestURI()
*/
@Override
public String getRequestURI() {
return path;
}
@Override
public StringBuffer getRequestURL() {
throw new UnsupportedOperationException();
}
@Override
public String getServletPath() {
throw new UnsupportedOperationException();
}
@Override
public HttpSession getSession(boolean create) {
throw new UnsupportedOperationException();
}
@Override
public HttpSession getSession() {
throw new UnsupportedOperationException();
}
@Override
public boolean isRequestedSessionIdValid() {
throw new UnsupportedOperationException();
}
@Override
public boolean isRequestedSessionIdFromCookie() {
throw new UnsupportedOperationException();
}
@Override
public boolean isRequestedSessionIdFromURL() {
throw new UnsupportedOperationException();
}
@Override
public boolean isRequestedSessionIdFromUrl() {
throw new UnsupportedOperationException();
}
@Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
throw new UnsupportedOperationException();
}
@Override
public void login(String username, String password) throws ServletException {
throw new UnsupportedOperationException();
}
@Override
public void logout() throws ServletException {
throw new UnsupportedOperationException();
}
@Override
public Collection<Part> getParts() throws IOException, ServletException {
throw new UnsupportedOperationException();
}
@Override
public Part getPart(String name) throws IOException, ServletException {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -1,164 +0,0 @@
/*
* 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<MediaType> acceptHeaderTypes = MediaType.parseMediaTypes(acceptType);
List<MediaType> acceptableTypes = new ArrayList<MediaType>();
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();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-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.
@@ -75,7 +75,7 @@ public class RepositoryController extends AbstractRepositoryRestController {
* @return
* @since 2.2
*/
@RequestMapping(value = "/", method = RequestMethod.OPTIONS)
@RequestMapping(value = { "/", "" }, method = RequestMethod.OPTIONS)
public HttpEntity<?> optionsForRepositories() {
HttpHeaders headers = new HttpHeaders();
@@ -90,7 +90,7 @@ public class RepositoryController extends AbstractRepositoryRestController {
* @return
* @since 2.2
*/
@RequestMapping(value = "/", method = RequestMethod.HEAD)
@RequestMapping(value = { "/", "" }, method = RequestMethod.HEAD)
public ResponseEntity<?> headForRepositories() {
return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);
}
@@ -100,7 +100,7 @@ public class RepositoryController extends AbstractRepositoryRestController {
*
* @return
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
@RequestMapping(value = { "/", "" }, method = RequestMethod.GET)
public HttpEntity<RepositoryLinksResource> listRepositories() {
RepositoryLinksResource resource = new RepositoryLinksResource();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-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.
@@ -33,6 +33,6 @@ import org.springframework.stereotype.Component;
@Component
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@BaseUriAwareController
@BasePathAwareController
public @interface RepositoryRestController {
}

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2012-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.data.rest.webmvc;
import java.util.List;
@@ -63,6 +78,6 @@ public class RepositoryRestHandlerAdapter extends ResourceProcessorInvokingHandl
Class<?> controllerType = handlerMethod.getBeanType();
return AnnotationUtils.findAnnotation(controllerType, BaseUriAwareController.class) != null;
return AnnotationUtils.findAnnotation(controllerType, BasePathAwareController.class) != null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-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.
@@ -16,6 +16,10 @@
package org.springframework.data.rest.webmvc;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
@@ -24,6 +28,8 @@ import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.webmvc.support.JpaHelper;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
import org.springframework.util.Assert;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
@@ -35,9 +41,10 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class RepositoryRestHandlerMapping extends BaseUriAwareHandlerMapping {
public class RepositoryRestHandlerMapping extends BasePathAwareHandlerMapping {
private final ResourceMappings mappings;
private final RepositoryRestConfiguration configuration;
private JpaHelper jpaHelper;
@@ -56,6 +63,7 @@ public class RepositoryRestHandlerMapping extends BaseUriAwareHandlerMapping {
Assert.notNull(config, "RepositoryRestConfiguration must not be null!");
this.mappings = mappings;
this.configuration = config;
setOrder(Ordered.LOWEST_PRECEDENCE - 100);
}
@@ -69,17 +77,28 @@ public class RepositoryRestHandlerMapping extends BaseUriAwareHandlerMapping {
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.BaseUriAwareHandlerMapping#supportsLookupPath(java.lang.String)
* @see org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod(java.lang.String, javax.servlet.http.HttpServletRequest)
*/
@Override
protected boolean supportsLookupPath(String lookupPath) {
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
if ("/".equals(lookupPath)) {
return true;
HandlerMethod handlerMethod = super.lookupHandlerMethod(lookupPath, request);
if (handlerMethod == null) {
return null;
}
String[] parts = lookupPath.split("/");
return mappings.exportsTopLevelResourceFor(parts[lookupPath.startsWith("/") ? 1 : 0]);
return new BaseUri(configuration.getBaseUri()).getRepositoryLookupPath(lookupPath) == null ? null : handlerMethod;
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleNoMatch(java.util.Set, java.lang.String, javax.servlet.http.HttpServletRequest)
*/
@Override
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos, String lookupPath,
HttpServletRequest request) throws ServletException {
return null;
}
/*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-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.
@@ -26,7 +26,7 @@ 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.BasePathAwareController;
import org.springframework.data.rest.webmvc.BaseUri;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.data.rest.webmvc.RootResourceInformation;
@@ -48,7 +48,7 @@ import org.springframework.web.util.UriComponentsBuilder;
* @author Oliver Gierke
* @see http://alps.io
*/
@BaseUriAwareController
@BasePathAwareController
public class AlpsController {
static final String ALPS_ROOT_MAPPING = "/alps";

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-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.
@@ -65,9 +65,9 @@ import org.springframework.data.rest.core.mapping.ResourceMappings;
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.webmvc.BasePathAwareController;
import org.springframework.data.rest.webmvc.BasePathAwareHandlerMapping;
import org.springframework.data.rest.webmvc.BaseUri;
import org.springframework.data.rest.webmvc.BaseUriAwareController;
import org.springframework.data.rest.webmvc.BaseUriAwareHandlerMapping;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.data.rest.webmvc.RepositoryRestExceptionHandler;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
@@ -139,7 +139,7 @@ import com.fasterxml.jackson.databind.SerializationFeature;
@Configuration
@EnableHypermediaSupport(type = HypermediaType.HAL)
@ComponentScan(basePackageClasses = RepositoryRestController.class,
includeFilters = @Filter(BaseUriAwareController.class), useDefaultFilters = false)
includeFilters = @Filter(BasePathAwareController.class), useDefaultFilters = false)
@ImportResource("classpath*:META-INF/spring-data-rest/**/*.xml")
@Import(SpringDataJacksonConfiguration.class)
public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebConfiguration {
@@ -239,6 +239,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
RepositoryRestConfiguration config = new RepositoryRestConfiguration(configuration, metadataConfiguration());
configureRepositoryRestConfiguration(config);
return config;
}
@@ -519,7 +520,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
@Bean
public RequestMappingHandlerMapping fallbackMapping() {
return new BaseUriAwareHandlerMapping(config());
return new BasePathAwareHandlerMapping(config());
}
@Bean

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2014-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.data.rest.webmvc;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Map;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
/**
* Unit tests for {@link BasePathAwareHandlerMapping}.
*
* @author Oliver Gierke
*/
public class AugmentingHandlerMappingUnitTest {
@Configuration
static class Config {
@Bean
SampleController sampleController() {
return new SampleController();
}
}
@Test
@SuppressWarnings("deprecation")
public void augmentsRequestMappingsWithBaseUriFromConfiguration() {
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration();
configuration.setBaseUri("api");
BasePathAwareHandlerMapping mapping = new BasePathAwareHandlerMapping(configuration);
mapping.setApplicationContext(new AnnotationConfigApplicationContext(Config.class));
mapping.afterPropertiesSet();
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
for (RequestMappingInfo info : handlerMethods.keySet()) {
assertThat(info.getPatternsCondition().getPatterns(), hasItem(startsWith("/api")));
}
}
@Controller
static class SampleController {
@RequestMapping("/foo")
void someMethod() {}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-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.
@@ -20,21 +20,20 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.net.URI;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.data.domain.Sort;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
/**
@@ -45,7 +44,13 @@ import org.springframework.web.method.HandlerMethod;
@RunWith(MockitoJUnitRunner.class)
public class RepositoryRestHandlerMappingUnitTests {
static final ApplicationContext CONTEXT = new AnnotationConfigApplicationContext(RepositoryRestMvcConfiguration.class);
static final AnnotationConfigWebApplicationContext CONTEXT = new AnnotationConfigWebApplicationContext();
static {
CONTEXT.register(RepositoryRestMvcConfiguration.class);
CONTEXT.setServletContext(new MockServletContext());
CONTEXT.refresh();
}
@Mock ResourceMappings mappings;
@@ -61,7 +66,6 @@ public class RepositoryRestHandlerMappingUnitTests {
handlerMapping = new RepositoryRestHandlerMapping(mappings, configuration);
handlerMapping.setApplicationContext(CONTEXT);
handlerMapping.afterPropertiesSet();
mockRequest = new MockHttpServletRequest();
@@ -85,6 +89,8 @@ public class RepositoryRestHandlerMappingUnitTests {
*/
@Test
public void returnsNullForUriNotMapped() throws Exception {
handlerMapping.afterPropertiesSet();
assertThat(handlerMapping.lookupHandlerMethod("/foo", mockRequest), is(nullValue()));
}
@@ -97,6 +103,7 @@ public class RepositoryRestHandlerMappingUnitTests {
when(mappings.exportsTopLevelResourceFor("people")).thenReturn(true);
mockRequest = new MockHttpServletRequest("GET", "/people");
handlerMapping.afterPropertiesSet();
HandlerMethod method = handlerMapping.lookupHandlerMethod("/people", mockRequest);
assertThat(method, is(notNullValue()));
@@ -112,7 +119,8 @@ public class RepositoryRestHandlerMappingUnitTests {
when(mappings.exportsTopLevelResourceFor("people")).thenReturn(true);
mockRequest = new MockHttpServletRequest("GET", "/base/people");
configuration.setBaseUri(URI.create("base"));
configuration.setBasePath("/base");
handlerMapping.afterPropertiesSet();
HandlerMethod method = handlerMapping.lookupHandlerMethod("/base/people", mockRequest);
@@ -129,7 +137,8 @@ public class RepositoryRestHandlerMappingUnitTests {
when(mappings.exportsTopLevelResourceFor("people")).thenReturn(true);
mockRequest = new MockHttpServletRequest("GET", "/base");
configuration.setBaseUri(URI.create("base"));
configuration.setBasePath("/base");
handlerMapping.afterPropertiesSet();
HandlerMethod method = handlerMapping.lookupHandlerMethod("/base", mockRequest);
@@ -141,14 +150,14 @@ public class RepositoryRestHandlerMappingUnitTests {
* @see DATAREST-276
*/
@Test
@SuppressWarnings("deprecation")
public void returnsRepositoryHandlerMethodForAbsoluteBaseUri() throws Exception {
String baseUri = "http://localhost/base";
when(mappings.exportsTopLevelResourceFor("people")).thenReturn(true);
mockRequest = new MockHttpServletRequest("GET", baseUri.concat("/people/"));
mockRequest = new MockHttpServletRequest("GET", "/base/people/");
configuration.setBaseUri(URI.create(baseUri));
configuration.setBaseUri("http://localhost/base");
handlerMapping.afterPropertiesSet();
HandlerMethod method = handlerMapping.lookupHandlerMethod("/base/people/", mockRequest);
@@ -160,18 +169,17 @@ public class RepositoryRestHandlerMappingUnitTests {
* @see DATAREST-276
*/
@Test
@SuppressWarnings("deprecation")
public void returnsRepositoryHandlerMethodForAbsoluteBaseUriWithServletMapping() throws Exception {
String baseUri = "http://localhost/base";
String uri = baseUri.concat("/people/");
when(mappings.exportsTopLevelResourceFor("people")).thenReturn(true);
mockRequest = new MockHttpServletRequest("GET", uri);
mockRequest.setServletPath(uri);
mockRequest = new MockHttpServletRequest("GET", "/base/people");
mockRequest.setServletPath("/base/people");
configuration.setBaseUri(URI.create(baseUri));
configuration.setBaseUri("http://localhost/base");
handlerMapping.afterPropertiesSet();
HandlerMethod method = handlerMapping.lookupHandlerMethod("/base/people/", mockRequest);
HandlerMethod method = handlerMapping.lookupHandlerMethod("/base/people", mockRequest);
assertThat(method, is(notNullValue()));
assertThat(method.getMethod(), is(listEntitiesMethod));
@@ -181,18 +189,16 @@ public class RepositoryRestHandlerMappingUnitTests {
* @see DATAREST-276
*/
@Test
@SuppressWarnings("deprecation")
public void refrainsFromMappingIfTheRequestDoesNotPointIntoAbsolutelyDefinedUriSpace() throws Exception {
String baseUri = "http://localhost/base";
String uri = baseUri.concat("/people/");
when(mappings.exportsTopLevelResourceFor("people")).thenReturn(true);
mockRequest = new MockHttpServletRequest("GET", uri);
mockRequest.setServletPath(uri);
mockRequest = new MockHttpServletRequest("GET", "/servlet-path");
mockRequest.setServletPath("/servlet-path");
configuration.setBaseUri(URI.create(baseUri));
configuration.setBaseUri("http://localhost/base");
HandlerMethod method = handlerMapping.lookupHandlerMethod("/people", mockRequest);
HandlerMethod method = handlerMapping.lookupHandlerMethod("/servlet-path", mockRequest);
assertThat(method, is(nullValue()));
}
@@ -201,6 +207,7 @@ public class RepositoryRestHandlerMappingUnitTests {
* @see DATAREST-276
*/
@Test
@SuppressWarnings("deprecation")
public void refrainsFromMappingWhenUrisDontMatch() throws Exception {
String baseUri = "foo";
@@ -210,7 +217,7 @@ public class RepositoryRestHandlerMappingUnitTests {
mockRequest = new MockHttpServletRequest("GET", uri);
mockRequest.setServletPath(uri);
configuration.setBaseUri(URI.create(baseUri));
configuration.setBaseUri(baseUri);
HandlerMethod method = handlerMapping.lookupHandlerMethod("/people", mockRequest);