From b103e4eaf68ac85b9138a6df3057248ecd323cce Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 5 Aug 2015 12:53:34 +0200 Subject: [PATCH] DATAMONGO-1269 - Retain position parameter in property path. We now retain position parameters in paths used in queries when mapping the field name. This allows to map "list.1.name" to the name property of the first element in the list. The change also fixes a glitch in mapping java.util.Map like structures having numeric keys. Original pull request: #314. --- .../mongodb/core/convert/QueryMapper.java | 77 ++++++++++++- .../mongodb/core/convert/UpdateMapper.java | 103 +----------------- .../core/convert/QueryMapperUnitTests.java | 42 +++++++ 3 files changed, 121 insertions(+), 101 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index bd5a5ccfe..5ba6ca37c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -867,7 +867,7 @@ public class QueryMapper { * @return */ protected Converter getPropertyConverter() { - return PropertyToFieldNameConverter.INSTANCE; + return new PositionParameterRetainingPropertyKeyConverter(name); } /** @@ -881,6 +881,24 @@ public class QueryMapper { return new AssociationConverter(getAssociation()); } + /** + * @author Christoph Strobl + * @since 1.8 + */ + static class PositionParameterRetainingPropertyKeyConverter implements Converter { + + private final KeyMapper keyMapper; + + PositionParameterRetainingPropertyKeyConverter(String rawKey) { + this.keyMapper = new KeyMapper(rawKey); + } + + @Override + public String convert(MongoPersistentProperty source) { + return keyMapper.mapPropertyName(source); + } + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#getTypeHint() @@ -901,6 +919,63 @@ public class QueryMapper { return NESTED_DOCUMENT; } + + /** + * @author Christoph Strobl + * @since 1.8 + */ + static class KeyMapper { + + Iterator iterator; + + public KeyMapper(String key) { + + this.iterator = Arrays.asList(key.split("\\.")).iterator(); + this.iterator.next(); + } + + /** + * Maps the property name while retaining potential positional operator {@literal $}. + * + * @param property + * @return + */ + protected String mapPropertyName(MongoPersistentProperty property) { + + String mappedName = PropertyToFieldNameConverter.INSTANCE.convert(property); + + boolean inspect = iterator.hasNext(); + while (inspect) { + + String partial = iterator.next(); + + boolean isPositional = (isPositionalParameter(partial) && (property.isMap() || property.isCollectionLike() || property + .isArray())); + if (isPositional) { + mappedName += "." + partial; + } + + inspect = isPositional && iterator.hasNext(); + } + + return mappedName; + } + + boolean isPositionalParameter(String partial) { + + if (partial.equals("$")) { + return true; + } + + try { + Long.valueOf(partial); + return true; + } catch (NumberFormatException e) { + return false; + } + } + } + } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java index 091a600d7..ea60fbb7d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java @@ -15,8 +15,6 @@ */ package org.springframework.data.mongodb.core.convert; -import java.util.Arrays; -import java.util.Iterator; import java.util.Map.Entry; import org.springframework.core.convert.converter.Converter; @@ -24,13 +22,11 @@ import org.springframework.data.mapping.Association; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; -import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update.Modifier; import org.springframework.data.mongodb.core.query.Update.Modifiers; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; -import org.springframework.util.Assert; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; @@ -213,7 +209,7 @@ public class UpdateMapper extends QueryMapper { */ @Override protected Converter getPropertyConverter() { - return new UpdatePropertyConverter(key); + return new PositionParameterRetainingPropertyKeyConverter(key); } /* @@ -225,99 +221,6 @@ public class UpdateMapper extends QueryMapper { return new UpdateAssociationConverter(getAssociation(), key); } - /** - * Special mapper handling positional parameter {@literal $} within property names. - * - * @author Christoph Strobl - * @since 1.7 - */ - private static class UpdateKeyMapper { - - private final Iterator iterator; - - protected UpdateKeyMapper(String rawKey) { - - Assert.hasText(rawKey, "Key must not be null or empty!"); - - this.iterator = Arrays.asList(rawKey.split("\\.")).iterator(); - this.iterator.next(); - } - - /** - * Maps the property name while retaining potential positional operator {@literal $}. - * - * @param property - * @return - */ - protected String mapPropertyName(MongoPersistentProperty property) { - - String mappedName = PropertyToFieldNameConverter.INSTANCE.convert(property); - - boolean inspect = iterator.hasNext(); - while (inspect) { - - String partial = iterator.next(); - - boolean isPositional = isPositionalParameter(partial); - if (isPositional) { - mappedName += "." + partial; - } - - inspect = isPositional && iterator.hasNext(); - } - - return mappedName; - } - - boolean isPositionalParameter(String partial) { - - if (partial.equals("$")) { - return true; - } - - try { - Long.valueOf(partial); - return true; - } catch (NumberFormatException e) { - return false; - } - } - - } - - /** - * Special {@link Converter} for {@link MongoPersistentProperty} instances that will concatenate the {@literal $} - * contained in the source update key. - * - * @author Oliver Gierke - * @author Christoph Strobl - */ - private static class UpdatePropertyConverter implements Converter { - - private final UpdateKeyMapper mapper; - - /** - * Creates a new {@link UpdatePropertyConverter} with the given update key. - * - * @param updateKey must not be {@literal null} or empty. - */ - public UpdatePropertyConverter(String updateKey) { - - Assert.hasText(updateKey, "Update key must not be null or empty!"); - - this.mapper = new UpdateKeyMapper(updateKey); - } - - /* - * (non-Javadoc) - * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) - */ - @Override - public String convert(MongoPersistentProperty property) { - return mapper.mapPropertyName(property); - } - } - /** * {@link Converter} retaining positional parameter {@literal $} for {@link Association}s. * @@ -325,7 +228,7 @@ public class UpdateMapper extends QueryMapper { */ protected static class UpdateAssociationConverter extends AssociationConverter { - private final UpdateKeyMapper mapper; + private final KeyMapper mapper; /** * Creates a new {@link AssociationConverter} for the given {@link Association}. @@ -335,7 +238,7 @@ public class UpdateMapper extends QueryMapper { public UpdateAssociationConverter(Association association, String key) { super(association); - this.mapper = new UpdateKeyMapper(key); + this.mapper = new KeyMapper(key); } /* diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index b510d7145..8cf5852b7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -790,6 +790,34 @@ public class QueryMapperUnitTests { assertThat(dbo, isBsonObject().containing("geoJsonPoint.$geoIntersects.$geometry.coordinates")); } + /** + * @see DATAMONGO-1269 + */ + @Test + public void mappingShouldRetainNumericMapKey() { + + Query query = query(where("map.1.stringProperty").is("ba'alzamon")); + + DBObject dbo = mapper.getMappedObject(query.getQueryObject(), + context.getPersistentEntity(EntityWithComplexValueTypeMap.class)); + + assertThat(dbo.containsField("map.1.stringProperty"), is(true)); + } + + /** + * @see DATAMONGO-1269 + */ + @Test + public void mappingShouldRetainNumericPositionInList() { + + Query query = query(where("list.1.stringProperty").is("ba'alzamon")); + + DBObject dbo = mapper.getMappedObject(query.getQueryObject(), + context.getPersistentEntity(EntityWithComplexValueTypeList.class)); + + assertThat(dbo.containsField("list.1.stringProperty"), is(true)); + } + @Document public class Foo { @Id private ObjectId id; @@ -890,4 +918,18 @@ public class QueryMapperUnitTests { GeoJsonPoint geoJsonPoint; @Field("geoJsonPointWithNameViaFieldAnnotation") GeoJsonPoint namedGeoJsonPoint; } + + static class SimpeEntityWithoutId { + + String stringProperty; + Integer integerProperty; + } + + static class EntityWithComplexValueTypeMap { + Map map; + } + + static class EntityWithComplexValueTypeList { + List list; + } }