From 4c6afc5c30fe35a22e8c3534dd6601ff5f84c0a4 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 25 Mar 2013 16:39:52 +0100 Subject: [PATCH] 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. --- .../data/annotation/AccessType.java | 45 +++++ .../data/mapping/PersistentEntity.java | 12 +- .../data/mapping/PersistentProperty.java | 24 ++- .../context/AbstractMappingContext.java | 159 ++++++++++++------ .../model/AbstractPersistentProperty.java | 49 +++--- .../AnnotationBasedPersistentProperty.java | 47 +++++- .../mapping/model/BasicPersistentEntity.java | 31 +++- .../data/mapping/model/BeanWrapper.java | 58 +++---- ...bstractMappingContextIntegrationTests.java | 21 ++- ...Tests.java => PropertyMatchUnitTests.java} | 43 +++-- .../AbstractPersistentPropertyUnitTests.java | 10 ++ ...tionBasedPersistentPropertyUnitTests.java} | 124 +++++++++++--- 12 files changed, 458 insertions(+), 165 deletions(-) create mode 100644 src/main/java/org/springframework/data/annotation/AccessType.java rename src/test/java/org/springframework/data/mapping/context/{FieldMatchUnitTests.java => PropertyMatchUnitTests.java} (50%) rename src/test/java/org/springframework/data/mapping/model/{AbstractAnnotationBasedPropertyUnitTests.java => AnnotationBasedPersistentPropertyUnitTests.java} (68%) diff --git a/src/main/java/org/springframework/data/annotation/AccessType.java b/src/main/java/org/springframework/data/annotation/AccessType.java new file mode 100644 index 000000000..f73619913 --- /dev/null +++ b/src/main/java/org/springframework/data/annotation/AccessType.java @@ -0,0 +1,45 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to define how Spring Data shall access values of persistent properties. Can either be {@link Type#FIELD} + * or {@link Type#PROPERTY}. Default is field access. + * + * @author Oliver Gierke + */ +@Documented +@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, }) +@Retention(RetentionPolicy.RUNTIME) +public @interface AccessType { + + /** + * The access type to be used. + * + * @return + */ + Type value(); + + enum Type { + FIELD, PROPERTY; + } +} diff --git a/src/main/java/org/springframework/data/mapping/PersistentEntity.java b/src/main/java/org/springframework/data/mapping/PersistentEntity.java index 3bc2024f4..bb033b218 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentEntity.java +++ b/src/main/java/org/springframework/data/mapping/PersistentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2012 the original author or authors. + * Copyright 2011-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. @@ -15,6 +15,8 @@ */ package org.springframework.data.mapping; +import java.lang.annotation.Annotation; + import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.util.TypeInformation; @@ -150,4 +152,12 @@ public interface PersistentEntity> { void doWithAssociations(AssociationHandler

handler); void doWithAssociations(SimpleAssociationHandler handler); + + /** + * Looks up the annotation of the given type on the {@link PersistentEntity}. + * + * @param annotationType must not be {@literal null}. + * @return + */ + A findAnnotation(Class annotationType); } diff --git a/src/main/java/org/springframework/data/mapping/PersistentProperty.java b/src/main/java/org/springframework/data/mapping/PersistentProperty.java index b3b52015e..164b82ac2 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/PersistentProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-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. @@ -31,6 +31,11 @@ import org.springframework.data.util.TypeInformation; */ public interface PersistentProperty

