From be0fcad483aab3a0225aea8a1f8dbb465e398746 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 29 Oct 2014 11:24:38 +0100 Subject: [PATCH] DATAJPA-619 - JpaPersistentPropertyImpl now considers JPA access definition annotations. JpaPersistentPropertyImpl now looks up both Spring Data and JPA access type definition annotations from both property and type level. Property-level definitions trump type-level ones, Spring Data ones trump JPA ones if defined on the same level. In support of DATAREST-269 to be able to work around id access issues when working with proxies created by JPA providers. Related ticket: DATAREST-269. --- .../mapping/JpaPersistentPropertyImpl.java | 46 ++++++ .../JpaPersistentPropertyImplUnitTests.java | 133 ++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java index a21c041cb..69c336d5a 100644 --- a/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java +++ b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java @@ -23,6 +23,8 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import javax.persistence.Access; +import javax.persistence.AccessType; import javax.persistence.Embeddable; import javax.persistence.Embedded; import javax.persistence.EmbeddedId; @@ -34,6 +36,7 @@ import javax.persistence.OneToOne; import javax.persistence.Transient; import javax.persistence.metamodel.Metamodel; +import org.springframework.data.annotation.AccessType.Type; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; @@ -71,6 +74,7 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty createAssociation() { return new Association(this, null); } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#usePropertyAccess() + */ + @Override + public boolean usePropertyAccess() { + return usePropertyAccess != null ? usePropertyAccess : super.usePropertyAccess(); + } + + /** + * Looks up both Spring Data's and JPA's access type definition annotations on the property or type level to determine + * the access type to be used. Will consider property-level annotations over type-level ones, favoring the Spring Data + * ones over the JPA ones if found on the same level. Returns {@literal null} if no explicit annotation can be found + * falling back to the defaults implemented in the super class. + * + * @return + */ + private Boolean detectPropertyAccess() { + + org.springframework.data.annotation.AccessType accessType = findAnnotation(org.springframework.data.annotation.AccessType.class); + + if (accessType != null) { + return Type.PROPERTY.equals(accessType.value()); + } + + Access access = findAnnotation(Access.class); + + if (access != null) { + return AccessType.PROPERTY.equals(access.value()); + } + + accessType = findPropertyOrOwnerAnnotation(org.springframework.data.annotation.AccessType.class); + + if (accessType != null) { + return Type.PROPERTY.equals(accessType.value()); + } + + access = findPropertyOrOwnerAnnotation(Access.class); + return access == null ? null : AccessType.PROPERTY.equals(access.value()); + } } diff --git a/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java b/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java index 32c4b0930..57712638f 100644 --- a/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java @@ -18,6 +18,8 @@ package org.springframework.data.jpa.mapping; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import javax.persistence.Access; +import javax.persistence.AccessType; import javax.persistence.Embeddable; import javax.persistence.Embedded; import javax.persistence.OneToOne; @@ -29,6 +31,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.annotation.AccessType.Type; /** * Unit tests for {@link JpaPersistentPropertyImpl}. @@ -92,6 +95,59 @@ public class JpaPersistentPropertyImplUnitTests { assertThat(entity.getPersistentProperty("embedded").isAssociation(), is(true)); } + /** + * @see DATAJPA-619 + */ + @Test + public void considersPropertyLevelAccessTypeDefinitions() { + + assertThat(getProperty(PropertyLevelPropertyAccess.class, "field").usePropertyAccess(), is(false)); + assertThat(getProperty(PropertyLevelPropertyAccess.class, "property").usePropertyAccess(), is(true)); + } + + /** + * @see DATAJPA-619 + */ + @Test + public void propertyLevelAccessTypeTrumpsTypeLevelDefinition() { + + assertThat(getProperty(PropertyLevelDefinitionTrumpsTypeLevelOne.class, "field").usePropertyAccess(), is(false)); + assertThat(getProperty(PropertyLevelDefinitionTrumpsTypeLevelOne.class, "property").usePropertyAccess(), is(true)); + + assertThat(getProperty(PropertyLevelDefinitionTrumpsTypeLevelOne2.class, "field").usePropertyAccess(), is(false)); + assertThat(getProperty(PropertyLevelDefinitionTrumpsTypeLevelOne2.class, "property").usePropertyAccess(), is(true)); + } + + /** + * @see DATAJPA-619 + */ + @Test + public void considersJpaAccessDefinitionAnnotations() { + assertThat(getProperty(TypeLevelPropertyAccess.class, "id").usePropertyAccess(), is(true)); + } + + /** + * @see DATAJPA-619 + */ + @Test + public void springDataAnnotationTrumpsJpaIfBothOnTypeLevel() { + assertThat(getProperty(CompetingTypeLevelAnnotations.class, "id").usePropertyAccess(), is(false)); + } + + /** + * @see DATAJPA-619 + */ + @Test + public void springDataAnnotationTrumpsJpaIfBothOnPropertyLevel() { + assertThat(getProperty(CompetingPropertyLevelAnnotations.class, "id").usePropertyAccess(), is(false)); + } + + private JpaPersistentProperty getProperty(Class ownerType, String propertyName) { + + JpaPersistentEntity entity = context.getPersistentEntity(ownerType); + return entity.getPersistentProperty(propertyName); + } + static class Sample { @OneToOne Sample other; @@ -108,4 +164,81 @@ public class JpaPersistentPropertyImplUnitTests { static class SampleEmbedded { } + + @Access(AccessType.PROPERTY) + static class TypeLevelPropertyAccess { + + private String id; + + public String getId() { + return id; + } + } + + static class PropertyLevelPropertyAccess { + + String field; + String property; + + /** + * @return the property + */ + @org.springframework.data.annotation.AccessType(Type.PROPERTY) + public String getProperty() { + return property; + } + } + + @Access(AccessType.FIELD) + static class PropertyLevelDefinitionTrumpsTypeLevelOne { + + String field; + String property; + + /** + * @return the property + */ + @org.springframework.data.annotation.AccessType(Type.PROPERTY) + public String getProperty() { + return property; + } + } + + @org.springframework.data.annotation.AccessType(Type.PROPERTY) + static class PropertyLevelDefinitionTrumpsTypeLevelOne2 { + + @Access(AccessType.FIELD) String field; + String property; + + /** + * @return the property + */ + public String getProperty() { + return property; + } + } + + @org.springframework.data.annotation.AccessType(Type.FIELD) + @Access(AccessType.PROPERTY) + static class CompetingTypeLevelAnnotations { + + private String id; + + public String getId() { + return id; + } + } + + @org.springframework.data.annotation.AccessType(Type.FIELD) + @Access(AccessType.PROPERTY) + static class CompetingPropertyLevelAnnotations { + + private String id; + + @org.springframework.data.annotation.AccessType(Type.FIELD) + @Access(AccessType.PROPERTY) + public String getId() { + return id; + } + } }