Permalink
Browse files

DATACMNS-243 - Allow dedicated control over the access type for prope…

…rties.

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.
  • Loading branch information...
1 parent 4c5012f commit 4c6afc5c30fe35a22e8c3534dd6601ff5f84c0a4 @olivergierke olivergierke committed Mar 25, 2013
@@ -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;
+ }
+}
@@ -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 @@
void doWithAssociations(AssociationHandler<P> 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 extends Annotation> A findAnnotation(Class<A> annotationType);
}
@@ -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 @@
*/
public interface PersistentProperty<P extends PersistentProperty<P>> {
+ /**
+ * Returns the {@link PersistentEntity} owning the current {@link PersistentProperty}.
+ *
+ * @return
+ */
PersistentEntity<?, P> getOwner();
/**
@@ -193,10 +198,27 @@
<A extends Annotation> A findAnnotation(Class<A> 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 extends Annotation> A findPropertyOrOwnerAnnotation(Class<A> annotationType);
+
+ /**
* Returns whether the {@link PersistentProperty} has an annotation of the given type.
*
* @param annotationType the annotation to lookup, must not be {@literal null}.
* @return whether the {@link PersistentProperty} has an annotation of the given type.
*/
boolean isAnnotationPresent(Class<? extends Annotation> 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();
}
@@ -291,8 +291,10 @@ protected E addPersistentEntity(TypeInformation<?> typeInformation) {
try {
- ReflectionUtils.doWithFields(type, new PersistentPropertyCreator(entity, descriptors),
- PersistentFieldFilter.INSTANCE);
+ PersistentPropertyCreator persistentPropertyCreator = new PersistentPropertyCreator(entity, descriptors);
+ ReflectionUtils.doWithFields(type, persistentPropertyCreator, PersistentPropertyFilter.INSTANCE);
+ persistentPropertyCreator.addPropertiesForRemainingDescriptors();
+
entity.verify();
} catch (MappingException e) {
@@ -394,30 +396,66 @@ protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
private final E entity;
private final Map<String, PropertyDescriptor> descriptors;
+ private final Map<String, PropertyDescriptor> 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<String, PropertyDescriptor> 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<String, PropertyDescriptor>(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 void doWith(Field field) {
}
/**
- * {@link FieldFilter} rejecting static fields as well as artifically introduced ones. See
- * {@link PersistentFieldFilter#UNMAPPED_FIELDS} for details.
+ * Filter rejecting static fields as well as artifically introduced ones. See
+ * {@link PersistentPropertyFilter#UNMAPPED_PROPERTIES} for details.
*
* @author Oliver Gierke
*/
- private static enum PersistentFieldFilter implements FieldFilter {
+ static enum PersistentPropertyFilter implements FieldFilter {
INSTANCE;
- private static final Iterable<FieldMatch> UNMAPPED_FIELDS;
+ private static final Iterable<PropertyMatch> UNMAPPED_PROPERTIES;
static {
- Set<FieldMatch> matches = new HashSet<FieldMatch>();
- matches.add(new FieldMatch("class", null));
- matches.add(new FieldMatch("this\\$.*", null));
- matches.add(new FieldMatch("metaClass", "groovy.lang.MetaClass"));
+ Set<PropertyMatch> matches = new HashSet<PropertyMatch>();
+ 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 boolean matches(Field field) {
return false;
}
- for (FieldMatch candidate : UNMAPPED_FIELDS) {
- if (candidate.matches(field)) {
+ for (PropertyMatch candidate : UNMAPPED_PROPERTIES) {
+ if (candidate.matches(field.getName(), field.getType())) {
return false;
}
}
return true;
}
- }
-
- /**
- * Value object to help defining field eclusion based on name patterns and types.
- *
- * @since 1.4
- * @author Oliver Gierke
- */
- static class FieldMatch {
-
- private final String namePattern;
- private final String typeName;
/**
- * Creates a new {@link FieldMatch} for the given name pattern and type name. At least one of the paramters must not
- * be {@literal null}.
+ * Returns whether the given {@link PropertyDescriptor} is one to create a {@link PersistentProperty} for.
*
- * @param namePattern a regex pattern to match field names, can be {@literal null}.
- * @param typeName the name of the type to exclude, can be {@literal null}.
+ * @param descriptor must not be {@literal null}.
+ * @return
*/
- public FieldMatch(String namePattern, String typeName) {
+ public boolean matches(PropertyDescriptor descriptor) {
- Assert.isTrue(!(namePattern == null && typeName == null), "Either name patter or type name must be given!");
+ Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
- this.namePattern = namePattern;
- this.typeName = typeName;
+ if (descriptor.getReadMethod() == null && descriptor.getWriteMethod() == null) {
+ return false;
+ }
+
+ for (PropertyMatch candidate : UNMAPPED_PROPERTIES) {
+ if (candidate.matches(descriptor.getName(), descriptor.getPropertyType())) {
+ return false;
+ }
+ }
+
+ return true;
}
/**
- * Returns whether the given {@link Field} matches the defined {@link FieldMatch}.
+ * Value object to help defining property exclusion based on name patterns and types.
*
- * @param field must not be {@literal null}.
- * @return
+ * @since 1.4
+ * @author Oliver Gierke
*/
- public boolean matches(Field field) {
+ static class PropertyMatch {
- if (namePattern != null && !field.getName().matches(namePattern)) {
- return false;
- }
+ private final String namePattern;
+ private final String typeName;
- if (typeName != null && !field.getType().getName().equals(typeName)) {
- return false;
+ /**
+ * Creates a new {@link PropertyMatch} for the given name pattern and type name. At least one of the paramters
+ * must not be {@literal null}.
+ *
+ * @param namePattern a regex pattern to match field names, can be {@literal null}.
+ * @param typeName the name of the type to exclude, can be {@literal null}.
+ */
+ public PropertyMatch(String namePattern, String typeName) {
+
+ Assert.isTrue(!(namePattern == null && typeName == null), "Either name patter or type name must be given!");
+
+ this.namePattern = namePattern;
+ this.typeName = typeName;
}
- return true;
+ /**
+ * Returns whether the given {@link Field} matches the defined {@link PropertyMatch}.
+ *
+ * @param field must not be {@literal null}.
+ * @return
+ */
+ public boolean matches(String name, Class<?> type) {
+
+ if (namePattern != null && !name.matches(namePattern)) {
+ return false;
+ }
+
+ if (typeName != null && !type.getName().equals(typeName)) {
+ return false;
+ }
+
+ return true;
+ }
}
}
}
Oops, something went wrong.

0 comments on commit 4c6afc5

Please sign in to comment.