> { + /** + * Returns the {@link PersistentEntity} owning the current {@link PersistentProperty}. + * + * @return + */ PersistentEntity getOwner(); /** @@ -192,6 +197,15 @@ public interface PersistentProperty

> { */ A findAnnotation(Class annotationType); + /** + * Looks up the annotation of the given type on the property and the owning type if no annotation can be found on it. + * Usefull to lookup annotations that can be configured on the type but overridden on an individual property. + * + * @param annotationType must not be {@literal null}. + * @return + */ + A findPropertyOrOwnerAnnotation(Class annotationType); + /** * Returns whether the {@link PersistentProperty} has an annotation of the given type. * @@ -199,4 +213,12 @@ public interface PersistentProperty

> { * @return whether the {@link PersistentProperty} has an annotation of the given type. */ boolean isAnnotationPresent(Class annotationType); + + /** + * Returns whether property access shall be used for reading the property value. This means it will use the getter + * instead of field access. + * + * @return + */ + boolean usePropertyAccess(); } diff --git a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java index ae8cbef2c..bc05971f0 100644 --- a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java @@ -291,8 +291,10 @@ public abstract class AbstractMappingContext descriptors; + private final Map remainingDescriptors; /** * Creates a new {@link PersistentPropertyCreator} for the given {@link PersistentEntity} and * {@link PropertyDescriptor}s. * - * @param entity - * @param descriptors + * @param entity must not be {@literal null}. + * @param descriptors must not be {@literal null}. */ private PersistentPropertyCreator(E entity, Map descriptors) { + + Assert.notNull(entity, "PersistentEntity must not be null!"); + Assert.notNull(descriptors, "PropertyDescriptors must not be null!"); + this.entity = entity; this.descriptors = descriptors; + this.remainingDescriptors = new HashMap(descriptors); } + /* + * (non-Javadoc) + * @see org.springframework.util.ReflectionUtils.FieldCallback#doWith(java.lang.reflect.Field) + */ public void doWith(Field field) { - PropertyDescriptor descriptor = descriptors.get(field.getName()); + String fieldName = field.getName(); ReflectionUtils.makeAccessible(field); + createAndRegisterProperty(field, descriptors.get(fieldName)); + + this.remainingDescriptors.remove(fieldName); + } + + /** + * Adds {@link PersistentProperty} instances for all suitable {@link PropertyDescriptor}s without a backing + * {@link Field}. + * + * @see PersistentPropertyFilter + */ + public void addPropertiesForRemainingDescriptors() { + + for (PropertyDescriptor descriptor : remainingDescriptors.values()) { + if (PersistentPropertyFilter.INSTANCE.matches(descriptor)) { + createAndRegisterProperty(null, descriptor); + } + } + } + + private void createAndRegisterProperty(Field field, PropertyDescriptor descriptor) { + P property = createPersistentProperty(field, descriptor, entity, simpleTypeHolder); if (property.isTransient()) { return; } + if (field == null && !property.usePropertyAccess()) { + return; + } + entity.addPersistentProperty(property); if (property.isAssociation()) { @@ -439,25 +477,25 @@ public abstract class AbstractMappingContext UNMAPPED_FIELDS; + private static final Iterable UNMAPPED_PROPERTIES; static { - Set matches = new HashSet(); - matches.add(new FieldMatch("class", null)); - matches.add(new FieldMatch("this\\$.*", null)); - matches.add(new FieldMatch("metaClass", "groovy.lang.MetaClass")); + Set matches = new HashSet(); + matches.add(new PropertyMatch("class", null)); + matches.add(new PropertyMatch("this\\$.*", null)); + matches.add(new PropertyMatch("metaClass", "groovy.lang.MetaClass")); - UNMAPPED_FIELDS = Collections.unmodifiableCollection(matches); + UNMAPPED_PROPERTIES = Collections.unmodifiableCollection(matches); } /* @@ -470,59 +508,82 @@ public abstract class AbstractMappingContext type) { + + if (namePattern != null && !name.matches(namePattern)) { + return false; + } + + if (typeName != null && !type.getName().equals(typeName)) { + return false; + } + + return true; + } + } } } diff --git a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java index c4b7a1176..f94dd38a4 100644 --- a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-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. @@ -16,19 +16,20 @@ package org.springframework.data.mapping.model; import java.beans.PropertyDescriptor; -import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.annotation.Reference; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * Simple impementation of {@link PersistentProperty}. @@ -38,6 +39,12 @@ import org.springframework.util.Assert; */ public abstract class AbstractPersistentProperty

> implements PersistentProperty

{ + private static final Field CAUSE_FIELD; + + static { + CAUSE_FIELD = ReflectionUtils.findField(Throwable.class, "cause"); + } + protected final String name; protected final PropertyDescriptor propertyDescriptor; protected final TypeInformation information; @@ -50,12 +57,11 @@ public abstract class AbstractPersistentProperty

public AbstractPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { - Assert.notNull(field); Assert.notNull(simpleTypeHolder); Assert.notNull(owner); - this.name = field.getName(); - this.rawType = field.getType(); + this.name = field == null ? propertyDescriptor.getName() : field.getName(); + this.rawType = field == null ? propertyDescriptor.getPropertyType() : field.getType(); this.information = owner.getTypeInformation().getProperty(this.name); this.propertyDescriptor = propertyDescriptor; this.field = field; @@ -113,18 +119,15 @@ public abstract class AbstractPersistentProperty

public Iterable> getPersistentEntityType() { List> result = new ArrayList>(); - TypeInformation type = getTypeInformation(); - if (isEntity()) { - result.add(type); - } - if (type.isCollectionLike() || isMap()) { TypeInformation nestedType = getTypeInformationIfNotSimpleType(getTypeInformation().getActualType()); if (nestedType != null) { result.add(nestedType); } + } else { + result.add(type); } return result; @@ -209,16 +212,7 @@ public abstract class AbstractPersistentProperty

* @see org.springframework.data.mapping.PersistentProperty#isAssociation() */ public boolean isAssociation() { - if (field.isAnnotationPresent(Reference.class)) { - return true; - } - for (Annotation annotation : field.getDeclaredAnnotations()) { - if (annotation.annotationType().isAnnotationPresent(Reference.class)) { - return true; - } - } - - return false; + return field == null ? false : AnnotationUtils.getAnnotation(field, Reference.class) != null; } public Association

getAssociation() { @@ -291,6 +285,14 @@ public abstract class AbstractPersistentProperty

return information.getActualType().getType(); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#usePropertyAccess() + */ + public boolean usePropertyAccess() { + return owner.getType().isInterface() || CAUSE_FIELD.equals(getField()); + } + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) @@ -307,7 +309,8 @@ public abstract class AbstractPersistentProperty

} AbstractPersistentProperty that = (AbstractPersistentProperty) obj; - return this.field.equals(that.field); + + return this.field == null ? this.propertyDescriptor.equals(that.propertyDescriptor) : this.field.equals(that.field); } /* @@ -316,7 +319,7 @@ public abstract class AbstractPersistentProperty

*/ @Override public int hashCode() { - return this.field.hashCode(); + return this.field == null ? this.propertyDescriptor.hashCode() : this.field.hashCode(); } /* @@ -325,6 +328,6 @@ public abstract class AbstractPersistentProperty

*/ @Override public String toString() { - return this.field.toString(); + return this.field == null ? this.propertyDescriptor.toString() : this.field.toString(); } } diff --git a/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java b/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java index e2233380b..871344c93 100644 --- a/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-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. @@ -26,6 +26,8 @@ import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.annotation.AccessType; +import org.springframework.data.annotation.AccessType.Type; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Reference; import org.springframework.data.annotation.Transient; @@ -47,6 +49,7 @@ public abstract class AnnotationBasedPersistentProperty

, Annotation> annotationCache = new HashMap, Annotation>(); private Boolean isTransient; + private boolean usePropertyAccess; /** * Creates a new {@link AnnotationBasedPersistentProperty}. @@ -59,7 +62,11 @@ public abstract class AnnotationBasedPersistentProperty

owner, SimpleTypeHolder simpleTypeHolder) { super(field, propertyDescriptor, owner, simpleTypeHolder); + populateAnnotationCache(field); + + AccessType accessType = findPropertyOrOwnerAnnotation(AccessType.class); + this.usePropertyAccess = accessType == null ? false : Type.PROPERTY.equals(accessType.value()); this.value = findAnnotation(Value.class); } @@ -84,21 +91,29 @@ public abstract class AnnotationBasedPersistentProperty

annotationType = annotation.annotationType(); - if (!annotationCache.containsKey(annotationType)) { - annotationCache.put(annotationType, annotation); + if (annotationCache.containsKey(annotationType)) { + throw new MappingException(String.format("Ambiguous mapping! Annotation %s configured " + + "on field %s and one of its accessor methods in class %s!", annotationType.getSimpleName(), + field.getName(), getOwner().getType().getSimpleName())); } + + annotationCache.put(annotationType, annotation); } } @@ -183,7 +198,18 @@ public abstract class AnnotationBasedPersistentProperty

A findPropertyOrOwnerAnnotation(Class annotationType) { + + A annotation = findAnnotation(annotationType); + return annotation == null ? owner.findAnnotation(annotationType) : annotation; } /** @@ -211,6 +237,15 @@ public abstract class AnnotationBasedPersistentProperty

> implement private final Set

properties; private final Set> associations; - private final Map propertyCache = new HashMap(); + private final Map propertyCache; + private final Map, Annotation> annotationCache; private P idProperty; private P versionProperty; @@ -70,8 +73,8 @@ public class BasicPersistentEntity> implement * {@link Comparator} will be used to define the order of the {@link PersistentProperty} instances added to the * entity. * - * @param information must not be {@literal null} - * @param comparator + * @param information must not be {@literal null}. + * @param comparator can be {@literal null}. */ public BasicPersistentEntity(TypeInformation information, Comparator

comparator) { @@ -82,6 +85,9 @@ public class BasicPersistentEntity> implement this.constructor = new PreferredConstructorDiscoverer(information, this).getConstructor(); this.associations = comparator == null ? new HashSet>() : new TreeSet>( new AssociationComparator

(comparator)); + + this.propertyCache = new HashMap(); + this.annotationCache = new HashMap, Annotation>(); } /* @@ -305,6 +311,25 @@ public class BasicPersistentEntity> implement } } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentEntity#findAnnotation(java.lang.Class) + */ + @Override + public A findAnnotation(Class annotationType) { + + A annotation = annotationType.getAnnotation(annotationType); + + if (annotation != null) { + return annotation; + } + + annotation = AnnotationUtils.findAnnotation(getType(), annotationType); + annotationCache.put(annotationType, annotation); + + return annotation; + } + /* (non-Javadoc) * @see org.springframework.data.mapping.MutablePersistentEntity#verify() */ diff --git a/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java b/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java index ad013e2b3..f86576d35 100644 --- a/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java +++ b/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2012 by the original author(s). + * Copyright 2011-2014 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,33 +41,24 @@ public class BeanWrapper, T> { * * @param * @param - * @param bean must not be {@literal null} - * @param conversionService + * @param bean must not be {@literal null}. + * @param conversionService can be {@literal null}. * @return */ public static , T> BeanWrapper create(T bean, ConversionService conversionService) { + + Assert.notNull(bean, "Wrapped instance must not be null!"); + return new BeanWrapper(bean, conversionService); } private BeanWrapper(T bean, ConversionService conversionService) { - Assert.notNull(bean); + this.bean = bean; this.conversionService = conversionService; } - /** - * Sets the given {@link PersistentProperty} to the given value. Will do type conversion if a - * {@link ConversionService} is configured. Will use the accessor method of the given {@link PersistentProperty} if it - * has one or field access otherwise. - * - * @param property must not be {@literal null}. - * @param value - */ - public void setProperty(PersistentProperty property, Object value) { - setProperty(property, value, false); - } - /** * Sets the given {@link PersistentProperty} to the given value. Will do type conversion if a * {@link ConversionService} is configured. @@ -78,23 +69,25 @@ public class BeanWrapper, T> { * @throws IllegalAccessException * @throws InvocationTargetException */ - public void setProperty(PersistentProperty property, Object value, boolean fieldAccessOnly) { + public void setProperty(PersistentProperty property, Object value) { Method setter = property.getSetter(); try { - if (fieldAccessOnly || null == setter) { + if (!property.usePropertyAccess()) { + Object valueToSet = getPotentiallyConvertedValue(value, property.getType()); ReflectionUtils.makeAccessible(property.getField()); ReflectionUtils.setField(property.getField(), bean, valueToSet); - return; - } - Class[] paramTypes = setter.getParameterTypes(); - Object valueToSet = getPotentiallyConvertedValue(value, paramTypes[0]); - ReflectionUtils.makeAccessible(setter); - ReflectionUtils.invokeMethod(setter, bean, valueToSet); + } else if (property.usePropertyAccess() && setter != null) { + + Class[] paramTypes = setter.getParameterTypes(); + Object valueToSet = getPotentiallyConvertedValue(value, paramTypes[0]); + ReflectionUtils.makeAccessible(setter); + ReflectionUtils.invokeMethod(setter, bean, valueToSet); + } } catch (IllegalStateException e) { throw new MappingException("Could not set object property!", e); @@ -109,7 +102,7 @@ public class BeanWrapper, T> { * @return */ public Object getProperty(PersistentProperty property) { - return getProperty(property, property.getType(), false); + return getProperty(property, property.getType()); } /** @@ -118,20 +111,23 @@ public class BeanWrapper, T> { * @param * @param property must not be {@literal null}. * @param type - * @param fieldAccessOnly * @return */ - public S getProperty(PersistentProperty property, Class type, boolean fieldAccessOnly) { + public S getProperty(PersistentProperty property, Class type) { try { - Object obj; - Field field = property.getField(); + + Object obj = null; Method getter = property.getGetter(); - if (fieldAccessOnly || null == getter) { + if (!property.usePropertyAccess()) { + + Field field = property.getField(); ReflectionUtils.makeAccessible(field); obj = ReflectionUtils.getField(field, bean); - } else { + + } else if (property.usePropertyAccess() && getter != null) { + ReflectionUtils.makeAccessible(getter); obj = ReflectionUtils.invokeMethod(getter, bean); } diff --git a/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextIntegrationTests.java b/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextIntegrationTests.java index 75b16ca36..bbf1e0a56 100644 --- a/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextIntegrationTests.java +++ b/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextIntegrationTests.java @@ -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 entity = context.getPersistentEntity(InterfaceOnly.class); + + assertThat(entity.getIdProperty(), is(notNullValue())); + } + /** * @see DATACMNS-65 */ @@ -121,7 +134,7 @@ public class AbstractMappingContextIntegrationTests createAssociation() { return null; @@ -381,6 +386,11 @@ public class AbstractPersistentPropertyUnitTests { public boolean isAnnotationPresent(Class annotationType) { return false; } + + @Override + public A findPropertyOrOwnerAnnotation(Class annotationType) { + return null; + } } static class Sample { diff --git a/src/test/java/org/springframework/data/mapping/model/AbstractAnnotationBasedPropertyUnitTests.java b/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java similarity index 68% rename from src/test/java/org/springframework/data/mapping/model/AbstractAnnotationBasedPropertyUnitTests.java rename to src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java index 31a6724f7..2dc0cea8d 100644 --- a/src/test/java/org/springframework/data/mapping/model/AbstractAnnotationBasedPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java @@ -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

> { +public class AnnotationBasedPersistentPropertyUnitTests

> { BasicPersistentEntity entity; SampleMappingContext context; @@ -49,27 +51,33 @@ public class AbstractAnnotationBasedPropertyUnitTests

, ?> entities = (Map, ?>) 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, Annotation> getAnnotationCache(SamplePersistentProperty property) { return (Map, Annotation>) ReflectionTestUtils.getField(property, "annotationCache"); @@ -126,22 +171,20 @@ public class AbstractAnnotationBasedPropertyUnitTests

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