DATACMNS-596 - Introduced PersistentPropertyAccessor.

To be able to customize the lookup of bean properties by persistent store we now expose a getPropertyAccessor(Object entity) method on PersistentEntity. It returns a PersistentPropertyAccessor which is basically an interface with a simplified API of BeanWrapper.

Reduced the plain BeanWrapper to not perform any type conversion to be able to drop the ConversionService dependency. To compensate for that we introduced a ConvertingPropertyAccessor that takes a ConversionService and delegates to a standard PersistentPropertyAccessor and applies conversions if necessary.

Refactored client APIs to use the PersistentPropertyAccessor instead of referring to BeanWrapper directly. Deprecated BeanWrapper.create(…) for removal in RC1.
This commit is contained in:
Oliver Gierke
2014-11-18 12:27:46 +01:00
parent 93dbc26969
commit 6a849bc8ef
10 changed files with 422 additions and 75 deletions

View File

@@ -26,6 +26,7 @@ import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.domain.Auditable;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.util.Assert;
@@ -82,7 +83,8 @@ class MappingAuditableBeanWrapperFactory extends AuditableBeanWrapperFactory {
metadataCache.put(type, metadata);
}
return metadata.isAuditable() ? new MappingMetadataAuditableBeanWrapper(source, metadata) : null;
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source);
return metadata.isAuditable() ? new MappingMetadataAuditableBeanWrapper(accessor, metadata) : null;
}
/**
@@ -132,7 +134,7 @@ class MappingAuditableBeanWrapperFactory extends AuditableBeanWrapperFactory {
*/
static class MappingMetadataAuditableBeanWrapper extends DateConvertingAuditableBeanWrapper {
private final BeanWrapper<Object> wrapper;
private final PersistentPropertyAccessor accessor;
private final MappingAuditingMetadata metadata;
/**
@@ -142,12 +144,12 @@ class MappingAuditableBeanWrapperFactory extends AuditableBeanWrapperFactory {
* @param target must not be {@literal null}.
* @param metadata must not be {@literal null}.
*/
public MappingMetadataAuditableBeanWrapper(Object target, MappingAuditingMetadata metadata) {
public MappingMetadataAuditableBeanWrapper(PersistentPropertyAccessor accessor, MappingAuditingMetadata metadata) {
Assert.notNull(target, "Target object must not be null!");
Assert.notNull(accessor, "Target object must not be null!");
Assert.notNull(metadata, "Auditing metadata must not be null!");
this.wrapper = BeanWrapper.create(target, null);
this.accessor = accessor;
this.metadata = metadata;
}
@@ -159,7 +161,7 @@ class MappingAuditableBeanWrapperFactory extends AuditableBeanWrapperFactory {
public void setCreatedBy(Object value) {
if (metadata.createdByProperty != null) {
this.wrapper.setProperty(metadata.createdByProperty, value);
this.accessor.setProperty(metadata.createdByProperty, value);
}
}
@@ -173,7 +175,7 @@ class MappingAuditableBeanWrapperFactory extends AuditableBeanWrapperFactory {
PersistentProperty<?> property = metadata.createdDateProperty;
if (property != null) {
this.wrapper.setProperty(property, getDateValueToSet(value, property.getType(), property));
this.accessor.setProperty(property, getDateValueToSet(value, property.getType(), property));
}
}
@@ -185,7 +187,7 @@ class MappingAuditableBeanWrapperFactory extends AuditableBeanWrapperFactory {
public void setLastModifiedBy(Object value) {
if (metadata.lastModifiedByProperty != null) {
this.wrapper.setProperty(metadata.lastModifiedByProperty, value);
this.accessor.setProperty(metadata.lastModifiedByProperty, value);
}
}
@@ -199,7 +201,7 @@ class MappingAuditableBeanWrapperFactory extends AuditableBeanWrapperFactory {
PersistentProperty<?> property = metadata.lastModifiedDateProperty;
if (property != null) {
this.wrapper.setProperty(property, getDateValueToSet(value, property.getType(), property));
this.accessor.setProperty(property, getDateValueToSet(value, property.getType(), property));
}
}
}

View File

@@ -170,4 +170,13 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> {
* @since 1.8
*/
<A extends Annotation> A findAnnotation(Class<A> annotationType);
/**
* Returns a {@link PersistentPropertyAccessor} to access property values of the given bean.
*
* @param bean must not be {@literal null}.
* @return
* @since 1.10
*/
PersistentPropertyAccessor getPropertyAccessor(Object bean);
}

View File

@@ -0,0 +1,53 @@
/*
* 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.mapping;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.model.MappingException;
/**
* Domain service to allow accessing and setting {@link PersistentProperty}s of an entity.
*
* @author Oliver Gierke
*/
public interface PersistentPropertyAccessor {
/**
* Sets the given {@link PersistentProperty} to the given value. Will do type conversion if a
* {@link ConversionService} is configured.
*
* @param property must not be {@literal null}.
* @param value can be {@literal null}.
* @throws MappingException in case an exception occurred when setting the property value.
*/
void setProperty(PersistentProperty<?> property, Object value);
/**
* Returns the value of the given {@link PersistentProperty} of the underlying bean instance.
*
* @param <S>
* @param property must not be {@literal null}.
* @return can be {@literal null}.
*/
Object getProperty(PersistentProperty<?> property);
/**
* Returns the underlying bean.
*
* @return will never be {@literal null}.
*/
Object getBean();
}

View File

@@ -17,7 +17,7 @@ package org.springframework.data.mapping.context;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.support.IsNewStrategy;
import org.springframework.data.support.IsNewStrategyFactory;
@@ -95,8 +95,8 @@ public class MappingContextIsNewStrategyFactory extends IsNewStrategyFactorySupp
*/
public boolean isNew(Object entity) {
BeanWrapper<Object> wrapper = BeanWrapper.create(entity, null);
Object propertyValue = wrapper.getProperty(property);
PersistentPropertyAccessor accessor = property.getOwner().getPropertyAccessor(entity);
Object propertyValue = accessor.getProperty(property);
return decideIsNew(propertyValue);
}

View File

@@ -30,6 +30,7 @@ import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.SimpleAssociationHandler;
@@ -357,11 +358,23 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
return annotation;
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.MutablePersistentEntity#verify()
*/
public void verify() {
public void verify() {}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getPropertyAccessor(java.lang.Object)
*/
@Override
public PersistentPropertyAccessor getPropertyAccessor(Object bean) {
Assert.notNull(bean, "Target bean must not be null!");
Assert.isTrue(getType().equals(bean.getClass()), "Target bean is not of type of the persistent entity!");
return new BeanWrapper<Object>(bean);
}
/**

View File

@@ -19,48 +19,48 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Value object to allow creation of objects using the metamodel, setting and getting properties.
* Domain service to allow accessing the values of {@link PersistentProperty}s on a given bean.
*
* @author Oliver Gierke
*/
public class BeanWrapper<T> {
public class BeanWrapper<T> implements PersistentPropertyAccessor {
private final T bean;
private final ConversionService conversionService;
/**
* Creates a new {@link BeanWrapper} for the given bean instance and {@link ConversionService}. If
* {@link ConversionService} is {@literal null} no property type conversion will take place.
* Creates a new {@link BeanWrapper} for the given bean and {@link ConversionService}.TODO: remove!
*
* @param <T>
* @param bean must not be {@literal null}.
* @param conversionService can be {@literal null}.
* @return
* @deprecated use {@link PersistentEntity#getPropertyAccessor(Object)} instead. Will be removed in 1.10 RC1.
*/
@Deprecated
public static <T> BeanWrapper<T> create(T bean, ConversionService conversionService) {
Assert.notNull(bean, "Wrapped instance must not be null!");
return new BeanWrapper<T>(bean, conversionService);
}
private BeanWrapper(T bean, ConversionService conversionService) {
this.bean = bean;
this.conversionService = conversionService;
return new BeanWrapper<T>(bean);
}
/**
* Sets the given {@link PersistentProperty} to the given value. Will do type conversion if a
* {@link ConversionService} is configured.
* Creates a new {@link BeanWrapper} for the given bean.
*
* @param property must not be {@literal null}.
* @param value can be {@literal null}.
* @throws MappingException in case an exception occurred when setting the property value.
* @param bean must not be {@literal null}.
*/
protected BeanWrapper(T bean) {
Assert.notNull(bean, "Bean must not be null!");
this.bean = bean;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentPropertyAccessor#setProperty(org.springframework.data.mapping.PersistentProperty, java.lang.Object)
*/
public void setProperty(PersistentProperty<?> property, Object value) {
@@ -72,16 +72,13 @@ public class BeanWrapper<T> {
if (!property.usePropertyAccess()) {
Object valueToSet = getPotentiallyConvertedValue(value, property.getType());
ReflectionUtils.makeAccessible(property.getField());
ReflectionUtils.setField(property.getField(), bean, valueToSet);
ReflectionUtils.setField(property.getField(), bean, value);
} else if (property.usePropertyAccess() && setter != null) {
Class<?>[] paramTypes = setter.getParameterTypes();
Object valueToSet = getPotentiallyConvertedValue(value, paramTypes[0]);
ReflectionUtils.makeAccessible(setter);
ReflectionUtils.invokeMethod(setter, bean, valueToSet);
ReflectionUtils.invokeMethod(setter, bean, value);
}
} catch (IllegalStateException e) {
@@ -89,12 +86,9 @@ public class BeanWrapper<T> {
}
}
/**
* Returns the value of the given {@link PersistentProperty} of the underlying bean instance.
*
* @param <S>
* @param property must not be {@literal null}.
* @return
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentPropertyAccessor#getProperty(org.springframework.data.mapping.PersistentProperty)
*/
public Object getProperty(PersistentProperty<?> property) {
return getProperty(property, property.getType());
@@ -109,59 +103,38 @@ public class BeanWrapper<T> {
* @return
* @throws MappingException in case an exception occured when accessing the property.
*/
@SuppressWarnings("unchecked")
public <S> S getProperty(PersistentProperty<?> property, Class<? extends S> type) {
Assert.notNull(property, "PersistentProperty must not be null!");
try {
Object obj = null;
Method getter = property.getGetter();
if (!property.usePropertyAccess()) {
Field field = property.getField();
ReflectionUtils.makeAccessible(field);
obj = ReflectionUtils.getField(field, bean);
return (S) ReflectionUtils.getField(field, bean);
} else if (property.usePropertyAccess() && getter != null) {
ReflectionUtils.makeAccessible(getter);
obj = ReflectionUtils.invokeMethod(getter, bean);
return (S) ReflectionUtils.invokeMethod(getter, bean);
} else {
return null;
}
return getPotentiallyConvertedValue(obj, type);
} catch (IllegalStateException e) {
throw new MappingException(String.format("Could not read property %s of %s!", property.toString(),
bean.toString()), e);
}
}
/**
* Converts the given source value if it is not assignable to the given target type.
*
* @param source can be {@literal null}.
* @param targetType can be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
private <S> S getPotentiallyConvertedValue(Object source, Class<S> targetType) {
boolean conversionServiceAvailable = conversionService != null;
boolean conversionNeeded = source == null || !targetType.isAssignableFrom(source.getClass());
if (conversionServiceAvailable && conversionNeeded) {
return conversionService.convert(source, targetType);
}
return (S) source;
}
/**
* Returns the underlying bean instance.
*
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentPropertyAccessor#getBean()
*/
public T getBean() {
return bean;

View File

@@ -0,0 +1,107 @@
/*
* 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.mapping.model;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.util.Assert;
/**
* {@link PersistentPropertyAccessor} that potentially converts the value handed to
* {@link #setProperty(PersistentProperty, Object)} to the type of the {@link PersistentProperty} using a
* {@link ConversionService}. Exposes {@link #getProperty(PersistentProperty, Class)} to allow obtaining the value of a
* property in a type the {@link ConversionService} can convert the raw type to.
*
* @author Oliver Gierke
*/
public class ConvertingPropertyAccessor implements PersistentPropertyAccessor {
private final PersistentPropertyAccessor accessor;
private final ConversionService conversionService;
/**
* Creates a new {@link ConvertingPropertyAccessor} for the given delegate {@link PersistentPropertyAccessor} and
* {@link ConversionService}.
*
* @param accessor must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public ConvertingPropertyAccessor(PersistentPropertyAccessor accessor, ConversionService conversionService) {
Assert.notNull(accessor, "PersistentPropertyAccessor must not be null!");
Assert.notNull(conversionService, "ConversionService must not be null!");
this.accessor = accessor;
this.conversionService = conversionService;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentPropertyAccessor#setProperty(org.springframework.data.mapping.PersistentProperty, java.lang.Object)
*/
@Override
public void setProperty(PersistentProperty<?> property, Object value) {
accessor.setProperty(property, convertIfNecessary(value, property.getType()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentPropertyAccessor#getProperty(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public Object getProperty(PersistentProperty<?> property) {
return accessor.getProperty(property);
}
/**
* Returns the value of the given {@link PersistentProperty} converted to the given type.
*
* @param property must not be {@literal null}.
* @param targetType must not be {@literal null}.
* @return
*/
public <T> T getProperty(PersistentProperty<?> property, Class<T> targetType) {
Assert.notNull(property, "PersistentProperty must not be null!");
Assert.notNull(targetType, "Target type must not be null!");
return convertIfNecessary(getProperty(property), targetType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentPropertyAccessor#getBean()
*/
@Override
public Object getBean() {
return accessor.getBean();
}
/**
* Triggers the conversion of the source value into the target type unless the value already is a value of given
* target type.
*
* @param source can be {@literal null}.
* @param type must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
private <T> T convertIfNecessary(Object source, Class<T> type) {
return (T) (source == null ? source : type.isAssignableFrom(source.getClass()) ? source : conversionService
.convert(source, type));
}
}

View File

@@ -49,7 +49,7 @@ public class PersistentEntityInformation<T, ID extends Serializable> extends Abs
*/
@Override
public ID getId(T entity) {
return (ID) BeanWrapper.create(entity, null).getProperty(this.persistentEntity.getIdProperty());
return (ID) persistentEntity.getPropertyAccessor(entity).getProperty(this.persistentEntity.getIdProperty());
}
/*