From bf85d8facd81e407378da8e196385626da70d6f6 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 11 Aug 2014 20:23:24 +0200 Subject: [PATCH] DATAMONGO-1005 - Polishing introduction of ObjectPath. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplified implementation of ObjectPath to use a static root instance and hand the path further down until final resolution in MappingMongoConverter.readValue(…). This removes a bit of boxing and unboxing code both in ObjectPath and the converter. Introduced ObjectPath.getPathItem(…) to internalize the iteration to find a potentially already resolved object. Renamed parameters and fields of type ObjectPath to path consistently. Removed obsolete method in MappingMongoConverter. Original pull request: #209. --- .../core/convert/MappingMongoConverter.java | 253 +++--------------- .../data/mongodb/core/convert/ObjectPath.java | 161 +++++++++++ 2 files changed, 205 insertions(+), 209 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ObjectPath.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 2cf49326d..a2e91ef11 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -20,7 +20,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -186,11 +185,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App } protected S read(TypeInformation type, DBObject dbo) { - return read(type, dbo, ObjectPath.toObjectPath(null)); + return read(type, dbo, ObjectPath.ROOT); } @SuppressWarnings("unchecked") - private S read(TypeInformation type, DBObject dbo, ObjectPath objectPath) { + private S read(TypeInformation type, DBObject dbo, ObjectPath path) { if (null == dbo) { return null; @@ -208,11 +207,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App } if (typeToUse.isCollectionLike() && dbo instanceof BasicDBList) { - return (S) readCollectionOrArray(typeToUse, (BasicDBList) dbo, objectPath); + return (S) readCollectionOrArray(typeToUse, (BasicDBList) dbo, path); } if (typeToUse.isMap()) { - return (S) readMap(typeToUse, dbo, objectPath); + return (S) readMap(typeToUse, dbo, path); } // Retrieve persistent entity info @@ -222,47 +221,47 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App throw new MappingException("No mapping metadata found for " + rawType.getName()); } - return read(persistentEntity, dbo, objectPath); + return read(persistentEntity, dbo, path); } private ParameterValueProvider getParameterProvider(MongoPersistentEntity entity, - DBObject source, DefaultSpELExpressionEvaluator evaluator, ObjectPath objectPath) { + DBObject source, DefaultSpELExpressionEvaluator evaluator, ObjectPath path) { - MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, objectPath); + MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, path); PersistentEntityParameterValueProvider parameterProvider = new PersistentEntityParameterValueProvider( - entity, provider, objectPath.getCurrentObject()); + entity, provider, path.getCurrentObject()); - return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider, - objectPath.getCurrentObject()); + return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider, path); } - private S read(final MongoPersistentEntity entity, final DBObject dbo, - final ObjectPath parentPath) { + private S read(final MongoPersistentEntity entity, final DBObject dbo, final ObjectPath path) { final DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(dbo, spELContext); - ParameterValueProvider provider = getParameterProvider(entity, dbo, evaluator, parentPath); + ParameterValueProvider provider = getParameterProvider(entity, dbo, evaluator, path); EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); S instance = instantiator.createInstance(entity, provider); final BeanWrapper wrapper = BeanWrapper.create(instance, conversionService); + final MongoPersistentProperty idProperty = entity.getIdProperty(); final S result = wrapper.getBean(); // make sure id property is set before all other properties Object idValue = null; - if (entity.getIdProperty() != null) { - idValue = getValueInternal(entity.getIdProperty(), dbo, evaluator, parentPath); - wrapper.setProperty(entity.getIdProperty(), idValue); + + if (idProperty != null) { + idValue = getValueInternal(idProperty, dbo, evaluator, path); + wrapper.setProperty(idProperty, idValue); } - final ObjectPath currentPath = parentPath.push(result, entity, idValue); + final ObjectPath currentPath = path.push(result, entity, idValue); // Set properties not already set in the constructor entity.doWithProperties(new PropertyHandler() { public void doWithPersistentProperty(MongoPersistentProperty prop) { // we skip the id property since it was already set - if (entity.getIdProperty() != null && entity.getIdProperty().equals(prop)) { + if (idProperty != null && idProperty.equals(prop)) { return; } @@ -815,9 +814,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App } protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator, - ObjectPath objectPath) { + ObjectPath path) { - return new MongoDbPropertyValueProvider(dbo, evaluator, objectPath).getPropertyValue(prop); + return new MongoDbPropertyValueProvider(dbo, evaluator, path).getPropertyValue(prop); } /** @@ -825,13 +824,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App * * @param targetType must not be {@literal null}. * @param sourceValue must not be {@literal null}. - * @param objectPath must not be {@literal null}. + * @param path must not be {@literal null}. * @return the converted {@link Collection} or array, will never be {@literal null}. */ - private Object readCollectionOrArray(TypeInformation targetType, BasicDBList sourceValue, ObjectPath objectPath) { + private Object readCollectionOrArray(TypeInformation targetType, BasicDBList sourceValue, ObjectPath path) { - Assert.notNull(targetType); - Assert.notNull(objectPath); + Assert.notNull(targetType, "Target type must not be null!"); + Assert.notNull(path, "Object path must not be null!"); Class collectionType = targetType.getType(); @@ -852,9 +851,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App if (dbObjItem instanceof DBRef) { items.add(DBRef.class.equals(rawComponentType) ? dbObjItem : read(componentType, readRef((DBRef) dbObjItem), - objectPath)); + path)); } else if (dbObjItem instanceof DBObject) { - items.add(read(componentType, (DBObject) dbObjItem, objectPath)); + items.add(read(componentType, (DBObject) dbObjItem, path)); } else { items.add(getPotentiallyConvertedSimpleRead(dbObjItem, rawComponentType)); } @@ -868,14 +867,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App * * @param type the {@link Map} {@link TypeInformation} to be used to unmarshall this {@link DBObject}. * @param dbObject must not be {@literal null} - * @param objectPath must not be {@literal null} + * @param path must not be {@literal null} * @return */ @SuppressWarnings("unchecked") - protected Map readMap(TypeInformation type, DBObject dbObject, ObjectPath objectPath) { + protected Map readMap(TypeInformation type, DBObject dbObject, ObjectPath path) { - Assert.notNull(dbObject); - Assert.notNull(objectPath); + Assert.notNull(dbObject, "DBObject must not be null!"); + Assert.notNull(path, "Object path must not be null!"); Class mapType = typeMapper.readType(dbObject, type).getType(); @@ -902,7 +901,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App Object value = entry.getValue(); if (value instanceof DBObject) { - map.put(key, read(valueType, (DBObject) value, objectPath)); + map.put(key, read(valueType, (DBObject) value, path)); } else if (value instanceof DBRef) { map.put(key, DBRef.class.equals(rawValueType) ? value : read(valueType, readRef((DBRef) value))); } else { @@ -914,21 +913,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return map; } - protected List unwrapList(BasicDBList dbList, TypeInformation targetType) { - List rootList = new ArrayList(); - for (int i = 0; i < dbList.size(); i++) { - Object obj = dbList.get(i); - if (obj instanceof BasicDBList) { - rootList.add(unwrapList((BasicDBList) obj, targetType.getComponentType())); - } else if (obj instanceof DBObject) { - rootList.add(read(targetType, (DBObject) obj)); - } else { - rootList.add(obj); - } - } - return rootList; - } - /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.convert.MongoWriter#convertToMongoType(java.lang.Object, org.springframework.data.util.TypeInformation) @@ -1048,7 +1032,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App private final DBObjectAccessor source; private final SpELExpressionEvaluator evaluator; - private final ObjectPath objectPath; + private final ObjectPath path; /** * Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and @@ -1058,14 +1042,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App * @param evaluator must not be {@literal null}. * @param parent can be {@literal null}. */ - public MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath objectPath) { + public MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path) { Assert.notNull(source); Assert.notNull(evaluator); this.source = new DBObjectAccessor(source); this.evaluator = evaluator; - this.objectPath = objectPath; + this.path = path; } /* @@ -1081,7 +1065,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return null; } - return readValue(value, property.getTypeInformation(), objectPath); + return readValue(value, property.getTypeInformation(), path); } } @@ -1094,7 +1078,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App private class ConverterAwareSpELExpressionParameterValueProvider extends SpELExpressionParameterValueProvider { - private final Object parent; + private final ObjectPath parent; /** * Creates a new {@link ConverterAwareSpELExpressionParameterValueProvider}. @@ -1104,7 +1088,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App * @param delegate must not be {@literal null}. */ public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator, - ConversionService conversionService, ParameterValueProvider delegate, Object parent) { + ConversionService conversionService, ParameterValueProvider delegate, ObjectPath parent) { super(evaluator, conversionService, delegate); this.parent = parent; @@ -1121,73 +1105,37 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App } @SuppressWarnings("unchecked") - private T readValue(Object value, TypeInformation type, Object parent) { - - ObjectPath objectPath = ObjectPath.toObjectPath(parent); + private T readValue(Object value, TypeInformation type, ObjectPath parent) { Class rawType = type.getType(); if (conversions.hasCustomReadTarget(value.getClass(), rawType)) { return (T) conversionService.convert(value, rawType); } else if (value instanceof DBRef) { - return potentiallyReadOrResolveDbRef((DBRef) value, type, objectPath, rawType); + return potentiallyReadOrResolveDbRef((DBRef) value, type, parent, rawType); } else if (value instanceof BasicDBList) { - return (T) readCollectionOrArray(type, (BasicDBList) value, objectPath); + return (T) readCollectionOrArray(type, (BasicDBList) value, parent); } else if (value instanceof DBObject) { - return (T) read(type, (DBObject) value, objectPath); + return (T) read(type, (DBObject) value, parent); } else { return (T) getPotentiallyConvertedSimpleRead(value, rawType); } } @SuppressWarnings("unchecked") - private T potentiallyReadOrResolveDbRef(DBRef dbref, TypeInformation type, ObjectPath objectPath, - Class rawType) { + private T potentiallyReadOrResolveDbRef(DBRef dbref, TypeInformation type, ObjectPath path, Class rawType) { if (rawType.equals(DBRef.class)) { return (T) dbref; } - Object object = getObjectFromPathForRefOrNull(objectPath, dbref); + Object object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getRef()); if (object != null) { return (T) object; } - return (T) (object != null ? object : read(type, readRef(dbref), objectPath)); - } - - /** - * Returns the object from the given {@link ObjectPath} iff the given {@link DBRef} points to it or {@literal null}. - * - * @param path - * @param dbref - * @return - */ - private Object getObjectFromPathForRefOrNull(ObjectPath path, DBRef dbref) { - - if (path == null || dbref == null) { - return null; - } - - for (ObjectPathItem item : path) { - - Object object = item.getObject(); - - if (object == null) { - continue; - } - - if (item.getIdValue() == null) { - continue; - } - - if (dbref.getRef().equals(item.getCollection()) && dbref.getId().equals(item.getIdValue())) { - return object; - } - } - - return null; + return (T) (object != null ? object : read(type, readRef(dbref), path)); } /** @@ -1199,117 +1147,4 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App DBObject readRef(DBRef ref) { return ref.fetch(); } - - /** - * An immutable ordered set of target objects for {@link DBObject} to {@link Object} conversions. Object paths can be - * constructed by the {@link #toObjectPath(Object)} method and extended via {@link #push(Object)}. - * - * @author Thomas Darimont - * @since 1.6 - */ - static class ObjectPath implements Iterable { - - private final List items; - - /** - * Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} by adding the provided - * {@link ObjectPathItem} to it. - * - * @param parent - * @param item - */ - private ObjectPath(ObjectPath parent, ObjectPathItem item) { - - if (parent == null) { - this.items = Collections.singletonList(item); - return; - } - - List list = new ArrayList(parent.items); - list.add(item); - - this.items = list; - } - - /** - * Returns a copy of the {@link ObjectPath} with the given {@link Object} as current object. - * - * @param object - * @param entity - * @param idValue - * @return - */ - public ObjectPath push(Object object, MongoPersistentEntity entity, Object idValue) { - return new ObjectPath(this, new ObjectPathItem(object, idValue, entity != null ? entity.getCollection() : null)); - } - - public Object getRootObject() { - return getRoot().getObject(); - } - - private ObjectPathItem getRoot() { - return items.get(0); - } - - public Object getCurrentObject() { - return getCurrent().getObject(); - } - - private ObjectPathItem getCurrent() { - return items.get(items.size() - 1); - } - - @Override - public Iterator iterator() { - return items.iterator(); - } - - /** - * Returns the {@link ObjectPath} represented by the given {@link Object} or creates a new {@link ObjectPath} - * wrapping the given {@link Object}. - * - * @param object - * @return - */ - public static ObjectPath toObjectPath(Object object) { - return object instanceof ObjectPath ? ((ObjectPath) object) : new ObjectPath(null, new ObjectPathItem(object, - null, null)); - } - } - - /** - * @author Thomas Darimont - */ - static class ObjectPathItem { - - private final Object object; - private final Object idValue; - private final String collection; - - /** - * Creates a new {@link ObjectPathItem}. - * - * @param object - * @param idValue - * @param collection - */ - ObjectPathItem(Object object, Object idValue, String collection) { - - this.object = object; - this.idValue = idValue; - this.collection = collection; - } - - public Object getObject() { - return object; - } - - public Object getIdValue() { - return idValue; - } - - public String getCollection() { - return collection; - } - } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ObjectPath.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ObjectPath.java new file mode 100644 index 000000000..cd633956f --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ObjectPath.java @@ -0,0 +1,161 @@ +/* + * 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.mongodb.core.convert; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.util.Assert; + +import com.mongodb.DBObject; + +/** + * A path of objects nested into each other. The type allows access to all parent objects currently in creation even + * when resolving more nested objects. This allows to avoid re-resolving object instances that are logically equivalent + * to already resolved ones. + *

