diff --git a/pom.xml b/pom.xml index d1093990f..3c86a084d 100644 --- a/pom.xml +++ b/pom.xml @@ -72,13 +72,6 @@ - - - org.springframework.data - spring-data-jpa - ${springdata.jpa} - test - org.hibernate.javax.persistence @@ -94,6 +87,13 @@ test + + org.springframework.data + spring-data-jpa + ${springdata.jpa} + test + + org.springframework.data spring-data-mongodb diff --git a/spring-data-rest-core/pom.xml b/spring-data-rest-core/pom.xml index a316b3519..e5ba45b4b 100644 --- a/spring-data-rest-core/pom.xml +++ b/spring-data-rest-core/pom.xml @@ -28,17 +28,17 @@ spring-tx - - org.springframework.data - spring-data-commons - ${springdata.commons} - - org.springframework.hateoas spring-hateoas ${spring.hateoas} + + + org.springframework.data + spring-data-commons + ${springdata.commons} + org.springframework.plugin diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryMethod.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryMethod.java deleted file mode 100644 index 006634d47..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryMethod.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.springframework.data.rest.core.invoke; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.springframework.core.MethodParameter; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.query.Param; -import org.springframework.data.rest.core.support.Methods; - -/** - * An abstraction to encapsulate metadata about a repository method. - * - * @author Jon Brisbin - */ -public class RepositoryMethod { - - private Method method; - private List methodParameters = new ArrayList(); - private List paramNames = new ArrayList(); - private boolean pageable = false; - private boolean sortable = false; - - public RepositoryMethod(Method method) { - this.method = method; - - Class[] paramTypes = method.getParameterTypes(); - String[] paramNames = Methods.NAME_DISCOVERER.getParameterNames(method); - if (null == paramNames) { - paramNames = new String[paramTypes.length]; - } - - Annotation[][] paramAnnos = method.getParameterAnnotations(); - for (int i = 0; i < paramAnnos.length; i++) { - if (paramAnnos[i].length > 0) { - for (Annotation anno : paramAnnos[i]) { - if (Param.class.isAssignableFrom(anno.getClass())) { - Param p = (Param) anno; - paramNames[i] = p.value(); - break; - } - } - } - if (null == paramNames[i]) { - paramNames[i] = "arg" + i; - } - } - - int idx = 0; - for (Class type : paramTypes) { - if (Pageable.class.isAssignableFrom(type)) { - pageable = true; - } - if (Sort.class.isAssignableFrom(type)) { - sortable = true; - } - methodParameters.add(new MethodParameter(method, idx)); - idx++; - } - - Collections.addAll(this.paramNames, paramNames); - } - - /** - * Get the method parameter types. - * - * @return Array of parameter types. - */ - public List getParameters() { - return methodParameters; - } - - /** - * Get the method parameter names. - * - * @return Array of parameter names. - */ - public List getParameterNames() { - return paramNames; - } - - /** - * Get the reflected {@link Method} to invoke. - * - * @return The {@link Method} to invoke. - */ - public Method getMethod() { - return method; - } - - /** - * Flag denoting whether this repository method returns a {@link org.springframework.data.domain.Page} result or not. - * - * @return {@literal true} if this method returns a {@link org.springframework.data.domain.Page}, {@literal false} - * otherwise. - */ - public boolean isPageable() { - return pageable; - } - - /** - * Flag denoting whether this repository method accepts sorting information. - * - * @return {@literal true} if this method accepts a {@link Sort}, {@literal false} otherwise. - */ - public boolean isSortable() { - return sortable; - } - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryQueryMethod.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryQueryMethod.java deleted file mode 100644 index 67c4655d5..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryQueryMethod.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.springframework.data.rest.core.invoke; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; - -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.query.Param; -import org.springframework.util.Assert; - -/** - * Represents a query method on a repository interface. - * - * @author Jon Brisbin - */ -public class RepositoryQueryMethod { - - private Method method; - private Class[] paramTypes; - private String[] paramNames; - - public RepositoryQueryMethod(Method method) { - this.method = method; - paramTypes = method.getParameterTypes(); - paramNames = new String[paramTypes.length]; - if (null == paramNames) { - paramNames = new String[paramTypes.length]; - } - Annotation[][] paramAnnos = method.getParameterAnnotations(); - for (int i = 0; i < paramAnnos.length; i++) { - if (paramAnnos[i].length == 0) { - continue; - } - - for (Annotation anno : paramAnnos[i]) { - if (Param.class.isAssignableFrom(anno.getClass())) { - Param p = (Param) anno; - paramNames[i] = p.value(); - break; - } - } - - if (Pageable.class.isAssignableFrom(paramTypes[i]) || Sort.class.isAssignableFrom(paramTypes[i])) { - continue; - } - - Assert.notNull(paramNames[i], "No @Param('name') was provided for parameter " + (i + 1) + " of type " - + paramTypes[i] + " on " + (method.getDeclaringClass().getName() + "." + method.getName())); - } - } - - /** - * The method's parameter types. - * - * @return - */ - public Class[] paramTypes() { - return paramTypes; - } - - /** - * The parameter names as pulled from the {@link Param} annotations. - * - * @return - */ - public String[] paramNames() { - return paramNames; - } - - /** - * The {@link Method} to invoke. - * - * @return - */ - public Method method() { - return method; - } - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceInformation.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceInformation.java index 8572fdf06..9f167982c 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceInformation.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -18,12 +18,14 @@ package org.springframework.data.rest.core.mapping; import org.springframework.context.annotation.Primary; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.Path; import org.springframework.util.Assert; /** + * {@link ResourceMetadata} for a single repository. + * * @author Oliver Gierke */ class RepositoryAwareResourceInformation implements ResourceMetadata { @@ -31,26 +33,37 @@ class RepositoryAwareResourceInformation implements ResourceMetadata { private final Repositories repositories; private final CollectionResourceMapping mapping; private final ResourceMappings provider; - private final RepositoryInformation repositoryInterface; + private final RepositoryMetadata repositoryInterface; /** + * Creates a new {@link RepositoryAwareResourceInformation} for the given {@link Repositories}, + * {@link CollectionResourceMapping}, {@link ResourceMappings} and {@link RepositoryMetadata}. + * * @param repositories must not be {@literal null}. * @param mapping must not be {@literal null}. * @param provider must not be {@literal null}. + * @param repositoryMetadata must not be {@literal null}. */ public RepositoryAwareResourceInformation(Repositories repositories, CollectionResourceMapping mapping, - ResourceMappings provider, RepositoryInformation repositoryInterface) { + ResourceMappings provider, RepositoryMetadata repositoryMetadata) { Assert.notNull(repositories, "Repositories must not be null!"); - Assert.notNull(mapping, "ResourceMapping must not be null!"); + Assert.notNull(mapping, "CollectionResourceMapping must not be null!"); Assert.notNull(provider, "ResourceMetadataProvider must not be null!"); + Assert.notNull(repositoryMetadata, "RepositoryMetadata must not be null!"); this.repositories = repositories; this.mapping = mapping; this.provider = provider; - this.repositoryInterface = repositoryInterface; + this.repositoryInterface = repositoryMetadata; } + /** + * Returns whether the current {@link ResourceMetadata} instance for the repository is the primary one to be used. + * Reflects to the primary state of the bean definition. + * + * @return + */ public boolean isPrimary() { return AnnotationUtils.findAnnotation(repositoryInterface.getRepositoryInterface(), Primary.class) != null; } @@ -129,6 +142,15 @@ class RepositoryAwareResourceInformation implements ResourceMetadata { return mapping.getPath(); } + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#isPagingResource() + */ + @Override + public boolean isPagingResource() { + return mapping.isPagingResource(); + } + /* * (non-Javadoc) * @see org.springframework.data.rest.core.mapping.ResourceMetadata#getSearchResourceMappings() diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMapping.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMapping.java index a03a019e3..54298b07e 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMapping.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -18,6 +18,7 @@ package org.springframework.data.rest.core.mapping; import java.lang.reflect.Modifier; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.rest.core.Path; import org.springframework.data.rest.core.annotation.RestResource; import org.springframework.data.rest.core.support.RepositoriesUtils; @@ -41,9 +42,10 @@ class RepositoryCollectionResourceMapping implements CollectionResourceMapping { private final RestResource annotation; private final CollectionResourceMapping domainTypeMapping; private final boolean repositoryIsExportCandidate; + private final RepositoryMetadata metadata; - public RepositoryCollectionResourceMapping(Class repositoryType) { - this(repositoryType, new EvoInflectorRelProvider()); + public RepositoryCollectionResourceMapping(RepositoryMetadata metadata) { + this(metadata, new EvoInflectorRelProvider()); } /** @@ -53,7 +55,12 @@ class RepositoryCollectionResourceMapping implements CollectionResourceMapping { * @param repositoryType must not be {@literal null}. * @param relProvider must not be {@literal null}. */ - public RepositoryCollectionResourceMapping(Class repositoryType, RelProvider relProvider) { + public RepositoryCollectionResourceMapping(RepositoryMetadata metadata, RelProvider relProvider) { + + Assert.notNull(metadata, "Repository metadata must not be null!"); + this.metadata = metadata; + + Class repositoryType = metadata.getRepositoryInterface(); Assert.isTrue(RepositoriesUtils.isRepositoryInterface(repositoryType), "Given type is not a repository!"); Assert.notNull(relProvider, "RelProvider must not be null!"); @@ -103,4 +110,13 @@ class RepositoryCollectionResourceMapping implements CollectionResourceMapping { public Boolean isExported() { return annotation == null ? repositoryIsExportCandidate && domainTypeMapping.isExported() : annotation.exported(); } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#isPagingResource() + */ + @Override + public boolean isPagingResource() { + return metadata.isPagingRepository(); + } } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java index 16ec0c08a..b9dcc3fb0 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java @@ -17,11 +17,13 @@ package org.springframework.data.rest.core.mapping; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.Path; import org.springframework.data.rest.core.annotation.RestResource; @@ -43,6 +45,7 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping { private final String rel; private final Path path; private final Method method; + private final boolean paging; private final List parameterNames; @@ -65,6 +68,7 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping { annotation.path()); this.method = method; this.parameterNames = discoverParameterNames(method); + this.paging = Arrays.asList(method.getParameterTypes()).contains(Pageable.class); } private static final List discoverParameterNames(Method method) { @@ -125,4 +129,13 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping { public List getParameterNames() { return parameterNames; } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ResourceMapping#isPagingResource() + */ + @Override + public boolean isPagingResource() { + return paging; + } } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMapping.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMapping.java index 3d8aed519..32132332b 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMapping.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMapping.java @@ -44,4 +44,11 @@ public interface ResourceMapping { * @return will never be {@literal null}. */ Path getPath(); + + /** + * Returns whether the resource is paging one. + * + * @return + */ + boolean isPagingResource(); } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMappings.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMappings.java index 36dcc8648..8ba7dcee2 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMappings.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMappings.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -98,7 +98,7 @@ public class ResourceMappings implements Iterable { RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(type); Class repositoryInterface = repositoryInformation.getRepositoryInterface(); - CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(repositoryInterface, relProvider); + CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(repositoryInformation, relProvider); RepositoryAwareResourceInformation information = new RepositoryAwareResourceInformation(repositories, mapping, this, repositoryInformation); @@ -137,8 +137,8 @@ public class ResourceMappings implements Iterable { if (resourceMapping.isExported()) { for (Method queryMethod : repositoryInformation.getQueryMethods()) { - RepositoryMethodResourceMapping methodMapping = new RepositoryMethodResourceMapping( - queryMethod, resourceMapping); + RepositoryMethodResourceMapping methodMapping = new RepositoryMethodResourceMapping(queryMethod, + resourceMapping); if (methodMapping.isExported()) { mappings.add(methodMapping); } @@ -305,5 +305,14 @@ public class ResourceMappings implements Iterable { return !typeMapping.isExported() ? false : annotation == null ? true : annotation.exported(); } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ResourceMapping#isPagingResource() + */ + @Override + public boolean isPagingResource() { + return false; + } } } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SearchResourceMappings.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SearchResourceMappings.java index 246f927ac..f859ed8d8 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SearchResourceMappings.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SearchResourceMappings.java @@ -104,6 +104,15 @@ public class SearchResourceMappings implements Iterable, return !mappings.isEmpty(); } + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ResourceMapping#isPagingResource() + */ + @Override + public boolean isPagingResource() { + return false; + } + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypeBasedCollectionResourceMapping.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypeBasedCollectionResourceMapping.java index 42b2a3047..d9ef57e14 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypeBasedCollectionResourceMapping.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/TypeBasedCollectionResourceMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -106,6 +106,15 @@ class TypeBasedCollectionResourceMapping implements CollectionResourceMapping { return relProvider.getSingleResourceRelFor(type); } + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#isPagingResource() + */ + @Override + public boolean isPagingResource() { + return false; + } + /** * Returns the default path to be used if the path is not configured manually. * diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/RepositoryMethodUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/RepositoryMethodUnitTests.java deleted file mode 100644 index cccda4954..000000000 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/RepositoryMethodUnitTests.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.springframework.data.rest.core.invoke; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; -import static org.springframework.util.ReflectionUtils.*; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.data.domain.Pageable; -import org.springframework.data.rest.core.domain.jpa.PersonRepository; -import org.springframework.data.rest.core.invoke.RepositoryMethod; -import org.springframework.data.rest.core.support.Methods; -import org.springframework.util.ReflectionUtils; - -/** - * Tests to verify the integrity of the {@link RepositoryMethod} abstraction. - * - * @author Jon Brisbin - */ -public class RepositoryMethodUnitTests { - - Map methods = new HashMap(); - RepositoryMethod method; - - @Before - public void setup() { - doWithMethods(PersonRepository.class, new ReflectionUtils.MethodCallback() { - @Override - public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - String name = method.getName(); - RepositoryMethod repoMethod = new RepositoryMethod(method); - methods.put(name, repoMethod); - } - }, Methods.USER_METHODS); - method = methods.get("findByFirstName"); - } - - @Test - public void shouldFindSimpleQueryMethods() throws Exception { - assertThat(method, notNullValue()); - } - - @Test - public void shouldFindPageableInformationOnMethod() throws Exception { - assertThat(method, notNullValue()); - assertThat(method.isPageable(), is(true)); - } - - @Test - public void shouldNotFindSortInformationOnMethod() throws Exception { - assertThat(method, notNullValue()); - assertThat(method.isSortable(), is(false)); - } - - @Test - public void shouldProvideParameterClassTypes() throws Exception { - assertThat(method, notNullValue()); - assertThat(method.getParameters().get(0).getParameterType(), is(typeCompatibleWith(String.class))); - assertThat(method.getParameters().get(1).getParameterType(), is(typeCompatibleWith(Pageable.class))); - } - - @Test - public void shouldProvideParameterNames() throws Exception { - assertThat(method, notNullValue()); - assertThat(method.getParameterNames(), contains("firstName", "arg1")); - } - -} diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMappingUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMappingUnitTests.java index 12fc7d545..df7973096 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMappingUnitTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMappingUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -19,12 +19,13 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.rest.core.Path; import org.springframework.data.rest.core.annotation.RestResource; -import org.springframework.data.rest.core.mapping.CollectionResourceMapping; -import org.springframework.data.rest.core.mapping.RepositoryCollectionResourceMapping; -import org.springframework.data.rest.core.mapping.ResourceMapping; /** * Unit tests for {@link RepositoryCollectionResourceMapping}. @@ -36,7 +37,7 @@ public class RepositoryCollectionResourceMappingUnitTests { @Test public void buildsDefaultMappingForRepository() { - CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(PersonRepository.class); + CollectionResourceMapping mapping = getResourceMappingFor(PersonRepository.class); assertThat(mapping.getPath(), is(new Path("persons"))); assertThat(mapping.getRel(), is("persons")); @@ -47,7 +48,7 @@ public class RepositoryCollectionResourceMappingUnitTests { @Test public void honorsAnnotatedsMapping() { - CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(AnnotatedPersonRepository.class); + CollectionResourceMapping mapping = getResourceMappingFor(AnnotatedPersonRepository.class); assertThat(mapping.getPath(), is(new Path("bar"))); assertThat(mapping.getRel(), is("foo")); @@ -58,8 +59,7 @@ public class RepositoryCollectionResourceMappingUnitTests { @Test public void repositoryAnnotationTrumpsDomainTypeMapping() { - CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping( - AnnotatedAnnotatedPersonRepository.class); + CollectionResourceMapping mapping = getResourceMappingFor(AnnotatedAnnotatedPersonRepository.class); assertThat(mapping.getPath(), is(new Path("/trumpsAll"))); assertThat(mapping.getRel(), is("foo")); @@ -70,16 +70,33 @@ public class RepositoryCollectionResourceMappingUnitTests { @Test public void doesNotExposeRepositoryForPublicDomainTypeIfRepoIsPackageProtected() { - ResourceMapping mapping = new RepositoryCollectionResourceMapping(PackageProtectedRepository.class); + ResourceMapping mapping = getResourceMappingFor(PackageProtectedRepository.class); assertThat(mapping.isExported(), is(false)); } + /** + * @see DATAREST-229 + */ + @Test + public void detectsPagingRepository() { + assertThat(getResourceMappingFor(PersonRepository.class).isPagingResource(), is(true)); + } + + private static CollectionResourceMapping getResourceMappingFor(Class repositoryInterface) { + + RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface); + return new RepositoryCollectionResourceMapping(metadata); + } + public static class Person {} @RestResource(path = "bar", rel = "foo", exported = false) static class AnnotatedPerson {} - public interface PersonRepository extends Repository {} + public interface PersonRepository extends Repository { + + Page findAll(Pageable pageable); + } interface AnnotatedPersonRepository extends Repository {} @@ -88,5 +105,5 @@ public class RepositoryCollectionResourceMappingUnitTests { public static class PublicClass {} - static interface PackageProtectedRepository extends Repository {} + interface PackageProtectedRepository extends Repository {} } diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java index a8baf895c..3593cc67d 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java @@ -21,7 +21,11 @@ import static org.junit.Assert.*; import java.lang.reflect.Method; import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.Path; import org.springframework.data.rest.core.annotation.RestResource; @@ -33,7 +37,8 @@ import org.springframework.data.rest.core.annotation.RestResource; */ public class RepositoryMethodResourceMappingUnitTests { - RepositoryCollectionResourceMapping resourceMapping = new RepositoryCollectionResourceMapping(PersonRepository.class); + RepositoryMetadata metadata = new DefaultRepositoryMetadata(PersonRepository.class); + RepositoryCollectionResourceMapping resourceMapping = new RepositoryCollectionResourceMapping(metadata); @Test public void defaultsMappingToMethodName() throws Exception { @@ -78,6 +83,18 @@ public class RepositoryMethodResourceMappingUnitTests { assertThat(mapping.getParameterNames(), hasItem("firstname")); } + /** + * @see DATAREST-229 + */ + @Test + public void considersPagingFinderAPagingResource() throws Exception { + + Method method = PersonRepository.class.getMethod("findByEmailAddress", String.class, Pageable.class); + MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping); + + assertThat(mapping.isPagingResource(), is(true)); + } + static class Person {} interface PersonRepository extends Repository { @@ -89,5 +106,8 @@ public class RepositoryMethodResourceMappingUnitTests { @RestResource(path = "foo") Iterable findByEmailAddress(String email); + + @RestResource(path = "fooPaged") + Page findByEmailAddress(String email, Pageable pageable); } } 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 a151d2f7a..5092d57be 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 @@ -244,7 +244,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon */ @Bean public EntityLinks entityLinks() { - return new RepositoryEntityLinks(repositories(), resourceMappings(), config()); + return new RepositoryEntityLinks(repositories(), resourceMappings(), config(), pageableResolver()); } /** diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinks.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinks.java index f1829cb31..01f677b5f 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinks.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinks.java @@ -5,12 +5,21 @@ 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.web.HateoasPageableHandlerMethodArgumentResolver; +import org.springframework.hateoas.EntityLinks; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkBuilder; +import org.springframework.hateoas.TemplateVariables; +import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.core.AbstractEntityLinks; import org.springframework.util.Assert; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; /** + * {@link EntityLinks} implementation that is able to create {@link Link} for domain classes managed by Spring Data + * REST. + * * @author Jon Brisbin * @author Oliver Gierke */ @@ -19,15 +28,29 @@ public class RepositoryEntityLinks extends AbstractEntityLinks { private final Repositories repositories; private final ResourceMappings mappings; private final RepositoryRestConfiguration config; + private final HateoasPageableHandlerMethodArgumentResolver resolver; + /** + * Creates a new {@link RepositoryEntityLinks}. + * + * @param repositories must not be {@literal null}. + * @param mappings must not be {@literal null}. + * @param config must not be {@literal null}. + * @param resolver must not be {@literal null}. + */ @Autowired - public RepositoryEntityLinks(Repositories repositories, ResourceMappings mappings, RepositoryRestConfiguration config) { + public RepositoryEntityLinks(Repositories repositories, ResourceMappings mappings, + RepositoryRestConfiguration config, HateoasPageableHandlerMethodArgumentResolver resolver) { Assert.notNull(repositories, "Repositories must not be null!"); + Assert.notNull(mappings, "ResourceMappings must not be null!"); + Assert.notNull(config, "RepositoryRestConfiguration must not be null!"); + Assert.notNull(resolver, "HateoasPageableHandlerMethodArgumentResolver must not be null!"); this.repositories = repositories; this.mappings = mappings; this.config = config; + this.resolver = resolver; } /* @@ -67,6 +90,17 @@ public class RepositoryEntityLinks extends AbstractEntityLinks { public Link linkToCollectionResource(Class type) { ResourceMetadata metadata = mappings.getMappingFor(type); + + if (metadata.isPagingResource()) { + + Link link = linkFor(type).withSelfRel(); + String href = link.getHref(); + UriComponents components = UriComponentsBuilder.fromUriString(href).build(); + TemplateVariables variables = resolver.getPaginationTemplateVariables(null, components); + + return new Link(new UriTemplate(href, variables), metadata.getRel()); + } + return linkFor(type).withRel(metadata.getRel()); } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractControllerIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractControllerIntegrationTests.java index da93e4b9e..b1059dff3 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractControllerIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractControllerIntegrationTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.rest.webmvc; +import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mapping.PersistentEntity; @@ -47,6 +48,11 @@ public abstract class AbstractControllerIntegrationTests { @Autowired RepositoryInvokerFactory invokerFactory; @Autowired ResourceMappings mappings; + @Before + public void initWebInfrastructure() { + WebTestUtils.initWebTest(); + } + /** * Returns a {@link RepositoryRestRequest} for the given domain type. * diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java index 2c8443097..f340d83a2 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java @@ -87,7 +87,7 @@ public abstract class AbstractWebIntegrationTests { } protected MockHttpServletResponse request(Link link) throws Exception { - return request(link.getHref()); + return request(link.expand().getHref()); } protected MockHttpServletResponse request(String href) throws Exception { @@ -95,7 +95,7 @@ public abstract class AbstractWebIntegrationTests { } protected ResultActions follow(Link link) throws Exception { - return follow(link.getHref()); + return follow(link.expand().getHref()); } protected ResultActions follow(String href) throws Exception { @@ -264,7 +264,7 @@ public abstract class AbstractWebIntegrationTests { request(link); // Schema - TODO:Improve by using hypermedia - mvc.perform(get(link.getHref() + "/schema").// + mvc.perform(get(link.expand().getHref() + "/schema").// accept(MediaType.parseMediaType("application/schema+json"))).// andExpect(status().isOk()); } @@ -313,7 +313,7 @@ public abstract class AbstractWebIntegrationTests { } @Test - public void postsPayloadToResource() throws Exception { + public void nic() throws Exception { Map payloads = getPayloadToPost(); assumeFalse(payloads.isEmpty()); @@ -325,9 +325,11 @@ public abstract class AbstractWebIntegrationTests { String payload = payloads.get(rel); if (payload != null) { - Link link = assertHasLinkWithRel(rel, response); - MockHttpServletRequestBuilder request = post(link.getHref()).// + Link link = assertHasLinkWithRel(rel, response); + String target = link.expand().getHref(); + + MockHttpServletRequestBuilder request = post(target).// content(payload).// contentType(MediaType.APPLICATION_JSON); diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/WebTestUtils.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/WebTestUtils.java new file mode 100644 index 000000000..c56670102 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/WebTestUtils.java @@ -0,0 +1,38 @@ +/* + * 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 org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * 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); + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java index f16eaf2b0..d5cf50ccd 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java @@ -48,6 +48,7 @@ public class JpaRepositoryConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); vendorAdapter.setDatabase(Database.HSQL); vendorAdapter.setGenerateDdl(true); @@ -55,6 +56,7 @@ public class JpaRepositoryConfig { LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setJpaVendorAdapter(vendorAdapter); factory.setPackagesToScan(getClass().getPackage().getName()); + factory.setPersistenceUnitName("spring-data-rest-webmvc"); factory.setDataSource(dataSource()); factory.afterPropertiesSet(); diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinksIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinksIntegrationTests.java new file mode 100644 index 000000000..7e92eb01f --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinksIntegrationTests.java @@ -0,0 +1,57 @@ +/* + * 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.support; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.AbstractControllerIntegrationTests; +import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig; +import org.springframework.data.rest.webmvc.jpa.Person; +import org.springframework.hateoas.Link; +import org.springframework.test.context.ContextConfiguration; + +/** + * Integration tests for {@link RepositoryEntityLinks}. + * + * @author Oliver Gierke + */ +@ContextConfiguration(classes = JpaRepositoryConfig.class) +public class RepositoryEntityLinksIntegrationTests extends AbstractControllerIntegrationTests { + + @Autowired RepositoryEntityLinks entityLinks; + + @Test + public void returnsLinkToSingleResource() { + + Link link = entityLinks.linkToSingleResource(Person.class, 1); + + assertThat(link.getHref(), endsWith("/people/1")); + assertThat(link.getRel(), is("person")); + } + + @Test + public void returnsTemplatedLinkForPagingResource() { + + Link link = entityLinks.linkToCollectionResource(Person.class); + + assertThat(link.isTemplated(), is(true)); + assertThat(link.getVariableNames(), hasItems("page", "size", "sort")); + assertThat(link.getRel(), is("people")); + } +}