DATAREST-111 - Slightly raised precedence of RepositoryRestHandlerMapping.

Set order of RepositoryRestHandlerMapping to LOWEST_PRECEDENCE - 100 to give core Spring Framework components the chance to hook into the right place in the chain.

Refactored RepositoryRestHandlerMapping to really check for the exposed path and added test cases for handler method resolution.
This commit is contained in:
Oliver Gierke
2013-08-21 12:14:40 +02:00
parent 252efc5196
commit 9acad5d55d
5 changed files with 211 additions and 17 deletions

View File

@@ -161,6 +161,25 @@ public class ResourceMappings implements Iterable<ResourceMetadata> {
return metadata.isExported();
}
/**
* Returns whether we export a top-level resource for the given path.
*
* @param path must not be {@literal null} or empty.
* @return
*/
public boolean exportsTopLevelResourceFor(String path) {
Assert.hasText(path);
for (ResourceMetadata metadata : cache.values()) {
if (metadata.getPath().matches(path)) {
return metadata.isExported();
}
}
return false;
}
/**
* Returns whether we have a {@link ResourceMapping} for the given type.
*

View File

@@ -97,4 +97,20 @@ public class ResourceMappingsIntegrationTest {
assertThat(mapping.getPath(), is(new Path("siblings")));
assertThat(mapping.isExported(), is(true));
}
/**
* @see DATAREST-111
*/
@Test
public void exposesResourceByPath() {
assertThat(mappings.exportsTopLevelResourceFor("people"), is(true));
assertThat(mappings.exportsTopLevelResourceFor("orders"), is(true));
ResourceMetadata creditCardMapping = mappings.getMappingFor(CreditCard.class);
assertThat(creditCardMapping, is(notNullValue()));
assertThat(creditCardMapping.getPath(), is(new Path("creditCards")));
assertThat(creditCardMapping.isExported(), is(false));
assertThat(mappings.exportsTopLevelResourceFor("creditCards"), is(false));
}
}

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2012-2013 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.*;
@@ -8,15 +23,14 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
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.webmvc.support.JpaHelper;
import org.springframework.http.MediaType;
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.annotation.RequestMappingHandlerMapping;
@@ -27,37 +41,66 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
* REST exporter to function properly.
*
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
@Autowired private Repositories repositories;
@Autowired private RepositoryRestConfiguration config;
@Autowired(required = false) private JpaHelper jpaHelper;
private final ResourceMappings mappings;
private final RepositoryRestConfiguration config;
private JpaHelper jpaHelper;
/**
* Creates a new {@link RepositoryRestHandlerMapping} for the given {@link ResourceMappings} and
* {@link RepositoryRestConfiguration}.
*
* @param mappings must not be {@literal null}.
* @param config must not be {@literal null}.
*/
public RepositoryRestHandlerMapping(ResourceMappings mappings, RepositoryRestConfiguration config) {
Assert.notNull(mappings, "ResourceMappings must not be null!");
Assert.notNull(config, "RepositoryRestConfiguration must not be null!");
public RepositoryRestHandlerMapping(ResourceMappings mappings) {
setOrder(Ordered.LOWEST_PRECEDENCE);
this.mappings = mappings;
this.config = config;
setOrder(Ordered.LOWEST_PRECEDENCE - 100);
}
/**
* @param jpaHelper the jpaHelper to set
*/
public void setJpaHelper(JpaHelper jpaHelper) {
this.jpaHelper = jpaHelper;
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod(java.lang.String, javax.servlet.http.HttpServletRequest)
*/
@Override
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest origRequest) throws Exception {
String acceptType = origRequest.getHeader("Accept");
if (null == acceptType) {
acceptType = config.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())))) {
if ("*".equals(mt.getType()) && "*".equals(mt.getSubtype()) || "application".equals(mt.getType())
&& "*".equals(mt.getSubtype())) {
mt = config.getDefaultMediaType();
}
if (!acceptableTypes.contains(mt)) {
acceptableTypes.add(mt);
}
}
if (acceptableTypes.size() > 1) {
acceptType = collectionToDelimitedString(acceptableTypes, ",");
} else if (acceptableTypes.size() == 1) {
@@ -72,29 +115,38 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
if (requestUri.startsWith("/")) {
requestUri = requestUri.substring(1);
}
if (!hasText(requestUri)) {
return super.lookupHandlerMethod(lookupPath, request);
}
String[] parts = requestUri.split("/");
if (parts.length == 0) {
// Root request
return super.lookupHandlerMethod(lookupPath, request);
}
for (Class<?> domainType : repositories) {
if (mappings.exportsMappingFor(domainType)) {
return super.lookupHandlerMethod(lookupPath, request);
}
if (mappings.exportsTopLevelResourceFor(parts[0])) {
return super.lookupHandlerMethod(lookupPath, request);
}
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler(java.lang.Class)
*/
@Override
protected boolean isHandler(Class<?> beanType) {
return AnnotationUtils.findAnnotation(beanType, RepositoryRestController.class) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.handler.AbstractHandlerMapping#extendInterceptors(java.util.List)
*/
@Override
protected void extendInterceptors(List<Object> interceptors) {
if (null != jpaHelper) {
@@ -105,6 +157,7 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
}
private static class DefaultAcceptTypeHttpServletRequest extends HttpServletRequestWrapper {
private final String defaultAcceptType;
private DefaultAcceptTypeHttpServletRequest(HttpServletRequest request, String defaultAcceptType) {
@@ -114,6 +167,7 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
@Override
public String getHeader(String name) {
if ("accept".equals(name.toLowerCase())) {
return defaultAcceptType;
} else {
@@ -121,5 +175,4 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
}
}
}
}

View File

@@ -146,7 +146,6 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
}
@Bean
@Lazy
public JpaHelper jpaHelper() {
if (IS_JPA_AVAILABLE) {
return new JpaHelper();
@@ -325,7 +324,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
*/
@Bean
public RequestMappingHandlerMapping repositoryExporterHandlerMapping() {
return new RepositoryRestHandlerMapping(resourceMappings());
RepositoryRestHandlerMapping mapping = new RepositoryRestHandlerMapping(resourceMappings(), config());
mapping.setJpaHelper(jpaHelper());
return mapping;
}
@Bean

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2013 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 static org.mockito.Mockito.*;
import java.lang.reflect.Method;
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.Pageable;
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.mock.web.MockHttpServletRequest;
import org.springframework.web.method.HandlerMethod;
/**
* Unit tests for {@link RepositoryRestHandlerMapping}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class RepositoryRestHandlerMappingUnitTests {
static final ApplicationContext CONTEXT = new AnnotationConfigApplicationContext(RepositoryRestMvcConfiguration.class);
@Mock ResourceMappings mappings;
RepositoryRestConfiguration configuration;
RepositoryRestHandlerMapping handlerMapping;
MockHttpServletRequest mockRequest;
Method listEntitiesMethod;
@Before
public void setUp() throws Exception {
configuration = new RepositoryRestConfiguration();
handlerMapping = new RepositoryRestHandlerMapping(mappings, configuration);
handlerMapping.setApplicationContext(CONTEXT);
handlerMapping.afterPropertiesSet();
mockRequest = new MockHttpServletRequest();
listEntitiesMethod = RepositoryEntityController.class.getMethod("listEntities", RepositoryRestRequest.class,
Pageable.class, Sort.class);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullMappings() {
new RepositoryRestHandlerMapping(null, configuration);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullConfiguration() {
new RepositoryRestHandlerMapping(mappings, null);
}
/**
* @see DATAREST-111
*/
@Test
public void returnsNullForUriNotMapped() throws Exception {
assertThat(handlerMapping.lookupHandlerMethod("/foo", mockRequest), is(nullValue()));
}
/**
* @see DATAREST-111
*/
@Test
public void looksUpRepositoryEntityControllerMethodCorrectly() throws Exception {
when(mappings.exportsTopLevelResourceFor("people")).thenReturn(true);
mockRequest = new MockHttpServletRequest("GET", "/people");
HandlerMethod method = handlerMapping.lookupHandlerMethod("/people", mockRequest);
assertThat(method, is(notNullValue()));
assertThat(method.getMethod(), is(listEntitiesMethod));
}
}