diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java index 85ce70ea4..d8a9f3c76 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java @@ -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!"); } /** 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/BasePathAwareController.java similarity index 92% rename from spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriAwareController.java rename to spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareController.java index cedb6d2ee..1c02c908c 100644 --- 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/BasePathAwareController.java @@ -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 { } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMapping.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMapping.java new file mode 100644 index 000000000..1e2c41f4d --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMapping.java @@ -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 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 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 getParameterNames() { + throw new UnsupportedOperationException(); + } + + @Override + public String[] getParameterValues(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public Map 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 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 getHeaders(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public Enumeration 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 getParts() throws IOException, ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public Part getPart(String name) throws IOException, ServletException { + throw new UnsupportedOperationException(); + } + } +} 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 deleted file mode 100644 index 6c2303e47..000000000 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriAwareHandlerMapping.java +++ /dev/null @@ -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 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 22850be8e..811328d02 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 @@ -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(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 listRepositories() { RepositoryLinksResource resource = new RepositoryLinksResource(); 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 fc7140d98..a080e244c 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-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 { } 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 69c096a04..b0bd89351 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 @@ -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; } } 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 26af6e8ce..d671fefd5 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 @@ -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 requestMappingInfos, String lookupPath, + HttpServletRequest request) throws ServletException { + return null; } /* 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 77a5c62c6..685c7f520 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 @@ -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"; 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 b086ebb9b..faf9b6952 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 @@ -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 diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AugmentingHandlerMappingUnitTest.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AugmentingHandlerMappingUnitTest.java new file mode 100644 index 000000000..4dfb4c5c7 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AugmentingHandlerMappingUnitTest.java @@ -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 handlerMethods = mapping.getHandlerMethods(); + + for (RequestMappingInfo info : handlerMethods.keySet()) { + assertThat(info.getPatternsCondition().getPatterns(), hasItem(startsWith("/api"))); + } + } + + @Controller + static class SampleController { + + @RequestMapping("/foo") + void someMethod() {} + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java index d2760c919..c96c0e38c 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java @@ -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);