From d549aaeceeb1c6669ccd8e167f1fa7dc57970485 Mon Sep 17 00:00:00 2001 From: Jon Brisbin Date: Wed, 2 May 2012 11:09:27 -0500 Subject: [PATCH] Tweaking query method support. --- build.gradle | 2 - .../data/rest/core/Handler.java | 2 +- .../springframework/data/rest/core/Link.java | 2 +- .../data/rest/core/SimpleLink.java | 2 +- .../data/rest/core/util/BeanUtils.java | 2 +- .../core/util/FluentBeanDeserializer.java | 2 +- .../rest/core/util/FluentBeanSerializer.java | 2 +- .../data/rest/core/util/FluentBeanUtils.java | 2 +- .../data/rest/core/util/RestHelper.java | 2 +- .../data/rest/core/util/UriUtils.java | 2 +- .../data/rest/core/spec/UriUtilsSpec.groovy | 2 +- .../rest/repository/AttributeMetadata.java | 65 +++++++- .../data/rest/repository/EntityMetadata.java | 35 +++- ...epositoryConstraintViolationException.java | 2 +- .../rest/repository/RepositoryExporter.java | 113 ++++++++----- .../repository/RepositoryExporterSupport.java | 24 ++- .../rest/repository/RepositoryMetadata.java | 49 +++++- .../RepositoryNotFoundException.java | 2 +- .../repository/RepositoryQueryMethod.java | 8 +- .../rest/repository/ValidationErrors.java | 2 +- .../annotation/HandleAfterDelete.java | 2 +- .../annotation/HandleAfterLinkSave.java | 2 +- .../annotation/HandleAfterSave.java | 2 +- .../annotation/HandleBeforeDelete.java | 2 +- .../annotation/HandleBeforeLinkSave.java | 2 +- .../annotation/HandleBeforeSave.java | 2 +- .../annotation/RepositoryEventHandler.java | 9 +- ...RestPathSegment.java => RestResource.java} | 11 +- .../AbstractRepositoryEventListener.java | 37 ++++- .../repository/context/AfterDeleteEvent.java | 2 +- .../context/AfterLinkSaveEvent.java | 2 +- .../repository/context/AfterSaveEvent.java | 2 +- ...notatedHandlerRepositoryEventListener.java | 36 +++- .../repository/context/BeforeDeleteEvent.java | 2 +- .../context/BeforeLinkSaveEvent.java | 2 +- .../repository/context/BeforeSaveEvent.java | 2 +- .../repository/context/LinkSaveEvent.java | 2 +- .../repository/context/RepositoryEvent.java | 2 +- .../ValidatingRepositoryEventListener.java | 22 ++- .../repository/jpa/JpaAttributeMetadata.java | 2 +- .../repository/jpa/JpaEntityMetadata.java | 2 +- .../repository/jpa/JpaRepositoryExporter.java | 16 +- .../repository/jpa/JpaRepositoryMetadata.java | 32 +++- .../repository/spec/ExtensionsSpec.groovy | 2 +- .../repository/spec/JpaMetadataSpec.groovy | 2 +- .../data/rest/repository/test/Family.java | 2 +- .../repository/test/FamilyRepository.java | 2 +- .../data/rest/repository/test/Person.java | 2 +- .../repository/test/PersonRepository.java | 6 +- .../data/rest/webmvc/JsonView.java | 2 +- .../data/rest/webmvc/Links.java | 2 +- .../webmvc/RepositoryRestConfiguration.java | 25 ++- .../rest/webmvc/RepositoryRestController.java | 157 +++++++++++++----- .../RepositoryRestMvcConfiguration.java | 2 +- ...rverHttpRequestMethodArgumentResolver.java | 2 +- .../data/rest/webmvc/UriListView.java | 2 +- .../src/main/webapp/WEB-INF/web.xml | 13 +- .../spec/RepositoryRestControllerSpec.groovy | 57 +++---- .../data/rest/test/RestBuilder.java | 2 +- .../data/rest/test/webmvc/Address.java | 2 +- .../rest/test/webmvc/AddressRepository.java | 2 +- .../data/rest/test/webmvc/Person.java | 2 +- .../data/rest/test/webmvc/PersonLoader.java | 2 +- .../rest/test/webmvc/PersonRepository.java | 8 +- .../rest/test/webmvc/PersonValidator.java | 2 +- .../data/rest/test/webmvc/Profile.java | 2 +- .../rest/test/webmvc/ProfileRepository.java | 2 +- 67 files changed, 609 insertions(+), 210 deletions(-) rename spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/{RestPathSegment.java => RestResource.java} (56%) diff --git a/build.gradle b/build.gradle index 007fdf9f2..42217cc73 100644 --- a/build.gradle +++ b/build.gradle @@ -16,8 +16,6 @@ allprojects { repositories { maven { url "http://repo.springsource.org/libs-milestone" } maven { url "http://repo.springsource.org/libs-release" } - //mavenCentral() - //mavenLocal() } } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Handler.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Handler.java index e0466fab1..80fe81ba6 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Handler.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Handler.java @@ -1,7 +1,7 @@ package org.springframework.data.rest.core; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public interface Handler { V handle(T t); diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Link.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Link.java index c22dd684c..c77b49256 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Link.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Link.java @@ -3,7 +3,7 @@ package org.springframework.data.rest.core; import java.net.URI; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public interface Link { diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/SimpleLink.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/SimpleLink.java index bb19a9de7..0cbbcf1de 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/SimpleLink.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/SimpleLink.java @@ -3,7 +3,7 @@ package org.springframework.data.rest.core; import java.net.URI; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class SimpleLink implements Link { diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/BeanUtils.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/BeanUtils.java index 547a0a40f..b219536ad 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/BeanUtils.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/BeanUtils.java @@ -21,7 +21,7 @@ import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public abstract class BeanUtils { diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanDeserializer.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanDeserializer.java index ac8083859..393bfadf5 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanDeserializer.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanDeserializer.java @@ -13,7 +13,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.util.ClassUtils; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class FluentBeanDeserializer extends StdDeserializer { diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanSerializer.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanSerializer.java index ce2130e64..69a2c89a1 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanSerializer.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanSerializer.java @@ -13,7 +13,7 @@ import org.codehaus.jackson.map.ser.std.SerializerBase; import org.springframework.util.ClassUtils; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class FluentBeanSerializer extends SerializerBase { diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanUtils.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanUtils.java index 075304fd8..e37cd8c12 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanUtils.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanUtils.java @@ -16,7 +16,7 @@ import org.slf4j.LoggerFactory; import org.springframework.util.ReflectionUtils; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public abstract class FluentBeanUtils { diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/RestHelper.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/RestHelper.java index 704d6b3dc..c74b2304f 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/RestHelper.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/RestHelper.java @@ -6,7 +6,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class RestHelper { diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java index 0be37378a..a43ff0774 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/UriUtils.java @@ -9,7 +9,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public abstract class UriUtils { diff --git a/spring-data-rest-core/src/test/groovy/org/springframework/data/rest/core/spec/UriUtilsSpec.groovy b/spring-data-rest-core/src/test/groovy/org/springframework/data/rest/core/spec/UriUtilsSpec.groovy index d9afdb0d6..578081862 100644 --- a/spring-data-rest-core/src/test/groovy/org/springframework/data/rest/core/spec/UriUtilsSpec.groovy +++ b/spring-data-rest-core/src/test/groovy/org/springframework/data/rest/core/spec/UriUtilsSpec.groovy @@ -4,7 +4,7 @@ import org.springframework.data.rest.core.util.UriUtils import spock.lang.Specification /** - * @author Jon Brisbin + * @author Jon Brisbin */ class UriUtilsSpec extends Specification { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/AttributeMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/AttributeMetadata.java index 9ce188578..881d22ba1 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/AttributeMetadata.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/AttributeMetadata.java @@ -5,30 +5,93 @@ import java.util.Map; import java.util.Set; /** - * @author Jon Brisbin + * Encapsulates necessary information about an attribute of a generic entity. + * + * @author Jon Brisbin */ public interface AttributeMetadata { + /** + * Name of the attribute. + * + * @return + */ String name(); + /** + * The type of this attribute. + * + * @return + */ Class type(); + /** + * The element type of this attribute, if this attribute is a "plural"-like attribute (a Collection, Map, etc...). + * + * @return + */ Class elementType(); + /** + * Can this attribute look like a {@link Collection}? + * + * @return + */ boolean isCollectionLike(); + /** + * Get the path of this attribute as a {@link Collection}. + * + * @param target + * @return + */ Collection asCollection(Object target); + /** + * Can this attribute look like a {@link Set}? + * + * @return + */ boolean isSetLike(); + /** + * Get the path of this attribute as a {@link Set}. + * + * @param target + * @return + */ Set asSet(Object target); + /** + * Can this attribute look like a {@link Map}? + * + * @return + */ boolean isMapLike(); + /** + * Get the path of this attribute as a {@link Map}. + * + * @param target + * @return + */ Map asMap(Object target); + /** + * Get the path of this attribute. + * + * @param target + * @return + */ Object get(Object target); + /** + * Set the path of this attribute. + * + * @param value + * @param target + * @return + */ AttributeMetadata set(Object value, Object target); } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/EntityMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/EntityMetadata.java index 695899e30..3a9edd1f2 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/EntityMetadata.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/EntityMetadata.java @@ -3,20 +3,53 @@ package org.springframework.data.rest.repository; import java.util.Map; /** - * @author Jon Brisbin + * Encapsulates necessary metadata about a generic entity. + * + * @author Jon Brisbin */ public interface EntityMetadata { + /** + * The class of this entity. + * + * @return + */ Class type(); + /** + * A Map of attribute metadata keyed on the attribute's name. + * + * @return + */ Map embeddedAttributes(); + /** + * A Map of linked attribute metadata keyed on the attribute's name. + * + * @return + */ Map linkedAttributes(); + /** + * The {@link AttributeMetadata} representing the ID of the entity. + * + * @return + */ A idAttribute(); + /** + * The {@link AttributeMetadata} representing the version of the entity, if applicable. + * + * @return + */ A versionAttribute(); + /** + * Get {@link AttributeMetadata} by name. + * + * @param name + * @return + */ A attribute(String name); } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryConstraintViolationException.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryConstraintViolationException.java index 508c91263..44752c4ab 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryConstraintViolationException.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryConstraintViolationException.java @@ -4,7 +4,7 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.validation.Errors; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class RepositoryConstraintViolationException extends DataIntegrityViolationException { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java index 474d86518..92375e65d 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java @@ -2,26 +2,29 @@ package org.springframework.data.rest.repository; import java.io.Serializable; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.support.RepositoryFactoryInformation; -import org.springframework.data.rest.repository.annotation.RestPathSegment; +import org.springframework.data.repository.support.Repositories; +import org.springframework.data.rest.repository.annotation.RestResource; import org.springframework.util.StringUtils; /** - * @author Jon Brisbin + * Abstract class that contains the basic functionality that any exporter will need + * to export a Repository implementation. + * + * @author Jon Brisbin */ public abstract class RepositoryExporter, R extends Repository, @@ -30,12 +33,30 @@ public abstract class RepositoryExporter, InitializingBean { protected ApplicationContext applicationContext; - protected EntityManager entityManager; + protected Repositories repositories; + protected List exportOnlyTheseClasses = Collections.emptyList(); protected Map repositoryMetadata; - @PersistenceContext - public void setEntityManager(EntityManager entityManager) { - this.entityManager = entityManager; + /** + * Get the list of class names of Repositories to export. + * + * @return a List of class names to export + */ + public List getExportOnlyTheseClasses() { + return exportOnlyTheseClasses; + } + + /** + * Set the class names of only those Repositories you want exported. + * Default is to export all found Repositories. + * + * @param exportOnlyTheseClasses + * @return @this + */ + @SuppressWarnings({"unchecked"}) + public M setExportOnlyTheseClasses(List exportOnlyTheseClasses) { + this.exportOnlyTheseClasses = exportOnlyTheseClasses; + return (M) this; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { @@ -44,15 +65,45 @@ public abstract class RepositoryExporter, @SuppressWarnings({"unchecked"}) @Override public void afterPropertiesSet() throws Exception { + repositories = new Repositories(applicationContext); + repositoryMetadata = new HashMap(); + Collection providers = BeanFactoryUtils.beansOfTypeIncludingAncestors( + applicationContext, + RepositoryFactoryInformation.class + ).values(); + + for (RepositoryFactoryInformation entry : providers) { + EntityInformation entityInfo = entry.getEntityInformation(); + Class repoClass = entry.getRepositoryInterface(); + String name; + RestResource pathSeg = repoClass.getAnnotation(RestResource.class); + if (null != pathSeg) { + name = pathSeg.path(); + } else { + name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", "")); + } + R repo = (R) BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, repoClass); + M repoMeta = createRepositoryMetadata(repoClass, repo, name, entityInfo); + repositoryMetadata.put(name, repoMeta); + } } + /** + * Get the list of Repository names being exported. + * + * @return + */ public Set repositoryNames() { - maybeCacheRepositoryFactoryInfo(); return repositoryMetadata.keySet(); } + /** + * Is a Repository being exporter that supports this domain type? + * + * @param domainType + * @return {@literal true} if a Repository is being exported, {@literal false} otherwise. + */ public boolean hasRepositoryFor(Class domainType) { - maybeCacheRepositoryFactoryInfo(); for (M repoMeta : repositoryMetadata.values()) { if (repoMeta.domainType().isAssignableFrom(domainType)) { return true; @@ -61,8 +112,13 @@ public abstract class RepositoryExporter, return false; } + /** + * Get the RepositoryMetadata for the Repository responsible for this domain type. + * + * @param domainType + * @return {@link RepositoryMetadata} instance + */ public M repositoryMetadataFor(Class domainType) { - maybeCacheRepositoryFactoryInfo(); for (M repoMeta : repositoryMetadata.values()) { if (repoMeta.domainType().isAssignableFrom(domainType)) { return repoMeta; @@ -71,8 +127,13 @@ public abstract class RepositoryExporter, return null; } + /** + * Get the {@link RepositoryMetadata} for the Repository exported under the given name. + * + * @param name + * @return {@link RepositoryMetadata} instance + */ public M repositoryMetadataFor(String name) { - maybeCacheRepositoryFactoryInfo(); return repositoryMetadata.get(name); } @@ -83,30 +144,4 @@ public abstract class RepositoryExporter, EntityInformation entityInfo ); - @SuppressWarnings({"unchecked"}) - private void maybeCacheRepositoryFactoryInfo() { - if (null == repositoryMetadata) { - repositoryMetadata = new HashMap(); - Collection providers = BeanFactoryUtils.beansOfTypeIncludingAncestors( - applicationContext, - RepositoryFactoryInformation.class - ).values(); - - for (RepositoryFactoryInformation entry : providers) { - EntityInformation entityInfo = entry.getEntityInformation(); - Class repoClass = entry.getRepositoryInterface(); - String name; - RestPathSegment pathSeg = AnnotationUtils.findAnnotation(repoClass, RestPathSegment.class); - if (null != pathSeg) { - name = pathSeg.value(); - } else { - name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", "")); - } - R repo = (R) BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, repoClass); - M repoMeta = createRepositoryMetadata(repoClass, repo, name, entityInfo); - repositoryMetadata.put(name, repoMeta); - } - } - } - } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java index 0e4480a2c..02a3c7530 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java @@ -6,25 +6,47 @@ import java.util.List; import org.springframework.beans.factory.annotation.Autowired; /** - * @author Jon Brisbin + * Abstract class used as a helper for those classes that need access to the exported repositories. + * + * @author Jon Brisbin */ public abstract class RepositoryExporterSupport> { @Autowired protected List repositoryExporters = Collections.emptyList(); + /** + * Get a List of {@link RepositoryExporter}s. + * + * @return + */ public List getRepositoryExporters() { return repositoryExporters; } + /** + * Set the List of {@link RepositoryExporter}s. + * + * @param repositoryExporters + */ public void setRepositoryExporters(List repositoryExporters) { this.repositoryExporters = repositoryExporters; } + /** + * Get a List of {@link RepositoryExporter}s. + * + * @return + */ public List repositoryExporters() { return repositoryExporters; } + /** + * Set the List of {@link RepositoryExporter}s. + * + * @param repositoryExporters + */ @SuppressWarnings({"unchecked"}) public S repositoryExporters(List repositoryExporters) { this.repositoryExporters = repositoryExporters; diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryMetadata.java index 3857e0743..4718c02ad 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryMetadata.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryMetadata.java @@ -6,20 +6,67 @@ import java.util.Map; import org.springframework.data.repository.Repository; /** - * @author Jon Brisbin + * Encapsulates necessary metadata about a {@link Repository}. + * + * @author Jon Brisbin */ public interface RepositoryMetadata, E extends EntityMetadata> { + /** + * The name this {@link Repository} is exported under. + * + * @return + */ String name(); + /** + * Get the string value to be used as part of a link {@literal rel} attribute. + * + * @return + */ + String rel(); + + /** + * The type of domain object this {@link Repository} is repsonsible for. + * + * @return + */ Class domainType(); + /** + * The Class of the {@link Repository} subinterface. + * + * @return + */ + Class> repositoryClass(); + + /** + * The {@link Repository} instance. + * + * @return + */ R repository(); + /** + * The {@link EntityMetadata} associated with the domain type of this {@literal Repository}. + * + * @return + */ E entityMetadata(); + /** + * Get a {@link RepositoryQueryMethod} by key. + * + * @param key + * @return + */ RepositoryQueryMethod queryMethod(String key); + /** + * Get a Map of all {@link RepositoryQueryMethod}s, keyed by name. + * + * @return + */ Map queryMethods(); } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryNotFoundException.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryNotFoundException.java index 10af0047b..15d1fea99 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryNotFoundException.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryNotFoundException.java @@ -3,7 +3,7 @@ package org.springframework.data.rest.repository; import org.springframework.dao.DataAccessResourceFailureException; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class RepositoryNotFoundException extends DataAccessResourceFailureException { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryQueryMethod.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryQueryMethod.java index f92c0e950..ec0f2343a 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryQueryMethod.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryQueryMethod.java @@ -7,16 +7,10 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.data.repository.query.Param; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class RepositoryQueryMethod { - private static final Class[] SIMPLE_TYPES = new Class[]{ - String.class, - Integer.class, - Long.class, - Boolean.class - }; private static final LocalVariableTableParameterNameDiscoverer nameLookup = new LocalVariableTableParameterNameDiscoverer(); private Method method; diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/ValidationErrors.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/ValidationErrors.java index 8647abc7f..ed6982344 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/ValidationErrors.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/ValidationErrors.java @@ -9,7 +9,7 @@ import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class ValidationErrors extends AbstractErrors { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterDelete.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterDelete.java index 7aacb594c..a2c3f6b0b 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterDelete.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterDelete.java @@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterLinkSave.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterLinkSave.java index f5104d4a3..8f43d2e60 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterLinkSave.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterLinkSave.java @@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterSave.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterSave.java index ab3f05889..b11c5f5a2 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterSave.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleAfterSave.java @@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeDelete.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeDelete.java index 0220120d4..d3f0b5610 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeDelete.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeDelete.java @@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeLinkSave.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeLinkSave.java index dad8b9f9a..e65ff0acd 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeLinkSave.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeLinkSave.java @@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeSave.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeSave.java index 44ee55485..b44029e2e 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeSave.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/HandleBeforeSave.java @@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RepositoryEventHandler.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RepositoryEventHandler.java index 56b3d0ac6..168401524 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RepositoryEventHandler.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RepositoryEventHandler.java @@ -7,13 +7,20 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * @author Jon Brisbin + * Advertises classes annotated with this that they are event handlers. + * + * @author Jon Brisbin */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface RepositoryEventHandler { + /** + * The list of {@link org.springframework.context.ApplicationEvent} classes this event handler cares about. + * + * @return + */ Class[] value() default {}; } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestPathSegment.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestResource.java similarity index 56% rename from spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestPathSegment.java rename to spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestResource.java index 4098b588a..a434d4ac5 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestPathSegment.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestResource.java @@ -7,7 +7,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * @author Jon Brisbin + * Annotate a {@link org.springframework.data.repository.Repository} with this to influence how it is exported and what + * the value of the {@literal rel} attribute will be in links. + * + * @author Jon Brisbin */ @Target({ ElementType.METHOD, @@ -15,8 +18,10 @@ import java.lang.annotation.Target; }) @Retention(RetentionPolicy.RUNTIME) @Inherited -public @interface RestPathSegment { +public @interface RestResource { - String value(); + String path(); + + String rel() default ""; } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java index 2228e4f83..e0b7f78ab 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java @@ -11,7 +11,10 @@ import org.springframework.data.rest.repository.RepositoryExporter; import org.springframework.data.rest.repository.RepositoryExporterSupport; /** - * @author Jon Brisbin + * Abstract class that listens for generic {@link RepositoryEvent}s and dispatches them to a specific + * method based on the event type. + * + * @author Jon Brisbin */ public abstract class AbstractRepositoryEventListener> extends RepositoryExporterSupport @@ -45,16 +48,48 @@ public abstract class AbstractRepositoryEventListener + * @author Jon Brisbin */ public class AfterDeleteEvent extends RepositoryEvent { public AfterDeleteEvent(Object source) { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AfterLinkSaveEvent.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AfterLinkSaveEvent.java index bfb4ec0f1..bd0eaa3e3 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AfterLinkSaveEvent.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AfterLinkSaveEvent.java @@ -1,7 +1,7 @@ package org.springframework.data.rest.repository.context; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class AfterLinkSaveEvent extends LinkSaveEvent { public AfterLinkSaveEvent(Object source, Object child) { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AfterSaveEvent.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AfterSaveEvent.java index 9302b37aa..64b440345 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AfterSaveEvent.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AfterSaveEvent.java @@ -3,7 +3,7 @@ package org.springframework.data.rest.repository.context; import org.springframework.context.ApplicationEvent; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class AfterSaveEvent extends RepositoryEvent { public AfterSaveEvent(Object source) { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerRepositoryEventListener.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerRepositoryEventListener.java index b0d3e6eaf..f2a25b07e 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerRepositoryEventListener.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerRepositoryEventListener.java @@ -15,20 +15,22 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave; import org.springframework.data.rest.repository.annotation.HandleAfterDelete; +import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave; import org.springframework.data.rest.repository.annotation.HandleAfterSave; -import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave; import org.springframework.data.rest.repository.annotation.HandleBeforeDelete; +import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave; import org.springframework.data.rest.repository.annotation.HandleBeforeSave; import org.springframework.data.rest.repository.annotation.RepositoryEventHandler; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** - * @author Jon Brisbin + * {@link ApplicationListener} that will dispatch {@link RepositoryEvent}s to handlers annotated with {@link + * RepositoryEventHandler}. + * + * @author Jon Brisbin */ public class AnnotatedHandlerRepositoryEventListener implements ApplicationListener, @@ -43,19 +45,41 @@ public class AnnotatedHandlerRepositoryEventListener this.applicationContext = applicationContext; } + /** + * Get the base package in which to search for event handlers. + * + * @return + */ public String getBasePackage() { return basePackage; } + /** + * Set the base package in which to search for event handlers. + * + * @param basePackage + * @return + */ public AnnotatedHandlerRepositoryEventListener setBasePackage(String basePackage) { this.basePackage = basePackage; return this; } + /** + * Get the base package in which to search for event handlers. + * + * @return + */ public String basePackage() { return basePackage; } + /** + * Set the base package in which to search for event handlers. + * + * @param basePackage + * @return + */ public AnnotatedHandlerRepositoryEventListener basePackage(String basePackage) { this.basePackage = basePackage; return this; @@ -67,7 +91,7 @@ public class AnnotatedHandlerRepositoryEventListener for (BeanDefinition beanDef : scanner.findCandidateComponents(basePackage)) { String typeName = beanDef.getBeanClassName(); Class handlerType = ClassUtils.forName(typeName, ClassUtils.getDefaultClassLoader()); - RepositoryEventHandler typeAnno = AnnotationUtils.findAnnotation(handlerType, RepositoryEventHandler.class); + RepositoryEventHandler typeAnno = handlerType.getAnnotation(RepositoryEventHandler.class); Class[] targetTypes = typeAnno.value(); if (targetTypes.length == 0) { targetTypes = new Class[]{null}; @@ -128,7 +152,7 @@ public class AnnotatedHandlerRepositoryEventListener Method method, Class annoType, Class eventType) { - T anno = AnnotationUtils.findAnnotation(method, annoType); + T anno = method.getAnnotation(annoType); if (null != anno) { try { Class[] targetTypes; diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeDeleteEvent.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeDeleteEvent.java index e3f6340cf..727579c10 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeDeleteEvent.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeDeleteEvent.java @@ -3,7 +3,7 @@ package org.springframework.data.rest.repository.context; import org.springframework.context.ApplicationEvent; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class BeforeDeleteEvent extends RepositoryEvent { public BeforeDeleteEvent(Object source) { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeLinkSaveEvent.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeLinkSaveEvent.java index f6288fff3..99ff72d7d 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeLinkSaveEvent.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeLinkSaveEvent.java @@ -1,7 +1,7 @@ package org.springframework.data.rest.repository.context; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class BeforeLinkSaveEvent extends LinkSaveEvent { public BeforeLinkSaveEvent(Object source, Object linked) { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeSaveEvent.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeSaveEvent.java index c4832b898..0a242e941 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeSaveEvent.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/BeforeSaveEvent.java @@ -3,7 +3,7 @@ package org.springframework.data.rest.repository.context; import org.springframework.context.ApplicationEvent; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class BeforeSaveEvent extends RepositoryEvent { public BeforeSaveEvent(Object source) { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/LinkSaveEvent.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/LinkSaveEvent.java index 6edf2a801..6b4f05f44 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/LinkSaveEvent.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/LinkSaveEvent.java @@ -1,7 +1,7 @@ package org.springframework.data.rest.repository.context; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class LinkSaveEvent extends RepositoryEvent { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/RepositoryEvent.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/RepositoryEvent.java index b07493fa5..f74b15e15 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/RepositoryEvent.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/RepositoryEvent.java @@ -3,7 +3,7 @@ package org.springframework.data.rest.repository.context; import org.springframework.context.ApplicationEvent; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public abstract class RepositoryEvent extends ApplicationEvent { protected RepositoryEvent(Object source) { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/ValidatingRepositoryEventListener.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/ValidatingRepositoryEventListener.java index 73166fa67..965c31f39 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/ValidatingRepositoryEventListener.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/ValidatingRepositoryEventListener.java @@ -15,7 +15,10 @@ import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; /** - * @author Jon Brisbin + * {@link org.springframework.context.ApplicationListener} implementation that dispatches {@link RepositoryEvent}s to a + * specific {@link Validator}. + * + * @author Jon Brisbin */ public class ValidatingRepositoryEventListener extends AbstractRepositoryEventListener @@ -43,10 +46,20 @@ public class ValidatingRepositoryEventListener } } + /** + * Get a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s. + * + * @return + */ public Map> getValidators() { return validators.asMap(); } + /** + * Assign a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s. + * + * @return + */ public ValidatingRepositoryEventListener setValidators(Map> validators) { for (Map.Entry> entry : validators.entrySet()) { this.validators.replaceValues(entry.getKey(), entry.getValue()); @@ -54,6 +67,13 @@ public class ValidatingRepositoryEventListener return this; } + /** + * Add a {@link Validator} that will be triggered on the given event. + * + * @param event + * @param validator + * @return + */ public ValidatingRepositoryEventListener addValidator(String event, Validator validator) { validators.put(event, validator); return this; diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaAttributeMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaAttributeMetadata.java index 706b4671a..8e3f0bd8d 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaAttributeMetadata.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaAttributeMetadata.java @@ -15,7 +15,7 @@ import org.springframework.data.rest.repository.AttributeMetadata; import org.springframework.util.ReflectionUtils; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class JpaAttributeMetadata implements AttributeMetadata { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java index a60efb709..ae0b78d0d 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java @@ -11,7 +11,7 @@ import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.repository.EntityMetadata; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class JpaEntityMetadata implements EntityMetadata { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java index 0c5ab057a..5c71268f9 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java @@ -1,20 +1,30 @@ package org.springframework.data.rest.repository.jpa; import java.io.Serializable; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.EntityInformation; -import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.repository.RepositoryExporter; /** - * @author Jon Brisbin + * Implementation of {@link RepositoryExporter} for exporting JPA {@link Repository} subinterfaces. + * + * @author Jon Brisbin */ public class JpaRepositoryExporter extends RepositoryExporter< JpaRepositoryMetadata>, Repository, JpaEntityMetadata> { + protected EntityManager entityManager; + + @PersistenceContext + public void setEntityManager(EntityManager entityManager) { + this.entityManager = entityManager; + } + @SuppressWarnings({"unchecked"}) @Override protected JpaRepositoryMetadata> createRepositoryMetadata( @@ -22,7 +32,7 @@ public class JpaRepositoryExporter extends RepositoryExporter< Repository repo, String name, EntityInformation entityInfo) { - return new JpaRepositoryMetadata(new Repositories(applicationContext), + return new JpaRepositoryMetadata(repositories, name, repoClass, repo, diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java index 21e9cb54c..a0bf66af3 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java @@ -8,31 +8,33 @@ import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.metamodel.Metamodel; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.repository.RepositoryMetadata; import org.springframework.data.rest.repository.RepositoryQueryMethod; -import org.springframework.data.rest.repository.annotation.RestPathSegment; +import org.springframework.data.rest.repository.annotation.RestResource; import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class JpaRepositoryMetadata> implements RepositoryMetadata { private final String name; - private final Class repoClass; + private final Class> repoClass; private final R repository; private final EntityInformation entityInfo; private final Map queryMethods = new HashMap(); + + private String rel; private JpaEntityMetadata entityMetadata; @SuppressWarnings({"unchecked"}) public JpaRepositoryMetadata(Repositories repositories, String name, - final Class repoClass, + final Class> repoClass, R repository, EntityInformation entityInfo, EntityManager entityManager) { @@ -41,11 +43,19 @@ public class JpaRepositoryMetadata> i this.repository = repository; this.entityInfo = entityInfo; + RestResource resourceAnno = repoClass.getAnnotation(RestResource.class); + if (null != resourceAnno && StringUtils.hasText(resourceAnno.rel())) { + rel = resourceAnno.rel(); + } else { + rel = name; + } + ReflectionUtils.doWithMethods( repoClass, new ReflectionUtils.MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - String pathSeg = AnnotationUtils.findAnnotation(method, RestPathSegment.class).value(); + RestResource resourceAnno = method.getAnnotation(RestResource.class); + String pathSeg = resourceAnno.path(); ReflectionUtils.makeAccessible(method); queryMethods.put(pathSeg, new RepositoryQueryMethod(method)); } @@ -56,7 +66,7 @@ public class JpaRepositoryMetadata> i && !method.isBridge() && method.getDeclaringClass() != Object.class && !method.getName().contains("$") - && null != AnnotationUtils.findAnnotation(method, RestPathSegment.class)); + && null != method.getAnnotation(RestResource.class)); } } ); @@ -69,10 +79,18 @@ public class JpaRepositoryMetadata> i return name; } + @Override public String rel() { + return rel; + } + @Override public Class domainType() { return entityMetadata.type(); } + @Override public Class> repositoryClass() { + return repoClass; + } + @Override public R repository() { return repository; } diff --git a/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/ExtensionsSpec.groovy b/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/ExtensionsSpec.groovy index b2fe60bd5..f8e0465a7 100644 --- a/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/ExtensionsSpec.groovy +++ b/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/ExtensionsSpec.groovy @@ -22,7 +22,7 @@ import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave /** - * @author Jon Brisbin + * @author Jon Brisbin */ @ContextConfiguration(locations = ["/ExtensionsSpec-test.xml"]) class ExtensionsSpec extends Specification { diff --git a/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/JpaMetadataSpec.groovy b/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/JpaMetadataSpec.groovy index d73113c60..b7d48cedf 100644 --- a/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/JpaMetadataSpec.groovy +++ b/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/JpaMetadataSpec.groovy @@ -15,7 +15,7 @@ import org.springframework.test.context.ContextConfiguration import spock.lang.Specification /** - * @author Jon Brisbin + * @author Jon Brisbin */ @ContextConfiguration(locations = ["/JpaMetadataSpec-test.xml"]) class JpaMetadataSpec extends Specification { diff --git a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/Family.java b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/Family.java index d160d1c8e..ecfba9869 100644 --- a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/Family.java +++ b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/Family.java @@ -7,7 +7,7 @@ import javax.persistence.Id; import javax.persistence.OneToMany; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Entity public class Family { diff --git a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/FamilyRepository.java b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/FamilyRepository.java index e25b5bddd..5bdd778ea 100644 --- a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/FamilyRepository.java +++ b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/FamilyRepository.java @@ -3,7 +3,7 @@ package org.springframework.data.rest.repository.test; import org.springframework.data.repository.CrudRepository; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public interface FamilyRepository extends CrudRepository { } diff --git a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/Person.java b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/Person.java index e0928543e..e295837df 100644 --- a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/Person.java +++ b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/Person.java @@ -5,7 +5,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Entity public class Person { diff --git a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/PersonRepository.java b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/PersonRepository.java index 6a6371b59..1d93d5812 100644 --- a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/PersonRepository.java +++ b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/test/PersonRepository.java @@ -3,14 +3,14 @@ package org.springframework.data.rest.repository.test; import java.util.List; import org.springframework.data.repository.CrudRepository; -import org.springframework.data.rest.repository.annotation.RestPathSegment; +import org.springframework.data.rest.repository.annotation.RestResource; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public interface PersonRepository extends CrudRepository { - @RestPathSegment("byName") + @RestResource(path = "byName") public List findByName(String name); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JsonView.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JsonView.java index f4a9cb787..0c2ed5a78 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JsonView.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JsonView.java @@ -14,7 +14,7 @@ import org.springframework.http.HttpStatus; import org.springframework.web.servlet.view.AbstractView; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @SuppressWarnings({"unchecked"}) public class JsonView extends AbstractView { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/Links.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/Links.java index 624dd9b2c..5b02e155d 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/Links.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/Links.java @@ -9,7 +9,7 @@ import org.springframework.data.rest.core.Link; import org.springframework.data.rest.core.SimpleLink; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class Links { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java index bbcf5efe2..d268c1bee 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java @@ -6,10 +6,11 @@ import java.util.List; import javax.persistence.EntityManagerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportResource; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener; import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter; @@ -19,10 +20,11 @@ import org.springframework.http.converter.json.MappingJacksonHttpMessageConverte import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor; /** - * @author Jon Brisbin + * Base configuration for the Spring Data REST Exporter. + * + * @author Jon Brisbin */ @Configuration -@ImportResource("classpath*:META-INF/spring-data-rest/**/*-export.xml") public class RepositoryRestConfiguration { @Autowired @@ -31,12 +33,17 @@ public class RepositoryRestConfiguration { JpaRepositoryExporter jpaRepositoryExporter; @Autowired(required = false) ConversionService customConversionService; - ConversionService defaultConversionService = new DefaultConversionService(); + ConfigurableConversionService defaultConversionService = new DefaultConversionService(); @Autowired(required = false) List> httpMessageConverters = new ArrayList>(); @Autowired(required = false) ValidatingRepositoryEventListener validatingRepositoryEventListener; + /** + * Either the user's pre-configured {@link ConversionService} or the {@link DefaultConversionService}. + * + * @return + */ @Bean ConversionService conversionService() { if (null != customConversionService) { return customConversionService; @@ -45,6 +52,11 @@ public class RepositoryRestConfiguration { } } + /** + * A list of {@link HttpMessageConverter}s to be used to read incoming data and to write outgoing responses. + * + * @return + */ @Bean List> httpMessageConverters() { if (httpMessageConverters.isEmpty()) { MappingJacksonHttpMessageConverter json = new MappingJacksonHttpMessageConverter(); @@ -56,6 +68,11 @@ public class RepositoryRestConfiguration { return httpMessageConverters; } + /** + * Export any JPA {@link org.springframework.data.repository.Repository} implementations we find. + * + * @return + */ @Bean JpaRepositoryExporter jpaRepositoryExporter() { if (null == jpaRepositoryExporter) { jpaRepositoryExporter = new JpaRepositoryExporter(); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java index 482f04125..688207592 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java @@ -26,6 +26,8 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.Repository; import org.springframework.data.rest.core.Handler; @@ -39,6 +41,7 @@ import org.springframework.data.rest.repository.RepositoryExporter; import org.springframework.data.rest.repository.RepositoryExporterSupport; import org.springframework.data.rest.repository.RepositoryMetadata; import org.springframework.data.rest.repository.RepositoryQueryMethod; +import org.springframework.data.rest.repository.annotation.RestResource; import org.springframework.data.rest.repository.context.AfterDeleteEvent; import org.springframework.data.rest.repository.context.AfterLinkSaveEvent; import org.springframework.data.rest.repository.context.AfterSaveEvent; @@ -55,10 +58,12 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.stereotype.Controller; +import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.Model; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; @@ -69,7 +74,7 @@ import org.springframework.web.context.request.WebRequest; import org.springframework.web.util.UriComponentsBuilder; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Controller public class RepositoryRestController @@ -90,6 +95,7 @@ public class RepositoryRestController private MediaType jsonMediaType = MediaType.parseMediaType("application/x-spring-data+json"); private ConversionService conversionService = new DefaultConversionService(); private List> httpMessageConverters = Collections.emptyList(); + private Map> resourceHandlers = Collections.emptyMap(); private ObjectMapper objectMapper = new ObjectMapper(); @Override public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { @@ -130,6 +136,24 @@ public class RepositoryRestController return this; } + public Map> getResourceHandlers() { + return resourceHandlers; + } + + public RepositoryRestController setResourceHandlers(Map> resourceHandlers) { + this.resourceHandlers = resourceHandlers; + return this; + } + + public Map> resourceHandlers() { + return resourceHandlers; + } + + public RepositoryRestController resourceHandlers(Map> resourceHandlers) { + this.resourceHandlers = resourceHandlers; + return this; + } + public MediaType getUriListMediaType() { return uriListMediaType; } @@ -199,9 +223,12 @@ public class RepositoryRestController URI baseUri = uriBuilder.build().toUri(); Links links = new Links(); - for (RepositoryExporter repoMeta : repositoryExporters) { - for (String name : (Set) repoMeta.repositoryNames()) { - links.add(new SimpleLink(name, buildUri(baseUri, name))); + for (RepositoryExporter repoExporter : repositoryExporters) { + for (String name : (Set) repoExporter.repositoryNames()) { + RepositoryMetadata repoMeta = repoExporter.repositoryMetadataFor(name); + String rel = repoMeta.rel(); + URI path = buildUri(baseUri, name); + links.add(new SimpleLink(rel, path)); } } @@ -229,13 +256,44 @@ public class RepositoryRestController while (iter.hasNext()) { Object o = iter.next(); Serializable id = (Serializable) repoMeta.entityMetadata().idAttribute().get(o); - links.add(new SimpleLink(repository + "." + o.getClass().getSimpleName(), + links.add(new SimpleLink(repoMeta.rel() + "." + o.getClass().getSimpleName(), buildUri(baseUri, repository, id.toString()))); } + links.add(new SimpleLink(repoMeta.rel() + ".search", + buildUri(baseUri, repository, "search"))); + + model.addAttribute(STATUS, HttpStatus.OK); + model.addAttribute(RESOURCE, links); + } + + @SuppressWarnings({"unchecked"}) + @RequestMapping( + value = "/{repository}/search", + method = RequestMethod.GET, + produces = { + "application/json" + } + ) + public void listQueryMethods(UriComponentsBuilder uriBuilder, + @PathVariable String repository, + Model model) { + URI baseUri = uriBuilder.build().toUri(); + + RepositoryMetadata repoMeta = repositoryMetadataFor(repository); + Links links = new Links(); + for (Map.Entry entry : ((Map) repoMeta.queryMethods()) .entrySet()) { - links.add(new SimpleLink(repository + "." + entry.getKey(), - buildUri(baseUri, repository, "search", entry.getKey()))); + String rel = repoMeta.rel() + "." + entry.getKey(); + URI path = buildUri(baseUri, repository, "search", entry.getKey()); + RestResource resourceAnno = entry.getValue().method().getAnnotation(RestResource.class); + if (null != resourceAnno) { + path = buildUri(baseUri, repository, "search", resourceAnno.path()); + if (StringUtils.hasText(resourceAnno.rel())) { + rel = repoMeta.rel() + "." + resourceAnno.rel(); + } + } + links.add(new SimpleLink(rel, path)); } model.addAttribute(STATUS, HttpStatus.OK); @@ -266,10 +324,23 @@ public class RepositoryRestController Object[] paramVals = new Object[paramTypes.length]; for (int i = 0; i < paramVals.length; i++) { String queryVal = request.getParameter(paramNames[i]); - if (paramTypes[i].isAssignableFrom(String.class)) { + if (String.class.isAssignableFrom(paramTypes[i])) { + // Param type is a String paramVals[i] = queryVal; - } else { + } else if (Pageable.class.isAssignableFrom(paramTypes[i])) { + // Handle paging + } else if (Sort.class.isAssignableFrom(paramTypes[i])) { + // Handle sorting + } else if (conversionService.canConvert(String.class, paramTypes[i])) { + // There's a converter from String -> param type paramVals[i] = conversionService.convert(queryVal, paramTypes[i]); + } else { + // Param type isn't a "simple" type or no converter exists, try JSON + try { + paramVals[i] = objectMapper.readValue(queryVal, paramTypes[i]); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } } } @@ -280,8 +351,10 @@ public class RepositoryRestController for (Object o : (Collection) result) { RepositoryMetadata elemRepoMeta = repositoryMetadataFor(o.getClass()); if (null != elemRepoMeta) { - Map dto = extractPropertiesLinkAware(repository, o, elemRepoMeta.entityMetadata(), baseUri); - coll.add(dto); + String id = elemRepoMeta.entityMetadata().idAttribute().get(o).toString(); + String rel = elemRepoMeta.rel() + "." + elemRepoMeta.entityMetadata().type().getSimpleName(); + URI path = buildUri(baseUri, repository, id); + coll.add(new SimpleLink(rel, path)); } else { coll.add(o); } @@ -292,11 +365,11 @@ public class RepositoryRestController } else { RepositoryMetadata elemRepoMeta = repositoryMetadataFor(result.getClass()); if (null != elemRepoMeta) { - Map dto = extractPropertiesLinkAware(repository, - result, - elemRepoMeta.entityMetadata(), - baseUri); - model.addAttribute(RESOURCE, dto); + String id = elemRepoMeta.entityMetadata().idAttribute().get(result).toString(); + String rel = elemRepoMeta.rel() + "." + elemRepoMeta.entityMetadata().type().getSimpleName(); + URI path = buildUri(baseUri, repository, id); + Link link = new SimpleLink(rel, path); + model.addAttribute(RESOURCE, link); } else { model.addAttribute(RESOURCE, result); } @@ -387,12 +460,10 @@ public class RepositoryRestController headers.set("ETag", "\"" + version.toString() + "\""); } Map entityDto = extractPropertiesLinkAware(repository, + repoMeta.rel(), entity, repoMeta.entityMetadata(), - UriComponentsBuilder.fromUri(baseUri) - .pathSegment(repository, id) - .build() - .toUri()); + buildUri(baseUri, repository, id)); addSelfLink(baseUri, entityDto, repository, id); model.addAttribute(HEADERS, headers); @@ -549,14 +620,14 @@ public class RepositoryRestController if (propVal instanceof Collection) { for (Object o : (Collection) propVal) { String propValId = idAttr.get(o).toString(); - URI uri = buildUri(baseUri, repository, id, property, propValId); - links.add(new SimpleLink(repository + "." + entity.getClass() - .getSimpleName() + "." + attrType.getSimpleName(), uri)); + String rel = repository + "." + entity.getClass().getSimpleName() + "." + attrType.getSimpleName(); + URI path = buildUri(baseUri, repository, id, property, propValId); + links.add(new SimpleLink(rel, path)); } } else if (propVal instanceof Map) { for (Map.Entry entry : ((Map) propVal).entrySet()) { String propValId = idAttr.get(entry.getValue()).toString(); - URI uri = buildUri(baseUri, repository, id, property, propValId); + URI path = buildUri(baseUri, repository, id, property, propValId); Object oKey = entry.getKey(); String sKey; if (ClassUtils.isAssignable(oKey.getClass(), String.class)) { @@ -564,14 +635,14 @@ public class RepositoryRestController } else { sKey = conversionService.convert(oKey, String.class); } - links.add(new SimpleLink(repository + "." + entity.getClass() - .getSimpleName() + "." + sKey, uri)); + String rel = repository + "." + entity.getClass().getSimpleName() + "." + sKey; + links.add(new SimpleLink(rel, path)); } } else { String propValId = idAttr.get(propVal).toString(); - URI uri = buildUri(baseUri, repository, id, property, propValId); - links.add(new SimpleLink(repository + "." + entity.getClass() - .getSimpleName() + "." + property, uri)); + String rel = repository + "." + entity.getClass().getSimpleName() + "." + property; + URI path = buildUri(baseUri, repository, id, property, propValId); + links.add(new SimpleLink(rel, path)); } model.addAttribute(RESOURCE, links); } else { @@ -597,12 +668,12 @@ public class RepositoryRestController "text/uri-list" } ) - public void updateLinks(final ServerHttpRequest request, - UriComponentsBuilder uriBuilder, - @PathVariable String repository, - @PathVariable String id, - final @PathVariable String property, - final Model model) throws IOException { + public void updatePropertyOfEntity(final ServerHttpRequest request, + UriComponentsBuilder uriBuilder, + @PathVariable String repository, + @PathVariable String id, + final @PathVariable String property, + final Model model) throws IOException { URI baseUri = uriBuilder.build().toUri(); final RepositoryMetadata repoMeta = repositoryMetadataFor(repository); @@ -625,7 +696,7 @@ public class RepositoryRestController @Override public Void handle(Object linkedEntity) { if (attrMeta.isCollectionLike()) { Collection c = new ArrayList(); - Collection current = (Collection) attrMeta.get(entity); + Collection current = attrMeta.asCollection(entity); if (request.getMethod() == HttpMethod.POST && null != current) { c.addAll(current); } @@ -633,7 +704,7 @@ public class RepositoryRestController attrMeta.set(c, entity); } else if (attrMeta.isSetLike()) { Set s = new HashSet(); - Set current = (Set) attrMeta.get(entity); + Set current = attrMeta.asSet(entity); if (request.getMethod() == HttpMethod.POST && null != current) { s.addAll(current); } @@ -641,7 +712,7 @@ public class RepositoryRestController attrMeta.set(s, entity); } else if (attrMeta.isMapLike()) { Map m = new HashMap(); - Map current = (Map) attrMeta.get(entity); + Map current = attrMeta.asMap(entity); if (request.getMethod() == HttpMethod.POST && null != current) { m.putAll(current); } @@ -671,7 +742,9 @@ public class RepositoryRestController } } } else if (jsonMediaType.equals(incomingMediaType)) { - final Map>> incoming = readIncoming(request, incomingMediaType, Map.class); + final Map>> incoming = readIncoming(request, + incomingMediaType, + Map.class); for (Map link : incoming.get(LINKS)) { String sLinkUri = link.get("href"); Object o = resolveTopLevelResource(baseUri, sLinkUri); @@ -782,6 +855,7 @@ public class RepositoryRestController Object linkedEntity = linkedRepo.findOne(sChildId); if (null != linkedEntity) { Map entityDto = extractPropertiesLinkAware(repository, + linkedRepoMeta.rel(), linkedEntity, linkedRepoMeta.entityMetadata(), baseUri); @@ -969,7 +1043,8 @@ public class RepositoryRestController } @SuppressWarnings({"unchecked"}) - private Map extractPropertiesLinkAware(String repository, + private Map extractPropertiesLinkAware(String repoName, + String repoRel, Object entity, EntityMetadata entityMetadata, URI baseUri) { @@ -988,7 +1063,7 @@ public class RepositoryRestController .pathSegment(attrName) .build() .toUri(); - Link l = new SimpleLink(repository + "." + entity.getClass().getSimpleName() + "." + attrName, uri); + Link l = new SimpleLink(repoRel + "." + entity.getClass().getSimpleName() + "." + attrName, uri); List links = (List) entityDto.get(LINKS); if (null == links) { links = new ArrayList(); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java index 5e82e754b..b0d09a3e7 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java @@ -19,7 +19,7 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Configuration public class RepositoryRestMvcConfiguration { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ServerHttpRequestMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ServerHttpRequestMethodArgumentResolver.java index 8a00ab4e8..6b7690199 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ServerHttpRequestMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ServerHttpRequestMethodArgumentResolver.java @@ -12,7 +12,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class ServerHttpRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListView.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListView.java index 84f902d4d..d8c94ca1d 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListView.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListView.java @@ -13,7 +13,7 @@ import org.springframework.http.HttpStatus; import org.springframework.web.servlet.view.AbstractView; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class UriListView extends AbstractView { diff --git a/spring-data-rest-webmvc/src/main/webapp/WEB-INF/web.xml b/spring-data-rest-webmvc/src/main/webapp/WEB-INF/web.xml index d6b051aaf..5e7c71d5c 100644 --- a/spring-data-rest-webmvc/src/main/webapp/WEB-INF/web.xml +++ b/spring-data-rest-webmvc/src/main/webapp/WEB-INF/web.xml @@ -4,13 +4,11 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> - - contextClass - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - contextConfigLocation - org.springframework.data.rest.webmvc.RepositoryRestConfiguration + + classpath*:META-INF/spring-data-rest/**/*-export.xml + @@ -26,7 +24,10 @@ contextConfigLocation - org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration + + org.springframework.data.rest.webmvc.RepositoryRestConfiguration + org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration + 1 diff --git a/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy b/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy index d8de9e4e5..1ef867794 100644 --- a/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy +++ b/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy @@ -2,12 +2,9 @@ package org.springframework.data.rest.webmvc.spec import org.codehaus.jackson.map.ObjectMapper import org.codehaus.jackson.map.ser.CustomSerializerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration +import org.springframework.context.support.ClassPathXmlApplicationContext import org.springframework.data.rest.core.SimpleLink import org.springframework.data.rest.core.util.FluentBeanSerializer -import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener import org.springframework.data.rest.test.webmvc.Address import org.springframework.data.rest.webmvc.RepositoryRestConfiguration import org.springframework.data.rest.webmvc.RepositoryRestController @@ -15,26 +12,25 @@ import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration import org.springframework.http.HttpStatus import org.springframework.http.server.ServletServerHttpRequest import org.springframework.mock.web.MockHttpServletRequest -import org.springframework.test.context.ContextConfiguration +import org.springframework.mock.web.MockServletConfig +import org.springframework.mock.web.MockServletContext import org.springframework.transaction.annotation.Transactional import org.springframework.ui.ExtendedModelMap +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext import org.springframework.web.util.UriComponentsBuilder import spock.lang.Shared import spock.lang.Specification /** - * @author Jon Brisbin + * @author Jon Brisbin */ -@ContextConfiguration(classes = [RepositoryRestConfiguration, RepositoryRestMvcConfiguration, RepositorySpecConfig]) class RepositoryRestControllerSpec extends Specification { @Shared UriComponentsBuilder uriBuilder @Shared ObjectMapper mapper = new ObjectMapper() - @Autowired - URI baseUri - @Autowired + @Shared RepositoryRestController controller MockHttpServletRequest createRequest(String method, String path) { @@ -46,6 +42,14 @@ class RepositoryRestControllerSpec extends Specification { } def setupSpec() { + def appCtx = new ClassPathXmlApplicationContext("classpath*:META-INF/spring-data-rest/**/*-export.xml") + def webAppCtx = new AnnotationConfigWebApplicationContext() + webAppCtx.setServletConfig(new MockServletConfig()) + webAppCtx.setServletContext(new MockServletContext()) + webAppCtx.setConfigLocations([RepositoryRestConfiguration.name, RepositoryRestMvcConfiguration.name] as String[]) + webAppCtx.setParent(appCtx) + webAppCtx.afterPropertiesSet() + controller = webAppCtx.getBean(RepositoryRestController) uriBuilder = UriComponentsBuilder.fromUriString("http://localhost:8080/data") def customSerializerFactory = new CustomSerializerFactory() customSerializerFactory.addSpecificMapping(SimpleLink, new FluentBeanSerializer(SimpleLink)) @@ -68,40 +72,40 @@ class RepositoryRestControllerSpec extends Specification { when: "adding an entity" model.clear() - def req = createRequest("POST", "person") + def req = createRequest("POST", "people") def data = mapper.writeValueAsBytes([name: "John Doe"]) req.content = data - controller.create(new ServletServerHttpRequest(req), uriBuilder, "person", model) + controller.create(new ServletServerHttpRequest(req), uriBuilder, "people", model) then: model.status == HttpStatus.CREATED when: "getting specific entity" model.clear() - req = createRequest("GET", "person/1") - controller.entity(new ServletServerHttpRequest(req), uriBuilder, "person", "1", model) + req = createRequest("GET", "people/1") + controller.entity(new ServletServerHttpRequest(req), uriBuilder, "people", "1", model) then: model.resource?.name == "John Doe" when: "updating an entity" model.clear() - req = createRequest("PUT", "person/1") + req = createRequest("PUT", "people/1") data = mapper.writeValueAsBytes([name: "Johnnie Doe", version: 0]) req.content = data - controller.createOrUpdate(new ServletServerHttpRequest(req), uriBuilder, "person", "1", model) + controller.createOrUpdate(new ServletServerHttpRequest(req), uriBuilder, "people", "1", model) then: model.status == HttpStatus.NO_CONTENT when: "listing available entities" model.clear() - controller.listEntities(uriBuilder, "person", model) - def personsLinks = model.resource?.links + controller.listEntities(uriBuilder, "people", model) + def peopleLinks = model.resource?.links then: model.status == HttpStatus.OK - personsLinks[0].href().toString() == "http://localhost:8080/data/person/1" + peopleLinks[0].href().toString() == "http://localhost:8080/data/people/1" when: "creating child entity" model.clear() @@ -115,18 +119,18 @@ class RepositoryRestControllerSpec extends Specification { when: "linking child to parent entity" model.clear() - req = createRequest("POST", "person/1/addresses") + req = createRequest("POST", "people/1/addresses") req.contentType = "text/uri-list" data = "http://localhost:8080/data/address/1".bytes req.content = data - controller.updateLinks(new ServletServerHttpRequest(req), uriBuilder, "person", "1", "addresses", model) + controller.updatePropertyOfEntity(new ServletServerHttpRequest(req), uriBuilder, "people", "1", "addresses", model) then: model.status == HttpStatus.CREATED when: "getting property of entity" model.clear() - controller.propertyOfEntity(uriBuilder, "person", "1", "addresses", model) + controller.propertyOfEntity(uriBuilder, "people", "1", "addresses", model) def addrLinks = model.resource?.links then: @@ -136,12 +140,3 @@ class RepositoryRestControllerSpec extends Specification { } } - -@Configuration -class RepositorySpecConfig { - - @Bean ValidatingRepositoryEventListener validator() { - new ValidatingRepositoryEventListener() - } - -} \ No newline at end of file diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/RestBuilder.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/RestBuilder.java index 0dc9e4915..83a1e9b85 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/RestBuilder.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/RestBuilder.java @@ -26,7 +26,7 @@ import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class RestBuilder { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Address.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Address.java index 9c1971c84..e271b1c95 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Address.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Address.java @@ -5,7 +5,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Entity public class Address { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/AddressRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/AddressRepository.java index 82a4191db..7884a924b 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/AddressRepository.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/AddressRepository.java @@ -3,7 +3,7 @@ package org.springframework.data.rest.test.webmvc; import org.springframework.data.repository.CrudRepository; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public interface AddressRepository extends CrudRepository { } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Person.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Person.java index f211aa0ae..7b2c46a48 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Person.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Person.java @@ -9,7 +9,7 @@ import javax.persistence.OneToMany; import javax.persistence.Version; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Entity public class Person { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonLoader.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonLoader.java index bd254b7ed..e8c67010b 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonLoader.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonLoader.java @@ -7,7 +7,7 @@ import java.util.Map; import org.springframework.beans.factory.InitializingBean; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class PersonLoader implements InitializingBean { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java index 3bb9efa27..50c3fce80 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java @@ -4,15 +4,15 @@ import java.util.List; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; -import org.springframework.data.rest.repository.annotation.RestPathSegment; +import org.springframework.data.rest.repository.annotation.RestResource; /** - * @author Jon Brisbin + * @author Jon Brisbin */ -@RestPathSegment("person") +@RestResource(path = "people", rel = "peeps") public interface PersonRepository extends CrudRepository { - @RestPathSegment("byName") + @RestResource(path = "name", rel = "names") public List findByName(@Param("name") String name); } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonValidator.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonValidator.java index 82665abf7..36dbc5ce8 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonValidator.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonValidator.java @@ -8,7 +8,7 @@ import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public class PersonValidator implements Validator { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Profile.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Profile.java index 8f8c9cac1..1931603a9 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Profile.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Profile.java @@ -5,7 +5,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; /** - * @author Jon Brisbin + * @author Jon Brisbin */ @Entity public class Profile { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ProfileRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ProfileRepository.java index 761f7c0a7..fc4ba7b3d 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ProfileRepository.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ProfileRepository.java @@ -3,7 +3,7 @@ package org.springframework.data.rest.test.webmvc; import org.springframework.data.repository.CrudRepository; /** - * @author Jon Brisbin + * @author Jon Brisbin */ public interface ProfileRepository extends CrudRepository { }