From 252efc5196e20488da1608bdf53efb1f59928a55 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 21 Aug 2013 10:20:37 +0200 Subject: [PATCH] DATAREST-112 - Moved property mapping into PropertyResourceMapping. PersistentEntityJackson2Module now entirely relies on the property mapping to do the right thing for link creation. The PropertyResourceMapping now returns the property name for both rel and path. --- .../rest/core/mapping/ResourceMappings.java | 67 ++++++++++++++++++- .../ResourceMappingsIntegrationTest.java | 23 ++++++- .../json/PersistentEntityJackson2Module.java | 14 ++-- .../PersistentEntitySerializationTests.java | 4 +- 4 files changed, 95 insertions(+), 13 deletions(-) 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 5a7de21e4..cea4b6a2b 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 @@ -25,6 +25,7 @@ import java.util.Map; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.support.Repositories; +import org.springframework.data.rest.core.Path; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.support.RepositoriesUtils; import org.springframework.hateoas.RelProvider; @@ -44,6 +45,7 @@ public class ResourceMappings implements Iterable { private final Map, ResourceMetadata> cache = new HashMap, ResourceMetadata>(); private final Map, SearchResourceMappings> searchCache = new HashMap, SearchResourceMappings>(); + private final Map, ResourceMapping> propertyCache = new HashMap, ResourceMapping>(); /** * Creates a new {@link ResourceMappings} using the given {@link RepositoryRestConfiguration} and {@link Repositories} @@ -187,7 +189,19 @@ public class ResourceMappings implements Iterable { * @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#getMappingFor(org.springframework.data.mapping.PersistentProperty) */ ResourceMapping getMappingFor(PersistentProperty property) { - return getMappingFor(property.getActualType()); + + ResourceMapping propertyMapping = propertyCache.get(property); + + if (propertyMapping != null) { + return propertyMapping; + } + + ResourceMetadata propertyTypeMapping = getMappingFor(property.getActualType()); + propertyMapping = new PersistentPropertyResourceMapping(property, propertyTypeMapping == null ? false + : propertyTypeMapping.isExported()); + propertyCache.put(property, propertyMapping); + + return propertyMapping; } /* @@ -208,4 +222,55 @@ public class ResourceMappings implements Iterable { public Iterator iterator() { return cache.values().iterator(); } + + /** + * Special resource mapping for {@link PersistentProperty} instances. + * + * @author Oliver Gierke + */ + private static class PersistentPropertyResourceMapping implements ResourceMapping { + + private final PersistentProperty property; + private final boolean exported; + + /** + * Creates a new {@link PersistentPropertyResourceMapping}. + * + * @param property must not be {@literal null}. + * @param exported whether the property is exported or not. + */ + public PersistentPropertyResourceMapping(PersistentProperty property, boolean exported) { + + Assert.notNull(property, "PersistentProperty must not be null!"); + this.property = property; + this.exported = exported; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ResourceMapping#getPath() + */ + @Override + public Path getPath() { + return new Path(property.getName()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ResourceMapping#getRel() + */ + @Override + public String getRel() { + return property.getName(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ResourceMapping#isExported() + */ + @Override + public Boolean isExported() { + return exported; + } + } } diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/ResourceMappingsIntegrationTest.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/ResourceMappingsIntegrationTest.java index e82c557a0..cafc00179 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/ResourceMappingsIntegrationTest.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/ResourceMappingsIntegrationTest.java @@ -24,13 +24,14 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.repository.support.Repositories; +import org.springframework.data.rest.core.Path; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.domain.jpa.CreditCard; import org.springframework.data.rest.core.domain.jpa.JpaRepositoryConfig; import org.springframework.data.rest.core.domain.jpa.Person; -import org.springframework.data.rest.core.mapping.ResourceMappings; -import org.springframework.data.rest.core.mapping.ResourceMetadata; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; @@ -78,4 +79,22 @@ public class ResourceMappingsIntegrationTest { assertThat(creditCardMapping.isExported(), is(false)); assertThat(creditCardMapping.getSearchResourceMappings().isExported(), is(false)); } + + /** + * @see DATAREST-112 + */ + @Test + public void usesPropertyNameAsRelForPropertyResourceMapping() { + + Repositories repositories = new Repositories(factory); + PersistentEntity entity = repositories.getPersistentEntity(Person.class); + PersistentProperty property = entity.getPersistentProperty("siblings"); + + ResourceMetadata metadata = mappings.getMappingFor(Person.class); + ResourceMapping mapping = metadata.getMappingFor(property); + + assertThat(mapping.getRel(), is("siblings")); + assertThat(mapping.getPath(), is(new Path("siblings"))); + assertThat(mapping.isExported(), is(true)); + } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java index c49db8d72..823ad787e 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java @@ -27,6 +27,7 @@ import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.UriDomainClassConverter; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; +import org.springframework.data.rest.core.mapping.ResourceMapping; import org.springframework.data.rest.core.mapping.ResourceMappings; import org.springframework.data.rest.core.mapping.ResourceMetadata; import org.springframework.data.rest.webmvc.PersistentEntityResource; @@ -72,19 +73,16 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init PersistentProperty persistentProperty, List links) { Assert.isTrue(persistentProperty.isAssociation(), "PersistentProperty must be an association!"); - ResourceMetadata metadata = mappings.getMappingFor(persistentProperty.getOwner().getType()); + ResourceMetadata ownerMetadata = mappings.getMappingFor(persistentProperty.getOwner().getType()); - if (!metadata.isManagedResource(persistentProperty)) { + if (!ownerMetadata.isManagedResource(persistentProperty)) { return false; } - metadata = mappings.getMappingFor(persistentProperty.getActualType()); - - if (metadata.isExported()) { - - String propertyRel = String.format("%s.%s", metadata.getSingleResourceRel(), persistentProperty.getName()); - links.add(builder.slash(persistentProperty.getName()).withRel(propertyRel)); + ResourceMapping propertyMapping = ownerMetadata.getMappingFor(persistentProperty); + if (propertyMapping.isExported()) { + links.add(builder.slash(propertyMapping.getPath()).withRel(propertyMapping.getRel())); // This is an association. We added a Link. return true; } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntitySerializationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntitySerializationTests.java index 92b75817e..8627f5a3f 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntitySerializationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntitySerializationTests.java @@ -68,10 +68,10 @@ public class PersistentEntitySerializationTests { String s = writer.toString(); - Link fatherLink = linkDiscoverer.findLinkWithRel("person.father", s); + Link fatherLink = linkDiscoverer.findLinkWithRel("father", s); assertThat(fatherLink.getHref(), endsWith(new UriTemplate("/{id}/father").expand(person.getId()).toString())); - Link siblingLink = linkDiscoverer.findLinkWithRel("person.siblings", s); + Link siblingLink = linkDiscoverer.findLinkWithRel("siblings", s); assertThat(siblingLink.getHref(), endsWith(new UriTemplate("/{id}/siblings").expand(person.getId()).toString())); } }