From 7ff224b350c6ae501066f55c53c61ac77210a2fe Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 25 Sep 2024 09:58:53 +0200 Subject: [PATCH] Consider projections without input properties open ones. Closes #3164 --- .../DefaultProjectionInformation.java | 2 +- .../projection/ProjectionInformation.java | 13 ++++++++++++ .../data/repository/query/ReturnedType.java | 13 ++++++++++++ .../ProxyProjectionFactoryUnitTests.java | 20 +++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/projection/DefaultProjectionInformation.java b/src/main/java/org/springframework/data/projection/DefaultProjectionInformation.java index 8f76ac6f0d..6ddf90d829 100644 --- a/src/main/java/org/springframework/data/projection/DefaultProjectionInformation.java +++ b/src/main/java/org/springframework/data/projection/DefaultProjectionInformation.java @@ -85,7 +85,7 @@ public List getInputProperties() { @Override public boolean isClosed() { - return this.properties.equals(getInputProperties()); + return hasInputProperties() && this.properties.equals(getInputProperties()); } /** diff --git a/src/main/java/org/springframework/data/projection/ProjectionInformation.java b/src/main/java/org/springframework/data/projection/ProjectionInformation.java index 13bcc840d0..c1bf7fd80c 100644 --- a/src/main/java/org/springframework/data/projection/ProjectionInformation.java +++ b/src/main/java/org/springframework/data/projection/ProjectionInformation.java @@ -18,6 +18,8 @@ import java.beans.PropertyDescriptor; import java.util.List; +import org.springframework.util.CollectionUtils; + /** * Information about a projection type. * @@ -40,6 +42,17 @@ public interface ProjectionInformation { */ List getInputProperties(); + /** + * Returns whether the projection has input properties. Projections without input types are typically open projections + * that do not follow Java's property accessor naming. + * + * @return + * @since 3.3.5 + */ + default boolean hasInputProperties() { + return !CollectionUtils.isEmpty(getInputProperties()); + } + /** * Returns whether supplying values for the properties returned via {@link #getInputProperties()} is sufficient to * create a working proxy instance. This will usually be used to determine whether the projection uses any dynamically diff --git a/src/main/java/org/springframework/data/repository/query/ReturnedType.java b/src/main/java/org/springframework/data/repository/query/ReturnedType.java index 780cbdfee5..bec8f0f653 100644 --- a/src/main/java/org/springframework/data/repository/query/ReturnedType.java +++ b/src/main/java/org/springframework/data/repository/query/ReturnedType.java @@ -33,6 +33,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ObjectUtils; @@ -130,9 +131,21 @@ public final boolean isInstance(@Nullable Object source) { * Returns the properties required to be used to populate the result. * * @return + * @see ProjectionInformation#getInputProperties() */ public abstract List getInputProperties(); + /** + * Returns whether the returned type has input properties. + * + * @return + * @since 3.3.5 + * @see ProjectionInformation#hasInputProperties() + */ + public boolean hasInputProperties() { + return !CollectionUtils.isEmpty(getInputProperties()); + } + /** * A {@link ReturnedType} that's backed by an interface. * diff --git a/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java b/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java index 983292721f..fd8c337fef 100755 --- a/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java @@ -31,6 +31,7 @@ import org.springframework.aop.Advisor; import org.springframework.aop.TargetClassAware; import org.springframework.aop.framework.Advised; +import org.springframework.beans.factory.annotation.Value; import org.springframework.test.util.ReflectionTestUtils; /** @@ -135,6 +136,19 @@ void returnsAllPropertiesAsInputProperties() { var result = projectionInformation.getInputProperties(); assertThat(result).hasSize(6); + assertThat(projectionInformation.hasInputProperties()).isTrue(); + assertThat(projectionInformation.isClosed()).isTrue(); + } + + @Test // DATACMNS-630 + void identifiersOpenProjectionCorrectly() { + + var projectionInformation = factory.getProjectionInformation(OpenProjection.class); + var result = projectionInformation.getInputProperties(); + + assertThat(result).isEmpty(); + assertThat(projectionInformation.hasInputProperties()).isFalse(); + assertThat(projectionInformation.isClosed()).isFalse(); } @Test // DATACMNS-655, GH-2831 @@ -357,6 +371,12 @@ interface CustomerExcerpt { Map getData(); } + interface OpenProjection { + + @Value("#{@greetingsFrom.groot(target.firstname)}") + String hello(); + } + interface CustomerExcerptWithDefaultMethod extends CustomerExcerpt { default String getFirstnameAndId() {