+ * An immutable ordered set of target objects for {@link DBObject} to {@link Object} conversions. Object paths can be + * constructed by the {@link #toObjectPath(Object)} method and extended via {@link #push(Object)}. + * + * @author Thomas Darimont + * @author Oliver Gierke + * @since 1.6 + */ +class ObjectPath { + + public static final ObjectPath ROOT = new ObjectPath(); + + private final List items; + + private ObjectPath() { + this.items = Collections.emptyList(); + } + + /** + * Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} by adding the provided + * {@link ObjectPathItem} to it. + * + * @param parent can be {@literal null}. + * @param item + */ + private ObjectPath(ObjectPath parent, ObjectPath.ObjectPathItem item) { + + List items = new ArrayList(parent.items); + items.add(item); + + this.items = Collections.unmodifiableList(items); + } + + /** + * Returns a copy of the {@link ObjectPath} with the given {@link Object} as current object. + * + * @param object must not be {@literal null}. + * @param entity must not be {@literal null}. + * @param id must not be {@literal null}. + * @return + */ + public ObjectPath push(Object object, MongoPersistentEntity entity, Object id) { + + Assert.notNull(object, "Object must not be null!"); + Assert.notNull(entity, "MongoPersistentEntity must not be null!"); + + ObjectPathItem item = new ObjectPathItem(object, id, entity.getCollection()); + return new ObjectPath(this, item); + } + + /** + * Returns the object with the given id and stored in the given collection if it's contained in the {@link ObjectPath} + * . + * + * @param id must not be {@literal null}. + * @param collection must not be {@literal null} or empty. + * @return + */ + public Object getPathItem(Object id, String collection) { + + Assert.notNull(id, "Id must not be null!"); + Assert.hasText(collection, "Collection name must not be null!"); + + for (ObjectPathItem item : items) { + + Object object = item.getObject(); + + if (object == null) { + continue; + } + + if (item.getIdValue() == null) { + continue; + } + + if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())) { + return object; + } + } + + return null; + } + + /** + * Returns the current object of the {@link ObjectPath} or {@literal null} if the path is empty. + * + * @return + */ + public Object getCurrentObject() { + return items.isEmpty() ? null : items.get(items.size() - 1).getObject(); + } + + /** + * An item in an {@link ObjectPath}. + * + * @author Thomas Darimont + * @author Oliver Gierke + */ + private static class ObjectPathItem { + + private final Object object; + private final Object idValue; + private final String collection; + + /** + * Creates a new {@link ObjectPathItem}. + * + * @param object + * @param idValue + * @param collection + */ + ObjectPathItem(Object object, Object idValue, String collection) { + + this.object = object; + this.idValue = idValue; + this.collection = collection; + } + + public Object getObject() { + return object; + } + + public Object getIdValue() { + return idValue; + } + + public String getCollection() { + return collection; + } + } +}