diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/AbstractMappingContext.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/AbstractMappingContext.java new file mode 100644 index 000000000..cc0b20a0e --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/AbstractMappingContext.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2011 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. + * 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 java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.mapping.event.MappingContextEvent; +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.mapping.model.PersistentEntity; +import org.springframework.data.mapping.model.PersistentProperty; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.ReflectionUtils.FieldCallback; +import org.springframework.validation.Validator; + + +/** + * Base class to build mapping metadata and thus create instances of {@link PersistentEntity} and {@link PersistentProperty}. + * + * @param E the concrete {@link PersistentEntity} type the {@link MappingContext} implementation creates + * @param P the concrete {@link PersistentProperty} type the {@link MappingContext} implementation creates + * @author Jon Brisbin + * @author Oliver Gierke + */ +public abstract class AbstractMappingContext, P extends PersistentProperty> implements MappingContext, InitializingBean, ApplicationEventPublisherAware { + + private static final Set UNMAPPED_FIELDS = new HashSet(Arrays.asList("class", "this$0")); + + private ApplicationEventPublisher applicationEventPublisher; + private ConcurrentMap persistentEntities = new ConcurrentHashMap(); + private ConcurrentMap> validators = new ConcurrentHashMap>(); + private List> customSimpleTypes = new ArrayList>(); + private Set> initialEntitySet = new HashSet>(); + private boolean strict = false; + + /** + * @param customSimpleTypes the customSimpleTypes to set + */ + public void setCustomSimpleTypes(List> customSimpleTypes) { + this.customSimpleTypes = customSimpleTypes; + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher) + */ + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + + /** + * Sets the {@link Set} of types to populate the context initially. + * + * @param initialEntitySet + */ + public void setInitialEntitySet(Set> initialEntitySet) { + this.initialEntitySet = initialEntitySet; + } + + /** + * Configures whether the {@link MappingContext} is in strict mode which means, that it will throw + * {@link MappingException}s in case one tries to lookup a {@link PersistentEntity} not already in the context. This + * defaults to {@literal false} so that unknown types will be transparently added to the MappingContext if not known + * in advance. + * + * @param strict + */ + public void setStrict(boolean strict) { + this.strict = strict; + } + + public Collection getPersistentEntities() { + return persistentEntities.values(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mapping.model.MappingContext#getPersistentEntity(java.lang.Class) + */ + public E getPersistentEntity(Class type) { + + return getPersistentEntity(new ClassTypeInformation(type)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.MappingContext#getPersistentEntity(org.springframework.data.util.TypeInformation) + */ + public E getPersistentEntity(TypeInformation type) { + + E entity = persistentEntities.get(type); + + if (entity != null) { + return entity; + } + + if (strict) { + throw new MappingException("Unknown persistent entity " + type); + } + + return addPersistentEntity(type); + } + + /** + * Adds the given type to the {@link MappingContext}. + * + * @param type + * @return + */ + protected E addPersistentEntity(Class type) { + + return addPersistentEntity(new ClassTypeInformation(type)); + } + + /** + * Adds the given {@link TypeInformation} to the {@link MappingContext}. + * + * @param typeInformation + * @return + */ + protected E addPersistentEntity(TypeInformation typeInformation) { + + E persistentEntity = persistentEntities.get(typeInformation); + + if (persistentEntity != null) { + return persistentEntity; + } + + Class type = typeInformation.getType(); + + try { + final E entity = createPersistentEntity(typeInformation); + BeanInfo info = Introspector.getBeanInfo(type); + + final Map descriptors = new HashMap(); + for (PropertyDescriptor descriptor : info.getPropertyDescriptors()) { + descriptors.put(descriptor.getName(), descriptor); + } + + ReflectionUtils.doWithFields(type, new FieldCallback() { + + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + + PropertyDescriptor descriptor = descriptors.get(field.getName()); + + ReflectionUtils.makeAccessible(field); + P property = createPersistentProperty(field, descriptor, entity); + + if (property.isTransient()) { + return; + } + + entity.addPersistentProperty(property); + + if (property.isAssociation()) { + entity.addAssociation(property.getAssociation()); + } + + if (property.isIdProperty()) { + entity.setIdProperty(property); + } + + TypeInformation nestedType = getNestedTypeToAdd(property, entity); + if (nestedType != null) { + addPersistentEntity(nestedType); + } + + + } + }, new ReflectionUtils.FieldFilter() { + public boolean matches(Field field) { + return !Modifier.isStatic(field.getModifiers()) && !UNMAPPED_FIELDS.contains(field.getName()); + } + }); + + entity.verify(); + + // Inform listeners + if (null != applicationEventPublisher) { + applicationEventPublisher.publishEvent(new MappingContextEvent(entity, typeInformation)); + } + + // Cache + persistentEntities.put(entity.getPropertyInformation(), (E) entity); + + return entity; + } catch (IntrospectionException e) { + throw new MappingException(e.getMessage(), e); + } + } + + /** + * Returns a potential nested type tha needs to be added when adding the given property in the course of adding a + * {@link PersistentEntity}. Will return the property's {@link TypeInformation} directly if it is a potential entity, + * a collections component type if it's a collection as well as the value type of a {@link Map} if it's a map + * property. + * + * @param property + * @return the TypeInformation to be added as {@link PersistentEntity} or {@literal + */ + private TypeInformation getNestedTypeToAdd(PersistentProperty property, PersistentEntity entity) { + + if (entity.getType().equals(property.getRawType())) { + return null; + } + + TypeInformation typeInformation = property.getTypeInformation(); + + if (customSimpleTypes.contains(typeInformation.getType())) { + return null; + } + + if (property.isEntity()) { + return typeInformation; + } + + if (property.isCollection()) { + return getTypeInformationIfNotSimpleType(typeInformation.getComponentType()); + } + + if (property.isMap()) { + return getTypeInformationIfNotSimpleType(typeInformation.getMapValueType()); + } + + return null; + } + + private TypeInformation getTypeInformationIfNotSimpleType(TypeInformation information) { + return information == null || MappingBeanHelper.isSimpleType(information.getType()) ? null : information; + } + + public List getEntityValidators(E entity) { + return validators.get(entity); + } + + protected abstract E createPersistentEntity(TypeInformation typeInformation); + + protected abstract P createPersistentProperty(Field field, PropertyDescriptor descriptor, E owner); + + + + public void afterPropertiesSet() throws Exception { + for (Class initialEntity : initialEntitySet) { + addPersistentEntity(initialEntity); + } + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentProperty.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/AbstractPersistentProperty.java similarity index 67% rename from spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentProperty.java rename to spring-data-commons-core/src/main/java/org/springframework/data/mapping/AbstractPersistentProperty.java index 2d404d231..f9f6d4528 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentProperty.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/AbstractPersistentProperty.java @@ -17,15 +17,13 @@ package org.springframework.data.mapping; import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.Reference; import org.springframework.data.mapping.model.Association; import org.springframework.data.mapping.model.PersistentEntity; import org.springframework.data.mapping.model.PersistentProperty; @@ -37,45 +35,30 @@ import org.springframework.data.util.TypeInformation; * @author Jon Brisbin * @author Oliver Gierke */ -public class BasicPersistentProperty implements PersistentProperty { +public abstract class AbstractPersistentProperty implements PersistentProperty { protected final String name; protected final PropertyDescriptor propertyDescriptor; protected final TypeInformation information; protected final Class rawType; protected final Field field; - protected Association association; - protected Value value; - protected boolean isTransient = false; - protected PersistentEntity owner; + protected final Association association; + protected final PersistentEntity owner; - public BasicPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, TypeInformation information) { + public AbstractPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, PersistentEntity owner) { this.name = field.getName(); this.rawType = field.getType(); - this.information = information.getProperty(this.name); + this.information = owner.getPropertyInformation().getProperty(this.name); this.propertyDescriptor = propertyDescriptor; this.field = field; - this.isTransient = Modifier.isTransient(field.getModifiers()) || field.isAnnotationPresent(Transient.class); - if (field.isAnnotationPresent(Value.class)) { - this.value = field.getAnnotation(Value.class); - // Fields with @Value annotations are considered the same as transient fields - this.isTransient = true; - } - if (field.isAnnotationPresent(Autowired.class)) { - this.isTransient = true; - } + this.association = isAssociation() ? new Association(this, null) : null; + this.owner = owner; } - public Object getOwner() { + public PersistentEntity getOwner() { return owner; } - public void setOwner(Object owner) { - if (null != owner && owner.getClass().isAssignableFrom(PersistentEntity.class)) { - this.owner = (PersistentEntity) owner; - } - } - public String getName() { return name; } @@ -100,26 +83,31 @@ public class BasicPersistentProperty implements PersistentProperty { return field; } - public Value getValueAnnotation() { - return value; + public String getSpelExpression() { + return null; } public boolean isTransient() { - return isTransient; + return Modifier.isTransient(field.getModifiers()); } public boolean isAssociation() { - return null != association; + if (field.isAnnotationPresent(Reference.class)) { + return true; + } + for (Annotation annotation : field.getDeclaredAnnotations()) { + if (annotation.annotationType().isAnnotationPresent(Reference.class)) { + return true; + } + } + + return false; } public Association getAssociation() { return association; } - public void setAssociation(Association association) { - this.association = association; - } - public boolean isCollection() { return Collection.class.isAssignableFrom(getType()) || isArray(); } @@ -157,8 +145,4 @@ public class BasicPersistentProperty implements PersistentProperty { public Class getMapValueType() { return isMap() ? information.getMapValueType().getType() : null; } - - public boolean isIdProperty() { - return field.isAnnotationPresent(Id.class); - } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/AnnotationBasedPersistentProperty.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/AnnotationBasedPersistentProperty.java new file mode 100644 index 000000000..e3d59bb8f --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/AnnotationBasedPersistentProperty.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2011 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. + * 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 java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Reference; +import org.springframework.data.annotation.Transient; +import org.springframework.data.mapping.model.Association; +import org.springframework.data.mapping.model.PersistentEntity; +import org.springframework.data.mapping.model.PersistentProperty; + + +/** + * Special {@link PersistentProperty} that takes annotations at a property into account. + * + * @author Oliver Gierke + */ +public class AnnotationBasedPersistentProperty extends AbstractPersistentProperty { + + private final Value value; + + /** + * Creates a new {@link AnnotationBasedPersistentProperty}. + * + * @param field + * @param propertyDescriptor + * @param owner + */ + public AnnotationBasedPersistentProperty(Field field, + PropertyDescriptor propertyDescriptor, PersistentEntity owner) { + + super(field, propertyDescriptor, owner); + this.value = field.getAnnotation(Value.class); + field.isAnnotationPresent(Autowired.class); + } + + /** + * Inspects a potentially available {@link Value} annotation at the property and returns the {@link String} value of + * it. + * + * @see org.springframework.data.mapping.AbstractPersistentProperty#getSpelExpression() + */ + public String getSpelExpression() { + return value == null ? null :value.value(); + } + + /** + * Considers plain transient fields, fields annotated with {@link Transient}, {@link Value} or {@link Autowired} as + * transien. + * + * @see org.springframework.data.mapping.BasicPersistentProperty#isTransient() + */ + public boolean isTransient() { + + boolean isTransient = super.isTransient() || field.isAnnotationPresent(Transient.class); + + return isTransient || field.isAnnotationPresent(Value.class) || field.isAnnotationPresent(Autowired.class); + } + + /** + * Regards the property as ID if there is an {@link Id} annotation found on it. + */ + public boolean isIdProperty() { + return field.isAnnotationPresent(Id.class); + } + + /** + * Considers the property an {@link Association} if it is annotated with {@link Reference}. + */ + @Override + public boolean isAssociation() { + + if (isTransient()) { + return false; + } + if (field.isAnnotationPresent(Reference.class)) { + return true; + } + + // TODO: do we need this? Shouldn't the section above already find that annotation? + for (Annotation annotation : field.getDeclaredAnnotations()) { + if (annotation.annotationType().isAnnotationPresent(Reference.class)) { + return true; + } + } + + return false; + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingContext.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingContext.java index a34e4af9a..d274c5403 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingContext.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingContext.java @@ -13,394 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.mapping; -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; import java.beans.PropertyDescriptor; -import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; -import org.springframework.core.convert.support.ConversionServiceFactory; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.PersistenceConstructor; -import org.springframework.data.annotation.Persistent; -import org.springframework.data.annotation.Reference; -import org.springframework.data.annotation.Transient; -import org.springframework.data.mapping.event.MappingContextEvent; -import org.springframework.data.mapping.model.Association; -import org.springframework.data.mapping.model.MappingConfigurationException; import org.springframework.data.mapping.model.MappingContext; -import org.springframework.data.mapping.model.MappingException; -import org.springframework.data.mapping.model.PersistentEntity; import org.springframework.data.mapping.model.PersistentProperty; -import org.springframework.data.mapping.model.PreferredConstructor; -import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.ReflectionUtils.FieldCallback; -import org.springframework.validation.Validator; /** + * Simple implementation of {@link MappingContext} creating {@link BasicPersistentEntity} and + * {@link AnnotationBasedPersistentProperty} instances. + * * @author Jon Brisbin * @author Oliver Gierke */ -public class BasicMappingContext implements MappingContext, InitializingBean, ApplicationEventPublisherAware { +public class BasicMappingContext extends AbstractMappingContext, PersistentProperty> { - private static final Set UNMAPPED_FIELDS = new HashSet(Arrays.asList("class", "this$0")); + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation) + */ + @Override + @SuppressWarnings("rawtypes") + protected BasicPersistentEntity createPersistentEntity( + TypeInformation typeInformation) { - protected Log log = LogFactory.getLog(getClass()); - protected ApplicationEventPublisher applicationEventPublisher; - protected ConcurrentMap> persistentEntities = new ConcurrentHashMap>(); - protected ConcurrentMap, List> validators = new ConcurrentHashMap, List>(); - protected final GenericConversionService conversionService; - private List> customSimpleTypes = new ArrayList>(); - private Set> initialEntitySet = new HashSet>(); + return new BasicPersistentEntity(typeInformation); + } - public BasicMappingContext() { - this(ConversionServiceFactory.createDefaultConversionService()); - } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.util.TypeInformation, org.springframework.data.mapping.BasicPersistentEntity) + */ + @Override + protected PersistentProperty createPersistentProperty(Field field, + PropertyDescriptor descriptor, + BasicPersistentEntity owner) { - public BasicMappingContext(GenericConversionService conversionService) { - Assert.notNull(conversionService); - this.conversionService = conversionService; - } - - /** - * @param customSimpleTypes the customSimpleTypes to set - */ - public void setCustomSimpleTypes(List> customSimpleTypes) { - this.customSimpleTypes = customSimpleTypes; - } - - - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - public void setInitialEntitySet(Set> initialEntitySet) { - this.initialEntitySet = initialEntitySet; - } - - public Collection> getPersistentEntities() { - return persistentEntities.values(); - } - - /* (non-Javadoc) - * @see org.springframework.data.mapping.model.MappingContext#getPersistentEntity(java.lang.Class) - */ - public PersistentEntity getPersistentEntity(Class type) { - return getPersistentEntity(new ClassTypeInformation(type)); - } - - @SuppressWarnings({"unchecked"}) - public PersistentEntity getPersistentEntity(TypeInformation type) { - return (PersistentEntity) persistentEntities.get(type); - } - - public PersistentEntity addPersistentEntity(Class type) { - return addPersistentEntity(new ClassTypeInformation(type)); - } - - @SuppressWarnings("unchecked") - public PersistentEntity addPersistentEntity(TypeInformation typeInformation) { - - PersistentEntity persistentEntity = persistentEntities.get(typeInformation); - - if (persistentEntity != null) { - return (PersistentEntity) persistentEntity; - } - - Class type = (Class) typeInformation.getType(); - - try { - final BasicPersistentEntity entity = createPersistentEntity(typeInformation, this); - BeanInfo info = Introspector.getBeanInfo(type); - - final Map descriptors = new HashMap(); - for (PropertyDescriptor descriptor : info.getPropertyDescriptors()) { - descriptors.put(descriptor.getName(), descriptor); - } - - ReflectionUtils.doWithFields(type, new FieldCallback() { - - public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { - try { - PropertyDescriptor descriptor = descriptors.get(field.getName()); - if (isPersistentProperty(field, descriptor)) { - ReflectionUtils.makeAccessible(field); - BasicPersistentProperty property = createPersistentProperty(field, descriptor, entity.getPropertyInformation()); - property.setOwner(entity); - entity.addPersistentProperty(property); - if (isAssociation(field, descriptor)) { - Association association = createAssociation(property); - entity.addAssociation(association); - } - - if (property.isIdProperty()) { - entity.setIdProperty(property); - } - - TypeInformation nestedType = getNestedTypeToAdd(property); - if (nestedType != null) { - addPersistentEntity(nestedType); - } - } - } catch (MappingConfigurationException e) { - log.error(e.getMessage(), e); - } - } - }, new ReflectionUtils.FieldFilter() { - public boolean matches(Field field) { - return !Modifier.isStatic(field.getModifiers()); - } - }); - - entity.setPreferredConstructor(getPreferredConstructor(type)); - - // Inform listeners - if (null != applicationEventPublisher) { - applicationEventPublisher.publishEvent(new MappingContextEvent(entity, typeInformation)); - } - - // Cache - persistentEntities.put(entity.getPropertyInformation(), entity); - - return entity; - } catch (MappingConfigurationException e) { - log.error(e.getMessage(), e); - } catch (IntrospectionException e) { - throw new MappingException(e.getMessage(), e); - } - - return null; - } - - /** - * Returns a potential nested type tha needs to be added when adding the given property in the course of adding a - * {@link PersistentEntity}. Will return the property's {@link TypeInformation} directly if it is a potential entity, - * a collections component type if it's a collection as well as the value type of a {@link Map} if it's a map - * property. - * - * @param property - * @return the TypeInformation to be added as {@link PersistentEntity} or {@literal - */ - private TypeInformation getNestedTypeToAdd(PersistentProperty property) { - - TypeInformation typeInformation = property.getTypeInformation(); - - if (customSimpleTypes.contains(typeInformation.getType())) { - return null; - } - - if (property.isEntity()) { - return typeInformation; - } - - if (property.isCollection()) { - return getTypeInformationIfNotSimpleType(typeInformation.getComponentType()); - } - - if (property.isMap()) { - return getTypeInformationIfNotSimpleType(typeInformation.getMapValueType()); - } - - return null; - } - - private TypeInformation getTypeInformationIfNotSimpleType(TypeInformation information) { - return information == null || MappingBeanHelper.isSimpleType(information.getType()) ? null : information; - } - - public List getEntityValidators(PersistentEntity entity) { - return validators.get(entity); - } - - public boolean isPersistentEntity(Object value) { - if (null != value) { - Class clazz; - if (value instanceof Class) { - clazz = ((Class) value); - } else { - clazz = value.getClass(); - } - return isPersistentEntity(clazz); - } - return false; - } - - public boolean isPersistentEntity(Class type) { - if (type.isAnnotationPresent(Persistent.class)) { - return true; - } else { - for (Annotation annotation : type.getDeclaredAnnotations()) { - if (annotation.annotationType().isAnnotationPresent(Persistent.class)) { - return true; - } - } - for (Field field : type.getDeclaredFields()) { - if (field.isAnnotationPresent(Id.class)) { - return true; - } - } - } - return false; - } - - protected BasicPersistentEntity createPersistentEntity(TypeInformation typeInformation, MappingContext mappingContext) - throws MappingConfigurationException { - return new BasicPersistentEntity(mappingContext, typeInformation); - } - - protected BasicPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, - TypeInformation information) throws MappingConfigurationException { - return new BasicPersistentProperty(field, descriptor, information); - } - - public boolean isPersistentProperty(Field field, PropertyDescriptor descriptor) throws MappingConfigurationException { - if (UNMAPPED_FIELDS.contains(field.getName()) || isTransient(field)) { - return false; - } - return true; - } - - @SuppressWarnings({"unchecked"}) - public PreferredConstructor getPreferredConstructor(Class type) throws MappingConfigurationException { - // Find the right constructor - PreferredConstructor preferredConstructor = null; - - for (Constructor constructor : type.getDeclaredConstructors()) { - if (constructor.getParameterTypes().length != 0) { - // Non-no-arg constructor - if (null == preferredConstructor || constructor.isAnnotationPresent(PersistenceConstructor.class)) { - preferredConstructor = new PreferredConstructor((Constructor) constructor); - - String[] paramNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(constructor); - Type[] paramTypes = constructor.getGenericParameterTypes(); - - for (int i = 0; i < paramTypes.length; i++) { - Class targetType = Object.class; - Class rawType = constructor.getParameterTypes()[i]; - if (paramTypes[i] instanceof ParameterizedType) { - ParameterizedType ptype = (ParameterizedType) paramTypes[i]; - targetType = getTargetType(ptype); - } else { - if (paramTypes[i] instanceof TypeVariable) { - targetType = getTargetType((TypeVariable) paramTypes[i]); - } else if (paramTypes[i] instanceof Class) { - targetType = (Class) paramTypes[i]; - } - } - String paramName = (null != paramNames ? paramNames[i] : "param" + i); - preferredConstructor.addParameter(paramName, targetType, rawType, targetType.getDeclaredAnnotations()); - } - - if (constructor.isAnnotationPresent(PersistenceConstructor.class)) { - // We're done - break; - } - } - } - } - - return preferredConstructor; - } - - public boolean isAssociation(Field field, PropertyDescriptor descriptor) throws MappingConfigurationException { - if (!isTransient(field)) { - if (field.isAnnotationPresent(Reference.class)) { - return true; - } - for (Annotation annotation : field.getDeclaredAnnotations()) { - if (annotation.annotationType().isAnnotationPresent(Reference.class)) { - return true; - } - } - } - return false; - } - - public Association createAssociation(BasicPersistentProperty property) { - // Only support uni-directional associations in the Basic configuration - Association association = new Association(property, null); - property.setAssociation(association); - - return association; - } - - protected boolean isTransient(Field field) { - if (Modifier.isTransient(field.getModifiers()) - || null != field.getAnnotation(Transient.class) - || null != field.getAnnotation(Value.class)) { - return true; - } - return false; - } - - protected Class getTargetType(TypeVariable tv) { - Class targetType = Object.class; - Type[] bounds = tv.getBounds(); - if (bounds.length > 0) { - if (bounds[0] instanceof ParameterizedType) { - return getTargetType((ParameterizedType) bounds[0]); - } else if (bounds[0] instanceof TypeVariable) { - return getTargetType((TypeVariable) bounds[0]); - } else { - targetType = (Class) bounds[0]; - } - } - return targetType; - } - - protected Class getTargetType(ParameterizedType ptype) { - Class targetType = Object.class; - Type[] types = ptype.getActualTypeArguments(); - if (types.length == 1) { - if (types[0] instanceof TypeVariable) { - // Placeholder type - targetType = Object.class; - } else { - if (types[0] instanceof ParameterizedType) { - return getTargetType((ParameterizedType) types[0]); - } else { - targetType = (Class) types[0]; - } - } - } else { - targetType = (Class) ptype.getRawType(); - } - return targetType; - } - - public void afterPropertiesSet() throws Exception { - for (Class initialEntity : initialEntitySet) { - addPersistentEntity(initialEntity); - } - } + return new AnnotationBasedPersistentProperty(field, descriptor, owner); + } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentEntity.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentEntity.java index 04ebd1aab..976eab08e 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentEntity.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentEntity.java @@ -13,50 +13,132 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.mapping; +import java.lang.reflect.Constructor; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.mapping.model.Association; -import org.springframework.data.mapping.model.MappingContext; import org.springframework.data.mapping.model.PersistentEntity; import org.springframework.data.mapping.model.PersistentProperty; import org.springframework.data.mapping.model.PreferredConstructor; import org.springframework.data.util.TypeInformation; /** + * Simple value object to capture information of {@link PersistentEntity}s. + * * @author Jon Brisbin */ public class BasicPersistentEntity implements PersistentEntity { protected final Class type; - protected PreferredConstructor preferredConstructor; - protected PersistentProperty idProperty; - protected Map persistentProperties = new HashMap(); - protected Map associations = new HashMap(); + protected final PreferredConstructor preferredConstructor; protected final TypeInformation information; - - protected MappingContext mappingContext; + protected final Map persistentProperties = new HashMap(); + protected final Map associations = new HashMap(); + protected PersistentProperty idProperty; - @SuppressWarnings({"unchecked"}) - public BasicPersistentEntity(MappingContext mappingContext, TypeInformation information) { - this.mappingContext = mappingContext; + /** + * Creates a new {@link BasicPersistentEntity} from the given {@link TypeInformation}. + * + * @param information + */ + @SuppressWarnings("unchecked") + public BasicPersistentEntity(TypeInformation information) { this.type = (Class) information.getType(); this.information = information; + this.preferredConstructor = getPreferredConstructor(type); } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private final PreferredConstructor getPreferredConstructor(Class type) { + // Find the right constructor + PreferredConstructor preferredConstructor = null; + + for (Constructor constructor : type.getDeclaredConstructors()) { + if (constructor.getParameterTypes().length != 0) { + // Non-no-arg constructor + if (null == preferredConstructor || constructor.isAnnotationPresent(PersistenceConstructor.class)) { + preferredConstructor = new PreferredConstructor((Constructor) constructor); + + String[] paramNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(constructor); + Type[] paramTypes = constructor.getGenericParameterTypes(); + + for (int i = 0; i < paramTypes.length; i++) { + Class targetType = Object.class; + Class rawType = constructor.getParameterTypes()[i]; + if (paramTypes[i] instanceof ParameterizedType) { + ParameterizedType ptype = (ParameterizedType) paramTypes[i]; + targetType = getTargetType(ptype); + } else { + if (paramTypes[i] instanceof TypeVariable) { + targetType = getTargetType((TypeVariable) paramTypes[i]); + } else if (paramTypes[i] instanceof Class) { + targetType = (Class) paramTypes[i]; + } + } + String paramName = (null != paramNames ? paramNames[i] : "param" + i); + preferredConstructor.addParameter(paramName, targetType, rawType, targetType.getDeclaredAnnotations()); + } + + if (constructor.isAnnotationPresent(PersistenceConstructor.class)) { + // We're done + break; + } + } + } + } + + return preferredConstructor; +} + + private Class getTargetType(TypeVariable tv) { + Class targetType = Object.class; + Type[] bounds = tv.getBounds(); + if (bounds.length > 0) { + if (bounds[0] instanceof ParameterizedType) { + return getTargetType((ParameterizedType) bounds[0]); + } else if (bounds[0] instanceof TypeVariable) { + return getTargetType((TypeVariable) bounds[0]); + } else { + targetType = (Class) bounds[0]; + } + } + return targetType; +} + + private Class getTargetType(ParameterizedType ptype) { + Class targetType = Object.class; + Type[] types = ptype.getActualTypeArguments(); + if (types.length == 1) { + if (types[0] instanceof TypeVariable) { + // Placeholder type + targetType = Object.class; + } else { + if (types[0] instanceof ParameterizedType) { + return getTargetType((ParameterizedType) types[0]); + } else { + targetType = (Class) types[0]; + } + } + } else { + targetType = (Class) ptype.getRawType(); + } + return targetType; +} public PreferredConstructor getPreferredConstructor() { return preferredConstructor; } - public void setPreferredConstructor(PreferredConstructor constructor) { - this.preferredConstructor = constructor; - } - public String getName() { return type.getName(); } @@ -101,14 +183,6 @@ public class BasicPersistentEntity implements PersistentEntity { return persistentProperties.keySet(); } - public MappingContext getMappingContext() { - return mappingContext; - } - - public void afterPropertiesSet() throws Exception { - - } - public void doWithProperties(PropertyHandler handler) { for (PersistentProperty property : persistentProperties.values()) { if (!property.isTransient() && !property.isAssociation() && !property.isIdProperty()) { @@ -122,4 +196,12 @@ public class BasicPersistentEntity implements PersistentEntity { handler.doWithAssociation(association); } } + + /** + * Callback method to trigger validation of the {@link PersistentEntity}. As {@link BasicPersistentEntity} is not + * immutable there might be some verification steps necessary after the object has reached is final state. + */ + public void verify() { + + } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/MappingBeanHelper.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/MappingBeanHelper.java index 679080811..08bef45c0 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/MappingBeanHelper.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/MappingBeanHelper.java @@ -106,15 +106,12 @@ public abstract class MappingBeanHelper { return type.isEnum(); } - public static T constructInstance(PersistentEntity entity, - PreferredConstructor.ParameterValueProvider provider) { + public static T constructInstance(PersistentEntity entity, PreferredConstructor.ParameterValueProvider provider) { return constructInstance(entity, provider, new StandardEvaluationContext()); } @SuppressWarnings({"unchecked"}) - public static T constructInstance(PersistentEntity entity, - PreferredConstructor.ParameterValueProvider provider, - EvaluationContext spelCtx) { + public static T constructInstance(PersistentEntity entity, PreferredConstructor.ParameterValueProvider provider, EvaluationContext spelCtx) { PreferredConstructor constructor = entity.getPreferredConstructor(); if (null == constructor) { diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/MappingContextAware.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/MappingContextAware.java index 0513804e1..21ebcc642 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/MappingContextAware.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/MappingContextAware.java @@ -30,6 +30,6 @@ public interface MappingContextAware { * * @param mappingContext */ - void setMappingContext(MappingContext mappingContext); + void setMappingContext(MappingContext mappingContext); } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContext.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContext.java index 23e6aeaa1..166cbbc14 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContext.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContext.java @@ -17,7 +17,6 @@ package org.springframework.data.mapping.model; import java.util.Collection; import java.util.List; -import org.springframework.beans.factory.InitializingBean; import org.springframework.data.util.TypeInformation; import org.springframework.validation.Validator; @@ -36,41 +35,25 @@ import org.springframework.validation.Validator; * @author Jon Brisbin * @author Oliver Gierke */ -public interface MappingContext extends InitializingBean { +public interface MappingContext> { - /** - * Adds a PersistentEntity instance - * - * @param type The Java class representing the entity - * @return The PersistentEntity instance - */ - PersistentEntity addPersistentEntity(Class type); - /** * Obtains a list of PersistentEntity instances * * @return A list of PersistentEntity instances */ - Collection> getPersistentEntities(); + Collection getPersistentEntities(); - PersistentEntity getPersistentEntity(Class type); + E getPersistentEntity(Class type); - PersistentEntity getPersistentEntity(TypeInformation type); + E getPersistentEntity(TypeInformation type); /** * Obtains a validator for the given entity + * TODO: Why do we need validators at the {@link MappingContext}? * * @param entity The entity * @return A validator or null if none exists for the given entity */ - List getEntityValidators(PersistentEntity entity); - - /** - * Returns whether the specified value is a persistent entity - * - * @param value The value to check - * @return True if it is - */ - boolean isPersistentEntity(Object value); - + List getEntityValidators(E entity); } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java index b64269a24..89668bee7 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java @@ -1,6 +1,5 @@ package org.springframework.data.mapping.model; -import org.springframework.beans.factory.InitializingBean; import org.springframework.data.mapping.AssociationHandler; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.util.TypeInformation; @@ -15,7 +14,7 @@ import java.util.Collection; * @author Jon Brisbin * @author Oliver Gierke */ -public interface PersistentEntity extends InitializingBean { +public interface PersistentEntity { /** * The entity name including any package prefix @@ -26,11 +25,14 @@ public interface PersistentEntity extends InitializingBean { PreferredConstructor getPreferredConstructor(); - /** - * Returns the identity of the instance - * - * @return The identity - */ + + /** + * Returns the id property of the {@link PersistentEntity}. Must never + * return {@literal null} as a {@link PersistentEntity} instance must not be + * created if there is no id property. + * + * @return the id property of the {@link PersistentEntity}. + */ PersistentProperty getIdProperty(); /** @@ -69,15 +71,7 @@ public interface PersistentEntity extends InitializingBean { */ Collection getPersistentPropertyNames(); - /** - * Obtains the MappingContext where this PersistentEntity is defined - * - * @return The MappingContext instance - */ - MappingContext getMappingContext(); - void doWithProperties(PropertyHandler handler); void doWithAssociations(AssociationHandler handler); - } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java index 13617cad9..c4a1bdcf9 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java @@ -15,7 +15,7 @@ import org.springframework.data.util.TypeInformation; */ public interface PersistentProperty { - Object getOwner(); + PersistentEntity getOwner(); /** * The name of the property @@ -37,7 +37,7 @@ public interface PersistentProperty { Field getField(); - Value getValueAnnotation(); + String getSpelExpression(); boolean isTransient(); diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java index 3794105b8..73383cd8f 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java @@ -4,6 +4,8 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Map; +import org.springframework.util.Assert; + /** * Property information for a plain {@link Class}. * @@ -50,12 +52,18 @@ public class ClassTypeInformation extends TypeDiscoverer { public TypeInformation getComponentType() { if (type.isArray()) { - return createInfo(type.getComponentType()); + return createInfo(resolveArrayType(type)); } TypeVariable[] typeParameters = type.getTypeParameters(); return typeParameters.length > 0 ? new TypeVariableTypeInformation(typeParameters[0], this.getType(), this) : null; } + + private static Type resolveArrayType(Class type) { + Assert.isTrue(type.isArray()); + Class componentType = type.getComponentType(); + return componentType.isArray() ? resolveArrayType(componentType) : componentType; + } /* (non-Javadoc) * @see org.springframework.data.util.TypeDiscoverer#equals(java.lang.Object) diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java index 8bf3d0a08..e46d2db31 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -62,6 +62,10 @@ class TypeDiscoverer implements TypeInformation { * @return */ protected TypeInformation createInfo(Type fieldType) { + + if (fieldType.equals(this.type)) { + return this; + } if (fieldType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) fieldType; diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/MappingMetadataTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/MappingMetadataTests.java index 5f736180a..9fe6413af 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/MappingMetadataTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/MappingMetadataTests.java @@ -19,12 +19,14 @@ package org.springframework.data.mapping; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.data.annotation.Id; import org.springframework.data.mapping.model.Association; +import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.PersistentEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import static junit.framework.Assert.*; +import static org.junit.Assert.*; /** * @author Jon Brisbin @@ -40,43 +42,21 @@ public class MappingMetadataTests { ctx = new BasicMappingContext(); } - @Test - public void testDoesRecognizePersistentEntities() { - boolean persistentEntity = ctx.isPersistentEntity(PersonNoId.class); - assertFalse(persistentEntity); - - persistentEntity = ctx.isPersistentEntity(PersonPersistent.class); - assertTrue(persistentEntity); - } @Test public void testPojoWithId() { - PersistentEntity person = ctx.addPersistentEntity(PersonWithId.class); + PersistentEntity person = ctx.addPersistentEntity(PersonWithId.class); assertNotNull(person.getIdProperty()); assertEquals(String.class, person.getIdProperty().getType()); } - @Test - public void testPojoWithoutId() { - PersistentEntity person = ctx.addPersistentEntity(PersonNoId.class); - assertNull(person.getIdProperty()); - } - @Test public void testAssociations() { - PersistentEntity person = ctx.addPersistentEntity(PersonWithChildren.class); + PersistentEntity person = ctx.addPersistentEntity(PersonWithChildren.class); assertNotNull(person.getAssociations()); for (Association association : person.getAssociations()) { assertEquals(Child.class, association.getInverse().getComponentType()); } } - - @Test - public void testDoesRecognizeDocumentEntities() { - // PersistentEntity person = ctx.addPersistentEntity(PersonDocument.class); - boolean persistentEntity = ctx.isPersistentEntity(PersonDocument.class); - assertTrue(persistentEntity); - } - } \ No newline at end of file diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PersonNoId.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PersonNoId.java index d193b4fcc..d6851bae7 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PersonNoId.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PersonNoId.java @@ -19,6 +19,7 @@ package org.springframework.data.mapping; /** * @author Jon Brisbin */ +@Document public class PersonNoId extends Person { public PersonNoId(Integer ssn, String firstName, String lastName) { diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PersonWithChildren.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PersonWithChildren.java index b3cb4481f..dca6a8ebd 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PersonWithChildren.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PersonWithChildren.java @@ -16,6 +16,7 @@ package org.springframework.data.mapping; +import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Reference; import java.util.List; @@ -25,6 +26,9 @@ import java.util.List; */ public class PersonWithChildren extends Person { + @Id + private String id; + @Reference private List children; diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorUnitTests.java index 6657ed46c..ed82034e3 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorUnitTests.java @@ -152,7 +152,6 @@ public class DomainClassPropertyEditorUnitTests { * * @see org.springframework.data.domain.Persistable#getId() */ - @Override public Integer getId() { return id; @@ -164,7 +163,6 @@ public class DomainClassPropertyEditorUnitTests { * * @see org.springframework.data.domain.Persistable#isNew() */ - @Override public boolean isNew() { return getId() != null; diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 1c35db42f..8083828ed 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -7,6 +7,7 @@ Changes in version 1.0.1 General * Extracted Querydsl support code from JPA and Mongo modules (DATACMNS-32) * Implementations of Page, Pageable and Sort are now serializable (DATACMNS-30) +* Refactored mapping subsystem (DATADOC-109) Changes in version 1.0.0 (2011-04-18) ----------------------------------------