From 4bec3229b5fe2f5da32d9562e12a2041124725f1 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 11 Apr 2016 09:56:56 +0200 Subject: [PATCH] DATAREST-809 - Fixed application of explicit projections for excerpts. A refactoring in the course of the 2.5 development dropped the application of explicit projection in case of excerpts being requested. This particularly applies to collection resources with explicit projection requests which need to get the explicit projection applied over the default excerpt projection potentially registered. This is now fixed by the explicitly selected projection (if existing) always trumping the excerpt one. --- .../support/PersistentEntityProjector.java | 43 ++++++++++++- .../PersistentEntityProjectorUnitTests.java | 62 +++++++++++++++---- 2 files changed, 90 insertions(+), 15 deletions(-) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/PersistentEntityProjector.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/PersistentEntityProjector.java index 504a8fe83..ea886dcaa 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/PersistentEntityProjector.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/PersistentEntityProjector.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package org.springframework.data.rest.webmvc.support; +import org.springframework.core.convert.converter.Converter; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.rest.core.mapping.ResourceMappings; import org.springframework.data.rest.core.projection.ProjectionDefinitions; @@ -59,13 +60,49 @@ public class PersistentEntityProjector extends DefaultExcerptProjector implement */ public Object project(Object source) { + return projectWithDefault(source, new Converter() { + + @Override + public Object convert(Object source) { + return source; + } + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.webmvc.support.DefaultExcerptProjector#projectExcerpt(java.lang.Object) + */ + @Override + public Object projectExcerpt(Object source) { + + return projectWithDefault(source, new Converter() { + + @Override + public Object convert(Object source) { + return PersistentEntityProjector.super.projectExcerpt(source); + } + }); + } + + /** + * Creates the projection for the given source instance falling back to the given {@link Converter} if no explicit + * projection is selected. + * + * @param source must not be {@literal null}. + * @param converter must not be {@literal null}. + * @return + */ + private Object projectWithDefault(Object source, Converter converter) { + Assert.notNull(source, "Projection source must not be null!"); + Assert.notNull(converter, "Converter must not be null!"); if (!StringUtils.hasText(projection)) { - return source; + return converter.convert(source); } Class projectionType = definitions.getProjectionType(source.getClass(), projection); - return projectionType == null ? source : factory.createProjection(projectionType, source); + return projectionType == null ? converter.convert(source) : factory.createProjection(projectionType, source); } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/PersistentEntityProjectorUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/PersistentEntityProjectorUnitTests.java index 9a8118789..02f81418c 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/PersistentEntityProjectorUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/support/PersistentEntityProjectorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,10 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration; import org.springframework.data.rest.core.mapping.ResourceMappings; +import org.springframework.data.rest.core.mapping.ResourceMetadata; /** * Unit tests for {@link PersistentEntityProjector}. @@ -36,24 +38,32 @@ import org.springframework.data.rest.core.mapping.ResourceMappings; @RunWith(MockitoJUnitRunner.class) public class PersistentEntityProjectorUnitTests { - @Mock ProjectionFactory factory; @Mock ResourceMappings mappings; + + Projector projector; + ProjectionFactory factory; ProjectionDefinitionConfiguration configuration; @Before public void setUp() { - configuration = new ProjectionDefinitionConfiguration(); + + this.configuration = new ProjectionDefinitionConfiguration(); + this.factory = new SpelAwareProxyProjectionFactory(); + this.projector = new PersistentEntityProjector(configuration, factory, "sample", mappings); + + ResourceMetadata metadata = mock(ResourceMetadata.class); + doReturn(metadata).when(mappings).getMetadataFor(Object.class); + doReturn(Excerpt.class).when(metadata).getExcerptProjection(); } /** * @see DATAREST-221 */ @Test - public void returnsObjectAsIfNoProjectionTypeFound() { - - Projector projector = new PersistentEntityProjector(configuration, factory, "sample", mappings); + public void returnsObjectAsIsIfNoProjectionTypeFound() { Object object = new Object(); + assertThat(projector.project(object), is(object)); } @@ -65,14 +75,42 @@ public class PersistentEntityProjectorUnitTests { configuration.addProjection(Sample.class, Object.class); - Projector projector = new PersistentEntityProjector(configuration, factory, "sample", mappings); - Object source = new Object(); - projector.project(source); - - verify(factory, times(1)).createProjection(Sample.class, source); + assertThat(projector.project(new Object()), is(instanceOf(Sample.class))); } - interface Sample { + /** + * @see DATAREST-806 + */ + @Test + public void favorsExplicitProjectionOverExcerpt() { + configuration.addProjection(Sample.class, Object.class); + + assertThat(projector.projectExcerpt(new Object()), is(instanceOf(Sample.class))); } + + /** + * @see DATAREST-806 + */ + @Test + public void excerptProjectionIsUsedForExcerpt() { + assertThat(projector.projectExcerpt(new Object()), is(instanceOf(Excerpt.class))); + } + + /** + * @see DATAREST-806 + */ + @Test + public void usesExcerptProjectionIfNoExplicitProjectionWasRequested() { + + configuration.addProjection(Sample.class, Object.class); + + PersistentEntityProjector projector = new PersistentEntityProjector(configuration, factory, null, mappings); + + assertThat(projector.projectExcerpt(new Object()), is(instanceOf(Excerpt.class))); + } + + interface Sample {} + + interface Excerpt {} }