DATACMNS-243 - Allow dedicated control over the access type for properties.
Introduced @AccessType annotation to allow user to control the way property values are accessed. The default is field access. Improved the property detection mechanism to also inspect all PropertyDescriptors not backed by a field and add them if property access is defined for the property or type. We also keep property accessors for interfaces as they strongly indicate property access to be used (as they cannot be field backed by definition). The BeanWrapper doesn't take a useFieldAccess attribute anymore as the access type is solely derived from the given PersistentProperty now. AnnotationBasedPersistentProperty now also rejects properties with the very same annotation both on the field and on an accessor.
This commit is contained in:
committed by
Oliver Gierke
parent
4c5012f637
commit
4c6afc5c30
@@ -24,6 +24,7 @@ import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
@@ -65,6 +66,18 @@ public class AbstractMappingContextIntegrationTests<T extends PersistentProperty
|
||||
assertThat(context.hasPersistentEntityFor(Person.class), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-243
|
||||
*/
|
||||
@Test
|
||||
public void createsPersistentEntityForInterfaceCorrectly() {
|
||||
|
||||
SampleMappingContext context = new SampleMappingContext();
|
||||
PersistentEntity<Object, SamplePersistentProperty> entity = context.getPersistentEntity(InterfaceOnly.class);
|
||||
|
||||
assertThat(entity.getIdProperty(), is(notNullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-65
|
||||
*/
|
||||
@@ -121,7 +134,7 @@ public class AbstractMappingContextIntegrationTests<T extends PersistentProperty
|
||||
PersistentProperty prop = mock(PersistentProperty.class);
|
||||
|
||||
when(prop.getTypeInformation()).thenReturn(owner.getTypeInformation());
|
||||
when(prop.getName()).thenReturn(field.getName());
|
||||
when(prop.getName()).thenReturn(field == null ? descriptor.getName() : field.getName());
|
||||
when(prop.getPersistentEntityType()).thenReturn(Collections.EMPTY_SET);
|
||||
|
||||
try {
|
||||
@@ -140,4 +153,10 @@ public class AbstractMappingContextIntegrationTests<T extends PersistentProperty
|
||||
String lastname;
|
||||
String email;
|
||||
}
|
||||
|
||||
interface InterfaceOnly {
|
||||
|
||||
@Id
|
||||
String getId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
* Copyright 2012-2014 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.
|
||||
@@ -19,56 +19,55 @@ import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext.FieldMatch;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext.PersistentPropertyFilter.PropertyMatch;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link FieldMatch}. Introduced for DATACMNS-228.
|
||||
* Unit tests for {@link PropertyMatch}. Introduced for DATACMNS-228.
|
||||
*
|
||||
* @since 1.4
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class FieldMatchUnitTests {
|
||||
public class PropertyMatchUnitTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsBothParametersBeingNull() {
|
||||
|
||||
new FieldMatch(null, null);
|
||||
new PropertyMatch(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesFieldByConcreteNameAndType() throws Exception {
|
||||
|
||||
FieldMatch match = new FieldMatch("name", "java.lang.String");
|
||||
assertThat(match.matches(Sample.class.getField("this$0")), is(false));
|
||||
assertThat(match.matches(Sample.class.getField("this$1")), is(false));
|
||||
assertThat(match.matches(Sample.class.getField("name")), is(true));
|
||||
PropertyMatch match = new PropertyMatch("name", "java.lang.String");
|
||||
assertThat(match.matches("this$0", Object.class), is(false));
|
||||
assertThat(match.matches("this$1", Object.class), is(false));
|
||||
assertThat(match.matches("name", String.class), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesFieldByNamePattern() throws Exception {
|
||||
|
||||
FieldMatch match = new FieldMatch("this\\$.*", "java.lang.Object");
|
||||
assertThat(match.matches(Sample.class.getField("this$0")), is(true));
|
||||
assertThat(match.matches(Sample.class.getField("this$1")), is(true));
|
||||
assertThat(match.matches(Sample.class.getField("name")), is(false));
|
||||
PropertyMatch match = new PropertyMatch("this\\$.*", "java.lang.Object");
|
||||
assertThat(match.matches("this$0", Object.class), is(true));
|
||||
assertThat(match.matches("this$1", Object.class), is(true));
|
||||
assertThat(match.matches("name", String.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesFieldByNameOnly() throws Exception {
|
||||
|
||||
FieldMatch match = new FieldMatch("this\\$.*", null);
|
||||
assertThat(match.matches(Sample.class.getField("this$0")), is(true));
|
||||
assertThat(match.matches(Sample.class.getField("this$1")), is(true));
|
||||
assertThat(match.matches(Sample.class.getField("name")), is(false));
|
||||
PropertyMatch match = new PropertyMatch("this\\$.*", null);
|
||||
assertThat(match.matches("this$0", Object.class), is(true));
|
||||
assertThat(match.matches("this$1", Object.class), is(true));
|
||||
assertThat(match.matches("name", String.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesFieldByTypeNameOnly() throws Exception {
|
||||
|
||||
FieldMatch match = new FieldMatch(null, "java.lang.Object");
|
||||
assertThat(match.matches(Sample.class.getField("this$0")), is(true));
|
||||
assertThat(match.matches(Sample.class.getField("this$1")), is(true));
|
||||
assertThat(match.matches(Sample.class.getField("name")), is(false));
|
||||
PropertyMatch match = new PropertyMatch(null, "java.lang.Object");
|
||||
assertThat(match.matches("this$0", Object.class), is(true));
|
||||
assertThat(match.matches("this$1", Object.class), is(true));
|
||||
assertThat(match.matches("name", String.class), is(false));
|
||||
}
|
||||
|
||||
static class Sample {
|
||||
@@ -367,6 +367,11 @@ public class AbstractPersistentPropertyUnitTests {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAssociation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Association<SamplePersistentProperty> createAssociation() {
|
||||
return null;
|
||||
@@ -381,6 +386,11 @@ public class AbstractPersistentPropertyUnitTests {
|
||||
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A extends Annotation> A findPropertyOrOwnerAnnotation(Class<A> annotationType) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class Sample {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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.
|
||||
@@ -27,17 +27,19 @@ import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.AccessType;
|
||||
import org.springframework.data.annotation.AccessType.Type;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.context.SampleMappingContext;
|
||||
import org.springframework.data.mapping.context.SamplePersistentProperty;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AnnotationBasedPersistentProperty}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AbstractAnnotationBasedPropertyUnitTests<P extends AnnotationBasedPersistentProperty<P>> {
|
||||
public class AnnotationBasedPersistentPropertyUnitTests<P extends AnnotationBasedPersistentProperty<P>> {
|
||||
|
||||
BasicPersistentEntity<Object, SamplePersistentProperty> entity;
|
||||
SampleMappingContext context;
|
||||
@@ -49,27 +51,33 @@ public class AbstractAnnotationBasedPropertyUnitTests<P extends AnnotationBasedP
|
||||
entity = context.getPersistentEntity(Sample.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-269
|
||||
*/
|
||||
@Test
|
||||
public void discoversAnnotationOnField() {
|
||||
assertAnnotationPresent(MyAnnotation.class, entity.getPersistentProperty("field"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-269
|
||||
*/
|
||||
@Test
|
||||
public void discoversAnnotationOnGetters() {
|
||||
assertAnnotationPresent(MyAnnotation.class, entity.getPersistentProperty("getter"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-269
|
||||
*/
|
||||
@Test
|
||||
public void discoversAnnotationOnSetters() {
|
||||
assertAnnotationPresent(MyAnnotation.class, entity.getPersistentProperty("setter"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefersAnnotationOnMethodsToOverride() {
|
||||
MyAnnotation annotation = assertAnnotationPresent(MyAnnotation.class, entity.getPersistentProperty("override"));
|
||||
assertThat(annotation.value(), is("method"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-269
|
||||
*/
|
||||
@Test
|
||||
public void findsMetaAnnotation() {
|
||||
|
||||
@@ -100,19 +108,56 @@ public class AbstractAnnotationBasedPropertyUnitTests<P extends AnnotationBasedP
|
||||
* @see DATACMNS-282
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void discoversAmbiguousMappingUsingDirectAnnotationsOnAccessors() {
|
||||
|
||||
try {
|
||||
context.getPersistentEntity(InvalidSample.class);
|
||||
fail("Expected MappingException!");
|
||||
} catch (MappingException o_O) {
|
||||
Map<TypeInformation<?>, ?> entities = (Map<TypeInformation<?>, ?>) ReflectionTestUtils.getField(context,
|
||||
"persistentEntities");
|
||||
assertThat(entities.containsKey(ClassTypeInformation.from(InvalidSample.class)), is(false));
|
||||
assertThat(context.hasPersistentEntityFor(InvalidSample.class), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-243
|
||||
*/
|
||||
@Test
|
||||
public void defaultsToFieldAccess() {
|
||||
assertThat(getProperty(FieldAccess.class, "name").usePropertyAccess(), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-243
|
||||
*/
|
||||
@Test
|
||||
public void usesAccessTypeDeclaredOnTypeAsDefault() {
|
||||
assertThat(getProperty(PropertyAccess.class, "firstname").usePropertyAccess(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-243
|
||||
*/
|
||||
@Test
|
||||
public void propertyAnnotationOverridesTypeConfiguration() {
|
||||
assertThat(getProperty(PropertyAccess.class, "lastname").usePropertyAccess(), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-243
|
||||
*/
|
||||
@Test
|
||||
public void fieldAnnotationOverridesTypeConfiguration() {
|
||||
assertThat(getProperty(PropertyAccess.class, "emailAddress").usePropertyAccess(), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-243
|
||||
*/
|
||||
@Test(expected = MappingException.class)
|
||||
public void detectsAmbiguityCausedByFieldAnAccessorAnnotation() {
|
||||
context.getPersistentEntity(AnotherInvalidSample.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<Class<? extends Annotation>, Annotation> getAnnotationCache(SamplePersistentProperty property) {
|
||||
return (Map<Class<? extends Annotation>, Annotation>) ReflectionTestUtils.getField(property, "annotationCache");
|
||||
@@ -126,22 +171,20 @@ public class AbstractAnnotationBasedPropertyUnitTests<P extends AnnotationBasedP
|
||||
return annotation;
|
||||
}
|
||||
|
||||
private SamplePersistentProperty getProperty(Class<?> type, String name) {
|
||||
return context.getPersistentEntity(type).getPersistentProperty(name);
|
||||
}
|
||||
|
||||
static class Sample {
|
||||
|
||||
@MyId
|
||||
String id;
|
||||
@MyId String id;
|
||||
|
||||
@MyAnnotation
|
||||
String field;
|
||||
@MyAnnotation String field;
|
||||
String getter;
|
||||
String setter;
|
||||
String doubleMapping;
|
||||
|
||||
@MyAnnotationAsMeta
|
||||
String meta;
|
||||
|
||||
@MyAnnotation("field")
|
||||
String override;
|
||||
@MyAnnotationAsMeta String meta;
|
||||
|
||||
@MyAnnotation
|
||||
public String getGetter() {
|
||||
@@ -153,11 +196,6 @@ public class AbstractAnnotationBasedPropertyUnitTests<P extends AnnotationBasedP
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
@MyAnnotation("method")
|
||||
public String getOverride() {
|
||||
return override;
|
||||
}
|
||||
|
||||
@MyAnnotation
|
||||
public String getDoubleMapping() {
|
||||
return doubleMapping;
|
||||
@@ -184,6 +222,16 @@ public class AbstractAnnotationBasedPropertyUnitTests<P extends AnnotationBasedP
|
||||
}
|
||||
}
|
||||
|
||||
static class AnotherInvalidSample {
|
||||
|
||||
@MyAnnotation String property;
|
||||
|
||||
@MyAnnotation
|
||||
public String getProperty() {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value = { FIELD, METHOD, ANNOTATION_TYPE })
|
||||
public static @interface MyAnnotation {
|
||||
@@ -202,4 +250,24 @@ public class AbstractAnnotationBasedPropertyUnitTests<P extends AnnotationBasedP
|
||||
@Id
|
||||
public static @interface MyId {
|
||||
}
|
||||
|
||||
static class FieldAccess {
|
||||
String name;
|
||||
}
|
||||
|
||||
@AccessType(Type.PROPERTY)
|
||||
static class PropertyAccess {
|
||||
|
||||
String firstname, lastname;
|
||||
@AccessType(Type.FIELD) String emailAddress;
|
||||
|
||||
public String getFirstname() {
|
||||
return firstname;
|
||||
}
|
||||
|
||||
@AccessType(Type.FIELD)
|
||||
public String getLastname() {
|
||||
return lastname;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user