diff --git a/Spring Data Commons.sonargraph b/Spring Data Commons.sonargraph index a06757e41..78949deee 100644 --- a/Spring Data Commons.sonargraph +++ b/Spring Data Commons.sonargraph @@ -1,424 +1,430 @@ - - + + - - - + + + - - - - - - + + + + + + + + + + + + - - - + + + - - - + + + - + - - - - + + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - + + + - + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - - + + + + - - - + + + - + - + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - + - - - + + + - + - + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index e13a02967..602792117 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 1.9.7 - 0.5.0.BUILD-SNAPSHOT + 0.6.0.BUILD-SNAPSHOT DATACMNS diff --git a/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java index 048b6349e..8e834eac1 100644 --- a/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolver.java @@ -218,7 +218,7 @@ public class PageableHandlerMethodArgumentResolver implements HandlerMethodArgum StringBuilder builder = new StringBuilder(prefix); - if (parameter.hasParameterAnnotation(Qualifier.class)) { + if (parameter != null && parameter.hasParameterAnnotation(Qualifier.class)) { builder.append(parameter.getParameterAnnotation(Qualifier.class).value()); builder.append(qualifierSeparator); } diff --git a/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java b/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java new file mode 100644 index 000000000..7afe0dd85 --- /dev/null +++ b/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java @@ -0,0 +1,172 @@ +/* + * 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.springframework.web.util.UriComponentsBuilder.*; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.PagedResources; +import org.springframework.hateoas.PagedResources.PageMetadata; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.ResourceAssembler; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.util.Assert; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * {@link ResourceAssembler} to easily convert {@link Page} instances into {@link PagedResources}. + * + * @since 1.6 + * @author Oliver Gierke + */ +public class PagedResourcesAssembler implements ResourceAssembler, PagedResources>> { + + private final PageableHandlerMethodArgumentResolver pageableResolver; + private final UriComponents baseUri; + + /** + * Creates a new {@link PagedResourcesAssembler} using the given {@link PageableHandlerMethodArgumentResolver} and + * base URI. If the former is {@literal null}, a default one will be created. If the latter is {@literal null}, calls + * to {@link #toResource(Page)} will use the current request's URI to build the relevant previous and next links. + * + * @param resolver + * @param baseUri + */ + public PagedResourcesAssembler(PageableHandlerMethodArgumentResolver resolver, UriComponents baseUri) { + + this.pageableResolver = resolver == null ? new PageableHandlerMethodArgumentResolver() : resolver; + this.baseUri = baseUri; + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.ResourceAssembler#toResource(java.lang.Object) + */ + @Override + public PagedResources> toResource(Page entity) { + return toResource(entity, new SimplePagedResourceAssembler()); + } + + /** + * Creates a new {@link PagedResources} by converting the given {@link Page} into a {@link PageMetadata} instance and + * wrapping the contained elements into {@link Resource} instances. Will add pagination links based on the given the + * self link. + * + * @param page must not be {@literal null}. + * @param selfLink must not be {@literal null}. + * @return + */ + public PagedResources> toResource(Page page, Link selfLink) { + return toResource(page, new SimplePagedResourceAssembler(), selfLink); + } + + /** + * Creates a new {@link PagedResources} by converting the given {@link Page} into a {@link PageMetadata} instance and + * using the given {@link ResourceAssembler} to turn elements of the {@link Page} into resources. + * + * @param page must not be {@literal null}. + * @param assembler must not be {@literal null}. + * @return + */ + public PagedResources toResource(Page page, ResourceAssembler assembler) { + return createResource(page, assembler, null); + } + + /** + * Creates a new {@link PagedResources} by converting the given {@link Page} into a {@link PageMetadata} instance and + * using the given {@link ResourceAssembler} to turn elements of the {@link Page} into resources. Will add pagination + * links based on the given the self link. + * + * @param page must not be {@literal null}. + * @param assembler must not be {@literal null}. + * @param link must not be {@literal null}. + * @return + */ + public PagedResources toResource(Page page, ResourceAssembler assembler, + Link link) { + + Assert.notNull(link, "Link must not be null!"); + return createResource(page, assembler, link); + } + + private PagedResources createResource(Page page, + ResourceAssembler assembler, Link link) { + + Assert.notNull(page, "Page must not be null!"); + Assert.notNull(assembler, "ResourceAssembler must not be null!"); + + List resources = new ArrayList(page.getNumberOfElements()); + + for (S element : page) { + resources.add(assembler.toResource(element)); + } + + PagedResources pagedResources = new PagedResources(resources, asPageMetadata(page)); + return addPaginationLinks(pagedResources, page, link == null ? getDefaultUriString().toUriString() : link.getHref()); + } + + private UriComponents getDefaultUriString() { + return baseUri == null ? ServletUriComponentsBuilder.fromCurrentRequest().build() : baseUri; + } + + private PagedResources addPaginationLinks(PagedResources resources, Page page, + String uri) { + + if (page.hasNextPage()) { + foo(resources, page.nextPageable(), uri, Link.REL_NEXT); + } + + if (page.hasPreviousPage()) { + foo(resources, page.previousPageable(), uri, Link.REL_PREVIOUS); + } + + return resources; + } + + private void foo(PagedResources resources, Pageable pageable, String uri, String rel) { + + UriComponentsBuilder builder = fromUriString(uri); + pageableResolver.enhance(builder, null, pageable); + resources.add(new Link(builder.build().toUriString(), rel)); + } + + /** + * Creates a new {@link PageMetadata} instance from the given {@link Page}. + * + * @param page must not be {@literal null}. + * @return + */ + private static PageMetadata asPageMetadata(Page page) { + + Assert.notNull(page, "Page must not be null!"); + return new PageMetadata(page.getSize(), page.getNumber(), page.getTotalElements(), page.getTotalPages()); + } + + private static class SimplePagedResourceAssembler implements ResourceAssembler> { + + @Override + public Resource toResource(T entity) { + return new Resource(entity); + } + } +} diff --git a/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java b/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java new file mode 100644 index 000000000..1b38b4e5f --- /dev/null +++ b/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java @@ -0,0 +1,77 @@ +/* + * 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.core.MethodParameter; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.MethodLinkBuilderFactory; +import org.springframework.hateoas.mvc.ControllerLinkBuilderFactory; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * {@link HandlerMethodArgumentResolver} to allow injection of {@link PagedResourcesAssembler} into Spring MVC + * controller methods. + * + * @since 1.6 + * @author Oliver Gierke + */ +public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArgumentResolver { + + private final PageableHandlerMethodArgumentResolver resolver; + private final MethodLinkBuilderFactory linkBuilderFactory; + + /** + * Creates a new {@link PagedResourcesAssemblerArgumentResolver} using the given + * {@link PageableHandlerMethodArgumentResolver} and {@link MethodLinkBuilderFactory}. + * + * @param resolver can be {@literal null}. + * @param linkBuilderFactory can be {@literal null}, will be defaulted to a {@link ControllerLinkBuilderFactory}. + */ + public PagedResourcesAssemblerArgumentResolver(PageableHandlerMethodArgumentResolver resolver, + MethodLinkBuilderFactory linkBuilderFactory) { + + this.resolver = resolver; + this.linkBuilderFactory = linkBuilderFactory == null ? new ControllerLinkBuilderFactory() : linkBuilderFactory; + } + + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) + */ + @Override + public boolean supportsParameter(MethodParameter parameter) { + return PagedResourcesAssembler.class.equals(parameter.getParameterType()); + } + + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory) + */ + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + + Link linkToMethod = linkBuilderFactory.linkTo(parameter.getMethod(), new Object[0]).withSelfRel(); + UriComponents fromUriString = UriComponentsBuilder.fromUriString(linkToMethod.getHref()).build(); + + return new PagedResourcesAssembler(resolver, fromUriString); + } +} diff --git a/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java index 22d7355e5..b0534f988 100644 --- a/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolver.java @@ -218,7 +218,7 @@ public class SortHandlerMethodArgumentResolver implements HandlerMethodArgumentR StringBuilder builder = new StringBuilder(); - if (parameter.hasParameterAnnotation(Qualifier.class)) { + if (parameter != null && parameter.hasParameterAnnotation(Qualifier.class)) { builder.append(parameter.getParameterAnnotation(Qualifier.class).value()).append(qualifierDelimiter); } diff --git a/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java b/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java new file mode 100644 index 000000000..e546f6adb --- /dev/null +++ b/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java @@ -0,0 +1,98 @@ +/* + * 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 java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.support.DomainClassConverter; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver; +import org.springframework.data.web.SortHandlerMethodArgumentResolver; +import org.springframework.util.ClassUtils; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; + +/** + * Annotation to automatically register the following beans for usage with Spring MVC. + *
    + *
  • {@link DomainClassConverter} - to allow usage of domain types managed by Spring Data repositories as controller + * method arguments bound with {@link PathVariable} or {@link RequestParam}.
  • + *
  • {@link PageableHandlerMethodArgumentResolver} - to allow injection of {@link Pageable} instances into controller + * methods automatically created from request parameters.
  • + *
  • {@link SortHandlerMethodArgumentResolver} - to allow injection of {@link Sort} instances into controller methods + * automatically created from request parameters.
  • + *
+ * If Spring HATEOAS is present on the classpath we will additionall register the following beans: + *
    + *
  • {@link PagedResourcesAssembler} - for injection into web components
  • + *
  • {@link PagedResourcesAssemblerArgumentResolver} - for injection of {@link PagedResourcesAssembler} into + * controller methods
  • + *
      + * + * @since 1.6 + * @see SpringDataWebConfiguration + * @see HateoasAwareSpringDataWebConfiguration + * @author Oliver Gierke + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +@Inherited +@Import(EnableSpringDataWebSupport.SpringDataWebConfigurationImportSelector.class) +public @interface EnableSpringDataWebSupport { + + /** + * Import selector to import the appropriate configuration class depending on whether Spring HATEOAS is present on the + * classpath. We need to register the HATEOAS specific class first as apparently only the first class implementing + * {@link WebMvcConfigurationSupport} gets callbacks invoked (see https://jira.springsource.org/browse/SPR-10565). + * + * @author Oliver Gierke + */ + class SpringDataWebConfigurationImportSelector implements ImportSelector { + + // Don't make final to allow test cases faking this to false + private static boolean HATEOAS_PRESENT = ClassUtils.isPresent("org.springframework.hateoas.Link", null); + + /* + * (non-Javadoc) + * @see org.springframework.context.annotation.ImportSelector#selectImports(org.springframework.core.type.AnnotationMetadata) + */ + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + + List configs = new ArrayList(); + + if (HATEOAS_PRESENT) { + configs.add(HateoasAwareSpringDataWebConfiguration.class.getName()); + } + + configs.add(SpringDataWebConfiguration.class.getName()); + return configs.toArray(new String[configs.size()]); + } + } +} diff --git a/src/main/java/org/springframework/data/web/config/HateoasAwareSpringDataWebConfiguration.java b/src/main/java/org/springframework/data/web/config/HateoasAwareSpringDataWebConfiguration.java new file mode 100644 index 000000000..80fe951ca --- /dev/null +++ b/src/main/java/org/springframework/data/web/config/HateoasAwareSpringDataWebConfiguration.java @@ -0,0 +1,59 @@ +/* + * 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 java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; + +/** + * JavaConfig class to register {@link PagedResourcesAssembler} and {@link PagedResourcesAssemblerArgumentResolver}. + * + * @since 1.6 + * @author Oliver Gierke + */ +@Configuration +class HateoasAwareSpringDataWebConfiguration extends WebMvcConfigurationSupport { + + @Autowired + SpringDataWebConfiguration config; + + @Bean + public PagedResourcesAssembler pagedResourcesAssembler() { + return new PagedResourcesAssembler(config.pageableResolver(), null); + } + + @Bean + public PagedResourcesAssemblerArgumentResolver pagedResourcesAssemblerArgumentResolver() { + return new PagedResourcesAssemblerArgumentResolver(config.pageableResolver(), null); + } + + /* + * (non-Javadoc) + * @see org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addArgumentResolvers(java.util.List) + */ + @Override + protected void addArgumentResolvers(List argumentResolvers) { + config.addArgumentResolvers(argumentResolvers); + argumentResolvers.add(pagedResourcesAssemblerArgumentResolver()); + } +} diff --git a/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java new file mode 100644 index 000000000..6bf6617c4 --- /dev/null +++ b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java @@ -0,0 +1,64 @@ +/* + * 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 java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.repository.support.DomainClassConverter; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.data.web.SortHandlerMethodArgumentResolver; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; + +/** + * Configuration class to register {@link PageableHandlerMethodArgumentResolver}, + * {@link SortHandlerMethodArgumentResolver} and {@link DomainClassConverter}. + * + * @since 1.6 + * @author Oliver Gierke + */ +@Configuration +class SpringDataWebConfiguration extends WebMvcConfigurationSupport { + + @Bean + public PageableHandlerMethodArgumentResolver pageableResolver() { + return new PageableHandlerMethodArgumentResolver(); + } + + @Bean + public SortHandlerMethodArgumentResolver sortResolver() { + return new SortHandlerMethodArgumentResolver(); + } + + @Bean + public DomainClassConverter mvcDomainClassConverter() { + return new DomainClassConverter(mvcConversionService()); + } + + /* + * (non-Javadoc) + * @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter#addArgumentResolvers(java.util.List) + */ + @Override + public void addArgumentResolvers(List argumentResolvers) { + + argumentResolvers.add(pageableResolver()); + argumentResolvers.add(sortResolver()); + } +} diff --git a/src/main/java/org/springframework/data/web/config/package-info.java b/src/main/java/org/springframework/data/web/config/package-info.java new file mode 100644 index 000000000..695a28ab4 --- /dev/null +++ b/src/main/java/org/springframework/data/web/config/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ +/** + * + * @author Oliver Gierke + */ +package org.springframework.data.web.config; \ No newline at end of file diff --git a/src/test/java/org/springframework/data/web/PagedResourcesAssemblerUnitTests.java b/src/test/java/org/springframework/data/web/PagedResourcesAssemblerUnitTests.java new file mode 100644 index 000000000..9287971b1 --- /dev/null +++ b/src/test/java/org/springframework/data/web/PagedResourcesAssemblerUnitTests.java @@ -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 assembler = new PagedResourcesAssembler(resolver, null); + PagedResources> 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 assembler = new PagedResourcesAssembler(resolver, null); + PagedResources> 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 assembler = new PagedResourcesAssembler(resolver, null); + PagedResources> 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 assembler = new PagedResourcesAssembler(resolver, baseUri); + PagedResources> 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 assembler = new PagedResourcesAssembler(resolver, null); + PagedResources> 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 createPage(int index) { + + PageRequest request = new PageRequest(index, 1); + return new PageImpl(Arrays.asList(new Person()), request, 3); + } + + static class Person { + + } +} diff --git a/src/test/java/org/springframework/data/web/WebTestUtils.java b/src/test/java/org/springframework/data/web/WebTestUtils.java new file mode 100644 index 000000000..2c8c6a0df --- /dev/null +++ b/src/test/java/org/springframework/data/web/WebTestUtils.java @@ -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; + } +} diff --git a/src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java b/src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java new file mode 100644 index 000000000..c6f09dc35 --- /dev/null +++ b/src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java @@ -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 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 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 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 resolvers = adapter.getCustomArgumentResolvers(); + + List> resolverMatchers = new ArrayList>(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); + } +} diff --git a/src/test/java/org/springframework/data/web/config/PageableResourcesAssemblerIntegrationTests.java b/src/test/java/org/springframework/data/web/config/PageableResourcesAssemblerIntegrationTests.java new file mode 100644 index 000000000..ee4d7bde9 --- /dev/null +++ b/src/test/java/org/springframework/data/web/config/PageableResourcesAssemblerIntegrationTests.java @@ -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> 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 assembler; + + @RequestMapping("/persons") + PagedResources> sample(Pageable pageable) { + + Page page = new PageImpl(Arrays.asList(new Person()), pageable, pageable.getOffset() + + pageable.getPageSize() + 1); + + return assembler.toResource(page); + } + } + + static class Person { + + } +} diff --git a/template.mf b/template.mf index 04d34ea4e..cd1922f52 100644 --- a/template.mf +++ b/template.mf @@ -17,8 +17,7 @@ Import-Template: org.springframework.context.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional, org.springframework.dao.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional, org.springframework.expression.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional, - org.springframework.expression.spel.standard.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional, - org.springframework.expression.spel.support.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional, + org.springframework.format.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional, org.springframework.hateoas.*;version="${springhateoas:[=.=.=,+1.1.0)}";resolution:=optional, org.springframework.oxm.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional, org.springframework.transaction.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional,