diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index bfa458b10..a1b95e5c1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -46,6 +46,7 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.authentication.UserCredentials; import org.springframework.data.convert.EntityReader; +import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.data.mapping.model.MappingException; @@ -932,7 +933,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { entityClass, null, queryObject); WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); if (LOGGER.isDebugEnabled()) { - LOGGER.debug("remove using query: " + queryObject + " in collection: " + collection.getName()); + LOGGER.debug("remove using query: " + dboq + " in collection: " + collection.getName()); } if (writeConcernToUse == null) { wr = collection.remove(dboq); @@ -1358,9 +1359,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { return; } + ConversionService conversionService = mongoConverter.getConversionService(); + BeanWrapper, Object> wrapper = BeanWrapper.create(savedObject, conversionService); + try { - BeanWrapper.create(savedObject, mongoConverter.getConversionService()).setProperty(idProp, id); - return; + + Object idValue = wrapper.getProperty(idProp); + + if (idValue != null) { + return; + } + + wrapper.setProperty(idProp, id); + } catch (IllegalAccessException e) { throw new MappingException(e.getMessage(), e); } catch (InvocationTargetException e) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SerializationUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SerializationUtils.java new file mode 100644 index 000000000..d21098843 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SerializationUtils.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012 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; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.core.convert.converter.Converter; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Utility methods for JSON serialization. + * + * @author Oliver Gierke + */ +public abstract class SerializationUtils { + + private SerializationUtils() { + + } + + /** + * Serializes the given object into pseudo-JSON meaning it's trying to create a JSON representation as far as possible + * but falling back to the given object's {@link Object#toString()} method if it's not serializable. Useful for + * printing raw {@link DBObject}s containing complex values before actually converting them into Mongo native types. + * + * @param value + * @return + */ + public static String serializeToJsonSafely(Object value) { + + if (value == null) { + return null; + } + + try { + return JSON.serialize(value); + } catch (Exception e) { + if (value instanceof Collection) { + return toString((Collection) value); + } else if (value instanceof Map) { + return toString((Map) value); + } else if (value instanceof DBObject) { + return toString(((DBObject) value).toMap()); + } else { + return String.format("{ $java : %s }", value.toString()); + } + } + } + + private static String toString(Map source) { + return iterableToDelimitedString(source.entrySet(), "{ ", " }", new Converter, Object>() { + public Object convert(Entry source) { + return String.format("\"%s\" : %s", source.getKey(), serializeToJsonSafely(source.getValue())); + } + }); + } + + private static String toString(Collection source) { + return iterableToDelimitedString(source, "[ ", " ]", new Converter() { + public Object convert(Object source) { + return serializeToJsonSafely(source); + } + }); + } + + /** + * Creates a string representation from the given {@link Iterable} prepending the postfix, applying the given + * {@link Converter} to each element before adding it to the result {@link String}, concatenating each element with + * {@literal ,} and applying the postfix. + * + * @param source + * @param prefix + * @param postfix + * @param transformer + * @return + */ + private static String iterableToDelimitedString(Iterable source, String prefix, String postfix, + Converter transformer) { + + StringBuilder builder = new StringBuilder(prefix); + Iterator iterator = source.iterator(); + + while (iterator.hasNext()) { + builder.append(transformer.convert(iterator.next())); + if (iterator.hasNext()) { + builder.append(", "); + } + } + + return builder.append(postfix).toString(); + } +} 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 6dde39e2f..b1ab52916 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 @@ -810,7 +810,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return null; } - Class target = conversions.getCustomWriteTarget(getClass()); + Class target = conversions.getCustomWriteTarget(obj.getClass()); if (target != null) { return conversionService.convert(obj, target); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java index 40565fd58..58798acaf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java @@ -15,6 +15,8 @@ */ package org.springframework.data.mongodb.core.query; +import static org.springframework.data.mongodb.core.SerializationUtils.*; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -140,4 +142,14 @@ public class Query { protected List getCriteria() { return new ArrayList(this.criteria.values()); } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("Query: %s, Fields: %s, Sort: %s", serializeToJsonSafely(getQueryObject()), + serializeToJsonSafely(getFieldsObject()), serializeToJsonSafely(getSortObject())); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index 8dfe9effa..e2653516f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -159,7 +159,7 @@ class MongoQueryCreator extends AbstractQueryCreator { QueryUtils.applySorting(query, sort); if (LOG.isDebugEnabled()) { - LOG.debug("Created query " + query.getQueryObject()); + LOG.debug("Created query " + query); } return query; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index 7c0f037f1..6cfe37802 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -1106,6 +1106,35 @@ public class MongoTemplateTests { assertThat(result.get(0).field, is("Beauford")); } + /** + * @see DATAMONGO-447 + */ + @Test + public void storesAndRemovesTypeWithComplexId() { + + MyId id = new MyId(); + id.first = "foo"; + id.second = "bar"; + + TypeWithMyId source = new TypeWithMyId(); + source.id = id; + + template.save(source); + template.remove(query(where("id").is(id)), TypeWithMyId.class); + } + + static class MyId { + + String first; + String second; + } + + static class TypeWithMyId { + + @Id + MyId id; + } + public static class Sample { @Id diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java new file mode 100644 index 000000000..883729bb6 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012 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; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.SerializationUtils.*; + +import java.util.Arrays; + +import org.hamcrest.Matcher; +import org.junit.Test; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Unit tests for {@link SerializationUtils}. + * + * @author Oliver Gierke + */ +public class SerializationUtilsUnitTests { + + @Test + public void writesSimpleDBObject() { + + DBObject dbObject = new BasicDBObject("foo", "bar"); + assertThat(serializeToJsonSafely(dbObject), is("{ \"foo\" : \"bar\"}")); + } + + @Test + public void writesComplexObjectAsPlainToString() { + + DBObject dbObject = new BasicDBObject("foo", new Complex()); + assertThat(serializeToJsonSafely(dbObject), + startsWith("{ \"foo\" : { $java : org.springframework.data.mongodb.core.SerializationUtilsUnitTests$Complex")); + } + + @Test + public void writesCollection() { + + DBObject dbObject = new BasicDBObject("foo", Arrays.asList("bar", new Complex())); + Matcher expectedOutput = allOf( + startsWith("{ \"foo\" : [ \"bar\", { $java : org.springframework.data.mongodb.core.SerializationUtilsUnitTests$Complex"), + endsWith(" } ] }")); + assertThat(serializeToJsonSafely(dbObject), is(expectedOutput)); + } + + static class Complex { + + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index fff8bf0dc..49b96072b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -128,6 +128,24 @@ public class MappingMongoConverterUnitTests { assertThat(result.birthDate, is(notNullValue())); } + @Test + public void convertsCustomTypeOnConvertToMongoType() { + + List> converters = new ArrayList>(); + converters.add(new LocalDateToDateConverter()); + converters.add(new DateToLocalDateConverter()); + + CustomConversions conversions = new CustomConversions(converters); + mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); + + converter = new MappingMongoConverter(factory, mappingContext); + converter.setCustomConversions(conversions); + converter.afterPropertiesSet(); + + LocalDate date = new LocalDate(); + converter.convertToMongoType(date); + } + /** * @see DATAMONGO-130 */