DATAREST-925 - Polishing.

Original pull request: #238.
This commit is contained in:
Oliver Gierke
2016-12-02 20:14:32 +01:00
parent 8149a02820
commit 21b7d7cbed
6 changed files with 93 additions and 60 deletions

View File

@@ -26,17 +26,19 @@ import java.util.UUID;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Reference;
import org.springframework.data.rest.core.config.Projection;
import org.springframework.data.rest.tests.shop.Product.ProductNameOnlyProjection;
/**
* @author Oliver Gierke
* @author Craig Andrews
*/
@Data
@EqualsAndHashCode(of = "id")
public class LineItem {
@Projection(name = "productsOnly", types = { LineItem.class })
@Projection(name = "productsOnly", types = LineItem.class)
public interface LineItemProductsOnlyProjection {
List<Product.ProductNameOnlyProjection> getProducts();
List<ProductNameOnlyProjection> getProducts();
}
private final @Id UUID id = UUID.randomUUID();

View File

@@ -24,16 +24,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;
import org.springframework.data.rest.tests.shop.LineItem.LineItemProductsOnlyProjection;
/**
* @author Oliver Gierke
* @author Craig Andrews
*/
@Value
public class Order {
@Projection(name = "itemsOnly", types = { Order.class })
@Projection(name = "itemsOnly", types = Order.class)
public interface OrderItemsOnlyProjection {
List<LineItem.LineItemProductsOnlyProjection> getItems();
List<LineItemProductsOnlyProjection> getItems();
}
private final @Id UUID id = UUID.randomUUID();

View File

@@ -26,13 +26,13 @@ import org.springframework.data.rest.core.config.Projection;
/**
* @author Oliver Gierke
* @author Craig Andrews
*/
@Value
@RequiredArgsConstructor
public class Product {
@Projection(name = "nameOnly", types = { Product.class })
@Projection(name = "nameOnly", types = Product.class)
public interface ProductNameOnlyProjection {
String getName();
}

View File

@@ -25,6 +25,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.map.repository.config.EnableMapRepositories;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.tests.shop.Customer.Gender;
import org.springframework.data.rest.tests.shop.Product.ProductNameOnlyProjection;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
@@ -59,15 +60,16 @@ public class ShopConfiguration {
}
@Bean
public ResourceProcessor<Resource<Product.ProductNameOnlyProjection>> productNameOnlyProjectionResourceProcessor() {
return new ResourceProcessor<Resource<Product.ProductNameOnlyProjection>>() {
public ResourceProcessor<Resource<ProductNameOnlyProjection>> productNameOnlyProjectionResourceProcessor() {
return new ResourceProcessor<Resource<ProductNameOnlyProjection>>() {
/*
* (non-Javadoc)
* @see org.springframework.hateoas.ResourceProcessor#process(org.springframework.hateoas.ResourceSupport)
*/
@Override
public Resource<Product.ProductNameOnlyProjection> process(Resource<Product.ProductNameOnlyProjection> resource) {
public Resource<ProductNameOnlyProjection> process(Resource<Product.ProductNameOnlyProjection> resource) {
resource.add(new Link("alpha", "beta"));
return resource;
}

View File

@@ -18,7 +18,7 @@ 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.Collections;
import java.util.Map;
import org.junit.Test;
@@ -32,7 +32,10 @@ import org.springframework.test.web.servlet.ResultActions;
import com.jayway.jsonpath.JsonPath;
/**
* Integration tests for projections.
*
* @author Oliver Gierke
* @author Craig Andrews
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ShopConfiguration.class)
@@ -59,6 +62,47 @@ public class ShopIntegrationTests extends AbstractWebIntegrationTests {
expectRelatedResource("customer", actions);
}
/**
* @see DATAREST-221
*/
@Test
public void renderProductNameOnlyProjection() throws Exception {
Map<String, Object> arguments = Collections.singletonMap("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());
}
/**
* @see DATAREST-221
*/
@Test
public void renderProductNameOnlyProjectionResourceProcessor() throws Exception {
Map<String, Object> arguments = Collections.singletonMap("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<String, Object> arguments = Collections.singletonMap("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());
}
private static void expectRelatedResource(String name, ResultActions actions) throws Exception {
int dotIndex = name.lastIndexOf('.');
@@ -70,36 +114,4 @@ 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<String, Object> 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<String, Object> 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<String, Object> 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());
}
}

View File

@@ -41,6 +41,7 @@ import org.springframework.data.repository.support.RepositoryInvokerFactory;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.support.EntityLookup;
import org.springframework.data.rest.core.support.SelfLinkProvider;
import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler;
@@ -110,13 +111,16 @@ public class PersistentEntityJackson2Module extends SimpleModule {
*
* @param associations must not be {@literal null}.
* @param entities must not be {@literal null}.
* @param config must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param linkProvider must not be {@literal null}.
* @param collector must not be {@literal null}.
* @param factory must not be {@literal null}.
* @param lookupObjectSerializer must not be {@literal null}.
* @param invoker must not be {@literal null}.
* @param assembler must not be {@literal null}.
*/
public PersistentEntityJackson2Module(Associations associations, PersistentEntities entities,
UriToEntityConverter converter, LinkCollector collector, RepositoryInvokerFactory factory,
LookupObjectSerializer lookupObjectSerializer, ResourceProcessorInvoker resourceProcessorInvoker,
LookupObjectSerializer lookupObjectSerializer, ResourceProcessorInvoker invoker,
EmbeddedResourcesAssembler assembler) {
super(new Version(2, 0, 0, null, "org.springframework.data.rest", "jackson-module"));
@@ -126,9 +130,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);
NestedEntitySerializer serializer = new NestedEntitySerializer(entities, assembler, invoker);
addSerializer(new PersistentEntityResourceSerializer(collector));
addSerializer(new ProjectionSerializer(collector, associations, resourceProcessorInvoker, false));
addSerializer(new ProjectionSerializer(collector, associations, invoker, false));
addSerializer(new ProjectionResourceContentSerializer(false));
setSerializerModifier(
@@ -540,7 +544,7 @@ public class PersistentEntityJackson2Module extends SimpleModule {
private final LinkCollector collector;
private final Associations associations;
private final ResourceProcessorInvoker resourceProcessorInvoker;
private final ResourceProcessorInvoker invoker;
private final boolean unwrapping;
/**
@@ -549,15 +553,17 @@ public class PersistentEntityJackson2Module extends SimpleModule {
*
* @param collector must not be {@literal null}.
* @param mappings must not be {@literal null}.
* @param invoker must not be {@literal null}.
* @param unwrapping
*/
private ProjectionSerializer(LinkCollector collector, Associations mappings, ResourceProcessorInvoker resourceProcessorInvoker, boolean unwrapping) {
private ProjectionSerializer(LinkCollector collector, Associations mappings, ResourceProcessorInvoker invoker,
boolean unwrapping) {
super(TargetAware.class);
this.collector = collector;
this.associations = mappings;
this.resourceProcessorInvoker = resourceProcessorInvoker;
this.invoker = invoker;
this.unwrapping = unwrapping;
}
@@ -583,14 +589,6 @@ public class PersistentEntityJackson2Module extends SimpleModule {
}
}
private ProjectionResource toResource(TargetAware value) {
Object target = value.getTarget();
Links links = associations.getMetadataFor(value.getTargetClass()).isExported() ? collector.getLinksFor(target)
: new Links();
Resource<TargetAware> resource = resourceProcessorInvoker.invokeProcessorsFor(new Resource<TargetAware>(value, links));
return new ProjectionResource(resource.getContent(), resource.getLinks());
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.JsonSerializer#isUnwrappingSerializer()
@@ -606,7 +604,24 @@ public class PersistentEntityJackson2Module extends SimpleModule {
*/
@Override
public JsonSerializer<TargetAware> unwrappingSerializer(NameTransformer unwrapper) {
return new ProjectionSerializer(collector, associations, resourceProcessorInvoker, true);
return new ProjectionSerializer(collector, associations, invoker, true);
}
/**
* Creates a {@link ProjectionResource} for the given {@link TargetAware}.
*
* @param value must not be {@literal null}.
* @return
*/
private ProjectionResource toResource(TargetAware value) {
Object target = value.getTarget();
ResourceMetadata metadata = associations.getMetadataFor(value.getTargetClass());
Links links = metadata.isExported() ? collector.getLinksFor(target) : new Links();
Resource<TargetAware> resource = invoker.invokeProcessorsFor(new Resource<TargetAware>(value, links));
return new ProjectionResource(resource.getContent(), resource.getLinks());
}
}