diff --git a/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java b/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java index 00815e3c3..d26397033 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java +++ b/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java @@ -42,7 +42,6 @@ import org.springframework.data.rest.core.support.SelfLinkProvider; import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.LookupObjectSerializer; -import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.NestedEntitySerializer; import org.springframework.data.rest.webmvc.mapping.Associations; import org.springframework.data.rest.webmvc.mapping.LinkCollector; import org.springframework.data.rest.webmvc.spi.BackendIdConverter; @@ -123,12 +122,10 @@ public class RepositoryTestsConfig { Associations associations = new Associations(mappings, config()); LinkCollector collector = new LinkCollector(persistentEntities(), selfLinkProvider, associations); - NestedEntitySerializer nestedEntitySerializer = new NestedEntitySerializer(persistentEntities(), - new EmbeddedResourcesAssembler(persistentEntities(), associations, mock(ExcerptProjector.class)), - new ResourceProcessorInvoker(Collections.> emptyList())); - return new PersistentEntityJackson2Module(associations, persistentEntities(), uriToEntityConverter, collector, - invokerFactory, nestedEntitySerializer, mock(LookupObjectSerializer.class)); + invokerFactory, mock(LookupObjectSerializer.class), + new ResourceProcessorInvoker(Collections.> emptyList()), + new EmbeddedResourcesAssembler(persistentEntities(), associations, mock(ExcerptProjector.class))); } @Bean diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java index 7472a8813..f430462fc 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java @@ -43,7 +43,6 @@ import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler; import org.springframework.data.rest.webmvc.jpa.Person; import org.springframework.data.rest.webmvc.jpa.PersonRepository; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.LookupObjectSerializer; -import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.NestedEntitySerializer; import org.springframework.data.rest.webmvc.mapping.Associations; import org.springframework.data.rest.webmvc.mapping.LinkCollector; import org.springframework.data.rest.webmvc.spi.BackendIdConverter; @@ -131,12 +130,10 @@ public class RepositoryTestsConfig { Associations associations = new Associations(mappings, config()); LinkCollector collector = new LinkCollector(persistentEntities(), selfLinkProvider, associations); - NestedEntitySerializer nestedEntitySerializer = new NestedEntitySerializer(persistentEntities(), - new EmbeddedResourcesAssembler(persistentEntities(), associations, mock(ExcerptProjector.class)), - new ResourceProcessorInvoker(Collections.> emptyList())); - return new PersistentEntityJackson2Module(associations, persistentEntities(), uriToEntityConverter, collector, - invokerFactory, nestedEntitySerializer, mock(LookupObjectSerializer.class)); + invokerFactory, mock(LookupObjectSerializer.class), + new ResourceProcessorInvoker(Collections.> emptyList()), + new EmbeddedResourcesAssembler(persistentEntities(), associations, mock(ExcerptProjector.class))); } @Bean diff --git a/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/LineItem.java b/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/LineItem.java index 007d7a7ba..020c0b0da 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/LineItem.java +++ b/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/LineItem.java @@ -25,6 +25,7 @@ import java.util.UUID; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Reference; +import org.springframework.data.rest.core.config.Projection; /** * @author Oliver Gierke @@ -33,6 +34,11 @@ import org.springframework.data.annotation.Reference; @EqualsAndHashCode(of = "id") public class LineItem { + @Projection(name = "productsOnly", types = { LineItem.class }) + public interface LineItemProductsOnlyProjection { + List getProducts(); + } + private final @Id UUID id = UUID.randomUUID(); private final String description; private final BigDecimal price; diff --git a/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Order.java b/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Order.java index 09c4d390c..ca2728ac0 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Order.java +++ b/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Order.java @@ -23,12 +23,18 @@ import java.util.UUID; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Reference; +import org.springframework.data.rest.core.config.Projection; /** * @author Oliver Gierke */ @Value public class Order { + + @Projection(name = "itemsOnly", types = { Order.class }) + public interface OrderItemsOnlyProjection { + List getItems(); + } private final @Id UUID id = UUID.randomUUID(); private final List items = new ArrayList<>(); diff --git a/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Product.java b/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Product.java index a2c2bb0f9..9cea9d608 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Product.java +++ b/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Product.java @@ -22,6 +22,7 @@ import java.math.BigDecimal; import java.util.UUID; import org.springframework.data.annotation.Id; +import org.springframework.data.rest.core.config.Projection; /** * @author Oliver Gierke @@ -31,6 +32,11 @@ import org.springframework.data.annotation.Id; @RequiredArgsConstructor public class Product { + @Projection(name = "nameOnly", types = { Product.class }) + public interface ProductNameOnlyProjection { + String getName(); + } + private final @Id UUID id = UUID.randomUUID(); private final String name; private final BigDecimal price; diff --git a/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopConfiguration.java b/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopConfiguration.java index 239ff9bdb..a6fe3fb20 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopConfiguration.java +++ b/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopConfiguration.java @@ -58,6 +58,22 @@ public class ShopConfiguration { }; } + @Bean + public ResourceProcessor> productNameOnlyProjectionResourceProcessor() { + return new ResourceProcessor>() { + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.ResourceProcessor#process(org.springframework.hateoas.ResourceSupport) + */ + @Override + public Resource process(Resource resource) { + resource.add(new Link("alpha", "beta")); + return resource; + } + }; + } + @PostConstruct public void init() { diff --git a/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopIntegrationTests.java b/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopIntegrationTests.java index b91082360..61b585408 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopIntegrationTests.java +++ b/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopIntegrationTests.java @@ -18,6 +18,9 @@ package org.springframework.data.rest.tests.shop; import static org.hamcrest.CoreMatchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.util.HashMap; +import java.util.Map; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.rest.tests.AbstractWebIntegrationTests; @@ -67,4 +70,36 @@ public class ShopIntegrationTests extends AbstractWebIntegrationTests { actions.andExpect(jsonPath(prefix.concat("_links.").concat(suffix)).exists()); actions.andExpect(jsonPath(prefix.concat("_embedded.").concat(suffix)).exists()); } + + @Test + public void renderProductNameOnlyProjection() throws Exception { + Map arguments = new HashMap<>(); + arguments.put("projection", "nameOnly"); + client.follow(client.discoverUnique("products").expand(arguments)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.products[0].name", notNullValue())) + .andExpect(jsonPath("$._embedded.products[0].price").doesNotExist()); + } + + @Test + public void renderProductNameOnlyProjectionResourceProcessor() throws Exception { + Map arguments = new HashMap<>(); + arguments.put("projection", "nameOnly"); + client.follow(client.discoverUnique("products").expand(arguments)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.products[0]._links.beta").exists()); + } + + /** + * @see DATAREST-221 + */ + @Test + public void renderOrderItemsOnlyProjectionResourceProcessor() throws Exception { + Map arguments = new HashMap<>(); + arguments.put("projection", "itemsOnly"); + client.follow(client.discoverUnique("orders").expand(arguments)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.orders[0].items[0].products[0].name").exists()) + .andExpect(jsonPath("$._embedded.orders[0].items[0].products[0]._links.beta").exists()); + } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java index 28a8955ca..36b07eb8d 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java @@ -97,7 +97,6 @@ import org.springframework.data.rest.webmvc.json.MappingAwarePageableArgumentRes import org.springframework.data.rest.webmvc.json.MappingAwareSortArgumentResolver; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.LookupObjectSerializer; -import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.NestedEntitySerializer; import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter; import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter.ValueTypeSchemaPropertyCustomizerFactory; import org.springframework.data.rest.webmvc.mapping.Associations; @@ -637,12 +636,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon EmbeddedResourcesAssembler assembler = new EmbeddedResourcesAssembler(entities, associationLinks(), excerptProjector()); - NestedEntitySerializer serializer = new NestedEntitySerializer(entities, assembler, resourceProcessorInvoker()); LookupObjectSerializer lookupObjectSerializer = new LookupObjectSerializer( OrderAwarePluginRegistry.create(getEntityLookups())); return new PersistentEntityJackson2Module(associationLinks(), entities, uriToEntityConverter, linkCollector(), - repositoryInvokerFactory, serializer, lookupObjectSerializer); + repositoryInvokerFactory, lookupObjectSerializer, resourceProcessorInvoker(), assembler); } @Bean 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 efabaa317..56cbbb624 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 @@ -116,7 +116,8 @@ public class PersistentEntityJackson2Module extends SimpleModule { */ public PersistentEntityJackson2Module(Associations associations, PersistentEntities entities, UriToEntityConverter converter, LinkCollector collector, RepositoryInvokerFactory factory, - NestedEntitySerializer serializer, LookupObjectSerializer lookupObjectSerializer) { + LookupObjectSerializer lookupObjectSerializer, ResourceProcessorInvoker resourceProcessorInvoker, + EmbeddedResourcesAssembler assembler) { super(new Version(2, 0, 0, null, "org.springframework.data.rest", "jackson-module")); @@ -125,8 +126,9 @@ public class PersistentEntityJackson2Module extends SimpleModule { Assert.notNull(converter, "UriToEntityConverter must not be null!"); Assert.notNull(collector, "LinkCollector must not be null!"); + NestedEntitySerializer serializer = new NestedEntitySerializer(entities, assembler, resourceProcessorInvoker); addSerializer(new PersistentEntityResourceSerializer(collector)); - addSerializer(new ProjectionSerializer(collector, associations, false)); + addSerializer(new ProjectionSerializer(collector, associations, resourceProcessorInvoker, false)); addSerializer(new ProjectionResourceContentSerializer(false)); setSerializerModifier( @@ -321,7 +323,7 @@ public class PersistentEntityJackson2Module extends SimpleModule { * @author Alex Leigh * @since 2.5 */ - public static class NestedEntitySerializer extends StdSerializer { + static class NestedEntitySerializer extends StdSerializer { private static final long serialVersionUID = -2327469118972125954L; @@ -538,6 +540,7 @@ public class PersistentEntityJackson2Module extends SimpleModule { private final LinkCollector collector; private final Associations associations; + private final ResourceProcessorInvoker resourceProcessorInvoker; private final boolean unwrapping; /** @@ -548,12 +551,13 @@ public class PersistentEntityJackson2Module extends SimpleModule { * @param mappings must not be {@literal null}. * @param unwrapping */ - private ProjectionSerializer(LinkCollector collector, Associations mappings, boolean unwrapping) { + private ProjectionSerializer(LinkCollector collector, Associations mappings, ResourceProcessorInvoker resourceProcessorInvoker, boolean unwrapping) { super(TargetAware.class); this.collector = collector; this.associations = mappings; + this.resourceProcessorInvoker = resourceProcessorInvoker; this.unwrapping = unwrapping; } @@ -565,10 +569,6 @@ public class PersistentEntityJackson2Module extends SimpleModule { public void serialize(TargetAware value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException { - Object target = value.getTarget(); - Links links = associations.getMetadataFor(value.getTargetClass()).isExported() ? collector.getLinksFor(target) - : new Links(); - if (!unwrapping) { jgen.writeStartObject(); } @@ -576,13 +576,21 @@ public class PersistentEntityJackson2Module extends SimpleModule { provider.// findValueSerializer(ProjectionResource.class, null).// unwrappingSerializer(null).// - serialize(new ProjectionResource(value, links), jgen, provider); + serialize(toResource(value), jgen, provider); if (!unwrapping) { jgen.writeEndObject(); } } + private ProjectionResource toResource(TargetAware value) { + Object target = value.getTarget(); + Links links = associations.getMetadataFor(value.getTargetClass()).isExported() ? collector.getLinksFor(target) + : new Links(); + Resource resource = resourceProcessorInvoker.invokeProcessorsFor(new Resource(value, links)); + return new ProjectionResource(resource.getContent(), resource.getLinks()); + } + /* * (non-Javadoc) * @see com.fasterxml.jackson.databind.JsonSerializer#isUnwrappingSerializer() @@ -598,7 +606,7 @@ public class PersistentEntityJackson2Module extends SimpleModule { */ @Override public JsonSerializer unwrappingSerializer(NameTransformer unwrapper) { - return new ProjectionSerializer(collector, associations, true); + return new ProjectionSerializer(collector, associations, resourceProcessorInvoker, true); } }