DATACMNS-330, DATACMNS-331 - Advanced web integration.

Added PagedResourcesAssembler to easily convert Page instances into a PagedResource instance and automatically build the required previous/next links based on the PageableHandlerMethodArgumentResolver present in the MVC configuration. The assembler can either be injected into a Spring MVC controller or a controller method. The latter will then assume the controller methods URI to be used as pagination link base.

Added @EnableSpringDataWebSupport to automatically register HandlerMethodArgumentResolvers for Pageable and Sort as well as a DomainClassConverter that will allow the usage of domain types being managed by Spring Data repositories in controller method signatures. In case Spring HATEOAS is present on the classpath, we'll also register a PagedResourcesAssembler for injection as well as the appropriate HandlerMethodArgumentResolver for injection into controller methods.

Adapted Sonargraph configuration accordingly. Upgraded to Spring HATEOAS 0.6.0.BUILD-SNAPSHOT.
This commit is contained in:
Oliver Gierke
2013-05-08 14:07:13 +02:00
parent a741651672
commit a93c2bff94
15 changed files with 1179 additions and 295 deletions

View File

@@ -0,0 +1,111 @@
/*
* 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.web;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Unit tests for {@link PagedResourcesAssembler}.
*
* @author Oliver Gierke
*/
public class PagedResourcesAssemblerUnitTests {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
@Before
public void setUp() {
WebTestUtils.initWebTest();
}
@Test
public void addsNextLinkForFirstPage() {
PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, null);
PagedResources<Resource<Person>> resources = assembler.toResource(createPage(0));
assertThat(resources.getLink(Link.REL_PREVIOUS), is(nullValue()));
assertThat(resources.getLink(Link.REL_NEXT), is(notNullValue()));
}
@Test
public void addsPreviousAndNextLinksForMiddlePage() {
PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, null);
PagedResources<Resource<Person>> resources = assembler.toResource(createPage(1));
assertThat(resources.getLink(Link.REL_PREVIOUS), is(notNullValue()));
assertThat(resources.getLink(Link.REL_NEXT), is(notNullValue()));
}
@Test
public void addsPreviousLinkForLastPage() {
PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, null);
PagedResources<Resource<Person>> resources = assembler.toResource(createPage(2));
assertThat(resources.getLink(Link.REL_PREVIOUS), is(notNullValue()));
assertThat(resources.getLink(Link.REL_NEXT), is(nullValue()));
}
@Test
public void usesBaseUriIfConfigured() {
UriComponents baseUri = UriComponentsBuilder.fromUriString("http://foo:9090").build();
PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, baseUri);
PagedResources<Resource<Person>> resources = assembler.toResource(createPage(1));
assertThat(resources.getLink(Link.REL_PREVIOUS).getHref(), startsWith(baseUri.toUriString()));
assertThat(resources.getLink(Link.REL_NEXT).getHref(), startsWith(baseUri.toUriString()));
}
@Test
public void usesCustomLinkProvided() {
Link link = new Link("http://foo:9090", "rel");
PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, null);
PagedResources<Resource<Person>> resources = assembler.toResource(createPage(1), link);
assertThat(resources.getLink(Link.REL_PREVIOUS).getHref(), startsWith(link.getHref()));
assertThat(resources.getLink(Link.REL_NEXT).getHref(), startsWith(link.getHref()));
}
private static Page<Person> createPage(int index) {
PageRequest request = new PageRequest(index, 1);
return new PageImpl<Person>(Arrays.asList(new Person()), request, 3);
}
static class Person {
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.web;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
/**
* Helper methods for web integration testing.
*
* @author Oliver Gierke
*/
public class WebTestUtils {
/**
* Initializes web tests. Will register a {@link MockHttpServletRequest} for the current thread.
*/
public static void initWebTest() {
MockHttpServletRequest request = new MockHttpServletRequest();
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes);
}
/**
* Creates a {@link WebApplicationContext} from the given configuration classes.
*
* @param configClasses
* @return
*/
public static WebApplicationContext createApplicationContext(Class<?>... configClasses) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
for (Class<?> configClass : configClasses) {
context.register(configClass);
}
context.refresh();
return context;
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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.web.config;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.data.web.WebTestUtils;
import org.springframework.data.web.config.EnableSpringDataWebSupport.SpringDataWebConfigurationImportSelector;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
/**
* Integration tests for {@link EnableSpringDataWebSupport}.
*
* @author Oliver Gierke
* @see DATACMNS-330
*/
public class EnableSpringDataWebSupportIntegrationTests {
@Configuration
@EnableSpringDataWebSupport
static class SampleConfig {
}
@After
public void tearDown() {
reEnableHateoas();
}
@Test
public void registersBasicBeanDefinitions() throws Exception {
ApplicationContext context = WebTestUtils.createApplicationContext(SampleConfig.class);
List<String> names = Arrays.asList(context.getBeanDefinitionNames());
assertThat(names, hasItems("pageableResolver", "sortResolver", "mvcDomainClassConverter"));
assertResolversRegistered(context, SortHandlerMethodArgumentResolver.class,
PageableHandlerMethodArgumentResolver.class);
}
@Test
public void registersHateoasSpecificBeanDefinitions() throws Exception {
ApplicationContext context = WebTestUtils.createApplicationContext(SampleConfig.class);
List<String> names = Arrays.asList(context.getBeanDefinitionNames());
assertThat(names, hasItems("pagedResourcesAssembler", "pagedResourcesAssemblerArgumentResolver"));
assertResolversRegistered(context, PagedResourcesAssemblerArgumentResolver.class);
}
@Test
public void doesNotRegisterHateoasSpecificComponentsIfHateoasNotPresent() throws Exception {
hideHateoas();
ApplicationContext context = WebTestUtils.createApplicationContext(SampleConfig.class);
List<String> names = Arrays.asList(context.getBeanDefinitionNames());
assertThat(names, not(hasItems("pagedResourcesAssembler", "pagedResourcesAssemblerArgumentResolver")));
}
@SuppressWarnings("unchecked")
private static void assertResolversRegistered(ApplicationContext context, Class<?>... resolverTypes) {
RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);
assertThat(adapter, is(notNullValue()));
List<HandlerMethodArgumentResolver> resolvers = adapter.getCustomArgumentResolvers();
List<Matcher<Object>> resolverMatchers = new ArrayList<Matcher<Object>>(resolverTypes.length);
for (Class<?> resolverType : resolverTypes) {
resolverMatchers.add(instanceOf(resolverType));
}
assertThat(resolvers, hasItems(resolverMatchers.toArray(new Matcher[resolverMatchers.size()])));
}
private static void hideHateoas() throws Exception {
Field field = ReflectionUtils.findField(SpringDataWebConfigurationImportSelector.class, "HATEOAS_PRESENT");
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, null, false);
}
private static void reEnableHateoas() {
Field field = ReflectionUtils.findField(SpringDataWebConfigurationImportSelector.class, "HATEOAS_PRESENT");
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, null, true);
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.web.config;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.data.web.WebTestUtils;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
/**
* Integration tests for {@link PagedResourcesAssembler}.
*
* @author Oliver Gierke
*/
public class PageableResourcesAssemblerIntegrationTests {
@Configuration
@EnableSpringDataWebSupport
static class Config {
@Bean
public SampleController controller() {
return new SampleController();
}
}
@Before
public void setUp() {
WebTestUtils.initWebTest();
}
@Test
public void injectsPagedResourcesAssembler() {
WebApplicationContext context = WebTestUtils.createApplicationContext(Config.class);
SampleController controller = context.getBean(SampleController.class);
assertThat(controller.assembler, is(notNullValue()));
PagedResources<Resource<Person>> resources = controller.sample(new PageRequest(1, 1));
assertThat(resources.getLink(Link.REL_PREVIOUS), is(notNullValue()));
assertThat(resources.getLink(Link.REL_NEXT), is(notNullValue()));
}
@Controller
static class SampleController {
@Autowired
PagedResourcesAssembler<Person> assembler;
@RequestMapping("/persons")
PagedResources<Resource<Person>> sample(Pageable pageable) {
Page<Person> page = new PageImpl<Person>(Arrays.asList(new Person()), pageable, pageable.getOffset()
+ pageable.getPageSize() + 1);
return assembler.toResource(page);
}
}
static class Person {
}
}