diff --git a/pom.xml b/pom.xml index c267f367b4..3dcbc3b244 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ Spring Data MongoDB Distribution Spring Data project for MongoDB http://www.springsource.org/spring-data/mongodb - 1.0.2.BUILD-SNAPSHOT + 1.0.5.BUILD-SNAPSHOT pom spring-data-mongodb @@ -151,7 +151,7 @@ version - ${pom.version} + ${project.version} diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index 901c60de6a..f5db5b081e 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -4,7 +4,7 @@ org.springframework.data spring-data-mongodb-parent - 1.0.2.BUILD-SNAPSHOT + 1.0.5.BUILD-SNAPSHOT ../spring-data-mongodb-parent/pom.xml spring-data-mongodb-cross-store @@ -68,24 +68,6 @@ log4j log4j - - - javax.mail - mail - - - javax.jms - jms - - - com.sun.jdmk - jmxtools - - - com.sun.jmx - jmxri - - runtime @@ -95,17 +77,6 @@ true - - org.mockito - mockito-all - test - - - - junit - junit - - org.aspectj aspectjrt diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index dc07dc40d3..c5bedea827 100644 --- a/spring-data-mongodb-log4j/pom.xml +++ b/spring-data-mongodb-log4j/pom.xml @@ -4,7 +4,7 @@ org.springframework.data spring-data-mongodb-parent - 1.0.2.BUILD-SNAPSHOT + 1.0.5.BUILD-SNAPSHOT ../spring-data-mongodb-parent/pom.xml spring-data-mongodb-log4j @@ -27,47 +27,9 @@ log4j log4j - - - javax.mail - mail - - - javax.jms - jms - - - com.sun.jdmk - jmxtools - - - com.sun.jmx - jmxri - - compile - - - org.mockito - mockito-all - test - - - - org.hamcrest - hamcrest-all - 1.1 - test - - - - junit - junit - test - - diff --git a/spring-data-mongodb-parent/pom.xml b/spring-data-mongodb-parent/pom.xml index b4b94171ec..6c44f8a7ad 100644 --- a/spring-data-mongodb-parent/pom.xml +++ b/spring-data-mongodb-parent/pom.xml @@ -6,14 +6,15 @@ Spring Data MongoDB Parent Spring Data project for MongoDB http://www.springsource.org/spring-data/mongodb - 1.0.2.BUILD-SNAPSHOT + 1.0.5.BUILD-SNAPSHOT pom UTF-8 - 4.8.1 - 1.2.15 + 4.10 + 1.2.16 1.8.4 + 1.2.1 1.5.10 1.6.1 3.0.7.RELEASE @@ -95,42 +96,6 @@ - - - strict - - false - - - - fast - - true - true - - - - staging - - - spring-site-staging - file:///${java.io.tmpdir}/spring-data/mongodb/docs - - - spring-milestone-staging - file:///${java.io.tmpdir}/spring-data/mongodb/milestone - - - spring-snapshot-staging - file:///${java.io.tmpdir}/spring-data/mongodb/snapshot - - - - - bootstrap - - - http://www.springsource.com/download/community @@ -187,6 +152,11 @@ spring-tx ${org.springframework.version.range} + + org.springframework + spring-context + ${org.springframework.version.range} + org.springframework spring-orm @@ -228,7 +198,7 @@ org.springframework.data spring-data-mongodb - 1.0.2.BUILD-SNAPSHOT + 1.0.5.BUILD-SNAPSHOT @@ -254,24 +224,6 @@ log4j log4j ${log4j.version} - - - javax.mail - mail - - - javax.jms - jms - - - com.sun.jdmk - jmxtools - - - com.sun.jmx - jmxri - - runtime @@ -284,14 +236,14 @@ org.mockito - mockito-all + mockito-core ${org.mockito.version} test junit - junit + junit-dep ${junit.version} test @@ -299,19 +251,34 @@ - + + log4j log4j ${log4j.version} test + + + org.hamcrest + hamcrest-library + ${hamcrest.version} + test + + + + org.mockito + mockito-core + test + + + + junit + junit-dep + test + + @@ -326,48 +293,11 @@ 3.1.0.RELEASE - - - ${project.basedir}/src/main/java - - **/* - - - **/*.java - - - - ${project.basedir}/src/main/resources - - **/* - - - - - - ${project.basedir}/src/test/java - - **/* - - - **/*.java - - - - ${project.basedir}/src/test/resources - - **/* - - - **/*.java - - - org.apache.maven.plugins maven-compiler-plugin - 2.3.2 + 2.5.1 1.5 1.5 @@ -387,13 +317,13 @@ org.apache.maven.plugins maven-surefire-plugin - 2.8 + 2.10 false **/*Tests.java - junit:junit + junit:junit-dep @@ -448,6 +378,10 @@ spring-plugins-release http://repo.springsource.org/plugins-release + + querydsl + http://source.mysema.com/maven2/releases + diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 51c7d91dc9..eb923eb582 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -4,7 +4,7 @@ org.springframework.data spring-data-mongodb-parent - 1.0.2.BUILD-SNAPSHOT + 1.0.5.BUILD-SNAPSHOT ../spring-data-mongodb-parent/pom.xml spring-data-mongodb @@ -18,10 +18,22 @@ + + org.springframework + spring-tx + + + org.springframework + spring-context + org.springframework spring-beans + + org.springframework + spring-core + org.springframework spring-expression @@ -57,19 +69,6 @@ querydsl-mongodb ${querydsl.version} true - - - com.google.code.morphia - morphia - - - - - - com.mysema.querydsl - querydsl-apt - ${querydsl.version} - provided @@ -79,25 +78,6 @@ - - org.mockito - mockito-all - test - - - - org.hamcrest - hamcrest-all - 1.1 - test - - - - junit - junit - test - - joda-time joda-time @@ -108,8 +88,8 @@ org.slf4j slf4j-api - test + org.slf4j jcl-over-slf4j @@ -138,7 +118,14 @@ com.mysema.maven maven-apt-plugin - 1.0.2 + 1.0.4 + + + com.mysema.querydsl + querydsl-apt + ${querydsl.version} + + generate-test-sources diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java index 1cddded9c7..a38566db48 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java @@ -17,7 +17,11 @@ import java.beans.PropertyEditorSupport; import java.net.UnknownHostException; +import java.util.HashSet; +import java.util.Set; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.util.StringUtils; import com.mongodb.ServerAddress; @@ -30,6 +34,8 @@ */ public class ServerAddressPropertyEditor extends PropertyEditorSupport { + private static final Log LOG = LogFactory.getLog(ServerAddressPropertyEditor.class); + /* * (non-Javadoc) * @see java.beans.PropertyEditorSupport#setAsText(java.lang.String) @@ -38,21 +44,49 @@ public class ServerAddressPropertyEditor extends PropertyEditorSupport { public void setAsText(String replicaSetString) { String[] replicaSetStringArray = StringUtils.commaDelimitedListToStringArray(replicaSetString); - ServerAddress[] serverAddresses = new ServerAddress[replicaSetStringArray.length]; + Set serverAddresses = new HashSet(replicaSetStringArray.length); - for (int i = 0; i < replicaSetStringArray.length; i++) { + for (String element : replicaSetStringArray) { - String[] hostAndPort = StringUtils.delimitedListToStringArray(replicaSetStringArray[i], ":"); + ServerAddress address = parseServerAddress(element); - try { - serverAddresses[i] = new ServerAddress(hostAndPort[0], Integer.parseInt(hostAndPort[1])); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Could not parse port " + hostAndPort[1], e); - } catch (UnknownHostException e) { - throw new IllegalArgumentException("Could not parse host " + hostAndPort[0], e); + if (address != null) { + serverAddresses.add(address); } } - setValue(serverAddresses); + if (serverAddresses.isEmpty()) { + throw new IllegalArgumentException( + "Could not resolve at least one server of the replica set configuration! Validate your config!"); + } + + setValue(serverAddresses.toArray(new ServerAddress[serverAddresses.size()])); + } + + /** + * Parses the given source into a {@link ServerAddress}. + * + * @param source + * @return the + */ + private ServerAddress parseServerAddress(String source) { + + String[] hostAndPort = StringUtils.delimitedListToStringArray(source.trim(), ":"); + + if (!StringUtils.hasText(source) || hostAndPort.length > 2) { + LOG.warn(String.format("Could not parse address source '%s'. Check your replica set configuration!", source)); + return null; + } + + try { + return hostAndPort.length == 1 ? new ServerAddress(hostAndPort[0]) : new ServerAddress(hostAndPort[0], + Integer.parseInt(hostAndPort[1])); + } catch (UnknownHostException e) { + LOG.warn(String.format("Could not parse host '%s'. Check your replica set configuration!", hostAndPort[0])); + } catch (NumberFormatException e) { + LOG.warn(String.format("Could not parse port '%s'. Check your replica set configuration!", hostAndPort[1])); + } + + return null; } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java index 6dfdc09916..679cece9c1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java @@ -15,14 +15,15 @@ */ package org.springframework.data.mongodb.core; -import com.mongodb.DB; -import com.mongodb.Mongo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.data.mongodb.CannotGetMongoDbConnectionException; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; +import com.mongodb.DB; +import com.mongodb.Mongo; + /** * Helper class featuring helper methods for internal MongoDb classes. *

