From 52e10da72e58d96e03eee949acf35a7f41bded3a 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 21253e85a..e582a5bda 100644 --- a/src/main/java/org/springframework/data/projection/DefaultProjectionInformation.java +++ b/src/main/java/org/springframework/data/projection/DefaultProjectionInformation.java @@ -82,7 +82,7 @@ class DefaultProjectionInformation implements ProjectionInformation { @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 13bcc840d..761ef6561 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 @@ package org.springframework.data.projection; 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.2.11 + */ + 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 796a6acdd..fbade6c9d 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.NonNull; 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; @@ -129,9 +130,21 @@ public abstract class ReturnedType { * 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.2.11 + * @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 983292721..fd8c337fe 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.junit.jupiter.api.Test; 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 @@ class ProxyProjectionFactoryUnitTests { 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 @@ class ProxyProjectionFactoryUnitTests { Map getData(); } + interface OpenProjection { + + @Value("#{@greetingsFrom.groot(target.firstname)}") + String hello(); + } + interface CustomerExcerptWithDefaultMethod extends CustomerExcerpt { default String getFirstnameAndId() {