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.
This commit is contained in:
Oliver Gierke
2014-10-29 11:24:38 +01:00
parent c585ed3c3b
commit be0fcad483
2 changed files with 179 additions and 0 deletions

View File

@@ -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<JpaPer
}
private final Metamodel metamodel;
private final Boolean usePropertyAccess;
/**
* Creates a new {@link JpaPersistentPropertyImpl}
@@ -89,6 +93,7 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty<JpaPer
Assert.notNull(metamodel, "Metamodel must not be null!");
this.metamodel = metamodel;
this.usePropertyAccess = detectPropertyAccess();
}
/*
@@ -159,4 +164,45 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty<JpaPer
protected Association<JpaPersistentProperty> createAssociation() {
return new Association<JpaPersistentProperty>(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());
}
}

View File

@@ -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;
}
}
}