@@ -78,7 +79,7 @@ public static DB doGetDB(Mongo mongo, String databaseName, String username, char DB db = null; if (TransactionSynchronizationManager.isSynchronizationActive() && dbHolder.doesNotHoldNonDefaultDB()) { // Spring transaction management is active -> - db = dbHolder.getDB(); + db = dbHolder.getDB(databaseName); if (db != null && !dbHolder.isSynchronizedWithTransaction()) { LOGGER.debug("Registering Spring transaction synchronization for existing Mongo DB"); TransactionSynchronizationManager.registerSynchronization(new MongoSynchronization(dbHolder, mongo)); @@ -96,10 +97,12 @@ public static DB doGetDB(Mongo mongo, String databaseName, String username, char boolean credentialsGiven = username != null && password != null; if (credentialsGiven && !db.isAuthenticated()) { // Note, can only authenticate once against the same com.mongodb.DB object. - if (!db.authenticate(username, password)) { - throw new CannotGetMongoDbConnectionException("Failed to authenticate to database [" + databaseName - + "], username = [" + username + "], password = [" + new String(password) + "]", databaseName, username, - password); + synchronized (db) { + if (!db.authenticate(username, password)) { + throw new CannotGetMongoDbConnectionException("Failed to authenticate to database [" + databaseName + + "], username = [" + username + "], password = [" + new String(password) + "]", databaseName, username, + password); + } } } @@ -110,9 +113,9 @@ public static DB doGetDB(Mongo mongo, String databaseName, String username, char LOGGER.debug("Registering Spring transaction synchronization for new Hibernate Session"); DbHolder holderToUse = dbHolder; if (holderToUse == null) { - holderToUse = new DbHolder(db); + holderToUse = new DbHolder(databaseName, db); } else { - holderToUse.addDB(db); + holderToUse.addDB(databaseName, db); } TransactionSynchronizationManager.registerSynchronization(new MongoSynchronization(holderToUse, mongo)); holderToUse.setSynchronizedWithTransaction(true); @@ -143,7 +146,7 @@ public static boolean isDBTransactional(DB db, Mongo mongo) { return false; } DbHolder dbHolder = (DbHolder) TransactionSynchronizationManager.getResource(mongo); - return (dbHolder != null && dbHolder.containsDB(db)); + return dbHolder != null && dbHolder.containsDB(db); } /** 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 0fcf67e29f..22b7ef043f 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 @@ -1,15 +1,11 @@ /* - * Copyright 2010-2011 the original author or authors. + * Copyright 2010-2012 the original author or authors. * - * Licensed under t -import org.springframework.util.ResourceUtils; - -import org.springframework.data.convert.EntityReader; -he Apache License, Version 2.0 (the "License"); + * 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 + * 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, @@ -20,6 +16,7 @@ package org.springframework.data.mongodb.core; import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.SerializationUtils.*; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -50,6 +47,7 @@ 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; @@ -108,6 +106,7 @@ * @author Graeme Rocher * @author Mark Pollack * @author Oliver Gierke + * @author Amol Nayak */ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -333,11 +332,12 @@ protected void executeQuery(Query query, String collectionName, DocumentCallback Assert.notNull(query); DBObject queryObject = query.getQueryObject(); + DBObject sortObject = query.getSortObject(); DBObject fieldsObject = query.getFieldsObject(); if (LOGGER.isDebugEnabled()) { - LOGGER.debug("find using query: " + queryObject + " fields: " + fieldsObject + " in collection: " - + collectionName); + LOGGER.debug(String.format("Executing query: %s sort: %s fields: %s in collection: $s", + serializeToJsonSafely(queryObject), sortObject, fieldsObject, collectionName)); } this.executeQueryInternal(new FindCallback(queryObject, fieldsObject), preparer, dch, collectionName); @@ -456,7 +456,7 @@ public T findOne(Query query, Class entityClass, String collectionName) { } else { query.limit(1); List results = find(query, entityClass, collectionName); - return (results.isEmpty() ? null : results.get(0)); + return results.isEmpty() ? null : results.get(0); } } @@ -732,11 +732,13 @@ public Object doInCollection(DBCollection collection) throws MongoException, Dat MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, entityClass, dbDoc, null); WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult wr; if (writeConcernToUse == null) { - collection.insert(dbDoc); + wr = collection.insert(dbDoc); } else { - collection.insert(dbDoc, writeConcernToUse); + wr = collection.insert(dbDoc, writeConcernToUse); } + handleAnyWriteResultErrors(wr, dbDoc, "insert"); return dbDoc.get(ID); } }); @@ -755,11 +757,13 @@ public Void doInCollection(DBCollection collection) throws MongoException, DataA MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null, null, null); WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult wr; if (writeConcernToUse == null) { - collection.insert(dbDocList); + wr = collection.insert(dbDocList); } else { - collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse); + wr = collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse); } + handleAnyWriteResultErrors(wr, null, "insert_list"); return null; } }); @@ -786,11 +790,13 @@ public Object doInCollection(DBCollection collection) throws MongoException, Dat MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass, dbDoc, null); WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult wr; if (writeConcernToUse == null) { - collection.save(dbDoc); + wr = collection.save(dbDoc); } else { - collection.save(dbDoc, writeConcernToUse); + wr = collection.save(dbDoc, writeConcernToUse); } + handleAnyWriteResultErrors(wr, dbDoc, "save"); return dbDoc.get(ID); } }); @@ -873,7 +879,7 @@ public void remove(Object object, String collection) { return; } - remove(getIdQueryFor(object), collection); + doRemove(collection, getIdQueryFor(object), object.getClass()); } /** @@ -938,7 +944,7 @@ public Void doInCollection(DBCollection collection) throws MongoException, DataA 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); @@ -996,26 +1002,15 @@ public MapReduceResults mapReduce(Query query, String inputCollectionName LOGGER.debug("Executing MapReduce on collection [" + command.getInput() + "], mapFunction [" + mapFunc + "], reduceFunction [" + reduceFunc + "]"); } - CommandResult commandResult = null; - try { - if (command.getOutputType() == MapReduceCommand.OutputType.INLINE) { - commandResult = executeCommand(commandObject, getDb().getOptions()); - } else { - commandResult = executeCommand(commandObject); - } - commandResult.throwOnError(); - } catch (RuntimeException ex) { - this.potentiallyConvertRuntimeException(ex); - } - String error = commandResult.getErrorMessage(); - if (error != null) { - throw new InvalidDataAccessApiUsageException("Command execution failed: Error [" + error + "], Command = " - + commandObject); - } + + CommandResult commandResult = command.getOutputType() == MapReduceCommand.OutputType.INLINE ? executeCommand( + commandObject, getDb().getOptions()) : executeCommand(commandObject); + handleCommandError(commandResult, commandObject); if (LOGGER.isDebugEnabled()) { - LOGGER.debug("MapReduce command result = [" + commandResult + "]"); + LOGGER.debug(String.format("MapReduce command result = [%s]", serializeToJsonSafely(commandObject))); } + MapReduceOutput mapReduceOutput = new MapReduceOutput(inputCollection, commandObject, commandResult); List mappedResults = new ArrayList(); DbObjectCallback callback = new ReadDbObjectCallback(mongoConverter, entityClass); @@ -1040,7 +1035,7 @@ public GroupByResults group(Criteria criteria, String inputCollectionName if (criteria == null) { dbo.put("cond", null); } else { - dbo.put("cond", criteria.getCriteriaObject()); + dbo.put("cond", mapper.getMappedObject(criteria.getCriteriaObject(), null)); } // If initial document was a JavaScript string, potentially loaded by Spring's Resource abstraction, load it and // convert to DBObject @@ -1066,21 +1061,12 @@ public GroupByResults group(Criteria criteria, String inputCollectionName DBObject commandObject = new BasicDBObject("group", dbo); if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing Group with DBObject [" + commandObject.toString() + "]"); - } - CommandResult commandResult = null; - try { - commandResult = executeCommand(commandObject, getDb().getOptions()); - commandResult.throwOnError(); - } catch (RuntimeException ex) { - this.potentiallyConvertRuntimeException(ex); - } - String error = commandResult.getErrorMessage(); - if (error != null) { - throw new InvalidDataAccessApiUsageException("Command execution failed: Error [" + error + "], Command = " - + commandObject); + LOGGER.debug(String.format("Executing Group with DBObject [%s]", serializeToJsonSafely(commandObject))); } + CommandResult commandResult = executeCommand(commandObject, getDb().getOptions()); + handleCommandError(commandResult, commandObject); + if (LOGGER.isDebugEnabled()) { LOGGER.debug("Group command result = [" + commandResult + "]"); } @@ -1251,11 +1237,14 @@ protected List doFind(String collectionName, DBObject query, DBObject fie protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, CursorPreparer preparer, DbObjectCallback objectCallback) { + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + if (LOGGER.isDebugEnabled()) { - LOGGER.debug("find using query: " + query + " fields: " + fields + " for class: " + entityClass - + " in collection: " + collectionName); + LOGGER.debug(String.format("find using query: %s fields: %s for class: %s in collection: %s", + serializeToJsonSafely(query), fields, entityClass, collectionName)); } + return executeFindMultiInternal(new FindCallback(mapper.getMappedObject(query, entity), fields), preparer, objectCallback, collectionName); } @@ -1337,13 +1326,15 @@ protected T doFindAndModify(String collectionName, DBObject query, DBObject updateObj.put(key, mongoConverter.convertToMongoType(updateObj.get(key))); } + DBObject mappedQuery = mapper.getMappedObject(query, entity); + if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findAndModify using query: " + query + " fields: " + fields + " sort: " + sort + " for class: " - + entityClass + " and update: " + updateObj + " in collection: " + collectionName); + LOGGER.debug("findAndModify using query: " + mappedQuery + " fields: " + fields + " sort: " + sort + + " for class: " + entityClass + " and update: " + updateObj + " in collection: " + collectionName); } - return executeFindOneInternal(new FindAndModifyCallback(mapper.getMappedObject(query, entity), fields, sort, - updateObj, options), new ReadDbObjectCallback(readerToUse, entityClass), collectionName); + return executeFindOneInternal(new FindAndModifyCallback(mappedQuery, fields, sort, updateObj, options), + new ReadDbObjectCallback(readerToUse, entityClass), collectionName); } /** @@ -1364,9 +1355,19 @@ protected void populateIdIfNecessary(Object savedObject, Object id) { 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, idProp.getType(), true); + + if (idValue != null) { + return; + } + + wrapper.setProperty(idProp, id); + } catch (IllegalAccessException e) { throw new MappingException(e.getMessage(), e); } catch (InvocationTargetException e) { @@ -1514,9 +1515,16 @@ protected void handleAnyWriteResultErrors(WriteResult wr, DBObject query, String String error = wr.getError(); if (error != null) { - - String message = String.format("Execution of %s%s failed: %s", operation, query == null ? "" : "' using '" - + query.toString() + "' query", error); + String message; + if (operation.equals("insert") || operation.equals("save")) { + // assuming the insert operations will begin with insert string + message = String.format("Insert/Save for %s failed: %s", query, error); + } else if (operation.equals("insert_list")) { + message = String.format("Insert list failed: %s", error); + } else { + message = String.format("Execution of %s%s failed: %s", operation, + query == null ? "" : "' using '" + query.toString() + "' query", error); + } if (WriteResultChecking.EXCEPTION == this.writeResultChecking) { throw new DataIntegrityViolationException(message); @@ -1539,6 +1547,27 @@ private RuntimeException potentiallyConvertRuntimeException(RuntimeException ex) return resolved == null ? ex : resolved; } + /** + * Inspects the given {@link CommandResult} for erros and potentially throws an + * {@link InvalidDataAccessApiUsageException} for that error. + * + * @param result must not be {@literal null}. + * @param source must not be {@literal null}. + */ + private void handleCommandError(CommandResult result, DBObject source) { + + try { + result.throwOnError(); + } catch (MongoException ex) { + + String error = result.getErrorMessage(); + error = error == null ? "NO MESSAGE" : error; + + throw new InvalidDataAccessApiUsageException("Command execution failed: Error [" + error + "], Command = " + + source, ex); + } + } + private static final MongoConverter getDefaultMongoConverter(MongoDbFactory factory) { MappingMongoConverter converter = new MappingMongoConverter(factory, new MongoMappingContext()); converter.afterPropertiesSet(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryMapper.java index cefb06821f..0aea53dd10 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryMapper.java @@ -86,6 +86,8 @@ public DBObject getMappedObject(DBObject query, MongoPersistentEntity entity) ids.add(convertId(id)); } valueDbo.put(inKey, ids.toArray(new Object[ids.size()])); + } else if (valueDbo.containsField("$ne")) { + valueDbo.put("$ne", convertId(valueDbo.get("$ne"))); } else { value = getMappedObject((DBObject) value, null); } @@ -99,11 +101,9 @@ public DBObject getMappedObject(DBObject query, MongoPersistentEntity entity) BasicBSONList newConditions = new BasicBSONList(); Iterator iter = conditions.iterator(); while (iter.hasNext()) { - newConditions.add(getMappedObject((DBObject) iter.next(), null)); + newConditions.add(getMappedObject((DBObject) iter.next(), entity)); } value = newConditions; - } else if (key.equals("$ne")) { - value = convertId(value); } newDbo.put(newKey, convertSimpleOrDBObject(value, null)); @@ -141,7 +141,11 @@ private Object convertSimpleOrDBObject(Object source, MongoPersistentEntity e */ private boolean isIdKey(String key, MongoPersistentEntity entity) { - if (null != entity && entity.getIdProperty() != null) { + if (entity == null) { + return false; + } + + if (entity.getIdProperty() != null) { MongoPersistentProperty idProperty = entity.getIdProperty(); return idProperty.getName().equals(key) || idProperty.getFieldName().equals(key); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java index 71f80b8ee1..27b1720e48 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java @@ -38,6 +38,8 @@ import org.springframework.data.mongodb.core.convert.MongoConverters.BigIntegerToStringConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigDecimalConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigIntegerConverter; +import org.springframework.data.mongodb.core.convert.MongoConverters.StringToURLConverter; +import org.springframework.data.mongodb.core.convert.MongoConverters.URLToStringConverter; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; import org.springframework.util.Assert; @@ -89,6 +91,8 @@ public CustomConversions(List converters) { this.converters.add(StringToBigDecimalConverter.INSTANCE); this.converters.add(BigIntegerToStringConverter.INSTANCE); this.converters.add(StringToBigIntegerConverter.INSTANCE); + this.converters.add(URLToStringConverter.INSTANCE); + this.converters.add(StringToURLConverter.INSTANCE); this.converters.addAll(converters); for (Object c : this.converters) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappedConstructor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappedConstructor.java index bc1f01152c..5f7e4b8549 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappedConstructor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappedConstructor.java @@ -18,6 +18,7 @@ import java.util.HashSet; import java.util.Set; +import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; @@ -25,6 +26,7 @@ import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.PersistentPropertyPath; +import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.util.TypeInformation; @@ -37,6 +39,9 @@ */ class MappedConstructor { + private static final String REJECT_CONSTRUCTOR = String.format("Entity doesn't have a usable constructor, either " + + "provide a custom converter or annotate a constructor with @%s!", PersistenceConstructor.class.getSimpleName()); + private final Set parameters; /** @@ -44,13 +49,19 @@ class MappedConstructor { * * @param entity must not be {@literal null}. * @param context must not be {@literal null}. + * @throws MappingException in case the {@link MongoPersistentEntity} handed in does not have a + * {@link PreferredConstructor}. */ public MappedConstructor(MongoPersistentEntity entity, - MappingContext, MongoPersistentProperty> context) { + MappingContext, MongoPersistentProperty> context) throws MappingException { Assert.notNull(entity); Assert.notNull(context); + if (entity.getPreferredConstructor() == null) { + throw new MappingException(REJECT_CONSTRUCTOR); + } + this.parameters = new HashSet(); for (Parameter parameter : entity.getPreferredConstructor().getParameters()) { @@ -83,6 +94,7 @@ public boolean isConstructorParameter(PersistentProperty property) { * * @param parameter must not be {@literal null}. * @return + * @throws MappingException in case no {@link MappedParameter} can be found for the given {@link Parameter}. */ public MappedParameter getFor(Parameter parameter) { @@ -92,7 +104,7 @@ public MappedParameter getFor(Parameter parameter) { } } - throw new IllegalStateException(String.format("Didn't find a MappedParameter for %s!", parameter.toString())); + throw new MappingException(String.format("Didn't find a MappedParameter for %s!", parameter.toString())); } /** @@ -165,4 +177,4 @@ public boolean maps(PersistentProperty property) { return this.property.equals(property); } } -} \ No newline at end of file +} 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 96b1c7160e..759d2f74ea 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 @@ -439,7 +439,6 @@ private static Collection asCollection(Object source) { * * @param collection must not be {@literal null}. * @param property must not be {@literal null}. - * * @return */ protected DBObject createCollection(Collection collection, MongoPersistentProperty property) { @@ -729,7 +728,7 @@ protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, St * @return the converted {@link Collections}, will never be {@literal null}. */ @SuppressWarnings("unchecked") - private Collection readCollectionOrArray(TypeInformation targetType, BasicDBList sourceValue) { + private Object readCollectionOrArray(TypeInformation targetType, BasicDBList sourceValue) { Assert.notNull(targetType); @@ -751,7 +750,7 @@ private Collection readCollectionOrArray(TypeInformation targetType, Basic } } - return items; + return getPotentiallyConvertedSimpleRead(items, targetType.getType()); } /** @@ -819,7 +818,7 @@ public Object convertToMongoType(Object obj) { 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/convert/MongoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java index 80e116ff83..07655c8440 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java @@ -17,8 +17,12 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; import org.bson.types.ObjectId; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.util.StringUtils; @@ -119,4 +123,28 @@ public BigInteger convert(String source) { return StringUtils.hasText(source) ? new BigInteger(source) : null; } } + + public static enum URLToStringConverter implements Converter { + INSTANCE; + + public String convert(URL source) { + return source == null ? null : source.toString(); + } + } + + public static enum StringToURLConverter implements Converter { + INSTANCE; + + private static final TypeDescriptor SOURCE = TypeDescriptor.valueOf(String.class); + private static final TypeDescriptor TARGET = TypeDescriptor.valueOf(URL.class); + + public URL convert(String source) { + + try { + return source == null ? null : new URL(source); + } catch (MalformedURLException e) { + throw new ConversionFailedException(SOURCE, TARGET, source, e); + } + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java index 19fdbd1716..b8acbf5c7d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright 2011-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 + * 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, @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.mongodb.core.index; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -24,14 +24,29 @@ /** * Mark a class to use compound indexes. * - * @author Jon Brisbin + * @author Jon Brisbin + * @author Oliver Gierke */ @Target({ ElementType.TYPE }) +@Documented @Retention(RetentionPolicy.RUNTIME) public @interface CompoundIndex { + /** + * The actual index definition in JSON format. The keys of the JSON document are the fields to be indexed, the values + * define the index direction (1 for ascending, -1 for descending). + * + * @return + */ String def(); + /** + * It does not actually make sense to use that attribute as the direction has to be defined in the {@link #def()} + * attribute actually. + * + * @return + */ + @Deprecated IndexDirection direction() default IndexDirection.ASCENDING; boolean unique() default false; @@ -40,8 +55,18 @@ boolean dropDups() default false; + /** + * The name of the index to be created. + * + * @return + */ String name() default ""; + /** + * The collection the index will be created in. Will default to the collection the annotated domain class will be + * stored in. + * + * @return + */ String collection() default ""; - } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java index a2f739fd3b..32cb73ae73 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright 2011-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 + * 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, @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.mongodb.core.index; import java.lang.reflect.Field; @@ -27,7 +26,6 @@ import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.event.MappingContextEvent; import org.springframework.data.mongodb.MongoDbFactory; -import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; @@ -39,10 +37,10 @@ import com.mongodb.util.JSON; /** - * Component that inspects {@link BasicMongoPersistentEntity} instances contained in the given - * {@link MongoMappingContext} for indexing metadata and ensures the indexes to be available. + * Component that inspects {@link MongoPersistentEntity} instances contained in the given {@link MongoMappingContext} + * for indexing metadata and ensures the indexes to be available. * - * @author Jon Brisbin + * @author Jon Brisbin * @author Oliver Gierke */ public class MongoPersistentEntityIndexCreator implements @@ -97,12 +95,12 @@ protected void checkForIndexes(final MongoPersistentEntity entity) { if (type.isAnnotationPresent(CompoundIndexes.class)) { CompoundIndexes indexes = type.getAnnotation(CompoundIndexes.class); for (CompoundIndex index : indexes.value()) { - String indexColl = index.collection(); - if ("".equals(indexColl)) { - indexColl = entity.getCollection(); - } - ensureIndex(indexColl, index.name(), index.def(), index.direction(), index.unique(), index.dropDups(), - index.sparse()); + + String indexColl = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection(); + DBObject definition = (DBObject) JSON.parse(index.def()); + + ensureIndex(indexColl, index.name(), definition, index.unique(), index.dropDups(), index.sparse()); + if (log.isDebugEnabled()) { log.debug("Created compound index " + index); } @@ -111,10 +109,14 @@ protected void checkForIndexes(final MongoPersistentEntity entity) { entity.doWithProperties(new PropertyHandler() { public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) { + Field field = persistentProperty.getField(); + if (field.isAnnotationPresent(Indexed.class)) { + Indexed index = field.getAnnotation(Indexed.class); String name = index.name(); + if (!StringUtils.hasText(name)) { name = persistentProperty.getFieldName(); } else { @@ -126,11 +128,17 @@ public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) } } } + String collection = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection(); - ensureIndex(collection, name, null, index.direction(), index.unique(), index.dropDups(), index.sparse()); + int direction = index.direction() == IndexDirection.ASCENDING ? 1 : -1; + DBObject definition = new BasicDBObject(persistentProperty.getFieldName(), direction); + + ensureIndex(collection, name, definition, index.unique(), index.dropDups(), index.sparse()); + if (log.isDebugEnabled()) { log.debug("Created property index " + index); } + } else if (field.isAnnotationPresent(GeoSpatialIndexed.class)) { GeoSpatialIndexed index = field.getAnnotation(GeoSpatialIndexed.class); @@ -155,21 +163,15 @@ public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) } } - protected void ensureIndex(String collection, final String name, final String def, final IndexDirection direction, - final boolean unique, final boolean dropDups, final boolean sparse) { - DBObject defObj; - if (null != def) { - defObj = (DBObject) JSON.parse(def); - } else { - defObj = new BasicDBObject(); - defObj.put(name, (direction == IndexDirection.ASCENDING ? 1 : -1)); - } + protected void ensureIndex(String collection, String name, DBObject indexDefinition, boolean unique, + boolean dropDups, boolean sparse) { + DBObject opts = new BasicDBObject(); opts.put("name", name); opts.put("dropDups", dropDups); opts.put("sparse", sparse); opts.put("unique", unique); - mongoDbFactory.getDb().getCollection(collection).ensureIndex(defObj, opts); - } + mongoDbFactory.getDb().getCollection(collection).ensureIndex(indexDefinition, opts); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java index adfbeaef96..34769d7ea4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-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. @@ -26,33 +26,39 @@ * Collects the results of performing a MapReduce operations. * * @author Mark Pollack - * - * @param The class in which the results are mapped onto, accessible via an interator. + * @author Oliver Gierke + * @param The class in which the results are mapped onto, accessible via an iterator. */ public class MapReduceResults implements Iterable { private final List mappedResults; - - private DBObject rawResults; - - private MapReduceTiming mapReduceTiming; - - private MapReduceCounts mapReduceCounts; - - private String outputCollection; - + private final DBObject rawResults; + private final String outputCollection; + private final MapReduceTiming mapReduceTiming; + private final MapReduceCounts mapReduceCounts; + + /** + * Creates a new {@link MapReduceResults} from the given mapped results and the raw one. + * + * @param mappedResults must not be {@literal null}. + * @param rawResults must not be {@literal null}. + */ public MapReduceResults(List mappedResults, DBObject rawResults) { + Assert.notNull(mappedResults); Assert.notNull(rawResults); + this.mappedResults = mappedResults; this.rawResults = rawResults; - parseTiming(rawResults); - parseCounts(rawResults); - if (rawResults.get("result") != null) { - this.outputCollection = (String) rawResults.get("result"); - } + this.mapReduceTiming = parseTiming(rawResults); + this.mapReduceCounts = parseCounts(rawResults); + this.outputCollection = parseOutputCollection(rawResults); } + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ public Iterator iterator() { return mappedResults.iterator(); } @@ -73,28 +79,71 @@ public DBObject getRawResults() { return rawResults; } - protected void parseTiming(DBObject rawResults) { + private MapReduceTiming parseTiming(DBObject rawResults) { + DBObject timing = (DBObject) rawResults.get("timing"); - if (timing != null) { - if (timing.get("mapTime") != null && timing.get("emitLoop") != null && timing.get("total") != null) { - mapReduceTiming = new MapReduceTiming((Long) timing.get("mapTime"), (Integer) timing.get("emitLoop"), - (Integer) timing.get("total")); - } - } else { - mapReduceTiming = new MapReduceTiming(-1, -1, -1); + + if (timing == null) { + return new MapReduceTiming(-1, -1, -1); + } + + if (timing.get("mapTime") != null && timing.get("emitLoop") != null && timing.get("total") != null) { + return new MapReduceTiming(getAsLong(timing, "mapTime"), getAsLong(timing, "emitLoop"), + getAsLong(timing, "total")); } + + return new MapReduceTiming(-1, -1, -1); + } + + /** + * Returns the value of the source's field with the given key as {@link Long}. + * + * @param source + * @param key + * @return + */ + private Long getAsLong(DBObject source, String key) { + Object raw = source.get(key); + return raw instanceof Long ? (Long) raw : (Integer) raw; } - protected void parseCounts(DBObject rawResults) { + /** + * Parses the raw {@link DBObject} result into a {@link MapReduceCounts} value object. + * + * @param rawResults + * @return + */ + private MapReduceCounts parseCounts(DBObject rawResults) { + DBObject counts = (DBObject) rawResults.get("counts"); - if (counts != null) { - if (counts.get("input") != null && counts.get("emit") != null && counts.get("output") != null) { - mapReduceCounts = new MapReduceCounts((Integer) counts.get("input"), (Integer) counts.get("emit"), - (Integer) counts.get("output")); - } - } else { - mapReduceCounts = new MapReduceCounts(-1, -1, -1); + + if (counts == null) { + return new MapReduceCounts(-1, -1, -1); + } + + if (counts.get("input") != null && counts.get("emit") != null && counts.get("output") != null) { + return new MapReduceCounts((Integer) counts.get("input"), (Integer) counts.get("emit"), + (Integer) counts.get("output")); } + + return new MapReduceCounts(-1, -1, -1); } + /** + * Parses the output collection from the raw {@link DBObject} result. + * + * @param rawResults + * @return + */ + private String parseOutputCollection(DBObject rawResults) { + + Object resultField = rawResults.get("result"); + + if (resultField == null) { + return null; + } + + return resultField instanceof DBObject ? ((DBObject) resultField).get("collection").toString() : resultField + .toString(); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java index 4ce58f14a9..e1ac523fd8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java @@ -15,6 +15,8 @@ */ package org.springframework.data.mongodb.core.query; +import static org.springframework.util.ObjectUtils.*; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -29,6 +31,7 @@ import org.springframework.data.mongodb.core.geo.Point; import org.springframework.data.mongodb.core.geo.Shape; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; @@ -429,11 +432,9 @@ public String getKey() { } /* - * (non-Javadoc) - * - * @see org.springframework.datastore.document.mongodb.query.Criteria# - * getCriteriaObject(java.lang.String) - */ + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.CriteriaDefinition#getCriteriaObject() + */ public DBObject getCriteriaObject() { if (this.criteriaChain.size() == 1) { return criteriaChain.get(0).getSingleCriteriaObject(); @@ -496,4 +497,82 @@ private void setValue(DBObject dbo, String key, Object value) { } } + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (obj == null || !getClass().equals(obj.getClass())) { + return false; + } + + Criteria that = (Criteria) obj; + + if (this.criteriaChain.size() != that.criteriaChain.size()) { + return false; + } + + for (int i = 0; i < this.criteriaChain.size(); i++) { + + Criteria left = this.criteriaChain.get(i); + Criteria right = that.criteriaChain.get(i); + + if (!simpleCriteriaEquals(left, right)) { + return false; + } + } + + return true; + } + + private boolean simpleCriteriaEquals(Criteria left, Criteria right) { + + boolean keyEqual = left.key == null ? right.key == null : left.key.equals(right.key); + boolean criteriaEqual = left.criteria.equals(right.criteria); + boolean valueEqual = isEqual(left.isValue, right.isValue); + + return keyEqual && criteriaEqual && valueEqual; + } + + /** + * Checks the given objects for equality. Handles {@link Pattern} and arrays correctly. + * + * @param left + * @param right + * @return + */ + private boolean isEqual(Object left, Object right) { + + if (left == null) { + return right == null; + } + + if (left instanceof Pattern) { + return right instanceof Pattern ? ((Pattern) left).pattern().equals(((Pattern) right).pattern()) : false; + } + + return ObjectUtils.nullSafeEquals(left, right); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + + int result = 17; + + result += nullSafeHashCode(key); + result += criteria.hashCode(); + result += nullSafeHashCode(isValue); + + return result; + } } 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 40565fd588..c92fa335d6 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,9 @@ */ package org.springframework.data.mongodb.core.query; +import static org.springframework.data.mongodb.core.query.SerializationUtils.*; +import static org.springframework.util.ObjectUtils.*; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -140,4 +143,60 @@ public String getHint() { 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())); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (obj == null || !getClass().equals(obj.getClass())) { + return false; + } + + Query that = (Query) obj; + + boolean criteriaEqual = this.criteria.equals(that.criteria); + boolean fieldsEqual = this.fieldSpec == null ? that.fieldSpec == null : this.fieldSpec.equals(that.fieldSpec); + boolean sortEqual = this.sort == null ? that.sort == null : this.sort.equals(that.sort); + boolean hintEqual = this.hint == null ? that.hint == null : this.hint.equals(that.hint); + boolean skipEqual = this.skip == that.skip; + boolean limitEqual = this.limit == that.limit; + + return criteriaEqual && fieldsEqual && sortEqual && hintEqual && skipEqual && limitEqual; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + + int result = 17; + + result += 31 * criteria.hashCode(); + result += 31 * nullSafeHashCode(fieldSpec); + result += 31 * nullSafeHashCode(sort); + result += 31 * nullSafeHashCode(hint); + result += 31 * skip; + result += 31 * limit; + + return result; + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java new file mode 100644 index 0000000000..b11948220c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/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.query; + +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/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java index ce7406cc28..a51f9a0c14 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2010-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. @@ -85,7 +85,7 @@ public Object execute(Object[] parameters) { } else if (method.isGeoNearQuery()) { return new GeoNearExecution(accessor).execute(query); } else if (method.isCollectionQuery()) { - return new CollectionExecution().execute(query); + return new CollectionExecution(accessor.getPageable()).execute(query); } else if (method.isPageQuery()) { return new PagedExecution(accessor.getPageable()).execute(query); } else { @@ -133,12 +133,23 @@ protected List readCollection(Query query) { */ class CollectionExecution extends Execution { + private final Pageable pageable; + + CollectionExecution(Pageable pageable) { + this.pageable = pageable; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query) */ @Override public Object execute(Query query) { + + if (pageable != null) { + query = applyPagination(query, pageable); + } + return readCollection(query); } } 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 c73aba066d..b3ea64e8df 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 @@ -116,13 +116,12 @@ protected Criteria and(Part part, Criteria base, Iterator iterator) { return create(part, iterator); } - PersistentPropertyPath path2 = context.getPersistentPropertyPath(part.getProperty()); - - Criteria criteria = from(part.getType(), - where(path2.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)), - (PotentiallyConvertingIterator) iterator); + PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty()); - return criteria.andOperator(criteria); + return new Criteria().andOperator( + base, + from(part.getType(), where(path.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)), + (PotentiallyConvertingIterator) iterator)); } /* @@ -157,7 +156,7 @@ protected Query complete(Criteria criteria, Sort sort) { QueryUtils.applySorting(query, sort); if (LOG.isDebugEnabled()) { - LOG.debug("Created query " + query.getQueryObject()); + LOG.debug("Created query " + query); } return query; @@ -269,4 +268,4 @@ private String toLikeRegex(String source) { return source.replaceAll("\\*", ".*"); } -} \ No newline at end of file +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepository.java index 8980ddabf1..4370c31f6a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepository.java @@ -28,6 +28,7 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.QueryMapper; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; @@ -235,6 +236,7 @@ static class SpringDataMongodbSerializer extends MongodbSerializer { private final MongoConverter converter; private final MappingContext, MongoPersistentProperty> mappingContext; + private final QueryMapper mapper; /** * Creates a new {@link SpringDataMongodbSerializer} for the given {@link MappingContext}. @@ -244,6 +246,7 @@ static class SpringDataMongodbSerializer extends MongodbSerializer { public SpringDataMongodbSerializer(MongoConverter converter) { this.mappingContext = converter.getMappingContext(); this.converter = converter; + this.mapper = new QueryMapper(converter); } @Override @@ -258,6 +261,10 @@ protected String getKeyForPath(Path expr, PathMetadata metadata) { @Override protected DBObject asDBObject(String key, Object value) { + if ("_id".equals(key)) { + return super.asDBObject(key, mapper.convertId(value)); + } + return super.asDBObject(key, value instanceof Pattern ? value : converter.convertToMongoType(value)); } } diff --git a/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.0.xsd b/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.0.xsd index 702c855b03..ff053fde54 100644 --- a/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.0.xsd +++ b/spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.0.xsd @@ -14,7 +14,7 @@ + schemaLocation="http://www.springframework.org/schema/data/repository/spring-repository-1.0.xsd" /> @@ -44,8 +44,9 @@ The name of the mongo definition (by default "mongoDbFactory").]]> - - The reference to a Mongo. Will default to 'mongo'. + diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java index 0affa2f55d..975812f774 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010 the original author or authors. + * Copyright 2011-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. @@ -16,6 +16,7 @@ package org.springframework.data.mongodb.config; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.util.List; @@ -29,6 +30,7 @@ import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.util.ReflectionTestUtils; import com.mongodb.CommandResult; import com.mongodb.Mongo; @@ -36,47 +38,45 @@ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -public class MongoNamespaceReplicaSetTests extends NamespaceTestSupport { +public class MongoNamespaceReplicaSetTests { @Autowired private ApplicationContext ctx; @Test + @SuppressWarnings("unchecked") public void testParsingMongoWithReplicaSets() throws Exception { + assertTrue(ctx.containsBean("replicaSetMongo")); MongoFactoryBean mfb = (MongoFactoryBean) ctx.getBean("&replicaSetMongo"); - List replicaSetSeeds = readField("replicaSetSeeds", mfb); - assertNotNull(replicaSetSeeds); - - assertEquals("127.0.0.1", replicaSetSeeds.get(0).getHost()); - assertEquals(10001, replicaSetSeeds.get(0).getPort()); - - assertEquals("localhost", replicaSetSeeds.get(1).getHost()); - assertEquals(10002, replicaSetSeeds.get(1).getPort()); + List replicaSetSeeds = (List) ReflectionTestUtils.getField(mfb, "replicaSetSeeds"); + assertThat(replicaSetSeeds, is(notNullValue())); + assertThat(replicaSetSeeds, hasItems(new ServerAddress("127.0.0.1", 10001), new ServerAddress("localhost", 10002))); } @Test + @SuppressWarnings("unchecked") public void testParsingWithPropertyPlaceHolder() throws Exception { + assertTrue(ctx.containsBean("manyReplicaSetMongo")); MongoFactoryBean mfb = (MongoFactoryBean) ctx.getBean("&manyReplicaSetMongo"); - List replicaSetSeeds = readField("replicaSetSeeds", mfb); - assertNotNull(replicaSetSeeds); - - assertEquals("192.168.174.130", replicaSetSeeds.get(0).getHost()); - assertEquals(27017, replicaSetSeeds.get(0).getPort()); - assertEquals("192.168.174.130", replicaSetSeeds.get(1).getHost()); - assertEquals(27018, replicaSetSeeds.get(1).getPort()); - assertEquals("192.168.174.130", replicaSetSeeds.get(2).getHost()); - assertEquals(27019, replicaSetSeeds.get(2).getPort()); + List replicaSetSeeds = (List) ReflectionTestUtils.getField(mfb, "replicaSetSeeds"); + assertThat(replicaSetSeeds, is(notNullValue())); + assertThat(replicaSetSeeds, hasSize(3)); + assertThat( + replicaSetSeeds, + hasItems(new ServerAddress("192.168.174.130", 27017), new ServerAddress("192.168.174.130", 27018), + new ServerAddress("192.168.174.130", 27019))); } @Test @Ignore("CI infrastructure does not yet support replica sets") public void testMongoWithReplicaSets() { + Mongo mongo = ctx.getBean(Mongo.class); assertEquals(2, mongo.getAllAddress().size()); List servers = mongo.getAllAddress(); @@ -88,6 +88,5 @@ public void testMongoWithReplicaSets() { MongoTemplate template = new MongoTemplate(mongo, "admin"); CommandResult result = template.executeCommand("{replSetGetStatus : 1}"); assertEquals("blort", result.getString("set")); - } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/NamespaceTestSupport.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/NamespaceTestSupport.java deleted file mode 100644 index bc5faf1fdb..0000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/NamespaceTestSupport.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2011 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.config; - -import java.lang.reflect.Field; - -public class NamespaceTestSupport { - - @SuppressWarnings({ "unchecked" }) - public static T readField(String name, Object target) throws Exception { - Field field = null; - Class clazz = target.getClass(); - do { - try { - field = clazz.getDeclaredField(name); - } catch (Exception ex) { - } - - clazz = clazz.getSuperclass(); - } while (field == null && !clazz.equals(Object.class)); - - if (field == null) - throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " - + target.getClass()); - field.setAccessible(true); - return (T) field.get(target); - } -} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java new file mode 100644 index 0000000000..3271b1da11 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java @@ -0,0 +1,80 @@ +/* + * 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.config; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Before; +import org.junit.Test; + +import com.mongodb.ServerAddress; + +/** + * Unit tests for {@link ServerAddressPropertyEditor}. + * + * @author Oliver Gierke + */ +public class ServerAddressPropertyEditorUnitTests { + + ServerAddressPropertyEditor editor; + + @Before + public void setUp() { + editor = new ServerAddressPropertyEditor(); + } + + /** + * @see DATAMONGO-454 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsAddressConfigWithoutASingleParsableServerAddress() { + + editor.setAsText("foo, bar"); + } + + /** + * @see DATAMONGO-454 + */ + @Test + public void skipsUnparsableAddressIfAtLeastOneIsParsable() throws UnknownHostException { + + editor.setAsText("foo, localhost"); + assertSingleAddressOfLocalhost(editor.getValue()); + } + + /** + * @see DATAMONGO-454 + */ + @Test + public void handlesEmptyAddressAsParseError() throws UnknownHostException { + + editor.setAsText(", localhost"); + assertSingleAddressOfLocalhost(editor.getValue()); + } + + private static void assertSingleAddressOfLocalhost(Object result) throws UnknownHostException { + + assertThat(result, is(instanceOf(ServerAddress[].class))); + Collection addresses = Arrays.asList((ServerAddress[]) result); + assertThat(addresses, hasSize(1)); + assertThat(addresses, hasItem(new ServerAddress("localhost"))); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectUtils.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectUtils.java new file mode 100644 index 0000000000..a9be3ba3af --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectUtils.java @@ -0,0 +1,81 @@ +/* + * 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 com.mongodb.BasicDBList; +import com.mongodb.DBObject; + +/** + * Helper classes to ease assertions on {@link DBObject}s. + * + * @author Oliver Gierke + */ +public abstract class DBObjectUtils { + + private DBObjectUtils() { + + } + + /** + * Expects the field with the given key to be not {@literal null} and a {@link DBObject} in turn and returns it. + * + * @param source the {@link DBObject} to lookup the nested one + * @param key the key of the field to lokup the nested {@link DBObject} + * @return + */ + public static DBObject getAsDBObject(DBObject source, String key) { + return getTypedValue(source, key, DBObject.class); + } + + /** + * Expects the field with the given key to be not {@literal null} and a {@link BasicDBList}. + * + * @param source the {@link DBObject} to lookup the {@link BasicDBList} in + * @param key the key of the field to find the {@link BasicDBList} in + * @return + */ + public static BasicDBList getAsDBList(DBObject source, String key) { + return getTypedValue(source, key, BasicDBList.class); + } + + /** + * Expects the list element with the given index to be a non-{@literal null} {@link DBObject} and returns it. + * + * @param source the {@link BasicDBList} to look up the {@link DBObject} element in + * @param index the index of the element expected to contain a {@link DBObject} + * @return + */ + public static DBObject getAsDBObject(BasicDBList source, int index) { + + assertThat(source.size(), greaterThanOrEqualTo(index + 1)); + Object value = source.get(index); + assertThat(value, is(instanceOf(DBObject.class))); + return (DBObject) value; + } + + @SuppressWarnings("unchecked") + private static T getTypedValue(DBObject source, String key, Class type) { + + Object value = source.get(key); + assertThat(value, is(notNullValue())); + assertThat(value, is(instanceOf(type))); + + return (T) value; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java new file mode 100644 index 0000000000..571e82b92b --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java @@ -0,0 +1,65 @@ +/* + * 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.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import com.mongodb.DB; +import com.mongodb.Mongo; + +/** + * Unit tests for {@link MongoDbUtils}. + * + * @author Oliver Gierke + */ +public class MongoDbUtilsUnitTests { + + Mongo mongo; + + @Before + public void setUp() throws Exception { + this.mongo = new Mongo(); + TransactionSynchronizationManager.initSynchronization(); + } + + @After + public void tearDown() { + + for (Object key : TransactionSynchronizationManager.getResourceMap().keySet()) { + TransactionSynchronizationManager.unbindResource(key); + } + + TransactionSynchronizationManager.clearSynchronization(); + } + + @Test + public void returnsNewInstanceForDifferentDatabaseName() { + + DB first = MongoDbUtils.getDB(mongo, "first"); + assertThat(first, is(notNullValue())); + assertThat(MongoDbUtils.getDB(mongo, "first"), is(first)); + + DB second = MongoDbUtils.getDB(mongo, "second"); + assertThat(second, is(not(first))); + assertThat(MongoDbUtils.getDB(mongo, "second"), is(second)); + } +} 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 045c9351b0..fd18323e8b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-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. @@ -39,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.mongodb.InvalidMongoDbApiUsageException; @@ -71,6 +72,7 @@ * * @author Oliver Gierke * @author Thomas Risberg + * @author Amol Nayak */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") @@ -160,6 +162,112 @@ public void bogusUpdateDoesNotTriggerException() throws Exception { mongoTemplate.updateFirst(q, u, Person.class); } + /** + * @see DATAMONGO-480 + */ + @Test + public void throwsExceptionForDuplicateIds() { + + MongoTemplate template = new MongoTemplate(factory); + template.setWriteResultChecking(WriteResultChecking.EXCEPTION); + + Person person = new Person(new ObjectId(), "Amol"); + person.setAge(28); + + template.insert(person); + + try { + template.insert(person); + fail("Expected DataIntegrityViolationException!"); + } catch (DataIntegrityViolationException e) { + assertThat(e.getMessage(), containsString("E11000 duplicate key error index: database.person.$_id_ dup key:")); + } + } + + /** + * @see DATAMONGO-480 + */ + @Test + public void throwsExceptionForUpdateWithInvalidPushOperator() { + + MongoTemplate template = new MongoTemplate(factory); + template.setWriteResultChecking(WriteResultChecking.EXCEPTION); + + ObjectId id = new ObjectId(); + Person person = new Person(id, "Amol"); + person.setAge(28); + + template.insert(person); + + try { + + Query query = new Query(Criteria.where("firstName").is("Amol")); + Update upd = new Update().push("age", 29); + template.updateFirst(query, upd, Person.class); + fail("Expected DataIntegrityViolationException!"); + + } catch (DataIntegrityViolationException e) { + + assertThat(e.getMessage(), + is("Execution of update with '{ \"$push\" : { \"age\" : 29}}'' using '{ \"firstName\" : \"Amol\"}' " + + "query failed: Cannot apply $push/$pushAll modifier to non-array")); + } + } + + /** + * @see DATAMONGO-480 + */ + @Test + public void throwsExceptionForIndexViolationIfConfigured() { + + MongoTemplate template = new MongoTemplate(factory); + template.setWriteResultChecking(WriteResultChecking.EXCEPTION); + template.indexOps(Person.class).ensureIndex(new Index().on("firstName", Order.DESCENDING).unique()); + + Person person = new Person(new ObjectId(), "Amol"); + person.setAge(28); + + template.save(person); + + person = new Person(new ObjectId(), "Amol"); + person.setAge(28); + + try { + template.save(person); + fail("Expected DataIntegrityViolationException!"); + } catch (DataIntegrityViolationException e) { + assertThat(e.getMessage(), + containsString("E11000 duplicate key error index: database.person.$firstName_-1 dup key:")); + } + } + + /** + * @see DATAMONGO-480 + */ + @Test + public void rejectsDuplicateIdInInsertAll() { + + MongoTemplate template = new MongoTemplate(factory); + template.setWriteResultChecking(WriteResultChecking.EXCEPTION); + + ObjectId id = new ObjectId(); + Person person = new Person(id, "Amol"); + person.setAge(28); + + List records = new ArrayList(); + records.add(person); + records.add(person); + + try { + template.insertAll(records); + fail("Expected DataIntegrityViolationException!"); + } catch (DataIntegrityViolationException e) { + assertThat( + e.getMessage(), + startsWith("Insert list failed: E11000 duplicate key error index: database.person.$_id_ dup key: { : ObjectId")); + } + } + @Test public void testEnsureIndex() throws Exception { @@ -190,7 +298,7 @@ public void testEnsureIndex() throws Exception { assertThat(dropDupes, is(true)); List indexInfoList = template.indexOps(Person.class).getIndexInfo(); - System.out.println(indexInfoList); + assertThat(indexInfoList.size(), is(2)); IndexInfo ii = indexInfoList.get(1); assertThat(ii.isUnique(), is(true)); @@ -907,8 +1015,6 @@ public void testWriteConcernResolver() { assertThat(lastMongoAction.getEntityClass().toString(), is(PersonWithIdPropertyOfTypeObjectId.class.toString())); assertThat(lastMongoAction.getMongoActionOperation(), is(MongoActionOperation.UPDATE)); assertThat(lastMongoAction.getQuery(), equalTo(q.getQueryObject())); - assertThat(lastMongoAction.getDocument(), equalTo(u.getUpdateObject())); - } private class FsyncSafeWriteConcernResolver implements WriteConcernResolver { @@ -934,7 +1040,7 @@ public void updatesDBRefsCorrectly() { DBRef first = new DBRef(factory.getDb(), "foo", new ObjectId()); DBRef second = new DBRef(factory.getDb(), "bar", new ObjectId()); - template.updateFirst(null, Update.update("dbRefs", Arrays.asList(first, second)), ClassWithDBRefs.class); + template.updateFirst(null, update("dbRefs", Arrays.asList(first, second)), ClassWithDBRefs.class); } class ClassWithDBRefs { @@ -1097,12 +1203,59 @@ public void executesQueryWithNegatedRegexCorrectly() { template.save(second); Query query = query(where("field").not().regex("Matthews")); - System.out.println(query.getQueryObject()); + List result = template.find(query, Sample.class); assertThat(result.size(), is(1)); 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); + } + + /** + * @see DATAMONGO-539 + */ + @Test + public void removesObjectFromExplicitCollection() { + + String collectionName = "explicit"; + template.remove(new Query(), collectionName); + + PersonWithConvertedId person = new PersonWithConvertedId(); + person.name = "Dave"; + template.save(person, collectionName); + assertThat(template.findAll(PersonWithConvertedId.class, collectionName).isEmpty(), is(false)); + + template.remove(person, collectionName); + assertThat(template.findAll(PersonWithConvertedId.class, collectionName).isEmpty(), is(true)); + } + + static class MyId { + + String first; + String second; + } + + static class TypeWithMyId { + + @Id + MyId id; + } + public static class Sample { @Id @@ -1120,6 +1273,12 @@ static class TestClass { } } + static class PersonWithConvertedId { + + String id; + String name; + } + static enum DateTimeToDateConverter implements Converter { INSTANCE; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java index 42853ab727..23413fbfa9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.*; import java.math.BigInteger; +import java.util.regex.Pattern; import org.bson.types.ObjectId; import org.junit.Before; @@ -136,6 +137,31 @@ public void autogeneratesIdForEntityWithAutogeneratableId() { assertThat(entity.id, is(notNullValue())); } + /** + * @see DATAMONGO-474 + */ + @Test + public void setsUnpopulatedIdField() { + + NotAutogenerateableId entity = new NotAutogenerateableId(); + + template.populateIdIfNecessary(entity, 5); + assertThat(entity.id, is(5)); + } + + /** + * @see DATAMONGO-474 + */ + @Test + public void doesNotSetAlreadyPopulatedId() { + + NotAutogenerateableId entity = new NotAutogenerateableId(); + entity.id = 5; + + template.populateIdIfNecessary(entity, 7); + assertThat(entity.id, is(5)); + } + class AutogenerateableId { @Id @@ -146,6 +172,10 @@ class NotAutogenerateableId { @Id Integer id; + + public Pattern getId() { + return Pattern.compile("."); + } } /** @@ -161,9 +191,10 @@ private MongoTemplate mockOutGetDb() { return template; } - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.core.MongoOperationsUnitTests#getOperations() - */ + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperationsUnitTests#getOperationsForExceptionHandling() + */ @Override protected MongoOperations getOperationsForExceptionHandling() { MongoTemplate template = spy(this.template); @@ -171,9 +202,10 @@ protected MongoOperations getOperationsForExceptionHandling() { return template; } - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.core.MongoOperationsUnitTests#getOperations() - */ + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperationsUnitTests#getOperations() + */ @Override protected MongoOperations getOperations() { return this.template; 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 0000000000..78ef19c037 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java @@ -0,0 +1,67 @@ +/* + * 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.query.SerializationUtils.*; + +import java.util.Arrays; + +import org.hamcrest.Matcher; +import org.junit.Test; +import org.springframework.data.mongodb.core.query.SerializationUtils; + +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/CustomConversionsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConversionsUnitTests.java index 8660c47cfa..9fae44fe68 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConversionsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConversionsUnitTests.java @@ -3,6 +3,7 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.net.URL; import java.text.DateFormat; import java.text.Format; import java.util.Arrays; @@ -152,6 +153,25 @@ public void considersBinaryASimpleType() { assertThat(conversions.isSimpleType(Binary.class), is(true)); } + /** + * @see DATAMONGO-462 + */ + @Test + public void hasWriteConverterForURL() { + + CustomConversions conversions = new CustomConversions(); + assertThat(conversions.hasCustomWriteTarget(URL.class), is(true)); + } + + /** + * @see DATAMONGO-462 + */ + @Test + public void readTargetForURL() { + CustomConversions conversions = new CustomConversions(); + assertThat(conversions.hasCustomReadTarget(String.class, URL.class), is(true)); + } + enum FormatToStringConverter implements Converter { INSTANCE; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappedConstructorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappedConstructorUnitTests.java new file mode 100644 index 0000000000..e44b23ecce --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappedConstructorUnitTests.java @@ -0,0 +1,44 @@ +/* + * 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.convert; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; + +/** + * Unit tests for {@link MappedConstructor}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class MappedConstructorUnitTests { + + @Mock + MongoPersistentEntity entity; + @Mock + MappingContext, MongoPersistentProperty> mappingContext; + + @Test(expected = MappingException.class) + public void rejectsEntityWithoutPersistenceConstructor() { + new MappedConstructor(entity, mappingContext); + } +} 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 41e051c4c7..5c31cec921 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 @@ -21,6 +21,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -114,12 +115,30 @@ public void convertsJodaTimeTypesCorrectly() { DBObject dbObject = new BasicDBObject(); converter.write(person, dbObject); - assertThat(dbObject.get("birthDate"), is(Date.class)); + assertThat(dbObject.get("birthDate"), is(instanceOf(Date.class))); Person result = converter.read(Person.class, dbObject); 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 */ @@ -157,7 +176,7 @@ public void usesDocumentsStoredTypeIfSubtypeOfRequest() { dbObject.put("birthDate", new LocalDate()); dbObject.put(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, Person.class.getName()); - assertThat(converter.read(Contact.class, dbObject), is(Person.class)); + assertThat(converter.read(Contact.class, dbObject), is(instanceOf(Person.class))); } /** @@ -170,7 +189,7 @@ public void ignoresDocumentsStoredTypeIfCompletelyDifferentTypeRequested() { dbObject.put("birthDate", new LocalDate()); dbObject.put(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, Person.class.getName()); - assertThat(converter.read(BirthDateContainer.class, dbObject), is(BirthDateContainer.class)); + assertThat(converter.read(BirthDateContainer.class, dbObject), is(instanceOf(BirthDateContainer.class))); } @Test @@ -198,7 +217,7 @@ public void writesEnumsCorrectly() { DBObject result = new BasicDBObject(); converter.write(value, result); - assertThat(result.get("sampleEnum"), is(String.class)); + assertThat(result.get("sampleEnum"), is(instanceOf(String.class))); assertThat(result.get("sampleEnum").toString(), is("FIRST")); } @@ -214,7 +233,7 @@ public void writesEnumCollectionCorrectly() { DBObject result = new BasicDBObject(); converter.write(value, result); - assertThat(result.get("enums"), is(BasicDBList.class)); + assertThat(result.get("enums"), is(instanceOf(BasicDBList.class))); BasicDBList enums = (BasicDBList) result.get("enums"); assertThat(enums.size(), is(1)); @@ -244,7 +263,7 @@ public void readsEnumCollectionsCorrectly() { ClassWithEnumProperty result = converter.read(ClassWithEnumProperty.class, dbObject); - assertThat(result.enums, is(List.class)); + assertThat(result.enums, is(instanceOf(List.class))); assertThat(result.enums.size(), is(1)); assertThat(result.enums, hasItem(SampleEnum.FIRST)); } @@ -309,7 +328,7 @@ public void writesCollectionWithInterfaceCorrectly() { converter.write(wrapper, dbObject); Object result = dbObject.get("contacts"); - assertThat(result, is(BasicDBList.class)); + assertThat(result, is(instanceOf(BasicDBList.class))); BasicDBList contacts = (BasicDBList) result; DBObject personDbObject = (DBObject) contacts.get(0); assertThat(personDbObject.get("foo").toString(), is("Oliver")); @@ -332,7 +351,7 @@ public void readsCollectionWithInterfaceCorrectly() { assertThat(result.contacts, is(notNullValue())); assertThat(result.contacts.size(), is(1)); Contact contact = result.contacts.get(0); - assertThat(contact, is(Person.class)); + assertThat(contact, is(instanceOf(Person.class))); assertThat(((Person) contact).firstname, is("Oliver")); } @@ -346,7 +365,7 @@ public void convertsLocalesOutOfTheBox() { converter.write(wrapper, dbObject); Object localeField = dbObject.get("locale"); - assertThat(localeField, is(String.class)); + assertThat(localeField, is(instanceOf(String.class))); assertThat((String) localeField, is("en_US")); LocaleWrapper read = converter.read(LocaleWrapper.class, dbObject); @@ -454,13 +473,13 @@ public void convertsObjectIdStringsToObjectIdCorrectly() { DBObject dbo1 = new BasicDBObject(); converter.write(p1, dbo1); - assertThat(dbo1.get("_id"), is(String.class)); + assertThat(dbo1.get("_id"), is(instanceOf(String.class))); PersonPojoStringId p2 = new PersonPojoStringId(new ObjectId().toString(), "Text-1"); DBObject dbo2 = new BasicDBObject(); converter.write(p2, dbo2); - assertThat(dbo2.get("_id"), is(ObjectId.class)); + assertThat(dbo2.get("_id"), is(instanceOf(ObjectId.class))); } /** @@ -474,8 +493,8 @@ public void convertsCustomEmptyMapCorrectly() { ClassWithSortedMap result = converter.read(ClassWithSortedMap.class, wrapper); - assertThat(result, is(ClassWithSortedMap.class)); - assertThat(result.map, is(SortedMap.class)); + assertThat(result, is(instanceOf(ClassWithSortedMap.class))); + assertThat(result.map, is(instanceOf(SortedMap.class))); } /** @@ -741,7 +760,7 @@ public void writesPlainMapOfCollectionsCorrectly() { assertThat(result.containsField("Foo"), is(true)); assertThat(result.get("Foo"), is(notNullValue())); - assertThat(result.get("Foo"), is(BasicDBList.class)); + assertThat(result.get("Foo"), is(instanceOf(BasicDBList.class))); BasicDBList list = (BasicDBList) result.get("Foo"); @@ -792,11 +811,11 @@ public void writesArraysAsMapValuesCorrectly() { converter.write(wrapper, result); Object mapObject = result.get("mapOfObjects"); - assertThat(mapObject, is(BasicDBObject.class)); + assertThat(mapObject, is(instanceOf(BasicDBObject.class))); DBObject map = (DBObject) mapObject; Object valueObject = map.get("foo"); - assertThat(valueObject, is(BasicDBList.class)); + assertThat(valueObject, is(instanceOf(BasicDBList.class))); List list = (List) valueObject; assertThat(list.size(), is(1)); @@ -887,7 +906,7 @@ public void writesNullValuesForCollection() { converter.write(wrapper, result); Object contacts = result.get("contacts"); - assertThat(contacts, is(Collection.class)); + assertThat(contacts, is(instanceOf(Collection.class))); assertThat(((Collection) contacts).size(), is(2)); assertThat(((Collection) contacts), hasItem(nullValue())); } @@ -950,7 +969,7 @@ public void writesListForObjectPropertyCorrectly() { Item read = converter.read(Item.class, result); assertThat(read.attributes.size(), is(1)); assertThat(read.attributes.get(0).key, is(attribute.key)); - assertThat(read.attributes.get(0).value, is(Collection.class)); + assertThat(read.attributes.get(0).value, is(instanceOf(Collection.class))); @SuppressWarnings("unchecked") Collection values = (Collection) read.attributes.get(0).value; @@ -1009,11 +1028,92 @@ public void convertsSetToBasicDBList() { address.street = "Foo"; Object result = converter.convertToMongoType(Collections.singleton(address)); - assertThat(result, is(BasicDBList.class)); + assertThat(result, is(instanceOf(BasicDBList.class))); Set readResult = converter.read(Set.class, (BasicDBList) result); assertThat(readResult.size(), is(1)); - assertThat(readResult.iterator().next(), is(Map.class)); + assertThat(readResult.iterator().next(), is(instanceOf(Map.class))); + } + + /** + * @see DATAMONGO-462 + */ + @Test + public void readsURLsAsStringsByDefault() throws Exception { + DBObject dbObject = new BasicDBObject("url", new URL("http://springsource.org")); + URLWrapper result = converter.read(URLWrapper.class, dbObject); + assertThat(result.url, is(new URL("http://springsource.org"))); + } + + /** + * @see DATAMONGO-462 + */ + @Test + public void writesURLsAsStringsByDefault() throws Exception { + + URLWrapper wrapper = new URLWrapper(); + wrapper.url = new URL("http://springsource.org"); + DBObject sink = new BasicDBObject(); + + converter.write(wrapper, sink); + assertThat(sink.get("url"), is((Object) "http://springsource.org")); + } + + /** + * @see DATAMONGO-485 + */ + @Test + public void writesComplexIdCorrectly() { + + ComplexId id = new ComplexId(); + id.innerId = 4711L; + + ClassWithComplexId entity = new ClassWithComplexId(); + entity.complexId = id; + + DBObject dbObject = new BasicDBObject(); + converter.write(entity, dbObject); + + Object idField = dbObject.get("_id"); + assertThat(idField, is(notNullValue())); + assertThat(idField, is(instanceOf(DBObject.class))); + assertThat(((DBObject) idField).get("innerId"), is((Object) 4711L)); + } + + /** + * @see DATAMONGO-485 + */ + @Test + public void readsComplexIdCorrectly() { + + DBObject innerId = new BasicDBObject("innerId", 4711L); + DBObject entity = new BasicDBObject("_id", innerId); + + ClassWithComplexId result = converter.read(ClassWithComplexId.class, entity); + + assertThat(result.complexId, is(notNullValue())); + assertThat(result.complexId.innerId, is(4711L)); + } + + /** + * @see DATAMONGO-489 + */ + @Test + public void readsArraysAsMapValuesCorrectly() { + + BasicDBList list = new BasicDBList(); + list.add("Foo"); + list.add("Bar"); + + DBObject map = new BasicDBObject("key", list); + DBObject wrapper = new BasicDBObject("mapOfStrings", map); + + ClassWithMapProperty result = converter.read(ClassWithMapProperty.class, wrapper); + assertThat(result.mapOfStrings, is(notNullValue())); + + String[] values = result.mapOfStrings.get("key"); + assertThat(values, is(notNullValue())); + assertThat(values, is(arrayWithSize(2))); } static class GenericType { @@ -1077,6 +1177,7 @@ static class ClassWithMapProperty { Map map; Map> mapOfLists; Map mapOfObjects; + Map mapOfStrings; } static class ClassWithNestedMaps { @@ -1147,6 +1248,20 @@ static class Attribute { Object value; } + static class URLWrapper { + URL url; + } + + static class ClassWithComplexId { + + @Id + ComplexId complexId; + } + + static class ComplexId { + Long innerId; + } + private class LocalDateToDateConverter implements Converter { public Date convert(LocalDate source) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java new file mode 100644 index 0000000000..b8c8c65332 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java @@ -0,0 +1,82 @@ +/* + * 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.index; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.Collections; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; + +import com.mongodb.DBObject; + +/** + * Unit tests for {@link MongoPersistentEntityIndexCreator}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class MongoPersistentEntityIndexCreatorUnitTests { + + @Mock + MongoDbFactory factory; + + @Test + public void buildsIndexDefinitionUsingFieldName() { + + MongoMappingContext mappingContext = new MongoMappingContext(); + mappingContext.setInitialEntitySet(Collections.singleton(Person.class)); + mappingContext.afterPropertiesSet(); + + DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory); + + assertThat(creator.indexDefinition, is(notNullValue())); + assertThat(creator.indexDefinition.keySet(), hasItem("fieldname")); + assertThat(creator.name, is("indexName")); + } + + static class Person { + + @Indexed(name = "indexName") + @Field("fieldname") + String field; + } + + static class DummyMongoPersistentEntityIndexCreator extends MongoPersistentEntityIndexCreator { + + DBObject indexDefinition; + String name; + + public DummyMongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) { + super(mappingContext, mongoDbFactory); + } + + @Override + protected void ensureIndex(String collection, String name, DBObject indexDefinition, boolean unique, + boolean dropDups, boolean sparse) { + + this.name = name; + this.indexDefinition = indexDefinition; + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java new file mode 100644 index 0000000000..ac7b29d61c --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java @@ -0,0 +1,72 @@ +/* + * 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.mapreduce; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.Collections; + +import org.junit.Test; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Unit tests for {@link MapReduceResults}. + * + * @author Oliver Gierke + */ +public class MapReduceResultsUnitTests { + + /** + * @see DATAMONGO-428 + */ + @Test + public void resolvesOutputCollectionForPlainResult() { + + DBObject rawResult = new BasicDBObject("result", "FOO"); + MapReduceResults results = new MapReduceResults(Collections.emptyList(), rawResult); + + assertThat(results.getOutputCollection(), is("FOO")); + } + + /** + * @see DATAMONGO-428 + */ + @Test + public void resolvesOutputCollectionForDBObjectResult() { + + DBObject rawResult = new BasicDBObject("result", new BasicDBObject("collection", "FOO")); + MapReduceResults results = new MapReduceResults(Collections.emptyList(), rawResult); + + assertThat(results.getOutputCollection(), is("FOO")); + } + + /** + * @see DATAMONGO-378 + */ + @Test + public void handlesLongTotalInResult() { + + DBObject inner = new BasicDBObject("total", 1L); + inner.put("mapTime", 1L); + inner.put("emitLoop", 1); + + DBObject source = new BasicDBObject("timing", inner); + new MapReduceResults(Collections.emptyList(), source); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java index cfe0cc1609..8f2b8fc79b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java @@ -15,10 +15,10 @@ */ package org.springframework.data.mongodb.core.query; -import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + import org.junit.Test; -import org.springframework.data.mongodb.core.query.Criteria; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; @@ -51,4 +51,14 @@ public void testChainedCriteria() { Criteria c = new Criteria("name").is("Bubba").and("age").lt(21); assertEquals("{ \"name\" : \"Bubba\" , \"age\" : { \"$lt\" : 21}}", c.getCriteriaObject().toString()); } + + @Test + public void equalIfCriteriaMatches() { + + Criteria left = new Criteria("name").is("Foo").and("lastname").is("Bar"); + Criteria right = new Criteria("name").is("Bar").and("lastname").is("Bar"); + + assertThat(left, is(not(right))); + assertThat(right, is(not(left))); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryMapperUnitTests.java index df042827d0..e631f843e6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryMapperUnitTests.java @@ -19,9 +19,12 @@ import static org.junit.Assert.*; import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Query.*; +import static org.springframework.data.mongodb.core.DBObjectUtils.*; import java.math.BigInteger; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.bson.types.ObjectId; import org.junit.Before; @@ -48,7 +51,6 @@ * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) -@SuppressWarnings("unused") public class QueryMapperUnitTests { QueryMapper mapper; @@ -83,15 +85,15 @@ public void translatesIdPropertyIntoIdKey() { public void convertsStringIntoObjectId() { DBObject query = new BasicDBObject("_id", new ObjectId().toString()); - DBObject result = mapper.getMappedObject(query, null); - assertThat(result.get("_id"), is(ObjectId.class)); + DBObject result = mapper.getMappedObject(query, context.getPersistentEntity(IdWrapper.class)); + assertThat(result.get("_id"), is(instanceOf(ObjectId.class))); } @Test public void handlesBigIntegerIdsCorrectly() { DBObject dbObject = new BasicDBObject("id", new BigInteger("1")); - DBObject result = mapper.getMappedObject(dbObject, null); + DBObject result = mapper.getMappedObject(dbObject, context.getPersistentEntity(IdWrapper.class)); assertThat(result.get("_id"), is((Object) "1")); } @@ -100,7 +102,7 @@ public void handlesObjectIdCapableBigIntegerIdsCorrectly() { ObjectId id = new ObjectId(); DBObject dbObject = new BasicDBObject("id", new BigInteger(id.toString(), 16)); - DBObject result = mapper.getMappedObject(dbObject, null); + DBObject result = mapper.getMappedObject(dbObject, context.getPersistentEntity(IdWrapper.class)); assertThat(result.get("_id"), is((Object) id)); } @@ -114,9 +116,9 @@ public void handlesObjectIdCapableBigIntegerIdsCorrectly() { DBObject result = mapper.getMappedObject(criteria.getCriteriaObject(), context.getPersistentEntity(Sample.class)); Object object = result.get("_id"); - assertThat(object, is(DBObject.class)); + assertThat(object, is(instanceOf(DBObject.class))); DBObject dbObject = (DBObject) object; - assertThat(dbObject.get("$ne"), is(ObjectId.class)); + assertThat(dbObject.get("$ne"), is(instanceOf(ObjectId.class))); } /** @@ -128,7 +130,7 @@ public void handlesEnumsCorrectly() { DBObject result = mapper.getMappedObject(query.getQueryObject(), null); Object object = result.get("foo"); - assertThat(object, is(String.class)); + assertThat(object, is(instanceOf(String.class))); } @Test @@ -137,10 +139,10 @@ public void handlesEnumsInNotEqualCorrectly() { DBObject result = mapper.getMappedObject(query.getQueryObject(), null); Object object = result.get("foo"); - assertThat(object, is(DBObject.class)); + assertThat(object, is(instanceOf(DBObject.class))); Object ne = ((DBObject) object).get("$ne"); - assertThat(ne, is(String.class)); + assertThat(ne, is(instanceOf(String.class))); assertThat(ne.toString(), is(Enum.INSTANCE.name())); } @@ -151,14 +153,14 @@ public void handlesEnumsInNotEqualCorrectly() { DBObject result = mapper.getMappedObject(query.getQueryObject(), null); Object object = result.get("foo"); - assertThat(object, is(DBObject.class)); + assertThat(object, is(instanceOf(DBObject.class))); Object in = ((DBObject) object).get("$in"); - assertThat(in, is(BasicDBList.class)); + assertThat(in, is(instanceOf(BasicDBList.class))); BasicDBList list = (BasicDBList) in; assertThat(list.size(), is(1)); - assertThat(list.get(0), is(String.class)); + assertThat(list.get(0), is(instanceOf(String.class))); assertThat(list.get(0).toString(), is(Enum.INSTANCE.name())); } @@ -198,6 +200,68 @@ public void transformsArraysCorrectly() { assertThat(result, is(query.getQueryObject())); } + @Test + public void doesNotHandleNestedFieldsWithDefaultIdNames() { + + BasicDBObject dbObject = new BasicDBObject("id", new ObjectId().toString()); + dbObject.put("nested", new BasicDBObject("id", new ObjectId().toString())); + + MongoPersistentEntity entity = context.getPersistentEntity(ClassWithDefaultId.class); + + DBObject result = mapper.getMappedObject(dbObject, entity); + assertThat(result.get("_id"), is(instanceOf(ObjectId.class))); + assertThat(((DBObject) result.get("nested")).get("id"), is(instanceOf(String.class))); + } + + /** + * @see DATAMONGO-493 + */ + @Test + public void doesNotTranslateNonIdPropertiesFor$NeCriteria() { + + ObjectId accidentallyAnObjectId = new ObjectId(); + + Query query = Query.query(Criteria.where("id").is("id_value").and("publishers") + .ne(accidentallyAnObjectId.toString())); + + DBObject dbObject = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(UserEntity.class)); + assertThat(dbObject.get("publishers"), is(instanceOf(DBObject.class))); + + DBObject publishers = (DBObject) dbObject.get("publishers"); + assertThat(publishers.containsField("$ne"), is(true)); + assertThat(publishers.get("$ne"), is(instanceOf(String.class))); + } + + /** + * @see DATAMONGO-494 + */ + @Test + public void usesEntityMetadataInOr() { + + Query query = query(new Criteria().orOperator(where("foo").is("bar"))); + DBObject result = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(Sample.class)); + + assertThat(result.keySet(), hasSize(1)); + assertThat(result.keySet(), hasItem("$or")); + + BasicDBList ors = getAsDBList(result, "$or"); + assertThat(ors, hasSize(1)); + DBObject criterias = getAsDBObject(ors, 0); + assertThat(criterias.keySet(), hasSize(1)); + assertThat(criterias.get("_id"), is(notNullValue())); + assertThat(criterias.get("foo"), is(nullValue())); + } + + class IdWrapper { + Object id; + } + + class ClassWithDefaultId { + + String id; + ClassWithDefaultId nested; + } + class Sample { @Id @@ -213,4 +277,9 @@ class BigIntegerId { enum Enum { INSTANCE; } + + class UserEntity { + String id; + List publishers = new ArrayList(); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index aba8c4145a..bc2b2e80a8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -310,6 +310,16 @@ public void findsPeopleBySexCorrectly() { assertThat(females.get(0), is(alicia)); } + /** + * @see DATAMONGO-446 + */ + @Test + public void findsPeopleBySexPaginated() { + + List males = repository.findBySex(Sex.MALE, new PageRequest(0, 2)); + assertThat(males.size(), is(2)); + } + @Test public void findsPeopleByNamedQuery() { List result = repository.findByNamedQuery("Dave"); @@ -422,4 +432,21 @@ public void bindsDateParameterForManuallyDefinedQueryCorrectly() { List result = repository.findByCreatedAtLessThanManually(boyd.createdAt); assertThat(result.isEmpty(), is(false)); } -} \ No newline at end of file + + /** + * @see DATAMONGO-521 + */ + @Test + public void executesAndQueryCorrectly() { + + List result = repository.findByFirstnameAndLastname("Dave", "Matthews"); + + assertThat(result, hasSize(1)); + assertThat(result, hasItem(dave)); + + result = repository.findByFirstnameAndLastname("Oliver August", "Matthews"); + + assertThat(result, hasSize(1)); + assertThat(result, hasItem(oliver)); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index 3b217f593d..ca5fc4d24d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2010-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. @@ -106,6 +106,8 @@ public interface PersonRepository extends MongoRepository, Query */ List findByFirstnameNotIn(Collection firstnames); + List findByFirstnameAndLastname(String firstname, String lastname); + /** * Returns all {@link Person}s with an age between the two given values. * @@ -147,6 +149,8 @@ public interface PersonRepository extends MongoRepository, Query List findBySex(Sex sex); + List findBySex(Sex sex, Pageable pageable); + List findByNamedQuery(String firstname); GeoResults findByLocationNear(Point point, Distance maxDistance); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests.java index 45ebae97e7..51196811ec 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2010-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. diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RepositoryIndexCreationIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RepositoryIndexCreationIntegrationTests.java index 8682399e5a..a3a20e1513 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RepositoryIndexCreationIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RepositoryIndexCreationIntegrationTests.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; +import org.hamcrest.Matcher; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,11 +45,9 @@ @ContextConfiguration public class RepositoryIndexCreationIntegrationTests { - @Autowired - MongoOperations operations; + @Autowired MongoOperations operations; - @Autowired - PersonRepository repository; + @Autowired PersonRepository repository; @After public void tearDown() { @@ -72,13 +71,18 @@ public Void doInCollection(DBCollection collection) throws MongoException, DataA public void testname() { operations.execute(Person.class, new CollectionCallback() { + @SuppressWarnings("unchecked") public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { List indexInfo = collection.getIndexInfo(); assertThat(indexInfo.isEmpty(), is(false)); assertThat(indexInfo.size(), is(greaterThan(2))); - assertThat(getIndexNamesFrom(indexInfo), hasItems("findByLastnameLike", "findByFirstnameLike")); + Matcher lastnameIndex = startsWith("findByLastname"); + Matcher firstnameIndex = startsWith("findByFirstname"); + Matcher> hasItems = hasItems(lastnameIndex, firstnameIndex); + + assertThat(getIndexNamesFrom(indexInfo), hasItems); return null; } }); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java index e5a2216104..25ed96b1e7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java @@ -82,12 +82,23 @@ public void createsQueryCorrectly() throws Exception { PartTree tree = new PartTree("findByFirstName", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "Oliver"), context); + Query query = creator.createQuery(); + assertThat(query, is(query(where("firstName").is("Oliver")))); + } - creator.createQuery(); + /** + * @see DATAMONGO-469 + */ + @Test + public void createsAndQueryCorrectly() { + + Person person = new Person(); + MongoQueryCreator creator = new MongoQueryCreator(new PartTree("findByFirstNameAndFriend", Person.class), + getAccessor(converter, "Oliver", person), context); + Query query = creator.createQuery(); - creator = new MongoQueryCreator(new PartTree("findByFirstNameAndFriend", Person.class), getAccessor(converter, - "Oliver", new Person()), context); - creator.createQuery(); + Criteria criteria = new Criteria().andOperator(where("firstName").is("Oliver"), where("friend").is(person)); + assertThat(query, is(query(criteria))); } @Test @@ -96,7 +107,7 @@ public void createsNotNullQueryCorrectly() { PartTree tree = new PartTree("findByFirstNameNotNull", Person.class); Query query = new MongoQueryCreator(tree, getAccessor(converter), context).createQuery(); - assertThat(query.getQueryObject(), is(new Query(Criteria.where("firstName").ne(null)).getQueryObject())); + assertThat(query, is(new Query(Criteria.where("firstName").ne(null)))); } @Test @@ -105,7 +116,7 @@ public void createsIsNullQueryCorrectly() { PartTree tree = new PartTree("findByFirstNameIsNull", Person.class); Query query = new MongoQueryCreator(tree, getAccessor(converter), context).createQuery(); - assertThat(query.getQueryObject(), is(new Query(Criteria.where("firstName").is(null)).getQueryObject())); + assertThat(query, is(new Query(Criteria.where("firstName").is(null)))); } @Test @@ -137,7 +148,7 @@ public void createsLessThanEqualQueryCorrectly() throws Exception { MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, 18), context); Query reference = query(where("age").lte(18)); - assertThat(creator.createQuery().getQueryObject(), is(reference.getQueryObject())); + assertThat(creator.createQuery(), is(reference)); } @Test @@ -147,7 +158,7 @@ public void createsGreaterThanEqualQueryCorrectly() throws Exception { MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, 18), context); Query reference = query(where("age").gte(18)); - assertThat(creator.createQuery().getQueryObject(), is(reference.getQueryObject())); + assertThat(creator.createQuery(), is(reference)); } /** @@ -160,7 +171,7 @@ public void honoursMappingInformationForPropertyPaths() { MongoQueryCreator creator = new MongoQueryCreator(partTree, getAccessor(converter, "Oliver"), context); Query reference = query(where("foo").is("Oliver")); - assertThat(creator.createQuery().getQueryObject(), is(reference.getQueryObject())); + assertThat(creator.createQuery(), is(reference)); } /** @@ -172,7 +183,7 @@ public void createsExistsClauseCorrectly() { PartTree tree = new PartTree("findByAgeExists", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, true), context); Query query = query(where("age").exists(true)); - assertThat(creator.createQuery().getQueryObject(), is(query.getQueryObject())); + assertThat(creator.createQuery(), is(query)); } /** @@ -184,7 +195,7 @@ public void createsRegexClauseCorrectly() { PartTree tree = new PartTree("findByFirstNameRegex", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, ".*"), context); Query query = query(where("firstName").regex(".*")); - assertThat(creator.createQuery().getQueryObject(), is(query.getQueryObject())); + assertThat(creator.createQuery(), is(query)); } /** @@ -196,7 +207,7 @@ public void createsTrueClauseCorrectly() { PartTree tree = new PartTree("findByActiveTrue", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context); Query query = query(where("active").is(true)); - assertThat(creator.createQuery().getQueryObject(), is(query.getQueryObject())); + assertThat(creator.createQuery(), is(query)); } /** @@ -208,11 +219,11 @@ public void createsFalseClauseCorrectly() { PartTree tree = new PartTree("findByActiveFalse", Person.class); MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context); Query query = query(where("active").is(false)); - assertThat(creator.createQuery().getQueryObject(), is(query.getQueryObject())); + assertThat(creator.createQuery(), is(query)); } /** - * @see DATAMONGO + * @see DATAMONGO-413 */ @Test public void createsOrQueryCorrectly() { @@ -221,8 +232,7 @@ public void createsOrQueryCorrectly() { MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "Dave", 42), context); Query query = creator.createQuery(); - assertThat(query.getQueryObject(), - is(query(new Criteria().orOperator(where("firstName").is("Dave"), where("age").is(42))).getQueryObject())); + assertThat(query, is(query(new Criteria().orOperator(where("firstName").is("Dave"), where("age").is(42))))); } private void assertBindsDistanceToQuery(Point point, Distance distance, Query reference) throws Exception { @@ -240,7 +250,7 @@ private void assertBindsDistanceToQuery(Point point, Distance distance, Query re Query query = new MongoQueryCreator(tree, new ConvertingParameterAccessor(converter, accessor), context) .createQuery(); - assertThat(query.getQueryObject(), is(query.getQueryObject())); + assertThat(query, is(query)); } interface PersonRepository extends Repository { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java index 1b36291f19..56505eb54c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-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. @@ -18,6 +18,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import org.bson.types.ObjectId; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,6 +35,8 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import com.mysema.query.types.expr.BooleanOperation; +import com.mysema.query.types.path.PathBuilder; import com.mysema.query.types.path.StringPath; /** @@ -77,12 +80,12 @@ public void convertsComplexObjectOnSerializing() { address.zipCode = "01234"; DBObject result = serializer.asDBObject("foo", address); - assertThat(result, is(BasicDBObject.class)); + assertThat(result, is(instanceOf(BasicDBObject.class))); BasicDBObject dbObject = (BasicDBObject) result; Object value = dbObject.get("foo"); assertThat(value, is(notNullValue())); - assertThat(value, is(BasicDBObject.class)); + assertThat(value, is(instanceOf(BasicDBObject.class))); Object reference = converter.convertToMongoType(address); assertThat(value, is(reference)); @@ -98,7 +101,25 @@ public void returnsEmptyStringIfNoPathExpressionIsGiven() { assertThat(serializer.getKeyForPath(address, address.getMetadata()), is("")); } + /** + * @see DATAMONGO-467 + */ + @Test + public void convertsIdPropertyCorrectly() { + + ObjectId id = new ObjectId(); + + PathBuilder
builder = new PathBuilder
(Address.class, "address"); + StringPath idPath = builder.getString("id"); + + DBObject result = (DBObject) serializer.visit((BooleanOperation) idPath.eq(id.toString()), (Void) null); + assertThat(result.get("_id"), is(notNullValue())); + assertThat(result.get("_id"), is(instanceOf(ObjectId.class))); + assertThat(result.get("_id"), is((Object) id)); + } + class Address { + String id; String street; @Field("zip_code") String zipCode; diff --git a/src/docbkx/introduction/introduction.xml b/src/docbkx/introduction/introduction.xml index 3ff9b07d94..5d9eb2bca3 100644 --- a/src/docbkx/introduction/introduction.xml +++ b/src/docbkx/introduction/introduction.xml @@ -12,17 +12,17 @@ Knowing Spring Spring Data uses Spring framework's core + url="http://static.springframework.org/spring/docs/3.0.x/reference/html/spring-core.html">core functionality, such as the IoC + url="http://static.springframework.org/spring/docs/3.0.x/reference/html/beans.html">IoC container, type - conv ersion system, expression + url="http://static.springsource.org/spring/docs/3.0.x/reference/html/validation.html#core-convert">type + conversion system, expression language, JMX + url="http://static.springsource.org/spring/docs/3.0.x/reference/html/jmx.html">JMX integration, and portable DAO + url="http://static.springsource.org/spring/docs/3.0.x/reference/html/dao.html#dao-exceptions">DAO exception hierarchy. While it is not important to know the Spring APIs, understanding the concepts behind them is. At a minimum, the idea behind IoC should be familiar for whatever IoC container you diff --git a/src/docbkx/reference/mongodb.xml b/src/docbkx/reference/mongodb.xml index 0e822369b3..8fd34cea33 100644 --- a/src/docbkx/reference/mongodb.xml +++ b/src/docbkx/reference/mongodb.xml @@ -251,7 +251,7 @@ public class MongoApp { - spring-data-mongodb-1.0.0.RELEASE.jar + spring-data-mongodb-1.0.2.RELEASE.jar @@ -624,7 +624,7 @@ public class MongoConfiguration { } - +
@@ -686,7 +686,7 @@ public class MongoConfiguration { <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/> </bean> - +
@@ -1546,8 +1546,17 @@ assertThat(p.getAge(), is(1)); follow a fluent API style so that you can easily chain together multiple method criteria and queries while having easy to understand code. Static imports in Java are used to help remove the need to see the 'new' keyword - for creating Query and Criteria instances so as to improve - readability.
+ for creating Query and + Criteria instances so as to improve readability. If + you like to create Query instances from a plain + JSON String use BasicQuery.
+ + + Creating a Query instance from a plain JSON String + + BasicQuery query = new BasicQuery("{ age : { $lt : 50 }, accounts.balance : { $gt : 1000.00 }}"); +List<Person> result = mongoTemplate.find(query, Person.class); + GeoSpatial queries are also supported and are described more in the section GeoSpatial Queries. @@ -1572,10 +1581,10 @@ assertThat(p.getAge(), is(1)); import static org.springframework.data.mongodb.core.query.Criteria.where; import static org.springframework.data.mongodb.core.query.Query.query; - ... +… - List<Person> result = mongoTemplate.find(query(where("age").lt(50).and("accounts.balance").gt(1000.00d)), Person.class); - +List<Person> result = mongoTemplate.find(query(where("age").lt(50) + .and("accounts.balance").gt(1000.00d)), Person.class); All find methods take a Query object as a @@ -1769,7 +1778,7 @@ import static org.springframework.data.mongodb.core.query.Query.query; Criteria withinBox (Box box) Creates a geospatial criterion using a $within $box operation - + @@ -2126,7 +2135,7 @@ MapReduceResults<ValueObject> results = mongoOperations.mapReduce(query, "
Group Operations - As an alternative to usiing Map-Reduce to perform data aggregation, + As an alternative to using Map-Reduce to perform data aggregation, you can use the group operation which feels similar to using SQL's group by query style, @@ -2722,4 +2731,4 @@ mongoTemplate.dropCollection("MyNewCollection"); } });
- \ No newline at end of file + diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 868868ba80..e3b7f155bf 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,65 @@ Spring Data Document Changelog ============================================= +Changes in version 1.0.4.RELEASE MongoDB (2012-08-24) +----------------------------------------------------- +** Bug + * [DATAMONGO-493] - Criteria.ne() method converts all value into ObjectId + * [DATAMONGO-494] - $or/$nor expressions do not consider entity class mapping + * [DATAMONGO-495] - JSON can't serialize Enum when printing Query in DEBUG message +** Improvement + * [DATAMONGO-499] - Namespace XSDs of current release version should refer to repositories XSD in version 1.0 +** Task + * [DATAMONGO-514] - Release 1.0.4. + + +Changes in version 1.0.3.RELEASE MongoDB (2012-07-23) +----------------------------------------------------- +** Bug + * [DATAMONGO-467] - String @id field is not mapped to ObjectId when using QueryDSL ".id" path + * [DATAMONGO-469] - Query creation from method names using AND criteria does not work anymore + * [DATAMONGO-474] - Wrong property is used for Id mapping + * [DATAMONGO-475] - 'group' operation fails where query references non primitive property + * [DATAMONGO-480] - The WriteResultChecking is not used in case of insert or save of documents. + * [DATAMONGO-483] - @Indexed(unique=true, name="foo") puts name's value to the 'key' in the MongoDB + * [DATAMONGO-489] - ClassCastException when loading Map + +** Improvement + * [DATAMONGO-466] - QueryMapper shouldn't map id properties of nested classes + * [DATAMONGO-470] - Criteria and Query should have proper equals(…) and hashCode() method. + * [DATAMONGO-482] - typo in documentation - 2 i's in usiing + +** Task + * [DATAMONGO-492] - Release 1.0.3 + +Changes in version 1.0.2.RELEASE MongoDB (2012-06-20) +----------------------------------------------------- +** Bug + * [DATAMONGO-360] - java.lang.ClassCastException when placing GeospatialIndex into IndexOperations and invoking IndexOperations.getIndexInfo() + * [DATAMONGO-366] - Chapter 3.2. points to wrong bugtracker + * [DATAMONGO-378] - MapReduceResults ClassCastException due to raw results counts as Long + * [DATAMONGO-411] - Potential ClassCastExceptions in MongoPersistentEntityIndexCreator + * [DATAMONGO-412] - getUserCredentials() is called twice in AbstractMongoConfiguration::mongoDbFactory() + * [DATAMONGO-413] - Using "Or" in repository query yields a ClassCastException + * [DATAMONGO-422] - UUIDToBinaryConverter not compatible with mongo java driver + * [DATAMONGO-423] - Criteria.regex should use java.util.Pattern instead of $regex + * [DATAMONGO-425] - Binding a Date to a manually defined repository query fails + * [DATAMONGO-428] - ClassCastException when using outputDatabase option in map-reduce + * [DATAMONGO-429] - using @Query annotation, arrays are translated somewhere between query creation and mongo interpretation + * [DATAMONGO-446] - Pageable query methods returning List are broken + * [DATAMONGO-447] - Removal of Documents fails in in debug mode for Documents with complex ids + * [DATAMONGO-450] - enabling DEBUG causes RuntimeException + * [DATAMONGO-454] - ServerAddressPropertyEditor fails if a hostname is unresolvable + * [DATAMONGO-461] - MappedConstructor potentially throws NullPointerException + * [DATAMONGO-462] - findAll() fails with NPE - discovering the root cause + +** Improvement + * [DATAMONGO-448] - Remove the need for Converters for complex classes that are used as IDs + * [DATAMONGO-455] - Document how to use raw queries using BasicQuery + +** Task + * [DATAMONGO-463] - Release 1.0.2 + Changes in version 1.0.1.RELEASE MongoDB (2012-02-11) ----------------------------------------------------- ** Bug