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:
Oliver Gierke
2013-03-25 16:39:52 +01:00
committed by Oliver Gierke
parent 4c5012f637
commit 4c6afc5c30
12 changed files with 458 additions and 165 deletions

View File

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

View File

@@ -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 {

View File

@@ -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 {

View File

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