Compare commits

..

76 Commits

Author SHA1 Message Date
Oliver Gierke
0ca297df50 DATAMONGO-955 - After release cleanups. 2014-06-18 14:36:37 +02:00
Spring Buildmaster
22078d9bb0 DATAMONGO-955 - Prepare next development iteration. 2014-06-18 04:11:46 -07:00
Spring Buildmaster
adfa7cdd88 DATAMONGO-955 - Release version 1.4.3.RELEASE (Codd SR3). 2014-06-18 04:11:43 -07:00
Oliver Gierke
88deac4ca7 DATAMONGO-955 - Prepare 1.4.3.RELEASE (Codd SR3). 2014-06-18 12:57:14 +02:00
Oliver Gierke
0275e6fb4c DATAMONGO-955 - Updated changelog. 2014-06-18 12:57:14 +02:00
Oliver Gierke
291b89a8b6 DATAMONGO-936 - Updated changelog. 2014-06-18 12:55:16 +02:00
Oliver Gierke
fe9d086334 DATAMONGO-919 - Updated changelog. 2014-06-18 12:54:56 +02:00
Christoph Strobl
cf4db90a7f DATAMONGO-952 - Derived queries should consider field specification in @Query.
PartTreeMongoQuery now explicitly check the presence of a manually defined field spec on the query method and creates a new Query if so.

Original pull request: #188.
2014-06-18 12:53:29 +02:00
Christoph Strobl
dc67830c6f DATAMONGO-948 - Sort should be taken as is when no type information available.
Object type mapping for sort is skipped in the case no type information is present when executing query using mongo template.
2014-06-18 08:17:22 +02:00
Thomas Darimont
33004f217d DATAMONGO-938 - Apply QueryMapper in MongoTemplate.mapReduce(…).
Previously MongoTemplate.mapReduce(...) didn't translate nested objects, e.g. GeoCommand, within the given query. That could lead to exceptions during query serialization. We now pass the query and sort object of the given Query through the QueryMapper to avoid such problems.

Original pull request: #184.
2014-06-18 08:17:15 +02:00
Thomas Darimont
72adb309ee DATAMONGO-745 - Added test cases for custom query with $in and pageable parameter.
Added test cases to verify that this works.

Original pull request: #186.
2014-06-18 07:49:58 +02:00
Christoph Strobl
2e493ea8c5 DATAMONGO-647 - Added test case to show that field names are mapped correctly.
Additional test added to check if the issue has truly been resolved by DATAMONGO-888.

Original pull request: #181.
Related pull Request: #162.
Related ticket: DATAMONGO-888.
2014-05-19 14:39:59 +02:00
Thomas Darimont
f0fc3961d2 DATAMONGO-924 - Improve aggregation field reference resolving.
Previously we didn't support referring to aliased fields defined in former stages of an aggregation pipeline. We now also consider field aliases during field reference lookup.

Original pull request: #176.
2014-05-02 14:47:47 +02:00
Kim Toms
f80fd92692 DATAMONGO-920 - Improve debug message for delete events in AbstractMongoEventListener.
Adjusted debug message to reflect the actual operation.

Original pull request: #95.
2014-04-29 15:55:17 +02:00
Christoph Strobl
87ef66cb12 DATAMONGO-847 - Allow usage of Query within an Update clause.
In case we detect Query within a value used for an Update value we map the query itself to build the expression to use. This allows to form query statements for e.g. $pull using the same API as for the query itself.

Update update = new Update().pull("list", query(where("value").in("foo", "bar")));

Original Pull Request: #172.
2014-04-28 13:30:24 +02:00
Thomas Darimont
332d9d95f4 DATAMONGO-917 - Improve Spring 4.0 framework version detection to avoid NPEs.
We now check for the presence of DefaultParameterNameDiscoverer in order to determine if we are running with a Spring version later than 4.0 since this avoids potential NullPointerExceptions in cases where the package version information is not available e.g. in cases where the application was bundled into an "uberjar" e.g. via the maven-shade-plugin.

Original pull request: #173.
2014-04-28 13:15:31 +02:00
Thomas Darimont
c18d6381aa DATAMONGO-913 - Improve DBRef handling in for LazyLoadingProxies.
We now use the captured DBRef of a given LazyLoadingProxy in MappingMongoConverter.toDBRef(..) in order to avoid a new DBRef creation that would fail for the proxy.

Original pull request: #174.
2014-04-28 13:09:49 +02:00
Thomas Darimont
53697179c3 DATAMONGO-914 - Improve resolving of lazy-loading proxies for classes that override equals(…)/hashCode().
We now properly resolve lazy-loading proxies for @DBRef's when an overridden equals or hash code method is called with Spring 4. We fall back to our old Objenesis proxy generation in order to circumvent the default handling for overridden hashcCode() and equals(…) methods in CglibAopProxies generated by Spring 4.

If we detect that we run with Spring 4 we use the repacked Objenesis that is included in Spring 4. Previously the generated proxy used some generic hashCode() or equals(…) logic that did not trigger a proper lazy loading in such cases.

Original pull request: #171.
2014-04-23 09:31:13 +02:00
Thomas Darimont
7b3acb2890 DATAMONGO-912 - Consider custom conversions in all stages of an aggregation pipeline.
We now consider custom mongo conversions in all stages of an aggregation pipeline. Previously we did this only for the first stage and returned object basically unmapped in later stages. We now pass the root AggregationOperationContext on to nested ExposedFieldsAggregationOperationContexts so that those can delegate any mongo Mapping to the root context.

Original pull request: #170.
2014-04-23 09:03:08 +02:00
Spring Buildmaster
d1ac323e4a DATAMONGO-911 - Prepare next development iteration. 2014-04-15 19:24:16 +02:00
Spring Buildmaster
b1068687bb DATAMONGO-911 - Release version 1.4.2.RELEASE (Codd SR2). 2014-04-15 10:08:32 -07:00
Christoph Strobl
6eae6d3e2c DATAMONGO-911 - Prepare 1.4.2.RELEASE (Codd SR2).
Updated to Spring Data Build 1.3.2.RELEASE and Spring Data Commons 1.7.2.RELEASE. Update readme, changelog, notice to reflect recent version. Update pom.xml, index.xml to recent version.
2014-04-15 18:29:38 +02:00
Christoph Strobl
abfb98afe1 DATAMONGO-893 - Converter must not write "_class" information for know types.
We now actively pass on property type information to MetadataBackedField to ensure type hints get picked up correctly when converting a value to the according DBObject.

This has to be done as the fix for DATAMONGO-812 enforced proper writing of _class information for Updates, which caused trouble when querying documents by nested (complex) properties using an 'in' clause.

Original pull request: #169.
2014-04-15 17:29:50 +02:00
Christoph Strobl
f361368893 DATAMONGO-892 - Reject nested MappingMongoConverter declarations in XML.
Mapping information is potentially required by multiple instances and thus must not be registered as nested bean. We now actively check for such an invalid scenario and explicitly reject it.

Original pull request: #165.
2014-04-15 09:06:54 +02:00
Christoph Strobl
063438002b DATAMONGO-897 - Fixed potential NullPointerException in QueryMapper.
If an association property points to an interface not containing the id property QueryMapper threw a NullPointerException in isAssociationConversionNecessary(…) as the lookup of the id property fails.

We now check for the presence of an id property on the target type and check for assignability to indicated the need for conversion (usually in case when developers use raw ids in their update clauses, not the actual target instance.

Original pull request: #164.
2014-04-15 09:01:22 +02:00
Thomas Darimont
9b54a5cd39 DATAMONGO-908 - Support for nested field references in group operations.
We now allow referring to nested field expressions if the root segment of the nested field expression was exposed in earlier stages of the aggregation pipeline.

Original pull request: #167.
2014-04-15 07:57:24 +02:00
Jeff Yemin
14360f2ab4 DATAMONGO-895, DATAMONGO-896 - Assert compatibility with latest MongoDB Java driver.
Upgrade next MongoDB driver version to 2.12.0. Strong upgrade coming in a subsequent commit to make sure we can backport the compatibility checks to the bugfix branch without forcing users into a driver upgrade.

Relaxing error message comparison in assertion so that it still matches against the message returned by MongoDB 2.6. When comparing the value of the version field, compare against a Long rather than an Integer, since the version field generated is a Long. This allows the test to pass against the upcoming 2.12.0 release of the Java driver, which has a stricter implementation of BasisDBObject.equals(…).

Original pull requests: #159, #160.
2014-04-10 15:57:54 +02:00
Christoph Strobl
81c368c851 DATAMONGO-888 - Sorting now considers mapping information.
We now pipe the DBObject containing sorting information for queries through the QueryMapper to make sure potential field mappings are applied.

Original Pull Request: #162.
2014-04-10 15:45:09 +02:00
Christoph Strobl
cf3818e04c DATAMONGO-907 - Assert compatibility with mongodb 2.6.
Fix test to only check on parts of the expected error message common in both 2.4 and 2.6.

Original Pull Request: #166.
2014-04-10 13:34:00 +02:00
Oliver Gierke
da9870504f DATAMONGO-905 - Removed obsolete dependency to CGLib from cross-store support.
Also we now optionally depend on the HIbernate JPA API JAR so that using other persistence providers doesn'T cause an API JAR duplication.
2014-04-09 20:45:56 +02:00
Thomas Darimont
1285f4f26e DATAMONGO-884 - Improved handling for Object methods in LazyLoadingInterceptor.
We now handle invocations of equals(…)/hashCode()/toString()  methods that are not overridden with custom proxy aware logic. This avoids potentially NullPointerExceptions and makes it easier to debug code that deals with proxies (due to a proper toString representation of a proxy).

Original pull request: #158.
2014-03-31 15:19:40 +02:00
Thomas Darimont
791938f05d DATAMONGO-884 - Fix potential NullPointerException for lazy DBRefs.
We now initialize the proxy in case an Object-method is called that is overridden in the traget class. Removed the additional check for initialization and to-DBRef-methods as they're repeated in the target method.

Original pull requests: #152, #153.
2014-03-27 17:59:44 +01:00
Oliver Gierke
1b2d98dd3d DATAMONGO-890 - Fixed Point.toString().
The toString() representation no lists x,y instead of the previously (wrong latitude, longitude).
2014-03-27 16:27:05 +01:00
Oliver Gierke
de364c65ab DATAMONGO-887 - Added unit tests to verify TreeMaps can be converted. 2014-03-27 09:28:55 +01:00
Oliver Gierke
57a74b0427 DATAMONGO-880 - Minor polishing in lazy-loading area.
Took the change to add @since tags to the types introduced for lazy loading. Polished JavaDoc where necessary. Removed methods solely existing for testing purposes and use reflection in tests to minimize the API being published.
2014-03-20 09:33:55 +01:00
Thomas Darimont
f35df8fe69 DATAMONGO-880 - Improved handling of persistence of lazy-loaded DBRefs.
Added LazyLoadingProxy interface that will be implemented by every LazyLoading-proxy that is created by the DefaultDbRefResolver. Clients can now cast those proxies to this interface and call it's methods initialize a proxy explicitly or to get the referenced DBRef if possible.

We now keep a reference to the DBRef that lead to the creation of a LazyLoadingProxy in order to be able to reuse it in case one assigns the proxy to a field that should be a DBRef. This avoids unnecessary conversion.

Previously saving of proxies wasn't possible since the mapping infrastructure did not know how to extract the entity information from the proxy. We now either store the DBRef backed by the proxy directly or we initialize the proxy first and use the result of LazyLoadingProxy.initialize().

Original pull request: #151.
2014-03-20 09:33:42 +01:00
Oliver Gierke
2d3aac1826 DATAMONGO-881 - Allow custom conversions to override default conversions.
User provided converters are now registered *after* the default converters to make sure they enjoy precedence over the default ones.

This is achieved by inverting the order of converters after the conversions have been registered. This is necessary as the registration order for convertible pairs is different from the one of the converters. For the pairs, earlier registered instances take precedence, while for the actual converter instances, instances registered later trump ones registered before.
2014-03-18 09:34:03 +01:00
Spring Buildmaster
15db4ba6ea DATAMONGO-860 - Prepare next development iteration. 2014-03-13 12:34:19 +01:00
Spring Buildmaster
f02ac5ea44 DATAMONGO-860 - Release version 1.4.1.RELEASE (Codd SR1). 2014-03-13 04:25:30 -07:00
Christoph Strobl
86633e01db DATAMONGO-860 - Prepare Release 1.4.1.
Update readme.md & mongodb.xml to reflect recent version. Update sd-commons/sb-build versions in pom.xml. Update pom.xml to use release repository.
Update docbkx to use recent sd-commons version. Update changelog to reflect changes and releases.

Original Pull Request: #148.
2014-03-13 12:02:19 +01:00
Oliver Gierke
5fe3763f9c DATAMONGO-877 - Added guard against null-package in AbstractMappingConfiguration.
AbstractMappingConfiguration.getMappingBasePackage() now quards against a null package returned for the configuration class. This can happen if the class resides in the default package.
2014-03-10 13:13:49 +01:00
Thomas Darimont
d1e2b143f3 DATAMONGO-773 - Verify that @DBRef fields can be included in query.
Added test cases to verify that projection search with included @DBRef fields works as expected.
2014-03-06 13:43:01 +01:00
Christoph Strobl
61ab232bc1 DATAMONGO-868 - MongoTemplate.findAndModify(…) increases version if not handled manually.
MongoTemplate.findAndModify(…) increments the version property in case it's not manually set in the Update object given.

Original Pull Request: #141.
2014-03-06 11:52:17 +01:00
Christoph Strobl
443cde6236 DATAMONGO-863 - UpdateMapper doesn't convert raw DBObjects anymore.
UpdateMapper now only performs simple conversion if it encounters a DBObject, instead of deep inspection on keywords used. This allows to use custom clauses nested in Update for operations not directly supported.

Original Pull Request: #138.
2014-03-06 11:46:57 +01:00
Oliver Gierke
b23796fb45 DATAMONGO-821 - Fixed handling of keyword expressions for DBRefs.
Query Mapper skips DBRef conversion in case the given source value is a nested DBObject. This allows to directly use mongodb operators wrapped in DBObject on association properties.

Original Pull Request: #139.
2014-03-06 11:26:37 +01:00
Oliver Gierke
605f7459f7 DATAMONGO-843 - Back-port of defaulting of the MappingContext for auditing.
@EnableMongoAuditing defaults the mapping context to make sure it can be used without a MappingContext defined explicitly.
2014-03-06 09:26:14 +01:00
Oliver Gierke
ef6db5970b DATAMONGO-871 - Add support for arrays as query method return types.
Changed AbstractMongoQuery to potentially convert all query execution results using the DefaultConversionService in case the query result doesn't match the expected return value.

This allows arrays to be returned for collection queries as the conversion service cam transparently convert between collections and arrays.
2014-03-05 10:07:37 +01:00
Thomas Darimont
47a5a32713 DATAMONGO-865 - Adjust test dependencies to avoid ClassNotFoundException during test runs.
Added jul-to-slf4j dependency to avoid exceptions being logged during test runs.

Original pull request: #135.
2014-03-04 09:48:34 +01:00
Christoph Strobl
1675528fc7 DATAMONGO-829 - NearQuery should not default 'num' to zero.
NearQuery now ignores query.getLimit() equal to zero, when adding Query to NearQuery. This has to be done as limit is defaulted to zero within Query which then results in unintended propagation of the parameter.

In case 'num' should be explicitly set to zero one might use 'NearQuery.num(0)' as an alternative to the query approach.

Introduced 'null' check for 'NearQuery.query(Query)' and 'NearQuery.with(Pageable)' along the way.

Original Pull Request: #133
2014-03-03 15:34:00 +01:00
Christoph Strobl
3455cbc634 DATAMONGO-862 - Fixed handling of unmapped paths for updates.
UpdateMapper uses key instead of cleaned property path when not directly pointing to a property.

Original pull request: #132.
2014-02-27 16:56:11 +01:00
Oliver Gierke
ed779e52b7 DATAMONGO-833 - Support for EnumSet and EnumMap in MappingMongoConverter.
Re-implemented the fix we already applied to master without referring to the custom CollctionFactory, which is only introduced in Spring Data Commons' master.

Related pull request: #113.
2014-02-26 05:56:19 +01:00
Spring Buildmaster
c70898b019 DATAMONGO-854 - Prepare next development iteration. 2014-02-24 15:30:19 +01:00
Spring Buildmaster
294616432d DATAMONGO-854 - Release version 1.4.0 RELEASE. 2014-02-24 06:25:32 -08:00
Christoph Strobl
47dd512f95 DATAMONGO-854 - Prepare 1.4.0.RELEASE.
Update artifact version in readme for release and snapshot.
Use commons 1.7.0 resources in docbkx.
Update changelog.
Update version information in notice and readme.

Original pull request: #130.
2014-02-24 15:11:40 +01:00
Thomas Darimont
f16e8d85e5 DATAMONGO-856 - Documentation update.
Removed outdated why-spring-data-doc. Removed CouchDB reference from requirements document. Fixed some typos. Added missing opening <para>-element. Fixed rendering of author information. Added copyright and product name information.

Original pull request: #128.
2014-02-24 11:43:58 +01:00
Christoph Strobl
eb03ae61f2 DATAMONGO-856 - Documentation updates.
Updated references from springsource.org to spring.io. Updated references to mongodb.org. Update vendor to Pivotal Software, Inc. Update required/recommended versions. Update CustomConversions configuration section. Added missing section id's. Fixed some typos. Added missing JavaDoc.

Original pull request: #128.
2014-02-24 11:42:59 +01:00
Christoph Strobl
5be66a3fee DATAMONGO-853 - Update does not allow null keys anymore.
Added check for blank / null keys when adding key to Update.

Original pull request: #129.
2014-02-24 10:51:48 +01:00
Thomas Darimont
d88e4c0e3e DATAMONGO-468 - Verify that one can use a domain object in DbRef field updates.
Added test case to demonstrate that using a domain object as a value for a DbRef field update is already supported.

Original pull request: #127.
2014-02-21 14:03:35 +01:00
Christoph Strobl
57d1449008 DATAMONGO-852 - Update keeps track of fields to be modified.
Update holds a set of fields that modifications are registered for. This information is used to determine if a modification is registered for the version field of a versioned entity. The change was introduced since the present solution did not correctly find the version property correctly within the DBObject resulting from the mapped update.

In case version property is already included in Update automatic version update via $inc is will be skipped.

Original pull request: #126
2014-02-21 13:57:58 +01:00
Oliver Gierke
8d00a0d926 DATAMONGO-404 - Polishing of DBRef creation in Update clauses.
Refactored the internals of UpdateMapper to simplify the code a little. Removed the special converter in favor of handling the mapped key generation directly. This can be removed again, once DATACMNS-444 is fixed.

MetadataBackedField.getPath(String) now also rejects PersistentPropertyPaths the refer to anything else but the id property in case it traverses an association.

Changed MetadataBackedField to return the association property in calls to ….getProperty() as it is the PersistentProperty to hand to the mapping infrastructure for object conversion.

Changed MappingMongoConverter to also check, whether the given source object handed into DBRef creation is of the ID type and simply use that for DBRef creation. This allows creating DBRefs from ids as well.
2014-02-19 21:13:01 +01:00
Oliver Gierke
e3fa844488 DATAMONGO-854 - Upgraded to Spring Data Commons snapshots.
Upgraded to snapshots of Spring Data Commons and the build parent.
2014-02-19 20:02:34 +01:00
Thomas Darimont
58bee75a6b DATAMONGO-404 - Fixed Update.pull(…) handling to work with DBRefs.
We now support pointing to DBRef-mapped properties in Update.pull(…) and also allow to refer to the id of the DBRef to avoid having to create an instance of the entity.
2014-02-19 18:00:49 +01:00
Oliver Gierke
a402395f5c DATAMONGO-849 - Fixed invalid class reference in readme.
Minor sample code optimizations to use MongoClient instead of Mongo. Fixed repository URL.
2014-02-17 16:38:25 +01:00
Oliver Gierke
9d5f8f3ba0 DATAMONGO-848 - Added tweaks to be compatible with Java driver 2.12.
Added build profile to be able to build against next Mongo Java driver version (2.12.0-rc0) currently. Tweaked Bundlor version replacements to allow binding non-OSGi compatible Mongo driver versions.

Exception translator now handles newly introduced MongoServerSelectionException which the driver throws to indicate it can't connect to a MongoDB instance as of driver version 2.12. GridFsTemplate now uses an empty query object instead of null to indicate that no query should be used. 

Adapted test cases to be able to deal with the slightly changed representation of serverUsed in command results (2.12 removed leading slash).
2014-02-17 12:42:19 +01:00
Christoph Strobl
7ebf953063 DATAMONGO-354 - Update.pushAll(…) now supports multiple values.
Update.pushAll(…) now is a multiFieldOperation which allows to send values for different fields within one command.

Original Pull Request: #122.
2014-02-17 11:45:15 +01:00
Christoph Strobl
617ebe0ca7 DATAMONGO-828 - Fixed version checks for updates in MongoTemplate.
Added inspection of the query object to check if the update should only apply to a given version. If so and no documents have been updated we still throw an OptimisticLockingException. For all other cases - like UpdateFirst - zero affected documents is fine.

Original Pull Request: #121.
2014-02-17 11:30:38 +01:00
Christoph Strobl
7f76789664 DATAMONGO-410 - Added test case to show that UpdateMapper considers custom converter.
Original pull request: #124.
2014-02-17 11:03:52 +01:00
Oliver Gierke
81e5919ace DATAMONGO-812 - Added assumptions to not break tests on old MongoDB versions. 2014-02-11 18:50:09 +01:00
Oliver Gierke
efd74956dc DATAMONGO-812 - Polishing.
Changed convertToMongoType(…) to forward type hints to recursive calls to make sure type information is written if a TypeInformation was provided initially. Make sure that UpdateMapper hands in an initial type hint to the converter to make sure type information gets written.

Changed the signature of QueryMapper.getMappedObjectForField(…) to allow customizing the entire entry being added to the result. This is in preparation of more advanced mappings that might have to customize the mapped key.

Fixed newly introduced test cases in MongoTemplateTests.

Original pull request: #112.
2014-02-11 17:39:08 +01:00
Christoph Strobl
49eee40f7e DATAMONGO-812 - Add support for $push $each since $pushAll is deprecated.
$pushAll has been deprecated in MongoDB 2.4. Instead of calling pushAll one can use push in combination with each. The abstraction for pushAll will remain in code for now but may be removed in a subsequent version.

Original pull request: #112.
2014-02-11 17:39:07 +01:00
Thomas Darimont
8e93b844c7 DATAMONGO-830 - Prevent NullPointerException during cache warmup in CustomConversions.
We now use a ConcurrentHashMap to cache the results of custom read target lookups in order to avoid having to traverse the readingPairs for every lookup. The use of ConcurrentHashMap should also prevent potentially NullPointerExceptions from being thrown if custom conversions are initialized in heavily threaded environments.

Original pull request: #117.
2014-02-10 18:49:01 +01:00
Thomas Darimont
3e64432f1a DATAMONGO-842 - Improve documentation in GridFS section.
Rephrased wording for better understanding.

Original pull request: #120.
2014-02-10 10:30:57 +01:00
Thomas Darimont
88c968ad36 DATAMONGO-840 - Improve support for nested field references in SpEL expressions within Projections.
We now correctly add a compound expression that represents a field reference to the previous operation arguments if necessary.

Original pull request: #118.
2014-02-10 10:18:14 +01:00
Thomas Darimont
99eefe0773 DATAMONGO-838 - Cannot refer to expression based field in group operation.
Previously we didn't set a proper target value for the generated expression field. As a potential fix we just use the alias as the target field.

Original pull request: #116.
2014-02-10 09:59:15 +01:00
Oliver Gierke
3d4569be14 DATAMONGO-826 - Remove obsolete milestone repository. 2014-02-09 14:33:27 +01:00
Spring Buildmaster
57455c4a26 DATAMONGO-826 - Prepare next development iteration. 2014-01-29 06:04:53 -08:00
85 changed files with 4697 additions and 613 deletions

View File

@@ -1,6 +1,6 @@
# Spring Data MongoDB
The primary goal of the [Spring Data](http://www.springsource.org/spring-data) project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
The primary goal of the [Spring Data](http://projects.spring.io/spring-data) project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
The Spring Data MongoDB project aims to provide a familiar and consistent Spring-based programming model for new datastores while retaining store-specific features and capabilities. The Spring Data MongoDB project provides integration with the MongoDB document database. Key functional areas of Spring Data MongoDB are a POJO centric model for interacting with a MongoDB DBCollection and easily writing a repository style data access layer.
@@ -8,12 +8,12 @@ The Spring Data MongoDB project aims to provide a familiar and consistent Spring
For a comprehensive treatment of all the Spring Data MongoDB features, please refer to:
* the [User Guide](http://static.springsource.org/spring-data/data-mongodb/docs/current/reference/html/)
* the [JavaDocs](http://static.springsource.org/spring-data/data-mongodb/docs/current/api/) have extensive comments in them as well.
* the home page of [Spring Data MongoDB](http://www.springsource.org/spring-data/mongodb) contains links to articles and other resources.
* for more detailed questions, use the [forum](http://forum.springsource.org/forumdisplay.php?f=80).
* the [User Guide](http://docs.spring.io/spring-data/mongodb/docs/current/reference/html/)
* the [JavaDocs](http://docs.spring.io/spring-data/mongodb/docs/current/api/) have extensive comments in them as well.
* the home page of [Spring Data MongoDB](http://projects.spring.io/spring-data-mongodb) contains links to articles and other resources.
* for more detailed questions, use the [forum](http://forum.spring.io/forum/spring-projects/data/nosql).
If you are new to Spring as well as to Spring Data, look for information about [Spring projects](http://www.springsource.org/projects).
If you are new to Spring as well as to Spring Data, look for information about [Spring projects](http://projects.spring.io/).
## Quick Start
@@ -26,7 +26,7 @@ Add the Maven dependency:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.3.3.RELEASE</version>
<version>1.4.2.RELEASE</version>
</dependency>
```
@@ -36,13 +36,13 @@ If you'd rather like the latest snapshots of the upcoming major version, use our
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.4.0.BUILD-SNAPSHOT</version>
<version>1.5.0.BUILD-SNAPSHOT</version>
</dependency>
<repository>
<id>spring-libs-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>http://repo.springsource.org/libs-snapshot</url>
<url>http://repo.spring.io/libs-snapshot</url>
</repository>
```
@@ -53,7 +53,7 @@ MongoTemplate is the central support class for Mongo database operations. It pro
* Basic POJO mapping support to and from BSON
* Convenience methods to interact with the store (insert object, update objects) and MongoDB specific ones (geo-spatial operations, upserts, map-reduce etc.)
* Connection affinity callback
* Exception translation into Spring's [technology agnostic DAO exception hierarchy](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/dao.html#dao-exceptions).
* Exception translation into Spring's [technology agnostic DAO exception hierarchy](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/dao.html#dao-exceptions).
### Spring Data repositories
@@ -81,7 +81,7 @@ class ApplicationConfig extends AbstractMongoConfiguration {
@Override
public Mongo mongo() throws Exception {
return new Mongo();
return new MongoClient();
}
@Override
@@ -94,9 +94,9 @@ class ApplicationConfig extends AbstractMongoConfiguration {
This sets up a connection to a local MongoDB instance and enables the detection of Spring Data repositories (through `@EnableMongoRepositories`). The same configuration would look like this in XML:
```xml
<bean id="template" class="org.springframework.data.document.mongodb.MongoTemplate">
<bean id="template" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg>
<bean class="com.mongodb.Mongo">
<bean class="com.mongodb.MongoClient">
<constructor-arg value="localhost" />
<constructor-arg value="27017" />
</bean>
@@ -139,9 +139,9 @@ public class MyService {
Here are some ways for you to get involved in the community:
* Get involved with the Spring community on the Spring Community Forums. Please help out on the [forum](http://forum.springsource.org/forumdisplay.php?f=80) by responding to questions and joining the debate.
* Get involved with the Spring community on the Spring Community Forums. Please help out on the [forum](http://forum.spring.io/forum/spring-projects/data/nosql) by responding to questions and joining the debate.
* Create [JIRA](https://jira.springframework.org/browse/DATADOC) tickets for bugs and new features and comment and vote on the ones that you are interested in.
* Github is for social coding: if you want to write code, we encourage contributions through pull requests from [forks of this repository](http://help.github.com/forking/). If you want to contribute code this way, please reference a JIRA ticket as well covering the specific issue you are addressing.
* Watch for upcoming articles on Spring by [subscribing](http://www.springsource.org/node/feed) to springframework.org
* Watch for upcoming articles on Spring by [subscribing](http://spring.io/blog) to spring.io.
Before we accept a non-trivial patch or pull request we will need you to sign the [contributor's agreement](https://support.springsource.com/spring_committer_signup). Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and given the ability to merge pull requests.

34
pom.xml
View File

@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.4.0.RC1</version>
<version>1.4.4.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
<description>MongoDB support for Spring Data</description>
<url>http://www.springsource.org/spring-data/mongodb</url>
<url>http://projects.spring.io/spring-data-mongodb</url>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>1.3.0.RC1</version>
<version>1.3.4.BUILD-SNAPSHOT</version>
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
</parent>
@@ -29,8 +29,9 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>1.7.0.RC1</springdata.commons>
<springdata.commons>1.7.4.BUILD-SNAPSHOT</springdata.commons>
<mongo>2.11.4</mongo>
<mongo-osgi>${mongo}</mongo-osgi>
</properties>
<developers>
@@ -102,6 +103,16 @@
</developer>
</developers>
<profiles>
<profile>
<id>mongo-next</id>
<properties>
<mongo>2.12.0</mongo>
<mongo-osgi>2.12.0</mongo-osgi>
</properties>
</profile>
</profiles>
<dependencies>
<!-- MongoDB -->
<dependency>
@@ -113,15 +124,14 @@
<repositories>
<repository>
<id>spring-libs-snapshot</id>
<id>spring-libs-snapshopt</id>
<url>http://repo.spring.io/libs-snapshot</url>
</repository>
<repository>
<id>spring-libs-milestone</id>
<url>http://repo.springsource.org/libs-milestone-local</url>
</repository>
</repositories>
<repository>
<id>spring-libs-snapshot</id>
<url>http://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.4.0.RC1</version>
<version>1.4.4.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -48,7 +48,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.4.0.RC1</version>
<version>1.4.4.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
@@ -56,17 +56,13 @@
<artifactId>aspectjrt</artifactId>
<version>${aspectj}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>${jpa}</version>
<optional>true</optional>
</dependency>
<!-- For Tests -->

View File

@@ -1,13 +1,13 @@
Bundle-SymbolicName: org.springframework.data.mongodb.crossstore
Bundle-Name: Spring Data MongoDB Cross Store Support
Bundle-Vendor: SpringSource
Bundle-Vendor: Pivotal Software, Inc.
Bundle-ManifestVersion: 2
Import-Package:
sun.reflect;version="0";resolution:=optional
Export-Template:
org.springframework.data.mongodb.crossstore.*;version="${project.version}"
Import-Template:
com.mongodb.*;version="0",
com.mongodb.*;version="${mongo-osgi:[=.=.=,+1.0.0)}",
javax.persistence.*;version="${jpa:[=.=.=,+1.0.0)}",
org.aspectj.*;version="${aspectj:[1.0.0, 2.0.0)}",
org.bson.*;version="0",

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.4.0.RC1</version>
<version>1.4.4.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.4.0.RC1</version>
<version>1.4.4.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -1,9 +1,9 @@
Bundle-SymbolicName: org.springframework.data.mongodb.log4j
Bundle-Name: Spring Data Mongo DB Log4J Appender
Bundle-Vendor: SpringSource
Bundle-Vendor: Pivotal Software, Inc.
Bundle-ManifestVersion: 2
Import-Package:
sun.reflect;version="0";resolution:=optional
Import-Template:
com.mongodb.*;version="${mongo:[=.=,+1.0.0)}",
com.mongodb.*;version="${mongo-osgi:[=.=.=,+1.0.0)}",
org.apache.log4j.*;version="${log4j:[=.=.=,+1.0.0)}"

View File

@@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.4.0.RC1</version>
<version>1.4.4.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -137,6 +137,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${slf4j}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -145,7 +152,7 @@
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.0.8</version>
<version>${apt}</version>
<dependencies>
<dependency>
<groupId>com.mysema.querydsl</groupId>

View File

@@ -116,7 +116,9 @@ public abstract class AbstractMongoConfiguration {
* entities.
*/
protected String getMappingBasePackage() {
return getClass().getPackage().getName();
Package mappingBasePackage = getClass().getPackage();
return mappingBasePackage == null ? null : mappingBasePackage.getName();
}
/**

View File

@@ -71,6 +71,7 @@ import org.w3c.dom.Element;
* @author Oliver Gierke
* @author Maciej Walkowiak
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class MappingMongoConverterParser implements BeanDefinitionParser {
@@ -83,8 +84,11 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
*/
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
if (parserContext.isNested()) {
parserContext.getReaderContext().error("Mongo Converter must not be defined as nested bean.", element);
}
BeanDefinitionRegistry registry = parserContext.getRegistry();
String id = element.getAttribute(AbstractBeanDefinitionParser.ID_ATTRIBUTE);
id = StringUtils.hasText(id) ? id : "mappingConverter";

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,17 +15,24 @@
*/
package org.springframework.data.mongodb.config;
import static org.springframework.beans.factory.config.BeanDefinition.*;
import static org.springframework.data.mongodb.config.BeanNames.*;
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.auditing.config.AnnotationAuditingConfiguration;
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.event.AuditingEventListener;
import org.springframework.data.support.IsNewStrategyFactory;
import org.springframework.util.Assert;
@@ -57,7 +64,7 @@ class MongoAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
registerIsNewStrategyFactoryIfNecessary(registry);
defaultDependenciesIfNecessary(registry, annotationMetadata);
super.registerBeanDefinitions(annotationMetadata, registry);
}
@@ -92,14 +99,33 @@ class MongoAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
}
/**
* @param registry, the {@link BeanDefinitionRegistry} to use to register an {@link IsNewStrategyFactory} to.
* Register default bean definitions for a {@link MongoMappingContext} and an {@link IsNewStrategyFactory} in case we
* don't find beans with the assumed names in the registry.
*
* @param registry the {@link BeanDefinitionRegistry} to use to register the components into.
* @param source the source which the registered components shall be registered with
*/
private void registerIsNewStrategyFactoryIfNecessary(BeanDefinitionRegistry registry) {
private void defaultDependenciesIfNecessary(BeanDefinitionRegistry registry, Object source) {
if (!registry.containsBeanDefinition(BeanNames.IS_NEW_STRATEGY_FACTORY)) {
registry.registerBeanDefinition(BeanNames.IS_NEW_STRATEGY_FACTORY,
BeanDefinitionBuilder.rootBeanDefinition(MappingContextIsNewStrategyFactory.class)
.addConstructorArgReference(BeanNames.MAPPING_CONTEXT).getBeanDefinition());
if (!registry.containsBeanDefinition(MAPPING_CONTEXT)) {
RootBeanDefinition definition = new RootBeanDefinition(MongoMappingContext.class);
definition.setRole(ROLE_INFRASTRUCTURE);
definition.setSource(source);
registry.registerBeanDefinition(MAPPING_CONTEXT, definition);
}
if (!registry.containsBeanDefinition(IS_NEW_STRATEGY_FACTORY)) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.rootBeanDefinition(MappingContextIsNewStrategyFactory.class);
builder.addConstructorArgReference(MAPPING_CONTEXT);
AbstractBeanDefinition definition = ParsingUtils.getSourceBeanDefinition(builder, source);
definition.setRole(ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(IS_NEW_STRATEGY_FACTORY, definition);
}
}
}

View File

@@ -60,6 +60,11 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
// Driver 2.12 throws this to indicate connection problems. String comparison to avoid hard dependency
if (ex.getClass().getName().equals("com.mongodb.MongoServerSelectionException")) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
if (ex instanceof MongoInternalException) {
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2010-2013 the original author or authors.
*
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -468,10 +468,32 @@ public interface MongoOperations {
*/
<T> T findOne(Query query, Class<T> entityClass, String collectionName);
/**
* Determine result of given {@link Query} contains at least one element.
*
* @param query the {@link Query} class that specifies the criteria used to find a record.
* @param collectionName name of the collection to check for objects.
* @return
*/
boolean exists(Query query, String collectionName);
/**
* Determine result of given {@link Query} contains at least one element.
*
* @param query the {@link Query} class that specifies the criteria used to find a record.
* @param entityClass the parameterized type.
* @return
*/
boolean exists(Query query, Class<?> entityClass);
/**
* Determine result of given {@link Query} contains at least one element.
*
* @param query the {@link Query} class that specifies the criteria used to find a record.
* @param entityClass the parameterized type.
* @param collectionName name of the collection to check for objects.
* @return
*/
boolean exists(Query query, Class<?> entityClass, String collectionName);
/**
@@ -529,12 +551,58 @@ public interface MongoOperations {
*/
<T> T findById(Object id, Class<T> entityClass, String collectionName);
/**
* Triggers <a href="http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param entityClass the parameterized type.
* @return
*/
<T> T findAndModify(Query query, Update update, Class<T> entityClass);
/**
* Triggers <a href="http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param entityClass the parameterized type.
* @param collectionName the collection to query.
* @return
*/
<T> T findAndModify(Query query, Update update, Class<T> entityClass, String collectionName);
/**
* Triggers <a href="http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
* {@link FindAndModifyOptions} into account.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param options the {@link FindAndModifyOptions} holding additional information.
* @param entityClass the parameterized type.
* @return
*/
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass);
/**
* Triggers <a href="http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
* {@link FindAndModifyOptions} into account.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param options the {@link FindAndModifyOptions} holding additional information.
* @param entityClass the parameterized type.
* @param collectionName the collection to query.
* @return
*/
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
String collectionName);
@@ -598,9 +666,9 @@ public interface MongoOperations {
* <p/>
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Spring 3.0's new Type Conversion API.
* See <a href="http://static.springsource.org/spring/docs/3.0.x/reference/validation.html#core-convert">Spring 3 Type
* Conversion"</a> for more details.
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See <a
* href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert"
* >Spring's Type Conversion"</a> for more details.
* <p/>
* <p/>
* Insert is used to initially store the object into the database. To update an existing object use the save method.
@@ -655,9 +723,9 @@ public interface MongoOperations {
* <p/>
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Spring 3.0's new Type Conversion API.
* See <a href="http://static.springsource.org/spring/docs/3.0.x/reference/validation.html#core-convert">Spring 3 Type
* Conversion"</a> for more details.
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See <a
* href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert"
* >Spring's Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection
*/
@@ -672,9 +740,9 @@ public interface MongoOperations {
* <p/>
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Spring 3.0's new Type Cobnversion API.
* See <a href="http://static.springsource.org/spring/docs/3.0.x/reference/validation.html#core-convert">Spring 3 Type
* Conversion"</a> for more details.
* property type will be handled by Spring's BeanWrapper class that leverages Type Cobnversion API. See <a
* http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert">Spring's
* Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection
* @param collectionName name of the collection to store the object in
@@ -814,6 +882,14 @@ public interface MongoOperations {
*/
void remove(Query query, Class<?> entityClass);
/**
* Remove all documents that match the provided query document criteria from the the collection used to store the
* entityClass. The Class parameter is also used to help convert the Id of the object if it is present in the query.
*
* @param query
* @param entityClass
* @param collectionName
*/
void remove(Query query, Class<?> entityClass, String collectionName);
/**

View File

@@ -1,8 +1,26 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import org.springframework.transaction.support.ResourceHolder;
import org.springframework.transaction.support.ResourceHolderSynchronization;
/**
* @author Oliver Gierke
*/
class MongoSynchronization extends ResourceHolderSynchronization<ResourceHolder, Object> {
public MongoSynchronization(ResourceHolder resourceHolder, Object resourceKey) {

View File

@@ -352,7 +352,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
public void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch) {
executeQuery(query, collectionName, dch, new QueryCursorPreparer(query));
executeQuery(query, collectionName, dch, new QueryCursorPreparer(query, null));
}
/**
@@ -530,7 +530,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
new QueryCursorPreparer(query));
new QueryCursorPreparer(query, entityClass));
}
public <T> T findById(Object id, Class<T> entityClass) {
@@ -612,8 +612,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
public <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
String collectionName) {
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(), query.getSortObject(),
entityClass, update, options);
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), entityClass, update, options);
}
// Find methods that take a Query to express the query and that return a single object that is also removed from the
@@ -624,8 +624,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
public <T> T findAndRemove(Query query, Class<T> entityClass, String collectionName) {
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(), query.getSortObject(),
entityClass);
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), entityClass);
}
public long count(Query query, Class<?> entityClass) {
@@ -1016,7 +1017,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
: collection.update(queryObj, updateObj, upsert, multi, writeConcernToUse);
if (entity != null && entity.hasVersionProperty() && !multi) {
if (writeResult.getN() == 0) {
if (writeResult.getN() == 0 && dbObjectContainsVersionProperty(queryObj, entity)) {
throw new OptimisticLockingFailureException("Optimistic lock exception on saving entity: "
+ updateObj.toMap().toString() + " to collection " + collectionName);
}
@@ -1031,14 +1032,22 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
private void increaseVersionForUpdateIfNecessary(MongoPersistentEntity<?> persistentEntity, Update update) {
if (persistentEntity != null && persistentEntity.hasVersionProperty()) {
String versionPropertyField = persistentEntity.getVersionProperty().getFieldName();
if (!update.getUpdateObject().containsField(versionPropertyField)) {
update.inc(versionPropertyField, 1L);
String versionFieldName = persistentEntity.getVersionProperty().getFieldName();
if (!update.modifies(versionFieldName)) {
update.inc(versionFieldName, 1L);
}
}
}
private boolean dbObjectContainsVersionProperty(DBObject dbObject, MongoPersistentEntity<?> persistentEntity) {
if (persistentEntity == null || !persistentEntity.hasVersionProperty()) {
return false;
}
return dbObject.containsField(persistentEntity.getVersionProperty().getFieldName());
}
public void remove(Object object) {
if (object == null) {
@@ -1362,13 +1371,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
"Can not use skip or field specification with map reduce operations");
}
if (query.getQueryObject() != null) {
copyMapReduceOptions.put("query", query.getQueryObject());
copyMapReduceOptions.put("query", queryMapper.getMappedObject(query.getQueryObject(), null));
}
if (query.getLimit() > 0) {
copyMapReduceOptions.put("limit", query.getLimit());
}
if (query.getSortObject() != null) {
copyMapReduceOptions.put("sort", query.getSortObject());
copyMapReduceOptions.put("sort", queryMapper.getMappedObject(query.getSortObject(), null));
}
}
return copyMapReduceOptions;
@@ -1565,6 +1574,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
increaseVersionForUpdateIfNecessary(entity, update);
DBObject mappedQuery = queryMapper.getMappedObject(query, entity);
DBObject mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
@@ -1848,6 +1859,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
return converter;
}
private DBObject getMappedSortObject(Query query, Class<?> type) {
if (query == null || query.getSortObject() == null) {
return null;
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
return queryMapper.getMappedObject(query.getSortObject(), entity);
}
// Callback implementations
/**
@@ -2041,9 +2062,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
class QueryCursorPreparer implements CursorPreparer {
private final Query query;
private final Class<?> type;
public QueryCursorPreparer(Query query, Class<?> type) {
public QueryCursorPreparer(Query query) {
this.query = query;
this.type = type;
}
/*
@@ -2071,7 +2095,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
cursorToUse = cursorToUse.limit(query.getLimit());
}
if (query.getSortObject() != null) {
cursorToUse = cursorToUse.sort(query.getSortObject());
DBObject sortDbo = type != null ? getMappedSortObject(query, type) : query.getSortObject();
cursorToUse = cursorToUse.sort(sortDbo);
}
if (StringUtils.hasText(query.getHint())) {
cursorToUse = cursorToUse.hint(query.getHint());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -248,7 +248,7 @@ public class Aggregation {
if (operation instanceof FieldsExposingAggregationOperation) {
FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation;
context = new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields());
context = new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), rootContext);
}
}

View File

@@ -268,14 +268,21 @@ public final class ExposedFields implements Iterable<ExposedField> {
return field.isAliased();
}
/**
* @return the synthetic
*/
public boolean isSynthetic() {
return synthetic;
}
/**
* Returns whether the field can be referred to using the given name.
*
* @param input
* @param name
* @return
*/
public boolean canBeReferredToBy(String input) {
return getTarget().equals(input);
public boolean canBeReferredToBy(String name) {
return getName().equals(name) || getTarget().equals(name);
}
/*
@@ -340,6 +347,7 @@ public final class ExposedFields implements Iterable<ExposedField> {
public FieldReference(ExposedField field) {
Assert.notNull(field, "ExposedField must not be null!");
this.field = field;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,16 +32,22 @@ import com.mongodb.DBObject;
class ExposedFieldsAggregationOperationContext implements AggregationOperationContext {
private final ExposedFields exposedFields;
private final AggregationOperationContext rootContext;
/**
* Creates a new {@link ExposedFieldsAggregationOperationContext} from the given {@link ExposedFields}.
* Creates a new {@link ExposedFieldsAggregationOperationContext} from the given {@link ExposedFields}. Uses the given
* {@link AggregationOperationContext} to perform a mapping to mongo types if necessary.
*
* @param exposedFields must not be {@literal null}.
* @param rootContext must not be {@literal null}.
*/
public ExposedFieldsAggregationOperationContext(ExposedFields exposedFields) {
public ExposedFieldsAggregationOperationContext(ExposedFields exposedFields, AggregationOperationContext rootContext) {
Assert.notNull(exposedFields, "ExposedFields must not be null!");
Assert.notNull(rootContext, "RootContext must not be null!");
this.exposedFields = exposedFields;
this.rootContext = rootContext;
}
/*
@@ -50,7 +56,7 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
*/
@Override
public DBObject getMappedObject(DBObject dbObject) {
return dbObject;
return rootContext.getMappedObject(dbObject);
}
/*
@@ -59,7 +65,7 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
*/
@Override
public FieldReference getReference(Field field) {
return getReference(field.getTarget());
return getReference(field, field.getTarget());
}
/*
@@ -68,11 +74,42 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
*/
@Override
public FieldReference getReference(String name) {
return getReference(null, name);
}
ExposedField field = exposedFields.getField(name);
/**
* Returns a {@link FieldReference} to the given {@link Field} with the given {@code name}.
*
* @param field may be {@literal null}
* @param name must not be {@literal null}
* @return
*/
private FieldReference getReference(Field field, String name) {
if (field != null) {
return new FieldReference(field);
Assert.notNull(name, "Name must not be null!");
ExposedField exposedField = exposedFields.getField(name);
if (exposedField != null) {
if (field != null) {
// we return a FieldReference to the given field directly to make sure that we reference the proper alias here.
return new FieldReference(new ExposedField(field, exposedField.isSynthetic()));
}
return new FieldReference(exposedField);
}
if (name.contains(".")) {
// for nested field references we only check that the root field exists.
ExposedField rootField = exposedFields.getField(name.split("\\.")[0]);
if (rootField != null) {
// We have to synthetic to true, in order to render the field-name as is.
return new FieldReference(new ExposedField(name, true));
}
}
throw new IllegalArgumentException(String.format("Invalid reference '%s'!", name));

View File

@@ -262,7 +262,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
@Override
public ProjectionOperation as(String alias) {
Field expressionField = Fields.field(alias, "expr");
Field expressionField = Fields.field(alias, alias);
return this.operation.and(new ExpressionProjection(expressionField, this.value.toString(), params));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -495,7 +495,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
if (currentNode.hasfirstChildNotOfType(Indexer.class)) {
// we have a property path expression like: foo.bar -> render as reference
return context.getFieldReference().toString();
return context.addToPreviousOrReturn(context.getFieldReference().toString());
}
return context.addToPreviousOrReturn(currentNode.getValue());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 the original author or authors.
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,14 @@ package org.springframework.data.mongodb.core.convert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,6 +57,7 @@ import org.springframework.util.Assert;
* .
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class CustomConversions {
@@ -67,7 +69,7 @@ public class CustomConversions {
private final Set<ConvertiblePair> writingPairs;
private final Set<Class<?>> customSimpleTypes;
private final SimpleTypeHolder simpleTypeHolder;
private final Map<Class<?>, HashMap<Class<?>, CacheValue>> cache;
private final ConcurrentMap<ConvertiblePair, CacheValue> customReadTargetTypes;
private final List<Object> converters;
@@ -90,24 +92,30 @@ public class CustomConversions {
this.readingPairs = new LinkedHashSet<ConvertiblePair>();
this.writingPairs = new LinkedHashSet<ConvertiblePair>();
this.customSimpleTypes = new HashSet<Class<?>>();
this.cache = new HashMap<Class<?>, HashMap<Class<?>, CacheValue>>();
this.customReadTargetTypes = new ConcurrentHashMap<GenericConverter.ConvertiblePair, CacheValue>();
this.converters = new ArrayList<Object>();
this.converters.addAll(converters);
this.converters.add(CustomToStringConverter.INSTANCE);
this.converters.add(BigDecimalToStringConverter.INSTANCE);
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.add(DBObjectToStringConverter.INSTANCE);
this.converters.addAll(JodaTimeConverters.getConvertersToRegister());
List<Object> toRegister = new ArrayList<Object>();
for (Object c : this.converters) {
toRegister.addAll(converters);
toRegister.add(CustomToStringConverter.INSTANCE);
toRegister.add(BigDecimalToStringConverter.INSTANCE);
toRegister.add(StringToBigDecimalConverter.INSTANCE);
toRegister.add(BigIntegerToStringConverter.INSTANCE);
toRegister.add(StringToBigIntegerConverter.INSTANCE);
toRegister.add(URLToStringConverter.INSTANCE);
toRegister.add(StringToURLConverter.INSTANCE);
toRegister.add(DBObjectToStringConverter.INSTANCE);
toRegister.addAll(JodaTimeConverters.getConvertersToRegister());
// Add user provided converters to make sure they can override the defaults
for (Object c : toRegister) {
registerConversion(c);
}
Collections.reverse(toRegister);
this.converters = Collections.unmodifiableList(toRegister);
this.simpleTypeHolder = new SimpleTypeHolder(customSimpleTypes, MongoSimpleTypes.HOLDER);
}
@@ -195,25 +203,25 @@ public class CustomConversions {
*
* @param pair
*/
private void register(ConverterRegistration context) {
private void register(ConverterRegistration converterRegistration) {
ConvertiblePair pair = context.getConvertiblePair();
ConvertiblePair pair = converterRegistration.getConvertiblePair();
if (context.isReading()) {
if (converterRegistration.isReading()) {
readingPairs.add(pair);
if (LOG.isWarnEnabled() && !context.isSimpleSourceType()) {
if (LOG.isWarnEnabled() && !converterRegistration.isSimpleSourceType()) {
LOG.warn(String.format(READ_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType()));
}
}
if (context.isWriting()) {
if (converterRegistration.isWriting()) {
writingPairs.add(pair);
customSimpleTypes.add(pair.getSourceType());
if (LOG.isWarnEnabled() && !context.isSimpleTargetType()) {
if (LOG.isWarnEnabled() && !converterRegistration.isSimpleTargetType()) {
LOG.warn(String.format(WRITE_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType()));
}
}
@@ -223,11 +231,11 @@ public class CustomConversions {
* Returns the target type to convert to in case we have a custom conversion registered to convert the given source
* type into a Mongo native one.
*
* @param source must not be {@literal null}
* @param sourceType must not be {@literal null}
* @return
*/
public Class<?> getCustomWriteTarget(Class<?> source) {
return getCustomWriteTarget(source, null);
public Class<?> getCustomWriteTarget(Class<?> sourceType) {
return getCustomWriteTarget(sourceType, null);
}
/**
@@ -235,72 +243,78 @@ public class CustomConversions {
* oth the given expected type though. If {@code expectedTargetType} is {@literal null} we will simply return the
* first target type matching or {@literal null} if no conversion can be found.
*
* @param source must not be {@literal null}
* @param expectedTargetType
* @param sourceType must not be {@literal null}
* @param requestedTargetType
* @return
*/
public Class<?> getCustomWriteTarget(Class<?> source, Class<?> expectedTargetType) {
public Class<?> getCustomWriteTarget(Class<?> sourceType, Class<?> requestedTargetType) {
Assert.notNull(source);
return getCustomTarget(source, expectedTargetType, writingPairs);
Assert.notNull(sourceType);
return getCustomTarget(sourceType, requestedTargetType, writingPairs);
}
/**
* Returns whether we have a custom conversion registered to write into a Mongo native type. The returned type might
* be a subclass oth the given expected type though.
* be a subclass of the given expected type though.
*
* @param source must not be {@literal null}
* @param sourceType must not be {@literal null}
* @return
*/
public boolean hasCustomWriteTarget(Class<?> source) {
return hasCustomWriteTarget(source, null);
public boolean hasCustomWriteTarget(Class<?> sourceType) {
Assert.notNull(sourceType);
return hasCustomWriteTarget(sourceType, null);
}
/**
* Returns whether we have a custom conversion registered to write an object of the given source type into an object
* of the given Mongo native target type.
*
* @param source must not be {@literal null}.
* @param expectedTargetType
* @param sourceType must not be {@literal null}.
* @param requestedTargetType
* @return
*/
public boolean hasCustomWriteTarget(Class<?> source, Class<?> expectedTargetType) {
return getCustomWriteTarget(source, expectedTargetType) != null;
public boolean hasCustomWriteTarget(Class<?> sourceType, Class<?> requestedTargetType) {
Assert.notNull(sourceType);
return getCustomWriteTarget(sourceType, requestedTargetType) != null;
}
/**
* Returns whether we have a custom conversion registered to read the given source into the given target type.
*
* @param source must not be {@literal null}
* @param expectedTargetType must not be {@literal null}
* @param sourceType must not be {@literal null}
* @param requestedTargetType must not be {@literal null}
* @return
*/
public boolean hasCustomReadTarget(Class<?> source, Class<?> expectedTargetType) {
public boolean hasCustomReadTarget(Class<?> sourceType, Class<?> requestedTargetType) {
Assert.notNull(source);
Assert.notNull(expectedTargetType);
Assert.notNull(sourceType);
Assert.notNull(requestedTargetType);
return getCustomReadTarget(source, expectedTargetType) != null;
return getCustomReadTarget(sourceType, requestedTargetType) != null;
}
/**
* Inspects the given {@link ConvertiblePair} for ones that have a source compatible type as source. Additionally
* checks assignabilty of the target type if one is given.
* checks assignability of the target type if one is given.
*
* @param source must not be {@literal null}
* @param expectedTargetType
* @param pairs must not be {@literal null}
* @param sourceType must not be {@literal null}.
* @param requestedTargetType can be {@literal null}.
* @param pairs must not be {@literal null}.
* @return
*/
private static Class<?> getCustomTarget(Class<?> source, Class<?> expectedTargetType, Iterable<ConvertiblePair> pairs) {
private static Class<?> getCustomTarget(Class<?> sourceType, Class<?> requestedTargetType,
Iterable<ConvertiblePair> pairs) {
Assert.notNull(source);
Assert.notNull(sourceType);
Assert.notNull(pairs);
for (ConvertiblePair typePair : pairs) {
if (typePair.getSourceType().isAssignableFrom(source)) {
if (typePair.getSourceType().isAssignableFrom(sourceType)) {
Class<?> targetType = typePair.getTargetType();
if (expectedTargetType == null || targetType.isAssignableFrom(expectedTargetType)) {
if (requestedTargetType == null || targetType.isAssignableFrom(requestedTargetType)) {
return targetType;
}
}
@@ -309,27 +323,33 @@ public class CustomConversions {
return null;
}
private Class<?> getCustomReadTarget(Class<?> source, Class<?> expectedTargetType) {
/**
* Returns the actual target type for the given {@code sourceType} and {@code requestedTargetType}. Note that the
* returned {@link Class} could be an assignable type to the given {@code requestedTargetType}.
*
* @param sourceType must not be {@literal null}.
* @param requestedTargetType can be {@literal null}.
* @return
*/
private Class<?> getCustomReadTarget(Class<?> sourceType, Class<?> requestedTargetType) {
Class<?> type = expectedTargetType == null ? PlaceholderType.class : expectedTargetType;
Assert.notNull(sourceType);
Map<Class<?>, CacheValue> map;
CacheValue toReturn;
if ((map = cache.get(source)) == null || (toReturn = map.get(type)) == null) {
Class<?> target = getCustomTarget(source, type, readingPairs);
if (cache.get(source) == null) {
cache.put(source, new HashMap<Class<?>, CacheValue>());
}
Map<Class<?>, CacheValue> value = cache.get(source);
toReturn = target == null ? CacheValue.NULL : new CacheValue(target);
value.put(type, toReturn);
if (requestedTargetType == null) {
return null;
}
return toReturn.clazz;
ConvertiblePair lookupKey = new ConvertiblePair(sourceType, requestedTargetType);
CacheValue readTargetTypeValue = customReadTargetTypes.get(lookupKey);
if (readTargetTypeValue != null) {
return readTargetTypeValue.getType();
}
readTargetTypeValue = CacheValue.of(getCustomTarget(sourceType, requestedTargetType, readingPairs));
CacheValue cacheValue = customReadTargetTypes.putIfAbsent(lookupKey, readTargetTypeValue);
return cacheValue != null ? cacheValue.getType() : readTargetTypeValue.getType();
}
@WritingConverter
@@ -338,8 +358,10 @@ public class CustomConversions {
INSTANCE;
public Set<ConvertiblePair> getConvertibleTypes() {
ConvertiblePair localeToString = new ConvertiblePair(Locale.class, String.class);
ConvertiblePair booleanToString = new ConvertiblePair(Character.class, String.class);
return new HashSet<ConvertiblePair>(Arrays.asList(localeToString, booleanToString));
}
@@ -348,29 +370,29 @@ public class CustomConversions {
}
}
/**
* Placeholder type to allow registering not-found values in the converter cache.
*
* @author Patryk Wasik
* @author Oliver Gierke
*/
private static class PlaceholderType {
}
/**
* Wrapper to safely store {@literal null} values in the type cache.
*
* @author Patryk Wasik
* @author Oliver Gierke
* @author Thomas Darimont
*/
private static class CacheValue {
public static final CacheValue NULL = new CacheValue(null);
private final Class<?> clazz;
private static final CacheValue ABSENT = new CacheValue(null);
public CacheValue(Class<?> clazz) {
this.clazz = clazz;
private final Class<?> type;
public CacheValue(Class<?> type) {
this.type = type;
}
public Class<?> getType() {
return type;
}
static CacheValue of(Class<?> type) {
return type == null ? ABSENT : new CacheValue(type);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,15 +24,22 @@ import com.mongodb.DBRef;
* Used to resolve associations annotated with {@link org.springframework.data.mongodb.core.mapping.DBRef}.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.4
*/
public interface DbRefResolver {
/**
* Resolves the given {@link DBRef} into an object of the given {@link MongoPersistentProperty}'s type. The method
* might return a proxy object for the {@link DBRef} or resolve it immediately. In both cases the
* {@link DbRefResolverCallback} will be used to obtain the actual backing object.
*
* @param property will never be {@literal null}.
* @param dbref the {@link DBRef} to resolve.
* @param callback will never be {@literal null}.
* @return
*/
Object resolveDbRef(MongoPersistentProperty property, DbRefResolverCallback callback);
Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback);
/**
* Creates a {@link DBRef} instance for the given {@link org.springframework.data.mongodb.core.mapping.DBRef}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
*/
package org.springframework.data.mongodb.core.convert;
import static org.springframework.util.ReflectionUtils.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
@@ -26,11 +28,11 @@ import org.aopalliance.intercept.MethodInvocation;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.core.SpringVersion;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.LazyLoadingException;
@@ -51,10 +53,10 @@ import com.mongodb.DBRef;
*
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.4
*/
public class DefaultDbRefResolver implements DbRefResolver {
private static final boolean IS_SPRING_4_OR_BETTER = SpringVersion.getVersion().startsWith("4");
private static final boolean OBJENESIS_PRESENT = ClassUtils.isPresent("org.objenesis.Objenesis", null);
private final MongoDbFactory mongoDbFactory;
@@ -78,13 +80,13 @@ public class DefaultDbRefResolver implements DbRefResolver {
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#resolveDbRef(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.convert.DbRefResolverCallback)
*/
@Override
public Object resolveDbRef(MongoPersistentProperty property, DbRefResolverCallback callback) {
public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback) {
Assert.notNull(property, "Property must not be null!");
Assert.notNull(callback, "Callback must not be null!");
if (isLazyDbRef(property)) {
return createLazyLoadingProxy(property, callback);
return createLazyLoadingProxy(property, dbref, callback);
}
return callback.resolve(property);
@@ -109,10 +111,11 @@ public class DefaultDbRefResolver implements DbRefResolver {
* eventually resolve the value of the property.
*
* @param property must not be {@literal null}.
* @param dbref can be {@literal null}.
* @param callback must not be {@literal null}.
* @return
*/
private Object createLazyLoadingProxy(MongoPersistentProperty property, DbRefResolverCallback callback) {
private Object createLazyLoadingProxy(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback) {
ProxyFactory proxyFactory = new ProxyFactory();
Class<?> propertyType = property.getType();
@@ -121,7 +124,9 @@ public class DefaultDbRefResolver implements DbRefResolver {
proxyFactory.addInterface(type);
}
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, exceptionTranslator, callback);
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, dbref, exceptionTranslator, callback);
proxyFactory.addInterface(LazyLoadingProxy.class);
if (propertyType.isInterface()) {
proxyFactory.addInterface(propertyType);
@@ -132,7 +137,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
proxyFactory.setProxyTargetClass(true);
proxyFactory.setTargetClass(propertyType);
if (IS_SPRING_4_OR_BETTER || !OBJENESIS_PRESENT) {
if (!OBJENESIS_PRESENT) {
proxyFactory.addAdvice(interceptor);
return proxyFactory.getProxy();
}
@@ -141,7 +146,9 @@ public class DefaultDbRefResolver implements DbRefResolver {
}
/**
* @param property
* Returns whether the property shall be resolved lazily.
*
* @param property must not be {@literal null}.
* @return
*/
private boolean isLazyDbRef(MongoPersistentProperty property) {
@@ -154,31 +161,46 @@ public class DefaultDbRefResolver implements DbRefResolver {
* guaranteed to be performed only once.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
static class LazyLoadingInterceptor implements MethodInterceptor, org.springframework.cglib.proxy.MethodInterceptor,
Serializable {
private static final Method INITIALIZE_METHOD, TO_DBREF_METHOD;
private final DbRefResolverCallback callback;
private final MongoPersistentProperty property;
private final PersistenceExceptionTranslator exceptionTranslator;
private volatile boolean resolved;
private Object result;
private DBRef dbref;
static {
try {
INITIALIZE_METHOD = LazyLoadingProxy.class.getMethod("initialize");
TO_DBREF_METHOD = LazyLoadingProxy.class.getMethod("toDBRef");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Creates a new {@link LazyLoadingInterceptor} for the given {@link MongoPersistentProperty},
* {@link PersistenceExceptionTranslator} and {@link DbRefResolverCallback}.
*
* @param property must not be {@literal null}.
* @param dbref can be {@literal null}.
* @param callback must not be {@literal null}.
*/
public LazyLoadingInterceptor(MongoPersistentProperty property, PersistenceExceptionTranslator exceptionTranslator,
DbRefResolverCallback callback) {
public LazyLoadingInterceptor(MongoPersistentProperty property, DBRef dbref,
PersistenceExceptionTranslator exceptionTranslator, DbRefResolverCallback callback) {
Assert.notNull(property, "Property must not be null!");
Assert.notNull(exceptionTranslator, "Exception translator must not be null!");
Assert.notNull(callback, "Callback must not be null!");
this.dbref = dbref;
this.callback = callback;
this.exceptionTranslator = exceptionTranslator;
this.property = property;
@@ -199,9 +221,95 @@ public class DefaultDbRefResolver implements DbRefResolver {
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return ReflectionUtils.isObjectMethod(method) ? method.invoke(obj, args) : method.invoke(ensureResolved(), args);
if (INITIALIZE_METHOD.equals(method)) {
return ensureResolved();
}
if (TO_DBREF_METHOD.equals(method)) {
return this.dbref;
}
if (isObjectMethod(method) && Object.class.equals(method.getDeclaringClass())) {
if (ReflectionUtils.isToStringMethod(method)) {
return proxyToString(proxy);
}
if (ReflectionUtils.isEqualsMethod(method)) {
return proxyEquals(proxy, args[0]);
}
if (ReflectionUtils.isHashCodeMethod(method)) {
return proxyHashCode(proxy);
}
}
Object target = ensureResolved();
if (target == null) {
return null;
}
return method.invoke(target, args);
}
/**
* Returns a to string representation for the given {@code proxy}.
*
* @param proxy
* @return
*/
private String proxyToString(Object proxy) {
StringBuilder description = new StringBuilder();
if (dbref != null) {
description.append(dbref.getRef());
description.append(":");
description.append(dbref.getId());
} else {
description.append(System.identityHashCode(proxy));
}
description.append("$").append(LazyLoadingProxy.class.getSimpleName());
return description.toString();
}
/**
* Returns the hashcode for the given {@code proxy}.
*
* @param proxy
* @return
*/
private int proxyHashCode(Object proxy) {
return proxyToString(proxy).hashCode();
}
/**
* Performs an equality check for the given {@code proxy}.
*
* @param proxy
* @param that
* @return
*/
private boolean proxyEquals(Object proxy, Object that) {
if (!(that instanceof LazyLoadingProxy)) {
return false;
}
if (that == proxy) {
return true;
}
return proxyToString(proxy).equals(that.toString());
}
/**
* Will trigger the resolution if the proxy is not resolved already or return a previously resolved result.
*
* @return
*/
private Object ensureResolved() {
if (!resolved) {
@@ -212,16 +320,28 @@ public class DefaultDbRefResolver implements DbRefResolver {
return this.result;
}
/**
* Callback method for serialization.
*
* @param out
* @throws IOException
*/
private void writeObject(ObjectOutputStream out) throws IOException {
ensureResolved();
out.writeObject(this.result);
}
/**
* Callback method for deserialization.
*
* @param in
* @throws IOException
*/
private void readObject(ObjectInputStream in) throws IOException {
try {
this.resolved = true; // Object is guaranteed to be resolved after serializations
this.resolved = true;
this.result = in.readObject();
} catch (ClassNotFoundException e) {
throw new LazyLoadingException("Could not deserialize result", e);
@@ -229,6 +349,8 @@ public class DefaultDbRefResolver implements DbRefResolver {
}
/**
* Resolves the proxy into its backing object.
*
* @return
*/
private synchronized Object resolve() {
@@ -248,24 +370,30 @@ public class DefaultDbRefResolver implements DbRefResolver {
return result;
}
public boolean isResolved() {
return resolved;
}
public Object getResult() {
return result;
}
}
/**
* Static class to accomodate optional dependency on Objenesis.
* Static class to accommodate optional dependency on Objenesis.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @since 1.4
*/
private static class ObjenesisProxyEnhancer {
private static final Objenesis OBJENESIS = new ObjenesisStd(true);
private static final boolean IS_SPRING_4_OR_BETTER = ClassUtils.isPresent(
"org.springframework.core.DefaultParameterNameDiscoverer", null);
private static final InstanceCreatorStrategy INSTANCE_CREATOR;
static {
if (IS_SPRING_4_OR_BETTER) {
INSTANCE_CREATOR = new Spring4ObjenesisInstanceCreatorStrategy();
} else {
INSTANCE_CREATOR = new DefaultObjenesisInstanceCreatorStrategy();
}
}
public static Object enhanceAndGet(ProxyFactory proxyFactory, Class<?> type,
org.springframework.cglib.proxy.MethodInterceptor interceptor) {
@@ -273,10 +401,79 @@ public class DefaultDbRefResolver implements DbRefResolver {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(type);
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
enhancer.setInterfaces(new Class[] { LazyLoadingProxy.class });
Factory factory = (Factory) OBJENESIS.newInstance(enhancer.createClass());
Factory factory = (Factory) INSTANCE_CREATOR.newInstance(enhancer.createClass());
factory.setCallbacks(new Callback[] { interceptor });
return factory;
}
/**
* Strategy for constructing new instances of a given {@link Class}.
*
* @author Thomas Darimont
*/
interface InstanceCreatorStrategy {
Object newInstance(Class<?> clazz);
}
/**
* An {@link InstanceCreatorStrategy} that uses Objenesis from the classpath.
*
* @author Thomas Darimont
*/
private static class DefaultObjenesisInstanceCreatorStrategy implements InstanceCreatorStrategy {
private static final Objenesis OBJENESIS = new ObjenesisStd(true);
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.ObjenesisProxyEnhancer.InstanceCreatorStrategy#newInstance(java.lang.Class)
*/
@Override
public Object newInstance(Class<?> clazz) {
return OBJENESIS.newInstance(clazz);
}
}
/**
* An {@link InstanceCreatorStrategy} that uses a repackaged version of Objenesis from Spring 4.
*
* @author Thomas Darimont
*/
private static class Spring4ObjenesisInstanceCreatorStrategy implements InstanceCreatorStrategy {
private static final String SPRING4_OBJENESIS_CLASS_NAME = "org.springframework.objenesis.ObjenesisStd";
private static final Object OBJENESIS;
private static final Method NEW_INSTANCE_METHOD;
static {
try {
Class<?> objenesisClass = ClassUtils.forName(SPRING4_OBJENESIS_CLASS_NAME,
ObjenesisProxyEnhancer.class.getClassLoader());
OBJENESIS = BeanUtils.instantiateClass(objenesisClass.getConstructor(boolean.class), true);
NEW_INSTANCE_METHOD = objenesisClass.getMethod("newInstance", Class.class);
} catch (Exception e) {
throw new RuntimeException("Could not setup Objenesis infrastructure with Spring 4 ", e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.ObjenesisProxyEnhancer.InstanceCreatorStrategy#newInstance(java.lang.Class)
*/
@Override
public Object newInstance(Class<?> clazz) {
try {
return NEW_INSTANCE_METHOD.invoke(OBJENESIS, clazz);
} catch (Exception e) {
throw new RuntimeException("Could not created instance for " + clazz, e);
}
}
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.LazyLoadingInterceptor;
import com.mongodb.DBRef;
/**
* Allows direct interaction with the underlying {@link LazyLoadingInterceptor}.
*
* @author Thomas Darimont
* @since 1.5
*/
public interface LazyLoadingProxy {
/**
* Initializes the proxy and returns the wrapped value.
*
* @return
* @since 1.5
*/
Object initialize();
/**
* Returns the {@link DBRef} represented by this {@link LazyLoadingProxy}, may be null.
*
* @return
* @since 1.5
*/
DBRef toDBRef();
}

View File

@@ -19,6 +19,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -71,6 +73,7 @@ import com.mongodb.DBRef;
* @author Jon Brisbin
* @author Patrik Wasik
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware {
@@ -273,9 +276,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
entity.doWithAssociations(new AssociationHandler<MongoPersistentProperty>() {
public void doWithAssociation(Association<MongoPersistentProperty> association) {
MongoPersistentProperty inverseProp = association.getInverse();
MongoPersistentProperty property = association.getInverse();
Object obj = dbRefResolver.resolveDbRef(inverseProp, new DbRefResolverCallback() {
Object value = dbo.get(property.getName());
DBRef dbref = value instanceof DBRef ? (DBRef) value : null;
Object obj = dbRefResolver.resolveDbRef(property, dbref, new DbRefResolverCallback() {
@Override
public Object resolve(MongoPersistentProperty property) {
@@ -283,7 +288,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
});
wrapper.setProperty(inverseProp, obj);
wrapper.setProperty(property, obj);
}
});
@@ -303,7 +308,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Assert.isTrue(annotation != null, "The referenced property has to be mapped with @DBRef!");
}
return createDBRef(object, annotation);
// @see DATAMONGO-913
if (object instanceof LazyLoadingProxy) {
return ((LazyLoadingProxy) object).toDBRef();
}
return createDBRef(object, referingProperty);
}
/**
@@ -400,6 +410,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Object propertyObj = wrapper.getProperty(prop, prop.getType(), fieldAccessOnly);
if (null != propertyObj) {
if (!conversions.isSimpleType(propertyObj.getClass())) {
writePropertyInternal(propertyObj, dbo, prop);
} else {
@@ -446,13 +457,32 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
if (prop.isDbReference()) {
DBRef dbRefObj = createDBRef(obj, prop.getDBRef());
DBRef dbRefObj = null;
/*
* If we already have a LazyLoadingProxy, we use it's cached DBRef value instead of
* unnecessarily initializing it only to convert it to a DBRef a few instructions later.
*/
if (obj instanceof LazyLoadingProxy) {
dbRefObj = ((LazyLoadingProxy) obj).toDBRef();
}
dbRefObj = dbRefObj != null ? dbRefObj : createDBRef(obj, prop);
if (null != dbRefObj) {
accessor.put(prop, dbRefObj);
return;
}
}
/*
* If we have a LazyLoadingProxy we make sure it is initialized first.
*/
if (obj instanceof LazyLoadingProxy) {
obj = ((LazyLoadingProxy) obj).initialize();
}
// Lookup potential custom target type
Class<?> basicTargetType = conversions.getCustomWriteTarget(obj.getClass(), null);
@@ -515,7 +545,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
continue;
}
DBRef dbRef = createDBRef(element, property.getDBRef());
DBRef dbRef = createDBRef(element, property);
dbList.add(dbRef);
}
@@ -548,7 +578,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (conversions.isSimpleType(key.getClass())) {
String simpleKey = potentiallyEscapeMapKey(key.toString());
dbObject.put(simpleKey, value != null ? createDBRef(value, property.getDBRef()) : null);
dbObject.put(simpleKey, value != null ? createDBRef(value, property) : null);
} else {
throw new MappingException("Cannot use a complex object as a key value.");
@@ -741,7 +771,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return target.isAssignableFrom(value.getClass()) ? value : conversionService.convert(value, target);
}
protected DBRef createDBRef(Object target, org.springframework.data.mongodb.core.mapping.DBRef dbref) {
protected DBRef createDBRef(Object target, MongoPersistentProperty property) {
Assert.notNull(target);
@@ -750,6 +780,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
MongoPersistentEntity<?> targetEntity = mappingContext.getPersistentEntity(target.getClass());
targetEntity = targetEntity == null ? targetEntity = mappingContext.getPersistentEntity(property) : targetEntity;
if (null == targetEntity) {
throw new MappingException("No mapping metadata found for " + target.getClass());
@@ -761,14 +792,21 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
throw new MappingException("No id property found on class " + targetEntity.getType());
}
BeanWrapper<MongoPersistentEntity<Object>, Object> wrapper = BeanWrapper.create(target, conversionService);
Object id = wrapper.getProperty(idProperty, Object.class, useFieldAccessOnly);
Object id = null;
if (target.getClass().equals(idProperty.getType())) {
id = target;
} else {
BeanWrapper<MongoPersistentEntity<Object>, Object> wrapper = BeanWrapper.create(target, conversionService);
id = wrapper.getProperty(idProperty, Object.class, useFieldAccessOnly);
}
if (null == id) {
throw new MappingException("Cannot create a reference to an object with a NULL id.");
}
return dbRefResolver.createDbRef(dbref, targetEntity, idMapper.convertId(id));
return dbRefResolver.createDbRef(property == null ? null : property.getDBRef(), targetEntity,
idMapper.convertId(id));
}
protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator eval,
@@ -785,7 +823,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param sourceValue must not be {@literal null}.
* @return the converted {@link Collection} or array, will never be {@literal null}.
*/
@SuppressWarnings("unchecked")
@SuppressWarnings({ "unchecked", "null" })
private Object readCollectionOrArray(TypeInformation<?> targetType, BasicDBList sourceValue, Object parent) {
Assert.notNull(targetType);
@@ -798,11 +836,20 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class;
Collection<Object> items = targetType.getType().isArray() ? new ArrayList<Object>() : CollectionFactory
.createCollection(collectionType, sourceValue.size());
TypeInformation<?> componentType = targetType.getComponentType();
Class<?> rawComponentType = componentType == null ? null : componentType.getType();
Collection<Object> items;
if (targetType.getType().isArray()) {
items = new ArrayList<Object>();
} else if (EnumSet.class.isAssignableFrom(collectionType)) {
Assert.notNull(rawComponentType, "Component type must not be null for enum sets!");
items = EnumSet.noneOf(rawComponentType.asSubclass(Enum.class));
} else {
items = CollectionFactory.createCollection(collectionType, sourceValue.size());
}
for (int i = 0; i < sourceValue.size(); i++) {
Object dbObjItem = sourceValue.get(i);
@@ -827,31 +874,43 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param dbObject
* @return
*/
@SuppressWarnings("unchecked")
@SuppressWarnings({ "unchecked", "null", "rawtypes" })
protected Map<Object, Object> readMap(TypeInformation<?> type, DBObject dbObject, Object parent) {
Assert.notNull(dbObject);
Class<?> mapType = typeMapper.readType(dbObject, type).getType();
Map<Object, Object> map = CollectionFactory.createMap(mapType, dbObject.keySet().size());
TypeInformation<?> keyType = type.getComponentType();
Class<?> rawKeyType = keyType == null ? null : keyType.getType();
TypeInformation<?> valueType = type.getMapValueType();
Class<?> rawValueType = valueType == null ? null : valueType.getType();
Map<Object, Object> map;
if (EnumMap.class.isAssignableFrom(mapType)) {
Assert.notNull(keyType, "Key type must nut be null for enum maps!");
map = new EnumMap(rawKeyType.asSubclass(Enum.class));
} else {
map = CollectionFactory.createMap(mapType, dbObject.keySet().size());
}
Map<String, Object> sourceMap = dbObject.toMap();
for (Entry<String, Object> entry : sourceMap.entrySet()) {
if (typeMapper.isTypeKey(entry.getKey())) {
continue;
}
Object key = potentiallyUnescapeMapKey(entry.getKey());
TypeInformation<?> keyTypeInformation = type.getComponentType();
if (keyTypeInformation != null) {
Class<?> keyType = keyTypeInformation.getType();
key = conversionService.convert(key, keyType);
if (rawKeyType != null) {
key = conversionService.convert(key, rawKeyType);
}
Object value = entry.getValue();
TypeInformation<?> valueType = type.getMapValueType();
Class<?> rawValueType = valueType == null ? null : valueType.getType();
if (value instanceof DBObject) {
map.put(key, read(valueType, (DBObject) value, parent));
@@ -902,15 +961,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return getPotentiallyConvertedSimpleWrite(obj);
}
TypeInformation<?> typeHint = typeInformation == null ? ClassTypeInformation.OBJECT : typeInformation;
if (obj instanceof BasicDBList) {
return maybeConvertList((BasicDBList) obj);
return maybeConvertList((BasicDBList) obj, typeHint);
}
if (obj instanceof DBObject) {
DBObject newValueDbo = new BasicDBObject();
for (String vk : ((DBObject) obj).keySet()) {
Object o = ((DBObject) obj).get(vk);
newValueDbo.put(vk, convertToMongoType(o));
newValueDbo.put(vk, convertToMongoType(o, typeHint));
}
return newValueDbo;
}
@@ -918,17 +979,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (obj instanceof Map) {
DBObject result = new BasicDBObject();
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) obj).entrySet()) {
result.put(entry.getKey().toString(), convertToMongoType(entry.getValue()));
result.put(entry.getKey().toString(), convertToMongoType(entry.getValue(), typeHint));
}
return result;
}
if (obj.getClass().isArray()) {
return maybeConvertList(Arrays.asList((Object[]) obj));
return maybeConvertList(Arrays.asList((Object[]) obj), typeHint);
}
if (obj instanceof Collection) {
return maybeConvertList((Collection<?>) obj);
return maybeConvertList((Collection<?>) obj, typeHint);
}
DBObject newDbo = new BasicDBObject();
@@ -941,11 +1002,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return !obj.getClass().equals(typeInformation.getType()) ? newDbo : removeTypeInfoRecursively(newDbo);
}
public BasicDBList maybeConvertList(Iterable<?> source) {
public BasicDBList maybeConvertList(Iterable<?> source, TypeInformation<?> typeInformation) {
BasicDBList newDbl = new BasicDBList();
for (Object element : source) {
newDbl.add(convertToMongoType(element));
newDbl.add(convertToMongoType(element, typeInformation));
}
return newDbl;
}

View File

@@ -17,18 +17,23 @@ package org.springframework.data.mongodb.core.convert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.bson.types.ObjectId;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.PropertyReferenceException;
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.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter;
@@ -47,6 +52,7 @@ import com.mongodb.DBRef;
* @author Oliver Gierke
* @author Patryk Wasik
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class QueryMapper {
@@ -105,21 +111,36 @@ public class QueryMapper {
}
Field field = createPropertyField(entity, key, mappingContext);
Entry<String, Object> entry = getMappedObjectForField(field, query.get(key));
Object rawValue = query.get(key);
String newKey = field.getMappedKey();
if (isNestedKeyword(rawValue) && !field.isIdField()) {
Keyword keyword = new Keyword((DBObject) rawValue);
result.put(newKey, getMappedKeyword(field, keyword));
} else {
result.put(newKey, getMappedValue(field, rawValue));
}
result.put(entry.getKey(), entry.getValue());
}
return result;
}
/**
* Extracts the mapped object value for given field out of rawValue taking nested {@link Keyword}s into account
*
* @param field
* @param rawValue
* @return
*/
protected Entry<String, Object> getMappedObjectForField(Field field, Object rawValue) {
String key = field.getMappedKey();
Object value;
if (isNestedKeyword(rawValue) && !field.isIdField()) {
Keyword keyword = new Keyword((DBObject) rawValue);
value = getMappedKeyword(field, keyword);
} else {
value = getMappedValue(field, rawValue);
}
return createMapEntry(key, value);
}
/**
* @param entity
* @param key
@@ -138,7 +159,7 @@ public class QueryMapper {
* @param entity
* @return
*/
private DBObject getMappedKeyword(Keyword keyword, MongoPersistentEntity<?> entity) {
protected DBObject getMappedKeyword(Keyword keyword, MongoPersistentEntity<?> entity) {
// $or/$nor
if (keyword.isOrOrNor() || keyword.hasIterableValue()) {
@@ -147,7 +168,7 @@ public class QueryMapper {
BasicDBList newConditions = new BasicDBList();
for (Object condition : conditions) {
newConditions.add(condition instanceof DBObject ? getMappedObject((DBObject) condition, entity)
newConditions.add(isDBObject(condition) ? getMappedObject((DBObject) condition, entity)
: convertSimpleOrDBObject(condition, entity));
}
@@ -164,13 +185,13 @@ public class QueryMapper {
* @param keyword
* @return
*/
private DBObject getMappedKeyword(Field property, Keyword keyword) {
protected DBObject getMappedKeyword(Field property, Keyword keyword) {
boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists();
Object value = keyword.getValue();
Object convertedValue = needsAssociationConversion ? convertAssociation(value, property.getProperty())
: getMappedValue(property.with(keyword.getKey()), value);
Object convertedValue = needsAssociationConversion ? convertAssociation(value, property) : getMappedValue(
property.with(keyword.getKey()), value);
return new BasicDBObject(keyword.key, convertedValue);
}
@@ -184,11 +205,11 @@ public class QueryMapper {
* @param newKey the key the value will be bound to eventually
* @return
*/
private Object getMappedValue(Field documentField, Object value) {
protected Object getMappedValue(Field documentField, Object value) {
if (documentField.isIdField()) {
if (value instanceof DBObject) {
if (isDBObject(value)) {
DBObject valueDbo = (DBObject) value;
DBObject resultDbo = new BasicDBObject(valueDbo.toMap());
@@ -217,7 +238,7 @@ public class QueryMapper {
}
if (isAssociationConversionNecessary(documentField, value)) {
return convertAssociation(value, documentField.getProperty());
return convertAssociation(value, documentField);
}
return convertSimpleOrDBObject(value, documentField.getPropertyEntity());
@@ -229,13 +250,31 @@ public class QueryMapper {
* type of the given value is compatible with the type of the given document field in order to deal with potential
* query field exclusions, since MongoDB uses the {@code int} {@literal 0} as an indicator for an excluded field.
*
* @param documentField
* @param documentField must not be {@literal null}.
* @param value
* @return
*/
private boolean isAssociationConversionNecessary(Field documentField, Object value) {
return documentField.isAssociation() && value != null
&& documentField.getProperty().getActualType().isAssignableFrom(value.getClass());
protected boolean isAssociationConversionNecessary(Field documentField, Object value) {
Assert.notNull(documentField, "Document field must not be null!");
if (value == null) {
return false;
}
if (!documentField.isAssociation()) {
return false;
}
Class<? extends Object> type = value.getClass();
MongoPersistentProperty property = documentField.getProperty();
if (property.getActualType().isAssignableFrom(type)) {
return true;
}
MongoPersistentEntity<?> entity = documentField.getPropertyEntity();
return entity.hasIdProperty() && entity.getIdProperty().getActualType().isAssignableFrom(type);
}
/**
@@ -245,13 +284,13 @@ public class QueryMapper {
* @param entity
* @return
*/
private Object convertSimpleOrDBObject(Object source, MongoPersistentEntity<?> entity) {
protected Object convertSimpleOrDBObject(Object source, MongoPersistentEntity<?> entity) {
if (source instanceof BasicDBList) {
return delegateConvertToMongoType(source, entity);
}
if (source instanceof DBObject) {
if (isDBObject(source)) {
return getMappedObject((DBObject) source, entity);
}
@@ -267,7 +306,11 @@ public class QueryMapper {
* @return the converted mongo type or null if source is null
*/
protected Object delegateConvertToMongoType(Object source, MongoPersistentEntity<?> entity) {
return converter.convertToMongoType(source);
return converter.convertToMongoType(source, entity == null ? null : entity.getTypeInformation());
}
protected Object convertAssociation(Object source, Field field) {
return convertAssociation(source, field.getProperty());
}
/**
@@ -277,17 +320,16 @@ public class QueryMapper {
* @param property
* @return
*/
private Object convertAssociation(Object source, MongoPersistentProperty property) {
protected Object convertAssociation(Object source, MongoPersistentProperty property) {
if (property == null || !property.isAssociation() || source == null || source instanceof DBRef
|| !property.isEntity()) {
if (property == null || source == null || source instanceof DBRef || source instanceof DBObject) {
return source;
}
if (source instanceof Iterable) {
BasicDBList result = new BasicDBList();
for (Object element : (Iterable<?>) source) {
result.add(element instanceof DBRef ? element : converter.toDBRef(element, property));
result.add(createDbRefFor(element, property));
}
return result;
}
@@ -296,12 +338,54 @@ public class QueryMapper {
BasicDBObject result = new BasicDBObject();
DBObject dbObject = (DBObject) source;
for (String key : dbObject.keySet()) {
Object o = dbObject.get(key);
result.put(key, o instanceof DBRef ? o : converter.toDBRef(o, property));
result.put(key, createDbRefFor(dbObject.get(key), property));
}
return result;
}
return createDbRefFor(source, property);
}
/**
* Checks whether the given value is a {@link DBObject}.
*
* @param value can be {@literal null}.
* @return
*/
protected final boolean isDBObject(Object value) {
return value instanceof DBObject;
}
/**
* Creates a new {@link Entry} for the given {@link Field} with the given value.
*
* @param field must not be {@literal null}.
* @param value can be {@literal null}.
* @return
*/
protected final Entry<String, Object> createMapEntry(Field field, Object value) {
return createMapEntry(field.getMappedKey(), value);
}
/**
* Creates a new {@link Entry} with the given key and value.
*
* @param key must not be {@literal null} or empty.
* @param value can be {@literal null}
* @return
*/
private Entry<String, Object> createMapEntry(String key, Object value) {
Assert.hasText(key, "Key must not be null or empty!");
return Collections.singletonMap(key, value).entrySet().iterator().next();
}
private DBRef createDbRefFor(Object source, MongoPersistentProperty property) {
if (source instanceof DBRef) {
return (DBRef) source;
}
return converter.toDBRef(source, property);
}
@@ -360,7 +444,7 @@ public class QueryMapper {
*
* @author Oliver Gierke
*/
private static class Keyword {
static class Keyword {
private static final String N_OR_PATTERN = "\\$.*or";
@@ -450,7 +534,9 @@ public class QueryMapper {
}
/**
* Returns the underlying {@link MongoPersistentProperty} backing the field.
* Returns the underlying {@link MongoPersistentProperty} backing the field. For path traversals this will be the
* property that represents the value to handle. This means it'll be the leaf property for plain paths or the
* association property in case we refer to an association somewhere in the path.
*
* @return
*/
@@ -484,6 +570,19 @@ public class QueryMapper {
public String getMappedKey() {
return isIdField() ? ID_KEY : name;
}
/**
* Returns whether the field references an association in case it refers to a nested field.
*
* @return
*/
public boolean containsAssociation() {
return false;
}
public Association<MongoPersistentProperty> getAssociation() {
return null;
}
}
/**
@@ -494,10 +593,13 @@ public class QueryMapper {
*/
protected static class MetadataBackedField extends Field {
private static final String INVALID_ASSOCIATION_REFERENCE = "Invalid path reference %s! Associations can only be pointed to directly or via their id property!";
private final MongoPersistentEntity<?> entity;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final MongoPersistentProperty property;
private final PersistentPropertyPath<MongoPersistentProperty> path;
private final Association<MongoPersistentProperty> association;
/**
* Creates a new {@link MetadataBackedField} with the given name, {@link MongoPersistentEntity} and
@@ -509,6 +611,21 @@ public class QueryMapper {
*/
public MetadataBackedField(String name, MongoPersistentEntity<?> entity,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context) {
this(name, entity, context, null);
}
/**
* Creates a new {@link MetadataBackedField} with the given name, {@link MongoPersistentEntity} and
* {@link MappingContext} with the given {@link MongoPersistentProperty}.
*
* @param name must not be {@literal null} or empty.
* @param entity must not be {@literal null}.
* @param context must not be {@literal null}.
* @param property may be {@literal null}.
*/
public MetadataBackedField(String name, MongoPersistentEntity<?> entity,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context,
MongoPersistentProperty property) {
super(name);
@@ -518,7 +635,8 @@ public class QueryMapper {
this.mappingContext = context;
this.path = getPath(name);
this.property = path == null ? null : path.getLeafProperty();
this.property = path == null ? property : path.getLeafProperty();
this.association = findAssociation();
}
/*
@@ -527,7 +645,7 @@ public class QueryMapper {
*/
@Override
public MetadataBackedField with(String name) {
return new MetadataBackedField(name, entity, mappingContext);
return new MetadataBackedField(name, entity, mappingContext, property);
}
/*
@@ -552,7 +670,7 @@ public class QueryMapper {
*/
@Override
public MongoPersistentProperty getProperty() {
return property;
return association == null ? property : association.getInverse();
}
/*
@@ -571,9 +689,34 @@ public class QueryMapper {
*/
@Override
public boolean isAssociation() {
return association != null;
}
MongoPersistentProperty property = getProperty();
return property == null ? false : property.isAssociation();
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#getAssociation()
*/
@Override
public Association<MongoPersistentProperty> getAssociation() {
return association;
}
/**
* Finds the association property in the {@link PersistentPropertyPath}.
*
* @return
*/
private final Association<MongoPersistentProperty> findAssociation() {
if (this.path != null) {
for (MongoPersistentProperty p : this.path) {
if (p.isAssociation()) {
return p.getAssociation();
}
}
}
return null;
}
/*
@@ -585,6 +728,10 @@ public class QueryMapper {
return path == null ? name : path.toDotPath(getPropertyConverter());
}
protected PersistentPropertyPath<MongoPersistentProperty> getPath() {
return path;
}
/**
* Returns the {@link PersistentPropertyPath} for the given <code>pathExpression</code>.
*
@@ -594,8 +741,28 @@ public class QueryMapper {
private PersistentPropertyPath<MongoPersistentProperty> getPath(String pathExpression) {
try {
PropertyPath path = PropertyPath.from(pathExpression, entity.getTypeInformation());
return mappingContext.getPersistentPropertyPath(path);
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
boolean associationDetected = false;
while (iterator.hasNext()) {
MongoPersistentProperty property = iterator.next();
if (property.isAssociation()) {
associationDetected = true;
continue;
}
if (associationDetected && !property.isIdProperty()) {
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
}
}
return propertyPath;
} catch (PropertyReferenceException e) {
return null;
}

View File

@@ -17,23 +17,33 @@ package org.springframework.data.mongodb.core.convert;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map.Entry;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update.Modifier;
import org.springframework.data.mongodb.core.query.Update.Modifiers;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* A subclass of {@link QueryMapper} that retains type information on the mongo types.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class UpdateMapper extends QueryMapper {
private final MongoWriter<?> converter;
private final MongoConverter converter;
/**
* Creates a new {@link UpdateMapper} using the given {@link MongoConverter}.
@@ -59,6 +69,75 @@ public class UpdateMapper extends QueryMapper {
entity.getTypeInformation());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper#getMappedObjectForField(org.springframework.data.mongodb.core.convert.QueryMapper.Field, java.lang.Object)
*/
@Override
protected Entry<String, Object> getMappedObjectForField(Field field, Object rawValue) {
if (isDBObject(rawValue)) {
return createMapEntry(field, convertSimpleOrDBObject(rawValue, field.getPropertyEntity()));
}
if (isQuery(rawValue)) {
return createMapEntry(field,
super.getMappedObject(((Query) rawValue).getQueryObject(), field.getPropertyEntity()));
}
if (isUpdateModifier(rawValue)) {
return getMappedUpdateModifier(field, rawValue);
}
return super.getMappedObjectForField(field, getMappedValue(field, rawValue));
}
private Entry<String, Object> getMappedUpdateModifier(Field field, Object rawValue) {
Object value = null;
if (rawValue instanceof Modifier) {
value = getMappedValue((Modifier) rawValue);
} else if (rawValue instanceof Modifiers) {
DBObject modificationOperations = new BasicDBObject();
for (Modifier modifier : ((Modifiers) rawValue).getModifiers()) {
modificationOperations.putAll(getMappedValue(modifier).toMap());
}
value = modificationOperations;
} else {
throw new IllegalArgumentException(String.format("Unable to map value of type '%s'!", rawValue.getClass()));
}
return createMapEntry(field, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper#isAssociationConversionNecessary(org.springframework.data.mongodb.core.convert.QueryMapper.Field, java.lang.Object)
*/
@Override
protected boolean isAssociationConversionNecessary(Field documentField, Object value) {
return super.isAssociationConversionNecessary(documentField, value) || documentField.containsAssociation();
}
private boolean isUpdateModifier(Object value) {
return value instanceof Modifier || value instanceof Modifiers;
}
private boolean isQuery(Object value) {
return value instanceof Query;
}
private DBObject getMappedValue(Modifier modifier) {
Object value = converter.convertToMongoType(modifier.getValue(), ClassTypeInformation.OBJECT);
return new BasicDBObject(modifier.getKey(), value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper#createPropertyField(org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.String, org.springframework.data.mapping.context.MappingContext)
@@ -100,13 +179,62 @@ public class UpdateMapper extends QueryMapper {
this.key = key;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper.MetadataBackedField#getMappedKey()
*/
@Override
public String getMappedKey() {
return this.getPath() == null ? key : super.getMappedKey();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper.MetadataBackedField#getPropertyConverter()
*/
@Override
protected Converter<MongoPersistentProperty, String> getPropertyConverter() {
return new UpdatePropertyConverter(key);
return isAssociation() ? new AssociationConverter(getAssociation()) : new UpdatePropertyConverter(key);
}
/**
* Converter to skip all properties after an association property was rendered.
*
* @author Oliver Gierke
*/
private static class AssociationConverter implements Converter<MongoPersistentProperty, String> {
private final MongoPersistentProperty property;
private boolean associationFound;
/**
* Creates a new {@link AssociationConverter} for the given {@link Association}.
*
* @param association must not be {@literal null}.
*/
public AssociationConverter(Association<MongoPersistentProperty> association) {
Assert.notNull(association, "Association must not be null!");
this.property = association.getInverse();
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public String convert(MongoPersistentProperty source) {
if (associationFound) {
return null;
}
if (property.equals(source)) {
associationFound = true;
}
return source.getFieldName();
}
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2011 the original author or authors.
* Copyright 2010-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,10 +30,8 @@ import org.springframework.util.Assert;
*/
public class Point {
@Field(order = 10)
private final double x;
@Field(order = 20)
private final double y;
@Field(order = 10) private final double x;
@Field(order = 20) private final double y;
@PersistenceConstructor
public Point(double x, double y) {
@@ -69,9 +67,9 @@ public class Point {
int result = 1;
long temp;
temp = Double.doubleToLongBits(x);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + (int) (temp ^ temp >>> 32);
temp = Double.doubleToLongBits(y);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + (int) (temp ^ temp >>> 32);
return result;
}
@@ -98,6 +96,6 @@ public class Point {
@Override
public String toString() {
return String.format("Point [latitude=%f, longitude=%f]", x, y);
return String.format("Point [x=%f, y=%f]", x, y);
}
}

View File

@@ -51,10 +51,24 @@ public @interface CompoundIndex {
@Deprecated
IndexDirection direction() default IndexDirection.ASCENDING;
/**
* @see http://docs.mongodb.org/manual/core/index-unique/
* @return
*/
boolean unique() default false;
/**
* If set to true index will skip over any document that is missing the indexed field.
*
* @see http://docs.mongodb.org/manual/core/index-sparse/
* @return
*/
boolean sparse() default false;
/**
* @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping
* @return
*/
boolean dropDups() default false;
/**

View File

@@ -41,8 +41,7 @@ public class Index implements IndexDefinition {
private boolean sparse = false;
public Index() {
}
public Index() {}
public Index(String key, Direction direction) {
fieldSpec.put(key, direction);
@@ -83,16 +82,33 @@ public class Index implements IndexDefinition {
return this;
}
/**
* Reject all documents that contain a duplicate value for the indexed field.
*
* @see http://docs.mongodb.org/manual/core/index-unique/
* @return
*/
public Index unique() {
this.unique = true;
return this;
}
/**
* Skip over any document that is missing the indexed field.
*
* @see http://docs.mongodb.org/manual/core/index-sparse/
* @return
*/
public Index sparse() {
this.sparse = true;
return this;
}
/**
* @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping
* @param duplicates
* @return
*/
public Index unique(Duplicates duplicates) {
if (duplicates == Duplicates.DROP) {
this.dropDuplicates = true;

View File

@@ -32,16 +32,42 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
public @interface Indexed {
/**
* If set to true reject all documents that contain a duplicate value for the indexed field.
*
* @see http://docs.mongodb.org/manual/core/index-unique/
* @return
*/
boolean unique() default false;
IndexDirection direction() default IndexDirection.ASCENDING;
/**
* If set to true index will skip over any document that is missing the indexed field.
*
* @see http://docs.mongodb.org/manual/core/index-sparse/
* @return
*/
boolean sparse() default false;
/**
* @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping
* @return
*/
boolean dropDups() default false;
/**
* Index name.
*
* @return
*/
String name() default "";
/**
* Colleciton name for index to be created on.
*
* @return
*/
String collection() default "";
/**

View File

@@ -126,13 +126,13 @@ public abstract class AbstractMongoEventListener<E> implements ApplicationListen
public void onAfterDelete(DBObject dbo) {
if (LOG.isDebugEnabled()) {
LOG.debug("onAfterConvert({})", dbo);
LOG.debug("onAfterDelete({})", dbo);
}
}
public void onBeforeDelete(DBObject dbo) {
if (LOG.isDebugEnabled()) {
LOG.debug("onAfterConvert({})", dbo);
LOG.debug("onBeforeDelete({})", dbo);
}
}
}

View File

@@ -43,6 +43,7 @@ import com.mongodb.DBObject;
* @author Thomas Risberg
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class Criteria implements CriteriaDefinition {
@@ -117,8 +118,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $ne operator
* Creates a criterion using the {@literal $ne} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/ne/
* @param o
* @return
*/
@@ -128,8 +130,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $lt operator
* Creates a criterion using the {@literal $lt} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/lt/
* @param o
* @return
*/
@@ -139,8 +142,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $lte operator
* Creates a criterion using the {@literal $lte} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/lte/
* @param o
* @return
*/
@@ -150,8 +154,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $gt operator
* Creates a criterion using the {@literal $gt} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/gt/
* @param o
* @return
*/
@@ -161,8 +166,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $gte operator
* Creates a criterion using the {@literal $gte} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/gte/
* @param o
* @return
*/
@@ -172,8 +178,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $in operator
* Creates a criterion using the {@literal $in} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/in/
* @param o the values to match against
* @return
*/
@@ -187,8 +194,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $in operator
* Creates a criterion using the {@literal $in} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/in/
* @param c the collection containing the values to match against
* @return
*/
@@ -198,8 +206,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $nin operator
* Creates a criterion using the {@literal $nin} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/nin/
* @param o
* @return
*/
@@ -207,14 +216,22 @@ public class Criteria implements CriteriaDefinition {
return nin(Arrays.asList(o));
}
/**
* Creates a criterion using the {@literal $nin} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/nin/
* @param o
* @return
*/
public Criteria nin(Collection<?> o) {
criteria.put("$nin", o);
return this;
}
/**
* Creates a criterion using the $mod operator
* Creates a criterion using the {@literal $mod} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/mod/
* @param value
* @param remainder
* @return
@@ -228,8 +245,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $all operator
* Creates a criterion using the {@literal $all} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/all/
* @param o
* @return
*/
@@ -237,14 +255,22 @@ public class Criteria implements CriteriaDefinition {
return all(Arrays.asList(o));
}
/**
* Creates a criterion using the {@literal $all} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/all/
* @param o
* @return
*/
public Criteria all(Collection<?> o) {
criteria.put("$all", o);
return this;
}
/**
* Creates a criterion using the $size operator
* Creates a criterion using the {@literal $size} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/size/
* @param s
* @return
*/
@@ -254,8 +280,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $exists operator
* Creates a criterion using the {@literal $exists} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/exists/
* @param b
* @return
*/
@@ -265,8 +292,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $type operator
* Creates a criterion using the {@literal $type} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/type/
* @param t
* @return
*/
@@ -276,22 +304,31 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $not meta operator which affects the clause directly following
* Creates a criterion using the {@literal $not} meta operator which affects the clause directly following
*
* @see http://docs.mongodb.org/manual/reference/operator/query/not/
* @return
*/
public Criteria not() {
return not(null);
}
/**
* Creates a criterion using the {@literal $not} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/not/
* @param value
* @return
*/
private Criteria not(Object value) {
criteria.put("$not", value);
return this;
}
/**
* Creates a criterion using a $regex
* Creates a criterion using a {@literal $regex} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/regex/
* @param re
* @return
*/
@@ -300,8 +337,10 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using a $regex and $options
* Creates a criterion using a {@literal $regex} and {@literal $options} operator.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/regex/
* @see http://docs.mongodb.org/manual/reference/operator/query/regex/#op._S_options
* @param re
* @param options
* @return
@@ -334,8 +373,11 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a geospatial criterion using a $within $center operation. This is only available for Mongo 1.7 and higher.
* Creates a geospatial criterion using a {@literal $within $centerSphere} operation. This is only available for Mongo
* 1.7 and higher.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/geoWithin/
* @see http://docs.mongodb.org/manual/reference/operator/query/centerSphere/
* @param circle must not be {@literal null}
* @return
*/
@@ -345,6 +387,13 @@ public class Criteria implements CriteriaDefinition {
return this;
}
/**
* Creates a geospatial criterion using a {@literal $within} operation.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/geoWithin/
* @param shape
* @return
*/
public Criteria within(Shape shape) {
Assert.notNull(shape);
@@ -353,8 +402,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a geospatial criterion using a $near operation
* Creates a geospatial criterion using a {@literal $near} operation.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/near/
* @param point must not be {@literal null}
* @return
*/
@@ -365,8 +415,10 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a geospatial criterion using a $nearSphere operation. This is only available for Mongo 1.7 and higher.
* Creates a geospatial criterion using a {@literal $nearSphere} operation. This is only available for Mongo 1.7 and
* higher.
*
* @see http://docs.mongodb.org/manual/reference/operator/query/nearSphere/
* @param point must not be {@literal null}
* @return
*/
@@ -377,8 +429,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a geospatical criterion using a $maxDistance operation, for use with $near
* Creates a geospatical criterion using a {@literal $maxDistance} operation, for use with $near
*
* @see http://docs.mongodb.org/manual/reference/operator/query/maxDistance/
* @param maxDistance
* @return
*/
@@ -388,8 +441,9 @@ public class Criteria implements CriteriaDefinition {
}
/**
* Creates a criterion using the $elemMatch operator
* Creates a criterion using the {@literal $elemMatch} operator
*
* @see http://docs.mongodb.org/manual/reference/operator/query/elemMatch/
* @param c
* @return
*/

View File

@@ -31,6 +31,7 @@ import com.mongodb.DBObject;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public final class NearQuery {
@@ -143,10 +144,12 @@ public final class NearQuery {
/**
* Configures the {@link Pageable} to use.
*
* @param pageable
* @param pageable must not be {@literal null}
* @return
*/
public NearQuery with(Pageable pageable) {
Assert.notNull(pageable, "Pageable must not be 'null'.");
this.num = pageable.getOffset() + pageable.getPageSize();
this.skip = pageable.getOffset();
return this;
@@ -311,13 +314,18 @@ public final class NearQuery {
/**
* Adds an actual query to the {@link NearQuery} to restrict the objects considered for the actual near operation.
*
* @param query
* @param query must not be {@literal null}.
* @return
*/
public NearQuery query(Query query) {
Assert.notNull(query, "Cannot apply 'null' query on NearQuery.");
this.query = query;
this.skip = query.getSkip();
this.num = query.getLimit();
if (query.getLimit() != 0) {
this.num = query.getLimit();
}
return this;
}

View File

@@ -99,11 +99,23 @@ public class Query {
return this.fieldSpec;
}
/**
* Set number of documents to skip before returning results.
*
* @param skip
* @return
*/
public Query skip(int skip) {
this.skip = skip;
return this;
}
/**
* Limit the number of returned documents to {@code limit}.
*
* @param limit
* @return
*/
public Query limit(int limit) {
this.limit = limit;
return this;
@@ -231,14 +243,27 @@ public class Query {
return dbo;
}
/**
* Get the number of documents to skip.
*
* @return
*/
public int getSkip() {
return this.skip;
}
/**
* Get the maximum number of documents to be return.
*
* @return
*/
public int getLimit() {
return this.limit;
}
/**
* @return
*/
public String getHint() {
return hint;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2013 the original author or authors.
* Copyright 2010-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,18 @@
package org.springframework.data.mongodb.core.query;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
@@ -32,6 +39,7 @@ import com.mongodb.DBObject;
* @author Mark Pollack
* @author Oliver Gierke
* @author Becca Gaspard
* @author Christoph Strobl
*/
public class Update {
@@ -39,7 +47,9 @@ public class Update {
LAST, FIRST
}
private HashMap<String, Object> modifierOps = new LinkedHashMap<String, Object>();
private Set<String> keysToUpdate = new HashSet<String>();
private Map<String, Object> modifierOps = new LinkedHashMap<String, Object>();
private Map<String, PushOperatorBuilder> pushCommandBuilders = new LinkedHashMap<String, PushOperatorBuilder>(1);
/**
* Static factory method to create an Update using the provided key
@@ -73,15 +83,22 @@ public class Update {
continue;
}
update.modifierOps.put(key, object.get(key));
Object value = object.get(key);
update.modifierOps.put(key, value);
if (isKeyword(key) && value instanceof DBObject) {
update.keysToUpdate.addAll(((DBObject) value).keySet());
} else {
update.keysToUpdate.add(key);
}
}
return update;
}
/**
* Update using the $set update modifier
* Update using the {@literal $set} update modifier
*
* @see http://docs.mongodb.org/manual/reference/operator/update/set/
* @param key
* @param value
* @return
@@ -92,8 +109,9 @@ public class Update {
}
/**
* Update using the $setOnInsert update modifier
* Update using the {@literal $setOnInsert} update modifier
*
* @see http://docs.mongodb.org/manual/reference/operator/update/setOnInsert/
* @param key
* @param value
* @return
@@ -104,8 +122,9 @@ public class Update {
}
/**
* Update using the $unset update modifier
* Update using the {@literal $unset} update modifier
*
* @see http://docs.mongodb.org/manual/reference/operator/update/unset/
* @param key
* @return
*/
@@ -115,8 +134,9 @@ public class Update {
}
/**
* Update using the $inc update modifier
* Update using the {@literal $inc} update modifier
*
* @see http://docs.mongodb.org/manual/reference/operator/update/inc/
* @param key
* @param inc
* @return
@@ -127,8 +147,9 @@ public class Update {
}
/**
* Update using the $push update modifier
* Update using the {@literal $push} update modifier
*
* @see http://docs.mongodb.org/manual/reference/operator/update/push/
* @param key
* @param value
* @return
@@ -139,26 +160,46 @@ public class Update {
}
/**
* Update using the $pushAll update modifier
* Update using {@code $push} modifier. <br/>
* Allows creation of {@code $push} command for single or multiple (using {@code $each}) values.
*
* @see http://docs.mongodb.org/manual/reference/operator/update/push/
* @see http://docs.mongodb.org/manual/reference/operator/update/each/
* @param key
* @return {@link PushOperatorBuilder} for given key
*/
public PushOperatorBuilder push(String key) {
if (!pushCommandBuilders.containsKey(key)) {
pushCommandBuilders.put(key, new PushOperatorBuilder(key));
}
return pushCommandBuilders.get(key);
}
/**
* Update using the {@code $pushAll} update modifier. <br>
* <b>Note</b>: In mongodb 2.4 the usage of {@code $pushAll} has been deprecated in favor of {@code $push $each}.
* {@link #push(String)}) returns a builder that can be used to populate the {@code $each} object.
*
* @see http://docs.mongodb.org/manual/reference/operator/update/pushAll/
* @param key
* @param values
* @return
*/
public Update pushAll(String key, Object[] values) {
Object[] convertedValues = new Object[values.length];
for (int i = 0; i < values.length; i++) {
convertedValues[i] = values[i];
}
DBObject keyValue = new BasicDBObject();
keyValue.put(key, convertedValues);
modifierOps.put("$pushAll", keyValue);
addMultiFieldOperation("$pushAll", key, convertedValues);
return this;
}
/**
* Update using the $addToSet update modifier
* Update using the {@literal $addToSet} update modifier
*
* @see http://docs.mongodb.org/manual/reference/operator/update/addToSet/
* @param key
* @param value
* @return
@@ -169,8 +210,9 @@ public class Update {
}
/**
* Update using the $pop update modifier
* Update using the {@literal $pop} update modifier
*
* @see http://docs.mongodb.org/manual/reference/operator/update/pop/
* @param key
* @param pos
* @return
@@ -181,8 +223,9 @@ public class Update {
}
/**
* Update using the $pull update modifier
* Update using the {@literal $pull} update modifier
*
* @see http://docs.mongodb.org/manual/reference/operator/update/pull/
* @param key
* @param value
* @return
@@ -193,26 +236,27 @@ public class Update {
}
/**
* Update using the $pullAll update modifier
* Update using the {@literal $pullAll} update modifier
*
* @see http://docs.mongodb.org/manual/reference/operator/update/pullAll/
* @param key
* @param values
* @return
*/
public Update pullAll(String key, Object[] values) {
Object[] convertedValues = new Object[values.length];
for (int i = 0; i < values.length; i++) {
convertedValues[i] = values[i];
}
DBObject keyValue = new BasicDBObject();
keyValue.put(key, convertedValues);
modifierOps.put("$pullAll", keyValue);
addFieldOperation("$pullAll", key, convertedValues);
return this;
}
/**
* Update using the $rename update modifier
* Update using the {@literal $rename} update modifier
*
* @see http://docs.mongodb.org/manual/reference/operator/update/rename/
* @param oldName
* @param newName
* @return
@@ -230,8 +274,16 @@ public class Update {
return dbo;
}
protected void addFieldOperation(String operator, String key, Object value) {
Assert.hasText(key, "Key/Path for update must not be null or blank.");
modifierOps.put(operator, new BasicDBObject(key, value));
this.keysToUpdate.add(key);
}
protected void addMultiFieldOperation(String operator, String key, Object value) {
Assert.hasText(key, "Key/Path for update must not be null or blank.");
Object existingValue = this.modifierOps.get(operator);
DBObject keyValueMap;
@@ -248,5 +300,146 @@ public class Update {
}
keyValueMap.put(key, value);
this.keysToUpdate.add(key);
}
/**
* Determine if a given {@code key} will be touched on execution.
*
* @param key
* @return
*/
public boolean modifies(String key) {
return this.keysToUpdate.contains(key);
}
/**
* Inspects given {@code key} for '$'.
*
* @param key
* @return
*/
private static boolean isKeyword(String key) {
return StringUtils.startsWithIgnoreCase(key, "$");
}
/**
* Modifiers holds a distinct collection of {@link Modifier}
*
* @author Christoph Strobl
*/
public static class Modifiers {
private HashMap<String, Modifier> modifiers;
public Modifiers() {
this.modifiers = new LinkedHashMap<String, Modifier>(1);
}
public Collection<Modifier> getModifiers() {
return Collections.unmodifiableCollection(this.modifiers.values());
}
public void addModifier(Modifier modifier) {
this.modifiers.put(modifier.getKey(), modifier);
}
}
/**
* Marker interface of nested commands.
*
* @author Christoph Strobl
*/
public static interface Modifier {
/**
* @return the command to send eg. {@code $push}
*/
String getKey();
/**
* @return value to be sent with command
*/
Object getValue();
}
/**
* Implementation of {@link Modifier} representing {@code $each}.
*
* @author Christoph Strobl
*/
private static class Each implements Modifier {
private Object[] values;
public Each(Object... values) {
this.values = extractValues(values);
}
private Object[] extractValues(Object[] values) {
if (values == null || values.length == 0) {
return values;
}
if (values.length == 1 && values[0] instanceof Collection) {
return ((Collection<?>) values[0]).toArray();
}
Object[] convertedValues = new Object[values.length];
for (int i = 0; i < values.length; i++) {
convertedValues[i] = values[i];
}
return convertedValues;
}
@Override
public String getKey() {
return "$each";
}
@Override
public Object getValue() {
return this.values;
}
}
/**
* Builder for creating {@code $push} modifiers
*
* @author Christop Strobl
*/
public class PushOperatorBuilder {
private final String key;
private final Modifiers modifiers;
PushOperatorBuilder(String key) {
this.key = key;
this.modifiers = new Modifiers();
}
/**
* Propagates {@code $each} to {@code $push}
*
* @param values
* @return
*/
public Update each(Object... values) {
this.modifiers.addModifier(new Each(values));
return Update.this.push(key, this.modifiers);
}
/**
* Propagates {@link #value(Object)} to {@code $push}
*
* @param values
* @return
*/
public Update value(Object value) {
return Update.this.push(key, value);
}
}
}

View File

@@ -232,7 +232,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
}
private DBObject getMappedQuery(Query query) {
return query == null ? null : getMappedQuery(query.getQueryObject());
return query == null ? new Query().getQueryObject() : getMappedQuery(query.getQueryObject());
}
private DBObject getMappedQuery(DBObject query) {

View File

@@ -79,22 +79,24 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(method, parameters);
Query query = createQuery(new ConvertingParameterAccessor(operations.getConverter(), accessor));
Object result = null;
if (method.isGeoNearQuery() && method.isPageQuery()) {
MongoParameterAccessor countAccessor = new MongoParametersParameterAccessor(method, parameters);
Query countQuery = createCountQuery(new ConvertingParameterAccessor(operations.getConverter(), countAccessor));
return new GeoNearExecution(accessor).execute(query, countQuery);
result = new GeoNearExecution(accessor).execute(query, countQuery);
} else if (method.isGeoNearQuery()) {
return new GeoNearExecution(accessor).execute(query);
} else if (method.isCollectionQuery()) {
return new CollectionExecution(accessor.getPageable()).execute(query);
result = new CollectionExecution(accessor.getPageable()).execute(query);
} else if (method.isPageQuery()) {
return new PagedExecution(accessor.getPageable()).execute(query);
result = new PagedExecution(accessor.getPageable()).execute(query);
} else {
result = new SingleEntityExecution(isCountQuery()).execute(query);
}
Object result = new SingleEntityExecution(isCountQuery()).execute(query);
if (result == null) {
return result;
}

View File

@@ -19,10 +19,14 @@ 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.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.StringUtils;
import com.mongodb.util.JSONParseException;
/**
* {@link RepositoryQuery} implementation for Mongo.
@@ -66,7 +70,24 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
protected Query createQuery(ConvertingParameterAccessor accessor) {
MongoQueryCreator creator = new MongoQueryCreator(tree, accessor, context, isGeoNearQuery);
return creator.createQuery();
Query query = creator.createQuery();
String fieldSpec = this.getQueryMethod().getFieldSpecification();
if (!StringUtils.hasText(fieldSpec)) {
return query;
}
try {
BasicQuery result = new BasicQuery(query.getQueryObject().toString(), fieldSpec);
result.setSortObject(query.getSortObject());
return result;
} catch (JSONParseException o_O) {
throw new IllegalStateException(String.format("Invalid query or field specification in %s!", getQueryMethod(),
o_O));
}
}
/*

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
/**
* Sample configuration class in default package.
*
* @see DATAMONGO-877
* @author Oliver Gierke
*/
@Configuration
public class ConfigClassInDefaultPackage extends AbstractMongoConfiguration {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.config.AbstractMongoConfiguration#getDatabaseName()
*/
@Override
protected String getDatabaseName() {
return "default";
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.config.AbstractMongoConfiguration#mongo()
*/
@Override
public Mongo mongo() throws Exception {
return new MongoClient();
}
}

View File

@@ -1,20 +1,34 @@
/*
* Copyright 2010-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.core;
public interface MongoDocumentWriter {
}
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* Unit test for {@link ConfigClassInDefaultPackage}.
*
* @see DATAMONGO-877
* @author Oliver Gierke
*/
public class ConfigClassInDefaultPackageUnitTests {
/**
* @see DATAMONGO-877
*/
@Test
public void loadsConfigClassFromDefaultPackage() {
new AnnotationConfigApplicationContext(ConfigClassInDefaultPackage.class).close();
}
}

View File

@@ -23,6 +23,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
@@ -96,6 +97,21 @@ public class AuditingViaJavaConfigRepositoriesTests {
assertThat(createdBy.getFirstname(), is(this.auditor.getFirstname()));
}
/**
* @see DATAMONGO-843
*/
@Test
@SuppressWarnings("resource")
public void defaultsMappingContextIfNoneConfigured() {
new AnnotationConfigApplicationContext(SampleConfig.class);
}
@Repository
static interface AuditablePersonRepository extends MongoRepository<AuditablePerson, String> {}
@Configuration
@EnableMongoAuditing
static class SampleConfig {
}
}

View File

@@ -21,9 +21,11 @@ import static org.junit.Assert.*;
import java.util.Collections;
import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.convert.TypeDescriptor;
@@ -45,37 +47,45 @@ import com.mongodb.DBObject;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class MappingMongoConverterParserIntegrationTests {
@Rule public ExpectedException exception = ExpectedException.none();
DefaultListableBeanFactory factory;
@Before
public void setUp() {
factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("namespace/converter.xml"));
}
/**
* @see DATAMONGO-243
*/
@Test
public void allowsDbFactoryRefAttribute() {
loadValidConfiguration();
factory.getBeanDefinition("converter");
factory.getBean("converter");
}
/**
* @see DATAMONGO-725
*/
@Test
public void hasCustomTypeMapper() {
loadValidConfiguration();
MappingMongoConverter converter = factory.getBean("converter", MappingMongoConverter.class);
MongoTypeMapper customMongoTypeMapper = factory.getBean(CustomMongoTypeMapper.class);
assertThat(converter.getTypeMapper(), is(customMongoTypeMapper));
}
/**
* @see DATAMONGO-301
*/
@Test
public void scansForConverterAndSetsUpCustomConversionsAccordingly() {
loadValidConfiguration();
CustomConversions conversions = factory.getBean(CustomConversions.class);
assertThat(conversions.hasCustomWriteTarget(Person.class), is(true));
assertThat(conversions.hasCustomWriteTarget(Account.class), is(true));
@@ -87,6 +97,7 @@ public class MappingMongoConverterParserIntegrationTests {
@Test
public void activatesAbbreviatingPropertiesCorrectly() {
loadValidConfiguration();
BeanDefinition definition = factory.getBeanDefinition("abbreviatingConverter.mappingContext");
Object value = definition.getPropertyValues().getPropertyValue("fieldNamingStrategy").getValue();
@@ -95,6 +106,32 @@ public class MappingMongoConverterParserIntegrationTests {
assertThat(strategy.getBeanClassName(), is(CamelCaseAbbreviatingFieldNamingStrategy.class.getName()));
}
/**
* @see DATAMONGO-892
*/
@Test
public void shouldThrowBeanDefinitionParsingExceptionIfConverterDefinedAsNestedBean() {
exception.expect(BeanDefinitionParsingException.class);
exception.expectMessage("Mongo Converter must not be defined as nested bean.");
loadNestedBeanConfiguration();
}
private void loadValidConfiguration() {
this.loadConfiguration("namespace/converter.xml");
}
private void loadNestedBeanConfiguration() {
this.loadConfiguration("namespace/converter-nested-bean-definition.xml");
}
private void loadConfiguration(String configLocation) {
factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource(configLocation));
}
@Component
public static class SampleConverter implements Converter<Person, DBObject> {
public DBObject convert(Person source) {

View File

@@ -1,11 +1,11 @@
/*
* Copyright (c) 2011 by the original author(s).
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -15,33 +15,32 @@
*/
package org.springframework.data.mongodb.config;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration tests for {@link MongoDbFactory}.
*
* @author Thomas Risbergf
* @author Thomas Risberg
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class MongoDbFactoryNoDatabaseRunningTests {
@Autowired
MongoTemplate mongoTemplate;
@Autowired MongoTemplate mongoTemplate;
/**
* @see DATADOC-139
* @see DATAMONGO-139
*/
@Test
public void startsUpWithoutADatabaseRunning() {

View File

@@ -70,7 +70,7 @@ public abstract class DBObjectTestUtils {
}
@SuppressWarnings("unchecked")
private static <T> T getTypedValue(DBObject source, String key, Class<T> type) {
public static <T> T getTypedValue(DBObject source, String key, Class<T> type) {
Object value = source.get(key);
assertThat(value, is(notNullValue()));

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
@@ -25,6 +26,7 @@ import static org.springframework.data.mongodb.core.query.Update.*;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -32,10 +34,8 @@ import java.util.List;
import java.util.Map;
import org.bson.types.ObjectId;
import org.hamcrest.CoreMatchers;
import org.joda.time.DateTime;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -60,6 +60,7 @@ import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.LazyLoadingProxy;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.Index.Duplicates;
@@ -76,6 +77,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.CommandResult;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
@@ -101,10 +103,14 @@ import com.mongodb.WriteResult;
@ContextConfiguration("classpath:infrastructure.xml")
public class MongoTemplateTests {
private static final org.springframework.data.util.Version TWO_DOT_FOUR = org.springframework.data.util.Version
.parse("2.4");
@Autowired MongoTemplate template;
@Autowired MongoDbFactory factory;
MongoTemplate mappingTemplate;
org.springframework.data.util.Version mongoVersion;
@Rule public ExpectedException thrown = ExpectedException.none();
@@ -135,6 +141,7 @@ public class MongoTemplateTests {
@Before
public void setUp() {
cleanDb();
queryMongoVersionIfNecessary();
}
@After
@@ -142,6 +149,14 @@ public class MongoTemplateTests {
cleanDb();
}
private void queryMongoVersionIfNecessary() {
if (mongoVersion == null) {
CommandResult result = template.executeCommand("{ buildInfo: 1 }");
mongoVersion = org.springframework.data.util.Version.parse(result.get("version").toString());
}
}
protected void cleanDb() {
template.dropCollection(Person.class);
template.dropCollection(PersonWithAList.class);
@@ -168,6 +183,11 @@ public class MongoTemplateTests {
template.dropCollection(BaseDoc.class);
template.dropCollection(ObjectWithEnumValue.class);
template.dropCollection(DocumentWithCollection.class);
template.dropCollection(DocumentWithCollectionOfSimpleType.class);
template.dropCollection(DocumentWithMultipleCollections.class);
template.dropCollection(DocumentWithDBRefCollection.class);
template.dropCollection(SomeContent.class);
template.dropCollection(SomeTemplate.class);
}
@Test
@@ -280,6 +300,9 @@ public class MongoTemplateTests {
@Test
public void rejectsDuplicateIdInInsertAll() {
thrown.expect(DataIntegrityViolationException.class);
thrown.expectMessage("E11000 duplicate key error index: database.person.$_id_");
MongoTemplate template = new MongoTemplate(factory);
template.setWriteResultChecking(WriteResultChecking.EXCEPTION);
@@ -291,15 +314,7 @@ public class MongoTemplateTests {
records.add(person);
records.add(person);
try {
template.insertAll(records);
fail("Expected DataIntegrityViolationException!");
} catch (DataIntegrityViolationException e) {
assertThat(
e.getMessage(),
CoreMatchers
.startsWith("Insert list failed: E11000 duplicate key error index: database.person.$_id_ dup key: { : ObjectId"));
}
template.insertAll(records);
}
@Test
@@ -887,7 +902,7 @@ public class MongoTemplateTests {
l2.add(31);
Query q3 = new Query(Criteria.where("age").in(l1, l2));
template.find(q3, PersonWithIdPropertyOfTypeObjectId.class);
Assert.fail("Should have trown an InvalidDocumentStoreApiUsageException");
fail("Should have trown an InvalidDocumentStoreApiUsageException");
} catch (InvalidMongoDbApiUsageException e) {}
}
@@ -2202,8 +2217,8 @@ public class MongoTemplateTests {
template.findAndModify(query, update, Document.class);
Document retrieved = template.findOne(query, Document.class);
Assert.assertThat(retrieved.model, instanceOf(ModelA.class));
Assert.assertThat(retrieved.model.value(), equalTo("value2"));
assertThat(retrieved.model, instanceOf(ModelA.class));
assertThat(retrieved.model.value(), equalTo("value2"));
}
/**
@@ -2212,16 +2227,16 @@ public class MongoTemplateTests {
@Test
public void updatesShouldRetainTypeInformationEvenForCollections() {
DocumentWithCollection doc = new DocumentWithCollection();
List<Model> models = Arrays.<Model> asList(new ModelA("foo"));
DocumentWithCollection doc = new DocumentWithCollection(models);
doc.id = "4711";
doc.model = new ArrayList<Model>();
doc.model.add(new ModelA("foo"));
template.insert(doc);
Query query = new Query(Criteria.where("id").is(doc.id));
query.addCriteria(where("model.value").is("foo"));
query.addCriteria(where("models.value").is("foo"));
String newModelValue = "bar";
Update update = Update.update("model.$", new ModelA(newModelValue));
Update update = Update.update("models.$", new ModelA(newModelValue));
template.updateFirst(query, update, DocumentWithCollection.class);
Query findQuery = new Query(Criteria.where("id").is(doc.id));
@@ -2229,23 +2244,502 @@ public class MongoTemplateTests {
assertThat(result, is(notNullValue()));
assertThat(result.id, is(doc.id));
assertThat(result.model, is(notNullValue()));
assertThat(result.model, hasSize(1));
assertThat(result.model.get(0).value(), is(newModelValue));
assertThat(result.models, is(notNullValue()));
assertThat(result.models, hasSize(1));
assertThat(result.models.get(0).value(), is(newModelValue));
}
/**
* @see DATAMONGO-812
*/
@Test
public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithComplexTypes() {
assumeThat(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR), is(true));
DocumentWithCollection document = new DocumentWithCollection(Collections.<Model> emptyList());
template.save(document);
Query query = query(where("id").is(document.id));
assumeThat(template.findOne(query, DocumentWithCollection.class).models, hasSize(1));
Update update = new Update().push("models").each(new ModelA("model-b"), new ModelA("model-c"));
template.updateMulti(query, update, DocumentWithCollection.class);
assertThat(template.findOne(query, DocumentWithCollection.class).models, hasSize(3));
}
/**
* @see DATAMONGO-812
*/
@Test
public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithSimpleTypes() {
assumeThat(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR), is(true));
DocumentWithCollectionOfSimpleType document = new DocumentWithCollectionOfSimpleType();
document.values = Arrays.asList("spring");
template.save(document);
Query query = query(where("id").is(document.id));
assumeThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(1));
Update update = new Update().push("values").each("data", "mongodb");
template.updateMulti(query, update, DocumentWithCollectionOfSimpleType.class);
assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3));
}
/**
* @see DATAMONOGO-828
*/
@Test
public void updateFirstShouldDoNothingWhenCalledForEntitiesThatDoNotExist() {
Query q = query(where("id").is(Long.MIN_VALUE));
template.updateFirst(q, Update.update("lastname", "supercalifragilisticexpialidocious"), VersionedPerson.class);
assertThat(template.findOne(q, VersionedPerson.class), nullValue());
}
/**
* @see DATAMONGO-354
*/
@Test
public void testUpdateShouldAllowMultiplePushAll() {
DocumentWithMultipleCollections doc = new DocumentWithMultipleCollections();
doc.id = "1234";
doc.string1 = Arrays.asList("spring");
doc.string2 = Arrays.asList("one");
template.save(doc);
Update update = new Update().pushAll("string1", new Object[] { "data", "mongodb" });
update.pushAll("string2", new String[] { "two", "three" });
Query findQuery = new Query(Criteria.where("id").is(doc.id));
template.updateFirst(findQuery, update, DocumentWithMultipleCollections.class);
DocumentWithMultipleCollections result = template.findOne(findQuery, DocumentWithMultipleCollections.class);
assertThat(result.string1, hasItems("spring", "data", "mongodb"));
assertThat(result.string2, hasItems("one", "two", "three"));
}
/**
* @see DATAMONGO-404
*/
@Test
public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollection() {
Sample sample1 = new Sample("1", "A");
Sample sample2 = new Sample("2", "B");
template.save(sample1);
template.save(sample2);
DocumentWithDBRefCollection doc = new DocumentWithDBRefCollection();
doc.id = "1";
doc.dbRefAnnotatedList = Arrays.asList( //
sample1, //
sample2 //
);
template.save(doc);
Update update = new Update().pull("dbRefAnnotatedList", doc.dbRefAnnotatedList.get(1));
Query qry = query(where("id").is("1"));
template.updateFirst(qry, update, DocumentWithDBRefCollection.class);
DocumentWithDBRefCollection result = template.findOne(qry, DocumentWithDBRefCollection.class);
assertThat(result, is(notNullValue()));
assertThat(result.dbRefAnnotatedList, hasSize(1));
assertThat(result.dbRefAnnotatedList.get(0), is(notNullValue()));
assertThat(result.dbRefAnnotatedList.get(0).id, is((Object) "1"));
}
/**
* @see DATAMONGO-404
*/
@Test
public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollectionWhenGivenAnIdValueOfComponentTypeEntity() {
Sample sample1 = new Sample("1", "A");
Sample sample2 = new Sample("2", "B");
template.save(sample1);
template.save(sample2);
DocumentWithDBRefCollection doc = new DocumentWithDBRefCollection();
doc.id = "1";
doc.dbRefAnnotatedList = Arrays.asList( //
sample1, //
sample2 //
);
template.save(doc);
Update update = new Update().pull("dbRefAnnotatedList.id", "2");
Query qry = query(where("id").is("1"));
template.updateFirst(qry, update, DocumentWithDBRefCollection.class);
DocumentWithDBRefCollection result = template.findOne(qry, DocumentWithDBRefCollection.class);
assertThat(result, is(notNullValue()));
assertThat(result.dbRefAnnotatedList, hasSize(1));
assertThat(result.dbRefAnnotatedList.get(0), is(notNullValue()));
assertThat(result.dbRefAnnotatedList.get(0).id, is((Object) "1"));
}
/**
* @see DATAMONGO-852
*/
@Test
public void updateShouldNotBumpVersionNumberIfVersionPropertyIncludedInUpdate() {
VersionedPerson person = new VersionedPerson();
person.firstname = "Dave";
person.lastname = "Matthews";
template.save(person);
assertThat(person.id, is(notNullValue()));
Query qry = query(where("id").is(person.id));
VersionedPerson personAfterFirstSave = template.findOne(qry, VersionedPerson.class);
assertThat(personAfterFirstSave.version, is(0L));
template.updateFirst(qry, Update.update("lastname", "Bubu").set("version", 100L), VersionedPerson.class);
VersionedPerson personAfterUpdateFirst = template.findOne(qry, VersionedPerson.class);
assertThat(personAfterUpdateFirst.version, is(100L));
assertThat(personAfterUpdateFirst.lastname, is("Bubu"));
}
/**
* @see DATAMONGO-468
*/
@Test
public void shouldBeAbleToUpdateDbRefPropertyWithDomainObject() {
Sample sample1 = new Sample("1", "A");
Sample sample2 = new Sample("2", "B");
template.save(sample1);
template.save(sample2);
DocumentWithDBRefCollection doc = new DocumentWithDBRefCollection();
doc.id = "1";
doc.dbRefProperty = sample1;
template.save(doc);
Update update = new Update().set("dbRefProperty", sample2);
Query qry = query(where("id").is("1"));
template.updateFirst(qry, update, DocumentWithDBRefCollection.class);
DocumentWithDBRefCollection updatedDoc = template.findOne(qry, DocumentWithDBRefCollection.class);
assertThat(updatedDoc, is(notNullValue()));
assertThat(updatedDoc.dbRefProperty, is(notNullValue()));
assertThat(updatedDoc.dbRefProperty.id, is(sample2.id));
assertThat(updatedDoc.dbRefProperty.field, is(sample2.field));
}
/**
* @see DATAMONGO-862
*/
@Test
public void testUpdateShouldWorkForPathsOnInterfaceMethods() {
DocumentWithCollection document = new DocumentWithCollection(Arrays.<Model> asList(new ModelA("spring"),
new ModelA("data")));
template.save(document);
Query query = query(where("id").is(document.id).and("models._id").exists(true));
Update update = new Update().set("models.$.value", "mongodb");
template.findAndModify(query, update, DocumentWithCollection.class);
DocumentWithCollection result = template.findOne(query(where("id").is(document.id)), DocumentWithCollection.class);
assertThat(result.models.get(0).value(), is("mongodb"));
}
/**
* @see DATAMONGO-773
*/
@Test
public void testShouldSupportQueryWithIncludedDbRefField() {
Sample sample = new Sample("47111", "foo");
template.save(sample);
DocumentWithDBRefCollection doc = new DocumentWithDBRefCollection();
doc.id = "4711";
doc.dbRefProperty = sample;
template.save(doc);
Query qry = query(where("id").is(doc.id));
qry.fields().include("dbRefProperty");
List<DocumentWithDBRefCollection> result = template.find(qry, DocumentWithDBRefCollection.class);
assertThat(result, is(notNullValue()));
assertThat(result, hasSize(1));
assertThat(result.get(0), is(notNullValue()));
assertThat(result.get(0).dbRefProperty, is(notNullValue()));
assertThat(result.get(0).dbRefProperty.field, is(sample.field));
}
/**
* @see DATAMONGO-880
*/
@Test
public void savingAndReassigningLazyLoadingProxies() {
template.dropCollection(SomeTemplate.class);
template.dropCollection(SomeMessage.class);
template.dropCollection(SomeContent.class);
SomeContent content = new SomeContent();
content.id = "C1";
content.text = "BUBU";
template.save(content);
SomeTemplate tmpl = new SomeTemplate();
tmpl.id = "T1";
tmpl.content = content; // @DBRef(lazy=true) tmpl.content
template.save(tmpl);
SomeTemplate savedTmpl = template.findById(tmpl.id, SomeTemplate.class);
SomeMessage message = new SomeMessage();
message.id = "M1";
message.dbrefContent = savedTmpl.content; // @DBRef message.dbrefContent
message.normalContent = savedTmpl.content;
template.save(message);
SomeMessage savedMessage = template.findById(message.id, SomeMessage.class);
assertThat(savedMessage.dbrefContent.text, is(content.text));
assertThat(savedMessage.normalContent.text, is(content.text));
}
/**
* @see DATAMONGO-884
*/
@Test
public void callingNonObjectMethodsOnLazyLoadingProxyShouldReturnNullIfUnderlyingDbrefWasDeletedInbetween() {
template.dropCollection(SomeTemplate.class);
template.dropCollection(SomeContent.class);
SomeContent content = new SomeContent();
content.id = "C1";
content.text = "BUBU";
template.save(content);
SomeTemplate tmpl = new SomeTemplate();
tmpl.id = "T1";
tmpl.content = content; // @DBRef(lazy=true) tmpl.content
template.save(tmpl);
SomeTemplate savedTmpl = template.findById(tmpl.id, SomeTemplate.class);
template.remove(content);
assertThat(savedTmpl.getContent().toString(), is("someContent:C1$LazyLoadingProxy"));
assertThat(savedTmpl.getContent(), is(instanceOf(LazyLoadingProxy.class)));
assertThat(savedTmpl.getContent().getText(), is(nullValue()));
}
/**
* @see DATAMONGO-888
*/
@Test
public void sortOnIdFieldPropertyShouldBeMappedCorrectly() {
DoucmentWithNamedIdField one = new DoucmentWithNamedIdField();
one.someIdKey = "1";
one.value = "a";
DoucmentWithNamedIdField two = new DoucmentWithNamedIdField();
two.someIdKey = "2";
two.value = "b";
template.save(one);
template.save(two);
Query query = query(where("_id").in("1", "2")).with(new Sort(Direction.DESC, "someIdKey"));
assertThat(template.find(query, DoucmentWithNamedIdField.class), contains(two, one));
}
/**
* @see DATAMONGO-888
*/
@Test
public void sortOnAnnotatedFieldPropertyShouldBeMappedCorrectly() {
DoucmentWithNamedIdField one = new DoucmentWithNamedIdField();
one.someIdKey = "1";
one.value = "a";
DoucmentWithNamedIdField two = new DoucmentWithNamedIdField();
two.someIdKey = "2";
two.value = "b";
template.save(one);
template.save(two);
Query query = query(where("_id").in("1", "2")).with(new Sort(Direction.DESC, "value"));
assertThat(template.find(query, DoucmentWithNamedIdField.class), contains(two, one));
}
/**
* @see DATAMONGO-913
*/
@Test
public void shouldRetrieveInitializedValueFromDbRefAssociationAfterLoad() {
SomeContent content = new SomeContent();
content.id = "content-1";
content.name = "Content 1";
content.text = "Some text";
template.save(content);
SomeTemplate tmpl = new SomeTemplate();
tmpl.id = "template-1";
tmpl.content = content;
template.save(tmpl);
SomeTemplate result = template.findOne(query(where("content").is(tmpl.getContent())), SomeTemplate.class);
assertThat(result, is(notNullValue()));
assertThat(result.getContent(), is(notNullValue()));
assertThat(result.getContent().getId(), is(notNullValue()));
assertThat(result.getContent().getName(), is(notNullValue()));
assertThat(result.getContent().getText(), is(content.getText()));
}
/**
* @see DATAMONGO-913
*/
@Test
public void shouldReuseExistingDBRefInQueryFromDbRefAssociationAfterLoad() {
SomeContent content = new SomeContent();
content.id = "content-1";
content.name = "Content 1";
content.text = "Some text";
template.save(content);
SomeTemplate tmpl = new SomeTemplate();
tmpl.id = "template-1";
tmpl.content = content;
template.save(tmpl);
SomeTemplate result = template.findOne(query(where("content").is(tmpl.getContent())), SomeTemplate.class);
// Use lazy-loading-proxy in query
result = template.findOne(query(where("content").is(result.getContent())), SomeTemplate.class);
assertNotNull(result.getContent().getName());
assertThat(result.getContent().getName(), is(content.getName()));
}
static class DoucmentWithNamedIdField {
@Id String someIdKey;
@Field(value = "val")//
String value;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (someIdKey == null ? 0 : someIdKey.hashCode());
result = prime * result + (value == null ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof DoucmentWithNamedIdField)) {
return false;
}
DoucmentWithNamedIdField other = (DoucmentWithNamedIdField) obj;
if (someIdKey == null) {
if (other.someIdKey != null) {
return false;
}
} else if (!someIdKey.equals(other.someIdKey)) {
return false;
}
if (value == null) {
if (other.value != null) {
return false;
}
} else if (!value.equals(other.value)) {
return false;
}
return true;
}
}
static class DocumentWithDBRefCollection {
@Id public String id;
@org.springframework.data.mongodb.core.mapping.DBRef//
public List<Sample> dbRefAnnotatedList;
@org.springframework.data.mongodb.core.mapping.DBRef//
public Sample dbRefProperty;
}
static class DocumentWithCollection {
@Id public String id;
public List<Model> model;
@Id String id;
List<Model> models;
DocumentWithCollection(List<Model> models) {
this.models = models;
}
}
static class DocumentWithCollectionOfSimpleType {
@Id String id;
List<String> values;
}
static class DocumentWithMultipleCollections {
@Id String id;
List<String> string1;
List<String> string2;
}
static interface Model {
String value();
String id();
}
static class ModelA implements Model {
@Id String id;
private String value;
ModelA(String value) {
@@ -2256,6 +2750,11 @@ public class MongoTemplateTests {
public String value() {
return this.value;
}
@Override
public String id() {
return id;
}
}
static class Document {
@@ -2279,6 +2778,13 @@ public class MongoTemplateTests {
@Id String id;
String field;
public Sample() {}
public Sample(String id, String field) {
this.id = id;
this.field = field;
}
}
static class TestClass {
@@ -2371,4 +2877,39 @@ public class MongoTemplateTests {
@Id String id;
EnumValue value;
}
public static class SomeTemplate {
String id;
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) SomeContent content;
public SomeContent getContent() {
return content;
}
}
public static class SomeContent {
String id;
String text;
String name;
public String getName() {
return name;
}
public String getId() {
return id;
}
public String getText() {
return text;
}
}
static class SomeMessage {
String id;
@org.springframework.data.mongodb.core.mapping.DBRef SomeContent dbrefContent;
SomeContent normalContent;
}
}

View File

@@ -25,10 +25,14 @@ import java.util.Collections;
import java.util.regex.Pattern;
import org.bson.types.ObjectId;
import org.hamcrest.core.Is;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
@@ -37,6 +41,8 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
@@ -44,12 +50,16 @@ import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
@@ -58,6 +68,7 @@ import com.mongodb.MongoException;
* Unit tests for {@link MongoTemplate}.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@@ -68,6 +79,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Mock Mongo mongo;
@Mock DB db;
@Mock DBCollection collection;
@Mock DBCursor cursor;
MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
MappingMongoConverter converter;
@@ -83,7 +95,6 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
this.mappingContext = new MongoMappingContext();
this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext);
this.template = new MongoTemplate(factory, converter);
}
@Test(expected = IllegalArgumentException.class)
@@ -205,6 +216,46 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
assertThat(entity.id, is(5));
}
/**
* @see DATAMONGO-868
*/
@Test
public void findAndModifyShouldBumpVersionByOneWhenVersionFieldNotIncludedInUpdate() {
VersionedEntity v = new VersionedEntity();
v.id = 1;
v.version = 0;
ArgumentCaptor<DBObject> captor = ArgumentCaptor.forClass(DBObject.class);
template.findAndModify(new Query(), new Update().set("id", "10"), VersionedEntity.class);
verify(collection, times(1)).findAndModify(Matchers.any(DBObject.class),
org.mockito.Matchers.isNull(DBObject.class), org.mockito.Matchers.isNull(DBObject.class), eq(false),
captor.capture(), eq(false), eq(false));
Assert.assertThat(captor.getValue().get("$inc"), Is.<Object> is(new BasicDBObject("version", 1L)));
}
/**
* @see DATAMONGO-868
*/
@Test
public void findAndModifyShouldNotBumpVersionByOneWhenVersionFieldAlreadyIncludedInUpdate() {
VersionedEntity v = new VersionedEntity();
v.id = 1;
v.version = 0;
ArgumentCaptor<DBObject> captor = ArgumentCaptor.forClass(DBObject.class);
template.findAndModify(new Query(), new Update().set("version", 100), VersionedEntity.class);
verify(collection, times(1)).findAndModify(Matchers.any(DBObject.class), isNull(DBObject.class),
isNull(DBObject.class), eq(false), captor.capture(), eq(false), eq(false));
Assert.assertThat(captor.getValue().get("$set"), Is.<Object> is(new BasicDBObject("version", 100)));
Assert.assertThat(captor.getValue().get("$inc"), nullValue());
}
/**
* @see DATAMONGO-533
*/
@@ -235,6 +286,28 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
}));
}
/**
* @see DATAMONGO-948
*/
@Test
public void sortShouldBeTakenAsIsWhenExecutingQueryWithoutSpecificTypeInformation() {
when(db.getCollection(Mockito.any(String.class))).thenReturn(collection);
when(collection.find(Mockito.any(DBObject.class))).thenReturn(cursor);
when(cursor.sort(Mockito.any(DBObject.class))).thenReturn(cursor);
Query query = Query.query(Criteria.where("foo").is("bar")).with(new Sort("foo"));
template.executeQuery(query, "collection1", new DocumentCallbackHandler() {
@Override
public void processDocument(DBObject dbObject) {}
});
ArgumentCaptor<DBObject> captor = ArgumentCaptor.forClass(DBObject.class);
verify(cursor, times(1)).sort(captor.capture());
assertThat(captor.getValue(), equalTo(new BasicDBObjectBuilder().add("foo", 1).get()));
}
class AutogenerateableId {
@Id BigInteger id;
@@ -249,6 +322,12 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
}
}
static class VersionedEntity {
@Id Integer id;
@Version Integer version;
}
enum MyConverter implements Converter<AutogenerateableId, String> {
INSTANCE;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011 the original author or authors.
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,9 @@
*/
package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.core.query.Query.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,14 +33,13 @@ import com.mongodb.DBCursor;
* Unit tests for {@link QueryCursorPreparer}.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class QueryCursorPreparerUnitTests {
@Mock
MongoDbFactory factory;
@Mock
DBCursor cursor;
@Mock MongoDbFactory factory;
@Mock DBCursor cursor;
/**
* @see DATAMONGO-185
@@ -50,7 +49,7 @@ public class QueryCursorPreparerUnitTests {
Query query = query(where("foo").is("bar")).withHint("hint");
CursorPreparer preparer = new MongoTemplate(factory).new QueryCursorPreparer(query);
CursorPreparer preparer = new MongoTemplate(factory).new QueryCursorPreparer(query, null);
preparer.prepare(cursor);
verify(cursor).hint("hint");

View File

@@ -47,6 +47,7 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.CollectionCallback;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.util.Version;
import org.springframework.test.context.ContextConfiguration;
@@ -189,7 +190,7 @@ public class AggregationTests {
AggregationResults<TagCount> results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, TagCount.class);
assertThat(results, is(notNullValue()));
assertThat(results.getServerUsed(), is("/127.0.0.1:27017"));
assertThat(results.getServerUsed(), endsWith("127.0.0.1:27017"));
List<TagCount> tagCount = results.getMappedResults();
@@ -217,7 +218,7 @@ public class AggregationTests {
AggregationResults<TagCount> results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class);
assertThat(results, is(notNullValue()));
assertThat(results.getServerUsed(), is("/127.0.0.1:27017"));
assertThat(results.getServerUsed(), endsWith("127.0.0.1:27017"));
List<TagCount> tagCount = results.getMappedResults();
@@ -241,7 +242,7 @@ public class AggregationTests {
AggregationResults<TagCount> results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class);
assertThat(results, is(notNullValue()));
assertThat(results.getServerUsed(), is("/127.0.0.1:27017"));
assertThat(results.getServerUsed(), endsWith("127.0.0.1:27017"));
List<TagCount> tagCount = results.getMappedResults();
@@ -782,6 +783,80 @@ public class AggregationTests {
assertThat(String.valueOf(firstItem.get("_id")), is("u1"));
}
/**
* @see DATAMONGO-840
*/
@Test
public void shouldAggregateOrderDataToAnInvoice() {
mongoTemplate.dropCollection(Order.class);
double taxRate = 0.19;
LineItem product1 = new LineItem("1", "p1", 1.23);
LineItem product2 = new LineItem("2", "p2", 0.87, 2);
LineItem product3 = new LineItem("3", "p3", 5.33);
Order order = new Order("o4711", "c42", new Date()).addItem(product1).addItem(product2).addItem(product3);
mongoTemplate.save(order);
AggregationResults<Invoice> results = mongoTemplate.aggregate(newAggregation(Order.class, //
match(where("id").is(order.getId())), unwind("items"), //
project("id", "customerId", "items") //
.andExpression("items.price * items.quantity").as("lineTotal"), //
group("id") //
.sum("lineTotal").as("netAmount") //
.addToSet("items").as("items"), //
project("id", "items", "netAmount") //
.and("orderId").previousOperation() //
.andExpression("netAmount * [0]", taxRate).as("taxAmount") //
.andExpression("netAmount * (1 + [0])", taxRate).as("totalAmount") //
), Invoice.class);
Invoice invoice = results.getUniqueMappedResult();
assertThat(invoice, is(notNullValue()));
assertThat(invoice.getOrderId(), is(order.getId()));
assertThat(invoice.getNetAmount(), is(closeTo(8.3, 000001)));
assertThat(invoice.getTaxAmount(), is(closeTo(1.577, 000001)));
assertThat(invoice.getTotalAmount(), is(closeTo(9.877, 000001)));
}
/**
* @see DATAMONGO-924
*/
@Test
public void shouldAllowGroupingByAliasedFieldDefinedInFormerAggregationStage() {
mongoTemplate.dropCollection(CarPerson.class);
CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000),
new CarDescriptor.Entry("MAKE1", "MODEL2", 2001), new CarDescriptor.Entry("MAKE2", "MODEL3", 2010),
new CarDescriptor.Entry("MAKE3", "MODEL4", 2014));
CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014));
CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2011));
mongoTemplate.save(person1);
mongoTemplate.save(person2);
mongoTemplate.save(person3);
TypedAggregation<CarPerson> agg = Aggregation.newAggregation(CarPerson.class,
unwind("descriptors.carDescriptor.entries"), //
project() //
.and("descriptors.carDescriptor.entries.make").as("make") //
.and("descriptors.carDescriptor.entries.model").as("model") //
.and("firstName").as("firstName") //
.and("lastName").as("lastName"), //
group("make"));
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
assertThat(result.getMappedResults(), hasSize(3));
}
private void assertLikeStats(LikeStats like, String id, long count) {
assertThat(like, is(notNullValue()));
@@ -898,4 +973,52 @@ public class AggregationTests {
this.createDate = createDate;
}
}
@org.springframework.data.mongodb.core.mapping.Document
static class CarPerson {
@Id private String id;
private String firstName;
private String lastName;
private Descriptors descriptors;
public CarPerson(String firstname, String lastname, Entry... entries) {
this.firstName = firstname;
this.lastName = lastname;
this.descriptors = new Descriptors();
this.descriptors.carDescriptor = new CarDescriptor(entries);
}
}
static class Descriptors {
private CarDescriptor carDescriptor;
}
static class CarDescriptor {
private List<Entry> entries = new ArrayList<AggregationTests.CarDescriptor.Entry>();
public CarDescriptor(Entry... entries) {
for (Entry entry : entries) {
this.entries.add(entry);
}
}
static class Entry {
private String make;
private String model;
private int year;
public Entry() {}
public Entry(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
@@ -161,4 +162,66 @@ public class AggregationUnitTests {
assertThat(fields.get("aCnt"), is((Object) 1));
assertThat(fields.get("a"), is((Object) "$_id.a"));
}
/**
* @see DATAMONGO-838
*/
@Test
public void expressionBasedFieldsShouldBeReferencableInFollowingOperations() {
DBObject agg = newAggregation( //
project("a").andExpression("b+c").as("foo"), //
group("a").sum("foo").as("foosum") //
).toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
@SuppressWarnings("unchecked")
DBObject secondProjection = ((List<DBObject>) agg.get("pipeline")).get(1);
DBObject fields = getAsDBObject(secondProjection, "$group");
assertThat(fields.get("foosum"), is((Object) new BasicDBObject("$sum", "$foo")));
}
/**
* @see DATAMONGO-908
*/
@Test
public void shouldSupportReferingToNestedPropertiesInGroupOperation() {
DBObject agg = newAggregation( //
project("cmsParameterId", "rules"), //
unwind("rules"), //
group("cmsParameterId", "rules.ruleType").count().as("totol") //
).toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
assertThat(agg, is(notNullValue()));
DBObject group = ((List<DBObject>) agg.get("pipeline")).get(2);
DBObject fields = getAsDBObject(group, "$group");
DBObject id = getAsDBObject(fields, "_id");
assertThat(id.get("ruleType"), is((Object) "$rules.ruleType"));
}
/**
* @see DATAMONGO-924
*/
@Test
public void referencingProjectionAliasesFromPreviousStepShouldReferToTheSameFieldTarget() {
DBObject agg = newAggregation( //
project().and("foo.bar").as("ba") //
, project().and("ba").as("b") //
).toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
DBObject projection0 = extractPipelineElement(agg, 0, "$project");
assertThat(projection0, is((DBObject) new BasicDBObject("ba", "$foo.bar")));
DBObject projection1 = extractPipelineElement(agg, 1, "$project");
assertThat(projection1, is((DBObject) new BasicDBObject("b", "$ba")));
}
private DBObject extractPipelineElement(DBObject agg, int index, String operation) {
List<DBObject> pipeline = (List<DBObject>) agg.get("pipeline");
return (DBObject) pipeline.get(index).get(operation);
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.List;
/**
* @author Thomas Darimont
*/
public class Invoice {
String orderId;
double taxAmount;
double netAmount;
double totalAmount;
List<LineItem> items;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public double getTaxAmount() {
return taxAmount;
}
public void setTaxAmount(double taxAmount) {
this.taxAmount = taxAmount;
}
public double getNetAmount() {
return netAmount;
}
public void setNetAmount(double netAmount) {
this.netAmount = netAmount;
}
public double getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(double totalAmount) {
this.totalAmount = totalAmount;
}
public List<LineItem> getItems() {
return items;
}
public void setItems(List<LineItem> items) {
this.items = items;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
/**
* @author Thomas Darimont
*/
public class LineItem {
final String id;
final String caption;
final double price;
int quantity = 1;
@SuppressWarnings("unused")
private LineItem() {
this(null, null, 0.0, 0);
}
public LineItem(String id, String caption, double price) {
this.id = id;
this.caption = caption;
this.price = price;
}
public LineItem(String id, String caption, double price, int quantity) {
this(id, caption, price);
this.quantity = quantity;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* @author Thomas Darimont
*/
public class Order {
final String id;
final String customerId;
final Date orderDate;
final List<LineItem> items;
public Order(String id, String customerId, Date orderDate) {
this(id, customerId, orderDate, new ArrayList<LineItem>());
}
public Order(String id, String customerId, Date orderDate, List<LineItem> items) {
this.id = id;
this.customerId = customerId;
this.orderDate = orderDate;
this.items = items;
}
public Order addItem(LineItem item) {
List<LineItem> newItems = new ArrayList<LineItem>(items != null ? items : Collections.<LineItem> emptyList());
newItems.add(item);
return new Order(id, customerId, orderDate, newItems);
}
public String getId() {
return id;
}
public String getCustomerId() {
return customerId;
}
public Date getOrderDate() {
return orderDate;
}
public List<LineItem> getItems() {
return items;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.data.mongodb.core.Person;
/**
* Unit tests for {@link SpelExpressionTransformer}.
@@ -174,6 +175,26 @@ public class SpelExpressionTransformerUnitTests {
is("{ \"$multiply\" : [ { \"$add\" : [ 1 , 42 , 1.2345]} , 23]}"));
}
/**
* @see DATAMONGO-840
*/
@Test
public void shouldRenderCompoundExpressionsWithIndexerAndFieldReference() {
Person person = new Person();
person.setAge(10);
assertThat(transform("[0].age + a.c", person), is("{ \"$add\" : [ 10 , \"$a.c\"]}"));
}
/**
* @see DATAMONGO-840
*/
@Test
public void shouldRenderCompoundExpressionsWithOnlyFieldReferences() {
assertThat(transform("a.b + a.c"), is("{ \"$add\" : [ \"$a.b\" , \"$a.c\"]}"));
}
@Test
public void shouldRenderStringFunctions() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,25 +17,40 @@ package org.springframework.data.mongodb.core.aggregation;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import java.util.Arrays;
import java.util.List;
import org.bson.types.ObjectId;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Criteria;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* Unit tests for {@link TypeBasedAggregationOperationContext}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
@RunWith(MockitoJUnitRunner.class)
public class TypeBasedAggregationOperationContextUnitTests {
@@ -89,6 +104,104 @@ public class TypeBasedAggregationOperationContextUnitTests {
assertThat(context.getReference("id"), is(new FieldReference(new ExposedField(Fields.field("id", "_id"), true))));
}
/**
* @see DATAMONGO-912
*/
@Test
public void shouldUseCustomConversionIfPresentAndConversionIsRequiredInFirstStage() {
CustomConversions customConversions = customAgeConversions();
converter.setCustomConversions(customConversions);
customConversions.registerConvertersIn((GenericConversionService) converter.getConversionService());
AggregationOperationContext context = getContext(FooPerson.class);
MatchOperation matchStage = match(Criteria.where("age").is(new Age(10)));
ProjectionOperation projectStage = project("age", "name");
DBObject agg = newAggregation(matchStage, projectStage).toDbObject("test", context);
DBObject age = getValue((DBObject) getValue(getPipelineElementFromAggregationAt(agg, 0), "$match"), "age");
assertThat(age, is((DBObject) new BasicDBObject("v", 10)));
}
/**
* @see DATAMONGO-912
*/
@Test
public void shouldUseCustomConversionIfPresentAndConversionIsRequiredInLaterStage() {
CustomConversions customConversions = customAgeConversions();
converter.setCustomConversions(customConversions);
customConversions.registerConvertersIn((GenericConversionService) converter.getConversionService());
AggregationOperationContext context = getContext(FooPerson.class);
MatchOperation matchStage = match(Criteria.where("age").is(new Age(10)));
ProjectionOperation projectStage = project("age", "name");
DBObject agg = newAggregation(projectStage, matchStage).toDbObject("test", context);
DBObject age = getValue((DBObject) getValue(getPipelineElementFromAggregationAt(agg, 1), "$match"), "age");
assertThat(age, is((DBObject) new BasicDBObject("v", 10)));
}
@Document(collection = "person")
public static class FooPerson {
final ObjectId id;
final String name;
final Age age;
@PersistenceConstructor
FooPerson(ObjectId id, String name, Age age) {
this.id = id;
this.name = name;
this.age = age;
}
}
public static class Age {
final int value;
Age(int value) {
this.value = value;
}
}
public CustomConversions customAgeConversions() {
return new CustomConversions(Arrays.<Converter<?, ?>> asList(ageWriteConverter(), ageReadConverter()));
}
Converter<Age, DBObject> ageWriteConverter() {
return new Converter<Age, DBObject>() {
@Override
public DBObject convert(Age age) {
return new BasicDBObject("v", age.value);
}
};
}
Converter<DBObject, Age> ageReadConverter() {
return new Converter<DBObject, Age>() {
@Override
public Age convert(DBObject dbObject) {
return new Age(((Integer) dbObject.get("v")));
}
};
}
@SuppressWarnings("unchecked")
static DBObject getPipelineElementFromAggregationAt(DBObject agg, int index) {
return ((List<DBObject>) agg.get("pipeline")).get(index);
}
@SuppressWarnings("unchecked")
static <T> T getValue(DBObject o, String key) {
return (T) o.get(key);
}
private TypeBasedAggregationOperationContext getContext(Class<?> type) {
return new TypeBasedAggregationOperationContext(type, context, mapper);
}

View File

@@ -7,6 +7,7 @@ import java.net.URL;
import java.text.DateFormat;
import java.text.Format;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
@@ -183,6 +184,19 @@ public class CustomConversionsUnitTests {
assertThat(conversions.getCustomWriteTarget(DateTime.class, null), is(equalTo((Class) String.class)));
}
/**
* @see DATAMONGO-881
*/
@Test
public void customConverterOverridesDefault() {
CustomConversions conversions = new CustomConversions(Arrays.asList(CustomDateTimeConverter.INSTANCE));
GenericConversionService conversionService = new DefaultConversionService();
conversions.registerConvertersIn(conversionService);
assertThat(conversionService.convert(new DateTime(), Date.class), is(new Date(0)));
}
enum FormatToStringConverter implements Converter<Format, String> {
INSTANCE;
@@ -227,4 +241,14 @@ public class CustomConversionsUnitTests {
return "";
}
}
enum CustomDateTimeConverter implements Converter<DateTime, Date> {
INSTANCE;
@Override
public Date convert(DateTime source) {
return new Date(0);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,7 +50,10 @@ import com.mongodb.DBObject;
import com.mongodb.DBRef;
/**
* Unit tests dor {@link DbRefMappingMongoConverterUnitTests}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
@RunWith(MockitoJUnitRunner.class)
public class DbRefMappingMongoConverterUnitTests {
@@ -283,6 +286,149 @@ public class DbRefMappingMongoConverterUnitTests {
assertThat(deserializedResult.dbRefToSerializableTarget.getValue(), is(value));
}
/**
* @see DATAMONGO-884
*/
@Test
public void lazyLoadingProxyForToStringObjectMethodOverridingDbref() {
String id = "42";
String value = "bubu";
MappingMongoConverter converterSpy = spy(converter);
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
BasicDBObject dbo = new BasicDBObject();
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
lazyDbRefs.dbRefToToStringObjectMethodOverride = new ToStringObjectMethodOverrideLazyDbRefTarget(id, value);
converterSpy.write(lazyDbRefs, dbo);
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
assertThat(result.dbRefToToStringObjectMethodOverride, is(notNullValue()));
assertProxyIsResolved(result.dbRefToToStringObjectMethodOverride, false);
assertThat(result.dbRefToToStringObjectMethodOverride.toString(), is(id + ":" + value));
assertProxyIsResolved(result.dbRefToToStringObjectMethodOverride, true);
}
/**
* @see DATAMONGO-884
*/
@Test
public void callingToStringObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() {
String id = "42";
String value = "bubu";
MappingMongoConverter converterSpy = spy(converter);
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
BasicDBObject dbo = new BasicDBObject();
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
lazyDbRefs.dbRefToPlainObject = new LazyDbRefTarget(id, value);
converterSpy.write(lazyDbRefs, dbo);
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
assertThat(result.dbRefToPlainObject, is(notNullValue()));
assertProxyIsResolved(result.dbRefToPlainObject, false);
// calling Object#toString does not initialize the proxy.
String proxyString = result.dbRefToPlainObject.toString();
assertThat(proxyString, is("lazyDbRefTarget" + ":" + id + "$LazyLoadingProxy"));
assertProxyIsResolved(result.dbRefToPlainObject, false);
// calling another method not declared on object triggers proxy initialization.
assertThat(result.dbRefToPlainObject.getValue(), is(value));
assertProxyIsResolved(result.dbRefToPlainObject, true);
}
/**
* @see DATAMONGO-884
*/
@Test
public void equalsObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() {
String id = "42";
String value = "bubu";
MappingMongoConverter converterSpy = spy(converter);
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
BasicDBObject dbo = new BasicDBObject();
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
lazyDbRefs.dbRefToPlainObject = new LazyDbRefTarget(id, value);
lazyDbRefs.dbRefToToStringObjectMethodOverride = new ToStringObjectMethodOverrideLazyDbRefTarget(id, value);
converterSpy.write(lazyDbRefs, dbo);
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
assertThat(result.dbRefToPlainObject, is(notNullValue()));
assertProxyIsResolved(result.dbRefToPlainObject, false);
assertThat(result.dbRefToPlainObject, is(equalTo(result.dbRefToPlainObject)));
assertThat(result.dbRefToPlainObject, is(not(equalTo(null))));
assertThat(result.dbRefToPlainObject, is(not(equalTo((Object) lazyDbRefs.dbRefToToStringObjectMethodOverride))));
assertProxyIsResolved(result.dbRefToPlainObject, false);
}
/**
* @see DATAMONGO-884
*/
@Test
public void hashcodeObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() {
String id = "42";
String value = "bubu";
MappingMongoConverter converterSpy = spy(converter);
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
BasicDBObject dbo = new BasicDBObject();
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
lazyDbRefs.dbRefToPlainObject = new LazyDbRefTarget(id, value);
lazyDbRefs.dbRefToToStringObjectMethodOverride = new ToStringObjectMethodOverrideLazyDbRefTarget(id, value);
converterSpy.write(lazyDbRefs, dbo);
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
assertThat(result.dbRefToPlainObject, is(notNullValue()));
assertProxyIsResolved(result.dbRefToPlainObject, false);
assertThat(result.dbRefToPlainObject.hashCode(), is(311365444));
assertProxyIsResolved(result.dbRefToPlainObject, false);
}
/**
* @see DATAMONGO-884
*/
@Test
public void lazyLoadingProxyForEqualsAndHashcodeObjectMethodOverridingDbref() {
String id = "42";
String value = "bubu";
MappingMongoConverter converterSpy = spy(converter);
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
BasicDBObject dbo = new BasicDBObject();
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
lazyDbRefs.dbRefEqualsAndHashcodeObjectMethodOverride1 = new EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget(
id, value);
lazyDbRefs.dbRefEqualsAndHashcodeObjectMethodOverride2 = new EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget(
id, value);
converterSpy.write(lazyDbRefs, dbo);
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
assertProxyIsResolved(result.dbRefEqualsAndHashcodeObjectMethodOverride1, false);
assertThat(result.dbRefEqualsAndHashcodeObjectMethodOverride1, is(notNullValue()));
result.dbRefEqualsAndHashcodeObjectMethodOverride1.equals(null);
assertProxyIsResolved(result.dbRefEqualsAndHashcodeObjectMethodOverride1, true);
assertProxyIsResolved(result.dbRefEqualsAndHashcodeObjectMethodOverride2, false);
assertThat(result.dbRefEqualsAndHashcodeObjectMethodOverride2, is(notNullValue()));
result.dbRefEqualsAndHashcodeObjectMethodOverride2.hashCode();
assertProxyIsResolved(result.dbRefEqualsAndHashcodeObjectMethodOverride2, true);
}
private Object transport(Object result) {
return SerializationUtils.deserialize(SerializationUtils.serialize(result));
}
@@ -345,6 +491,7 @@ public class DbRefMappingMongoConverterUnitTests {
}
}
@SuppressWarnings("serial")
static class LazyDbRefTargetWithPeristenceConstructor extends LazyDbRefTarget {
boolean persistenceConstructorCalled;
@@ -362,6 +509,7 @@ public class DbRefMappingMongoConverterUnitTests {
}
}
@SuppressWarnings("serial")
static class LazyDbRefTargetWithPeristenceConstructorWithoutDefaultConstructor extends LazyDbRefTarget {
boolean persistenceConstructorCalled;
@@ -387,4 +535,74 @@ public class DbRefMappingMongoConverterUnitTests {
private static final long serialVersionUID = 1L;
}
static class ToStringObjectMethodOverrideLazyDbRefTarget extends LazyDbRefTarget {
private static final long serialVersionUID = 1L;
public ToStringObjectMethodOverrideLazyDbRefTarget() {}
public ToStringObjectMethodOverrideLazyDbRefTarget(String id, String value) {
super(id, value);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.id + ":" + this.value;
}
}
static class EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget extends LazyDbRefTarget {
private static final long serialVersionUID = 1L;
public EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget() {}
public EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget(String id, String value) {
super(id, value);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget other = (EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}
static class WithObjectMethodOverrideLazyDbRefs {
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) LazyDbRefTarget dbRefToPlainObject;
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) ToStringObjectMethodOverrideLazyDbRefTarget dbRefToToStringObjectMethodOverride;
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget dbRefEqualsAndHashcodeObjectMethodOverride2;
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget dbRefEqualsAndHashcodeObjectMethodOverride1;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import static org.junit.Assert.*;
import org.springframework.aop.framework.Advised;
import org.springframework.cglib.proxy.Factory;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.LazyLoadingInterceptor;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Utility class to test proxy handling for lazy loading.
@@ -39,8 +40,8 @@ public class LazyLoadingTestUtils {
public static void assertProxyIsResolved(Object target, boolean expected) {
LazyLoadingInterceptor interceptor = extractInterceptor(target);
assertThat(interceptor.isResolved(), is(expected));
assertThat(interceptor.getResult(), is(expected ? notNullValue() : nullValue()));
assertThat(ReflectionTestUtils.getField(interceptor, "resolved"), is((Object) expected));
assertThat(ReflectionTestUtils.getField(interceptor, "result"), is(expected ? notNullValue() : nullValue()));
}
private static LazyLoadingInterceptor extractInterceptor(Object proxy) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 the original author or authors.
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core.convert;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -27,6 +28,8 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -34,6 +37,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.bson.types.ObjectId;
import org.hamcrest.Matcher;
@@ -57,7 +61,9 @@ import org.springframework.data.mongodb.core.convert.DBObjectAccessorUnitTests.P
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.PersonPojoStringId;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.BasicDBList;
@@ -72,6 +78,7 @@ import com.mongodb.util.JSON;
*
* @author Oliver Gierke
* @author Patrik Wasik
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class MappingMongoConverterUnitTests {
@@ -1021,12 +1028,12 @@ public class MappingMongoConverterUnitTests {
address.city = "London";
address.street = "Foo";
Object result = converter.convertToMongoType(Collections.singleton(address));
Object result = converter.convertToMongoType(Collections.singleton(address), ClassTypeInformation.OBJECT);
assertThat(result, is(instanceOf(BasicDBList.class)));
Set<?> readResult = converter.read(Set.class, (BasicDBList) result);
assertThat(readResult.size(), is(1));
assertThat(readResult.iterator().next(), is(instanceOf(Map.class)));
assertThat(readResult.iterator().next(), is(instanceOf(Address.class)));
}
/**
@@ -1240,9 +1247,9 @@ public class MappingMongoConverterUnitTests {
DB db = mock(DB.class);
DBRef dbRef = new DBRef(db, "collection", "id");
org.springframework.data.mongodb.core.mapping.DBRef annotation = mock(org.springframework.data.mongodb.core.mapping.DBRef.class);
MongoPersistentProperty property = mock(MongoPersistentProperty.class);
assertThat(converter.createDBRef(dbRef, annotation), is(dbRef));
assertThat(converter.createDBRef(dbRef, property), is(dbRef));
}
/**
@@ -1381,6 +1388,146 @@ public class MappingMongoConverterUnitTests {
assertThat(aValue.get("c"), is((Object) "C"));
}
/**
* @see DATAMONGO-812
* @see DATAMONGO-893
*/
@Test
public void convertsListToBasicDBListAndRetainsTypeInformationForComplexObjects() {
Address address = new Address();
address.city = "London";
address.street = "Foo";
Object result = converter.convertToMongoType(Collections.singletonList(address),
ClassTypeInformation.from(InterfaceType.class));
assertThat(result, is(instanceOf(BasicDBList.class)));
BasicDBList dbList = (BasicDBList) result;
assertThat(dbList, hasSize(1));
assertThat(getTypedValue(getAsDBObject(dbList, 0), "_class", String.class), equalTo(Address.class.getName()));
}
/**
* @see DATAMONGO-812
*/
@Test
public void convertsListToBasicDBListWithoutTypeInformationForSimpleTypes() {
Object result = converter.convertToMongoType(Collections.singletonList("foo"));
assertThat(result, is(instanceOf(BasicDBList.class)));
BasicDBList dbList = (BasicDBList) result;
assertThat(dbList, hasSize(1));
assertThat(dbList.get(0), instanceOf(String.class));
}
/**
* @see DATAMONGO-812
*/
@Test
public void convertsArrayToBasicDBListAndRetainsTypeInformationForComplexObjects() {
Address address = new Address();
address.city = "London";
address.street = "Foo";
Object result = converter.convertToMongoType(new Address[] { address }, ClassTypeInformation.OBJECT);
assertThat(result, is(instanceOf(BasicDBList.class)));
BasicDBList dbList = (BasicDBList) result;
assertThat(dbList, hasSize(1));
assertThat(getTypedValue(getAsDBObject(dbList, 0), "_class", String.class), equalTo(Address.class.getName()));
}
/**
* @see DATAMONGO-812
*/
@Test
public void convertsArrayToBasicDBListWithoutTypeInformationForSimpleTypes() {
Object result = converter.convertToMongoType(new String[] { "foo" });
assertThat(result, is(instanceOf(BasicDBList.class)));
BasicDBList dbList = (BasicDBList) result;
assertThat(dbList, hasSize(1));
assertThat(dbList.get(0), instanceOf(String.class));
}
/**
* @see DATAMONGO-833
*/
@Test
public void readsEnumSetCorrectly() {
BasicDBList enumSet = new BasicDBList();
enumSet.add("SECOND");
DBObject dbObject = new BasicDBObject("enumSet", enumSet);
ClassWithEnumProperty result = converter.read(ClassWithEnumProperty.class, dbObject);
assertThat(result.enumSet, is(instanceOf(EnumSet.class)));
assertThat(result.enumSet.size(), is(1));
assertThat(result.enumSet, hasItem(SampleEnum.SECOND));
}
/**
* @see DATAMONGO-833
*/
@Test
public void readsEnumMapCorrectly() {
BasicDBObject enumMap = new BasicDBObject("FIRST", "Dave");
ClassWithEnumProperty result = converter.read(ClassWithEnumProperty.class, new BasicDBObject("enumMap", enumMap));
assertThat(result.enumMap, is(instanceOf(EnumMap.class)));
assertThat(result.enumMap.size(), is(1));
assertThat(result.enumMap.get(SampleEnum.FIRST), is("Dave"));
}
/**
* @see DATAMONGO-887
*/
@Test
public void readsTreeMapCorrectly() {
DBObject person = new BasicDBObject("foo", "Dave");
DBObject treeMapOfPerson = new BasicDBObject("key", person);
DBObject document = new BasicDBObject("treeMapOfPersons", treeMapOfPerson);
ClassWithMapProperty result = converter.read(ClassWithMapProperty.class, document);
assertThat(result.treeMapOfPersons, is(notNullValue()));
assertThat(result.treeMapOfPersons.get("key"), is(notNullValue()));
assertThat(result.treeMapOfPersons.get("key").firstname, is("Dave"));
}
/**
* @see DATAMONGO-887
*/
@Test
public void writesTreeMapCorrectly() {
Person person = new Person();
person.firstname = "Dave";
ClassWithMapProperty source = new ClassWithMapProperty();
source.treeMapOfPersons = new TreeMap<String, Person>();
source.treeMapOfPersons.put("key", person);
DBObject result = new BasicDBObject();
converter.write(source, result);
DBObject map = getAsDBObject(result, "treeMapOfPersons");
DBObject entry = getAsDBObject(map, "key");
assertThat(entry.get("foo"), is((Object) "Dave"));
}
static class GenericType<T> {
T content;
}
@@ -1389,6 +1536,8 @@ public class MappingMongoConverterUnitTests {
SampleEnum sampleEnum;
List<SampleEnum> enums;
EnumSet<SampleEnum> enumSet;
EnumMap<SampleEnum, String> enumMap;
}
static enum SampleEnum {
@@ -1406,7 +1555,11 @@ public class MappingMongoConverterUnitTests {
abstract void method();
}
static class Address {
static interface InterfaceType {
}
static class Address implements InterfaceType {
String street;
String city;
}
@@ -1446,6 +1599,7 @@ public class MappingMongoConverterUnitTests {
Map<String, Object> mapOfObjects;
Map<String, String[]> mapOfStrings;
Map<String, Person> mapOfPersons;
TreeMap<String, Person> treeMapOfPersons;
}
static class ClassWithNestedMaps {

View File

@@ -34,11 +34,14 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.Person;
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
@@ -48,6 +51,7 @@ import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
@@ -57,6 +61,7 @@ import com.mongodb.QueryBuilder;
* @author Oliver Gierke
* @author Patryk Wasik
* @author Thomas Darimont
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class QueryMapperUnitTests {
@@ -501,10 +506,119 @@ public class QueryMapperUnitTests {
assertThat(idValuesAfter, is(idValuesBefore));
}
/**
* @see DATAMONGO-821
*/
@Test
public void queryMapperShouldNotTryToMapDBRefListPropertyIfNestedInsideDBObjectWithinDBObject() {
DBObject queryObject = query(
where("referenceList").is(new BasicDBObject("$nested", new BasicDBObject("$keys", 0L)))).getQueryObject();
DBObject mappedObject = mapper.getMappedObject(queryObject, context.getPersistentEntity(WithDBRefList.class));
DBObject referenceObject = getAsDBObject(mappedObject, "referenceList");
DBObject nestedObject = getAsDBObject(referenceObject, "$nested");
assertThat(nestedObject, is((DBObject) new BasicDBObject("$keys", 0L)));
}
/**
* @see DATAMONGO-821
*/
@Test
public void queryMapperShouldNotTryToMapDBRefPropertyIfNestedInsideDBObjectWithinDBObject() {
DBObject queryObject = query(where("reference").is(new BasicDBObject("$nested", new BasicDBObject("$keys", 0L))))
.getQueryObject();
DBObject mappedObject = mapper.getMappedObject(queryObject, context.getPersistentEntity(WithDBRef.class));
DBObject referenceObject = getAsDBObject(mappedObject, "reference");
DBObject nestedObject = getAsDBObject(referenceObject, "$nested");
assertThat(nestedObject, is((DBObject) new BasicDBObject("$keys", 0L)));
}
/**
* @see DATAMONGO-821
*/
@Test
public void queryMapperShouldMapDBRefPropertyIfNestedInDBObject() {
Reference sample = new Reference();
sample.id = 321L;
DBObject queryObject = query(where("reference").is(new BasicDBObject("$in", Arrays.asList(sample))))
.getQueryObject();
DBObject mappedObject = mapper.getMappedObject(queryObject, context.getPersistentEntity(WithDBRef.class));
DBObject referenceObject = getAsDBObject(mappedObject, "reference");
BasicDBList inObject = getAsDBList(referenceObject, "$in");
assertThat(inObject.get(0), is(instanceOf(com.mongodb.DBRef.class)));
}
/**
* @see DATAMONGO-773
*/
@Test
public void queryMapperShouldBeAbleToProcessQueriesThatIncludeDbRefFields() {
BasicMongoPersistentEntity<?> persistentEntity = context.getPersistentEntity(WithDBRef.class);
Query qry = query(where("someString").is("abc"));
qry.fields().include("reference");
DBObject mappedFields = mapper.getMappedObject(qry.getFieldsObject(), persistentEntity);
assertThat(mappedFields, is(notNullValue()));
}
/**
* @see DATAMONGO-893
*/
@Test
public void classInformationShouldNotBePresentInDBObjectUsedInFinderMethods() {
EmbeddedClass embedded = new EmbeddedClass();
embedded.id = "1";
EmbeddedClass embedded2 = new EmbeddedClass();
embedded2.id = "2";
Query query = query(where("embedded").in(Arrays.asList(embedded, embedded2)));
DBObject dbo = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(Foo.class));
assertThat(dbo.toString(), equalTo("{ \"embedded\" : { \"$in\" : [ { \"_id\" : \"1\"} , { \"_id\" : \"2\"}]}}"));
}
/**
* @see DATAMONGO-647
*/
@Test
public void customizedFieldNameShouldBeMappedCorrectlyWhenApplyingSort() {
Query query = query(where("field").is("bar")).with(new Sort(Direction.DESC, "field"));
DBObject dbo = mapper.getMappedObject(query.getSortObject(), context.getPersistentEntity(CustomizedField.class));
assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("foo", -1).get()));
}
@Document
public class Foo {
@Id private ObjectId id;
EmbeddedClass embedded;
}
public class EmbeddedClass {
public String id;
}
class IdWrapper {
Object id;
}
class ClassWithEmbedded {
@Id String id;
Sample sample;
}
class ClassWithDefaultId {
String id;
@@ -541,6 +655,12 @@ public class QueryMapperUnitTests {
@DBRef Reference reference;
}
class WithDBRefList {
String someString;
@DBRef List<Reference> referenceList;
}
class Reference {
Long id;

View File

@@ -16,28 +16,47 @@
package org.springframework.data.mongodb.core.convert;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.collection.IsMapContaining.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.Matcher;
import org.hamcrest.collection.IsIterableContainingInOrder;
import org.hamcrest.core.IsEqual;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
/**
* Unit tests for {@link UpdateMapper}.
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
*/
@RunWith(MockitoJUnitRunner.class)
public class UpdateMapperUnitTests {
@@ -45,12 +64,26 @@ public class UpdateMapperUnitTests {
@Mock MongoDbFactory factory;
MappingMongoConverter converter;
MongoMappingContext context;
UpdateMapper mapper;
private Converter<NestedEntity, DBObject> writingConverterSpy;
@SuppressWarnings("unchecked")
@Before
public void setUp() {
this.writingConverterSpy = Mockito.spy(new NestedEntityWriteConverter());
CustomConversions conversions = new CustomConversions(Arrays.asList(writingConverterSpy));
this.context = new MongoMappingContext();
this.context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
this.context.initialize();
this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context);
this.converter.setCustomConversions(conversions);
this.converter.afterPropertiesSet();
this.mapper = new UpdateMapper(converter);
}
/**
@@ -60,7 +93,6 @@ public class UpdateMapperUnitTests {
public void updateMapperRetainsTypeInformationForCollectionField() {
Update update = new Update().push("list", new ConcreteChildClass("2", "BAR"));
UpdateMapper mapper = new UpdateMapper(converter);
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
@@ -175,6 +207,326 @@ public class UpdateMapperUnitTests {
assertThat(someObject.get("value"), is((Object) "bubu"));
}
/**
* @see DATAMONGO-812
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingSimpleTypes() {
Update update = new Update().push("values").each("spring", "data", "mongodb");
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Model.class));
DBObject push = getAsDBObject(mappedObject, "$push");
DBObject values = getAsDBObject(push, "values");
BasicDBList each = getAsDBList(values, "$each");
assertThat(push.get("_class"), nullValue());
assertThat(values.get("_class"), nullValue());
assertThat(each.toMap(), (Matcher) allOf(hasValue("spring"), hasValue("data"), hasValue("mongodb")));
}
/**
* @see DATAMONGO-812
*/
@Test
public void updateMapperShouldConvertPushWhithoutAddingClassInformationWhenUsedWithEvery() {
Update update = new Update().push("values").each("spring", "data", "mongodb");
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Model.class));
DBObject push = getAsDBObject(mappedObject, "$push");
DBObject values = getAsDBObject(push, "values");
assertThat(push.get("_class"), nullValue());
assertThat(values.get("_class"), nullValue());
}
/**
* @see DATAMONGO-812
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingCustomTypes() {
Update update = new Update().push("models").each(new ListModel("spring", "data", "mongodb"));
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ModelWrapper.class));
DBObject push = getAsDBObject(mappedObject, "$push");
DBObject model = getAsDBObject(push, "models");
BasicDBList each = getAsDBList(model, "$each");
BasicDBList values = getAsDBList((DBObject) each.get(0), "values");
assertThat(values.toMap(), (Matcher) allOf(hasValue("spring"), hasValue("data"), hasValue("mongodb")));
}
/**
* @see DATAMONGO-812
*/
@Test
public void updateMapperShouldRetainClassInformationForPushCorrectlyWhenCalledWithEachUsingCustomTypes() {
Update update = new Update().push("models").each(new ListModel("spring", "data", "mongodb"));
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ModelWrapper.class));
DBObject push = getAsDBObject(mappedObject, "$push");
DBObject model = getAsDBObject(push, "models");
BasicDBList each = getAsDBList(model, "$each");
assertThat(((DBObject) each.get(0)).get("_class").toString(), equalTo(ListModel.class.getName()));
}
/**
* @see DATAMONGO-812
*/
@Test
public void testUpdateShouldAllowMultiplePushEachForDifferentFields() {
Update update = new Update().push("category").each("spring", "data").push("type").each("mongodb");
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
DBObject push = getAsDBObject(mappedObject, "$push");
assertThat(getAsDBObject(push, "category").containsField("$each"), is(true));
assertThat(getAsDBObject(push, "type").containsField("$each"), is(true));
}
/**
* @see DATAMONGO-410
*/
@Test
public void testUpdateMapperShouldConsiderCustomWriteTarget() {
List<NestedEntity> someValues = Arrays.asList(new NestedEntity("spring"), new NestedEntity("data"),
new NestedEntity("mongodb"));
NestedEntity[] array = new NestedEntity[someValues.size()];
Update update = new Update().pushAll("collectionOfNestedEntities", someValues.toArray(array));
mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(DomainEntity.class));
verify(writingConverterSpy, times(3)).convert(Mockito.any(NestedEntity.class));
}
/**
* @see DATAMONGO-404
*/
@Test
public void createsDbRefForEntityIdOnPulls() {
Update update = new Update().pull("dbRefAnnotatedList.id", "2");
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithDBRefCollection.class));
DBObject pullClause = getAsDBObject(mappedObject, "$pull");
assertThat(pullClause.get("dbRefAnnotatedList"), is((Object) new DBRef(null, "entity", "2")));
}
/**
* @see DATAMONGO-404
*/
@Test
public void createsDbRefForEntityOnPulls() {
Entity entity = new Entity();
entity.id = "5";
Update update = new Update().pull("dbRefAnnotatedList", entity);
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithDBRefCollection.class));
DBObject pullClause = getAsDBObject(mappedObject, "$pull");
assertThat(pullClause.get("dbRefAnnotatedList"), is((Object) new DBRef(null, "entity", entity.id)));
}
/**
* @see DATAMONGO-404
*/
@Test(expected = MappingException.class)
public void rejectsInvalidFieldReferenceForDbRef() {
Update update = new Update().pull("dbRefAnnotatedList.name", "NAME");
mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(DocumentWithDBRefCollection.class));
}
/**
* @see DATAMONGO-404
*/
@Test
public void rendersNestedDbRefCorrectly() {
Update update = new Update().pull("nested.dbRefAnnotatedList.id", "2");
DBObject mappedObject = mapper
.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Wrapper.class));
DBObject pullClause = getAsDBObject(mappedObject, "$pull");
assertThat(pullClause.containsField("mapped.dbRefAnnotatedList"), is(true));
}
/**
* @see DATAMONGO-468
*/
@Test
public void rendersUpdateOfDbRefPropertyWithDomainObjectCorrectly() {
Entity entity = new Entity();
entity.id = "5";
Update update = new Update().set("dbRefProperty", entity);
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithDBRefCollection.class));
DBObject setClause = getAsDBObject(mappedObject, "$set");
assertThat(setClause.get("dbRefProperty"), is((Object) new DBRef(null, "entity", entity.id)));
}
/**
* @see DATAMONGO-862
*/
@Test
public void rendersUpdateAndPreservesKeyForPathsNotPointingToProperty() {
Update update = new Update().set("listOfInterface.$.value", "expected-value");
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
DBObject setClause = getAsDBObject(mappedObject, "$set");
assertThat(setClause.containsField("listOfInterface.$.value"), is(true));
}
/**
* @see DATAMONGO-863
*/
@Test
public void doesNotConvertRawDbObjects() {
Update update = new Update();
update.pull("options",
new BasicDBObject("_id", new BasicDBObject("$in", converter.convertToMongoType(Arrays.asList(1L, 2L)))));
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
DBObject setClause = getAsDBObject(mappedObject, "$pull");
DBObject options = getAsDBObject(setClause, "options");
DBObject idClause = getAsDBObject(options, "_id");
BasicDBList inClause = getAsDBList(idClause, "$in");
assertThat(inClause, IsIterableContainingInOrder.<Object> contains(1L, 2L));
}
/**
* @see DATAMONGO-897
*/
@Test
public void updateOnDbrefPropertyOfInterfaceTypeWithoutExplicitGetterForIdShouldBeMappedCorrectly() {
Update update = new Update().set("referencedDocument", new InterfaceDocumentDefinitionImpl("1", "Foo"));
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithReferenceToInterfaceImpl.class));
DBObject $set = DBObjectTestUtils.getAsDBObject(mappedObject, "$set");
Object model = $set.get("referencedDocument");
DBRef expectedDBRef = new DBRef(factory.getDb(), "interfaceDocumentDefinitionImpl", "1");
assertThat(model, allOf(instanceOf(DBRef.class), IsEqual.<Object> equalTo(expectedDBRef)));
}
/**
* @see DATAMONGO-847
*/
@Test
public void updateMapperConvertsNestedQueryCorrectly() {
Update update = new Update().pull("list", Query.query(Criteria.where("value").in("foo", "bar")));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
DBObject $pull = DBObjectTestUtils.getAsDBObject(mappedUpdate, "$pull");
DBObject list = DBObjectTestUtils.getAsDBObject($pull, "aliased");
DBObject value = DBObjectTestUtils.getAsDBObject(list, "value");
BasicDBList $in = DBObjectTestUtils.getAsDBList(value, "$in");
assertThat($in, IsIterableContainingInOrder.<Object> contains("foo", "bar"));
}
/**
* @see DATAMONGO-847
*/
@Test
public void updateMapperConvertsPullWithNestedQuerfyOnDBRefCorrectly() {
Update update = new Update().pull("dbRefAnnotatedList", Query.query(Criteria.where("id").is("1")));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithDBRefCollection.class));
DBObject $pull = DBObjectTestUtils.getAsDBObject(mappedUpdate, "$pull");
DBObject list = DBObjectTestUtils.getAsDBObject($pull, "dbRefAnnotatedList");
assertThat(list, equalTo(new BasicDBObjectBuilder().add("_id", "1").get()));
}
@org.springframework.data.mongodb.core.mapping.Document(collection = "DocumentWithReferenceToInterface")
static interface DocumentWithReferenceToInterface {
String getId();
InterfaceDocumentDefinitionWithoutId getReferencedDocument();
}
static interface InterfaceDocumentDefinitionWithoutId {
String getValue();
}
static class InterfaceDocumentDefinitionImpl implements InterfaceDocumentDefinitionWithoutId {
@Id String id;
String value;
public InterfaceDocumentDefinitionImpl(String id, String value) {
this.id = id;
this.value = value;
}
@Override
public String getValue() {
return this.value;
}
}
static class DocumentWithReferenceToInterfaceImpl implements DocumentWithReferenceToInterface {
private @Id String id;
@org.springframework.data.mongodb.core.mapping.DBRef//
private InterfaceDocumentDefinitionWithoutId referencedDocument;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void setModel(InterfaceDocumentDefinitionWithoutId referencedDocument) {
this.referencedDocument = referencedDocument;
}
@Override
public InterfaceDocumentDefinitionWithoutId getReferencedDocument() {
return this.referencedDocument;
}
}
static interface Model {}
static class ModelImpl implements Model {
@@ -183,12 +535,27 @@ public class UpdateMapperUnitTests {
public ModelImpl(int value) {
this.value = value;
}
}
public class ModelWrapper {
Model model;
}
static class ListModelWrapper {
List<Model> models;
}
static class ListModel {
List<String> values;
public ListModel(String... values) {
this.values = Arrays.asList(values);
}
}
static class ParentClass {
String id;
@@ -196,6 +563,9 @@ public class UpdateMapperUnitTests {
@Field("aliased")//
List<? extends AbstractChildClass> list;
@Field//
List<Model> listOfInterface;
public ParentClass(String id, List<? extends AbstractChildClass> list) {
this.id = id;
this.list = list;
@@ -223,4 +593,49 @@ public class UpdateMapperUnitTests {
super(id, value);
}
}
static class DomainEntity {
List<NestedEntity> collectionOfNestedEntities;
}
static class NestedEntity {
String name;
public NestedEntity(String name) {
super();
this.name = name;
}
}
@WritingConverter
static class NestedEntityWriteConverter implements Converter<NestedEntity, DBObject> {
@Override
public DBObject convert(NestedEntity source) {
return new BasicDBObject();
}
}
static class DocumentWithDBRefCollection {
@Id public String id;
@org.springframework.data.mongodb.core.mapping.DBRef//
public List<Entity> dbRefAnnotatedList;
@org.springframework.data.mongodb.core.mapping.DBRef//
public Entity dbRefProperty;
}
static class Entity {
@Id public String id;
String name;
}
static class Wrapper {
@Field("mapped") DocumentWithDBRefCollection nested;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 the original author or authors.
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
*/
package org.springframework.data.mongodb.core.mapreduce;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.mapreduce.GroupBy.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
@@ -42,6 +44,12 @@ import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
/**
* Integration tests for group-by operations.
*
* @author Mark Pollack
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
public class GroupByTests {
@@ -84,49 +92,46 @@ public class GroupByTests {
@Test
public void singleKeyCreation() {
DBObject gc = new GroupBy("a").getGroupByObject();
// String expected =
// "{ \"group\" : { \"ns\" : \"test\" , \"key\" : { \"a\" : 1} , \"cond\" : null , \"$reduce\" : null , \"initial\" : null }}";
String expected = "{ \"key\" : { \"a\" : 1} , \"$reduce\" : null , \"initial\" : null }";
Assert.assertEquals(expected, gc.toString());
assertThat(gc.toString(), is("{ \"key\" : { \"a\" : 1} , \"$reduce\" : null , \"initial\" : null }"));
}
@Test
public void multipleKeyCreation() {
DBObject gc = GroupBy.key("a", "b").getGroupByObject();
// String expected =
// "{ \"group\" : { \"ns\" : \"test\" , \"key\" : { \"a\" : 1 , \"b\" : 1} , \"cond\" : null , \"$reduce\" : null , \"initial\" : null }}";
String expected = "{ \"key\" : { \"a\" : 1 , \"b\" : 1} , \"$reduce\" : null , \"initial\" : null }";
Assert.assertEquals(expected, gc.toString());
assertThat(gc.toString(), is("{ \"key\" : { \"a\" : 1 , \"b\" : 1} , \"$reduce\" : null , \"initial\" : null }"));
}
@Test
public void keyFunctionCreation() {
DBObject gc = GroupBy.keyFunction("classpath:keyFunction.js").getGroupByObject();
String expected = "{ \"$keyf\" : \"classpath:keyFunction.js\" , \"$reduce\" : null , \"initial\" : null }";
Assert.assertEquals(expected, gc.toString());
assertThat(gc.toString(),
is("{ \"$keyf\" : \"classpath:keyFunction.js\" , \"$reduce\" : null , \"initial\" : null }"));
}
@Test
public void SimpleGroup() {
createGroupByData();
GroupByResults<XObject> results;
results = mongoTemplate.group(
createGroupByData();
GroupByResults<XObject> results = mongoTemplate.group(
"group_test_collection",
GroupBy.key("x").initialDocument(new BasicDBObject("count", 0))
.reduceFunction("function(doc, prev) { prev.count += 1 }"), XObject.class);
assertMapReduceResults(results);
}
@Test
public void SimpleGroupWithKeyFunction() {
createGroupByData();
GroupByResults<XObject> results;
results = mongoTemplate.group(
createGroupByData();
GroupByResults<XObject> results = mongoTemplate.group(
"group_test_collection",
GroupBy.keyFunction("function(doc) { return { x : doc.x }; }").initialDocument("{ count: 0 }")
.reduceFunction("function(doc, prev) { prev.count += 1 }"), XObject.class);
@@ -136,30 +141,35 @@ public class GroupByTests {
@Test
public void SimpleGroupWithFunctionsAsResources() {
createGroupByData();
GroupByResults<XObject> results;
results = mongoTemplate.group("group_test_collection", GroupBy.keyFunction("classpath:keyFunction.js")
.initialDocument("{ count: 0 }").reduceFunction("classpath:groupReduce.js"), XObject.class);
createGroupByData();
GroupByResults<XObject> results = mongoTemplate.group(
"group_test_collection",
GroupBy.keyFunction("classpath:keyFunction.js").initialDocument("{ count: 0 }")
.reduceFunction("classpath:groupReduce.js"), XObject.class);
assertMapReduceResults(results);
}
@Test
public void SimpleGroupWithQueryAndFunctionsAsResources() {
createGroupByData();
GroupByResults<XObject> results;
results = mongoTemplate.group(where("x").gt(0), "group_test_collection", keyFunction("classpath:keyFunction.js")
.initialDocument("{ count: 0 }").reduceFunction("classpath:groupReduce.js"), XObject.class);
createGroupByData();
GroupByResults<XObject> results = mongoTemplate.group(
where("x").gt(0),
"group_test_collection",
keyFunction("classpath:keyFunction.js").initialDocument("{ count: 0 }").reduceFunction(
"classpath:groupReduce.js"), XObject.class);
assertMapReduceResults(results);
}
private void assertMapReduceResults(GroupByResults<XObject> results) {
DBObject dboRawResults = results.getRawResults();
String expected = "{ \"serverUsed\" : \"/127.0.0.1:27017\" , \"retval\" : [ { \"x\" : 1.0 , \"count\" : 2.0} , { \"x\" : 2.0 , \"count\" : 1.0} , { \"x\" : 3.0 , \"count\" : 3.0}] , \"count\" : 6.0 , \"keys\" : 3 , \"ok\" : 1.0}";
Assert.assertEquals(expected, dboRawResults.toString());
assertThat(dboRawResults.containsField("serverUsed"), is(true));
assertThat(dboRawResults.get("serverUsed").toString(), endsWith("127.0.0.1:27017"));
int numResults = 0;
for (XObject xObject : results) {
@@ -174,13 +184,15 @@ public class GroupByTests {
}
numResults++;
}
Assert.assertEquals(3, numResults);
Assert.assertEquals(6, results.getCount(), 0.001);
Assert.assertEquals(3, results.getKeys());
assertThat(numResults, is(3));
assertThat(results.getKeys(), is(3));
assertEquals(6, results.getCount(), 0.001);
}
private void createGroupByData() {
DBCollection c = mongoTemplate.getDb().getCollection("group_test_collection");
c.save(new BasicDBObject("x", 1));
c.save(new BasicDBObject("x", 1));
c.save(new BasicDBObject("x", 2));
@@ -188,5 +200,4 @@ public class GroupByTests {
c.save(new BasicDBObject("x", 3));
c.save(new BasicDBObject("x", 3));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011 the original author or authors.
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.geo.Box;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.test.context.ContextConfiguration;
@@ -50,6 +51,7 @@ import com.mongodb.Mongo;
* Integration test for {@link MongoTemplate}'s Map-Reduce operations
*
* @author Mark Pollack
* @author Thomas Darimont
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
@@ -276,6 +278,31 @@ public class MapReduceTests {
}
/**
* @see DATAMONGO-938
*/
@Test
public void mapReduceShouldUseQueryMapper() {
DBCollection c = mongoTemplate.getDb().getCollection("jmrWithGeo");
c.save(new BasicDBObject("x", new String[] { "a", "b" }).append("loc", new double[] { 0, 0 }));
c.save(new BasicDBObject("x", new String[] { "b", "c" }).append("loc", new double[] { 0, 0 }));
c.save(new BasicDBObject("x", new String[] { "c", "d" }).append("loc", new double[] { 0, 0 }));
Query query = new Query(where("x").ne(new String[] { "a", "b" }).and("loc")
.within(new Box(new double[] { 0, 0 }, new double[] { 1, 1 })));
MapReduceResults<ValueObject> results = template.mapReduce(query, "jmrWithGeo", mapFunction, reduceFunction,
ValueObject.class);
Map<String, Float> m = copyToMap(results);
assertEquals(3, m.size());
assertEquals(1, m.get("b").intValue());
assertEquals(2, m.get("c").intValue());
assertEquals(1, m.get("d").intValue());
}
private void performMapReduce(boolean inline, boolean withQuery) {
createMapReduceData();
MapReduceResults<ValueObject> results;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 the original author or authors.
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.Metric;
import org.springframework.data.mongodb.core.geo.Metrics;
@@ -31,6 +32,7 @@ import org.springframework.data.mongodb.core.geo.Point;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class NearQueryUnitTests {
@@ -123,4 +125,36 @@ public class NearQueryUnitTests {
assertThat(query.getSkip(), is(pageable.getPageNumber() * pageable.getPageSize()));
assertThat((Integer) query.toDBObject().get("num"), is((pageable.getPageNumber() + 1) * pageable.getPageSize()));
}
/**
* @see DATAMONGO-829
*/
@Test
public void nearQueryShouldInoreZeroLimitFromQuery() {
NearQuery query = NearQuery.near(new Point(1, 2)).query(Query.query(Criteria.where("foo").is("bar")));
assertThat(query.toDBObject().get("num"), nullValue());
}
/**
* @see DATAMONOGO-829
*/
@Test(expected = IllegalArgumentException.class)
public void nearQueryShouldThrowExceptionWhenGivenANullQuery() {
NearQuery.near(new Point(1, 2)).query(null);
}
/**
* @see DATAMONGO-829
*/
@Test
public void numShouldNotBeAlteredByQueryWithoutPageable() {
int num = 100;
NearQuery query = NearQuery.near(new Point(1, 2));
query.num(num);
query.query(Query.query(Criteria.where("foo").is("bar")));
assertThat(DBObjectTestUtils.getTypedValue(query.toDBObject(), "num", Integer.class), is(num));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2013 the original author or authors.
* Copyright 2010-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.junit.Test;
* @author Oliver Gierke
* @author Thomas Risberg
* @author Becca Gaspard
* @author Christoph Strobl
*/
public class UpdateTests {
@@ -94,6 +95,23 @@ public class UpdateTests {
is("{ \"$pushAll\" : { \"authors\" : [ { \"name\" : \"Sven\"} , { \"name\" : \"Maria\"}]}}"));
}
/**
* @see DATAMONGO-354
*/
@Test
public void testMultiplePushAllShouldBePossibleWhenUsingDifferentFields() {
Map<String, String> m1 = Collections.singletonMap("name", "Sven");
Map<String, String> m2 = Collections.singletonMap("name", "Maria");
Update u = new Update().pushAll("authors", new Object[] { m1, m2 });
u.pushAll("books", new Object[] { "Spring in Action" });
assertThat(
u.getUpdateObject().toString(),
is("{ \"$pushAll\" : { \"authors\" : [ { \"name\" : \"Sven\"} , { \"name\" : \"Maria\"}] , \"books\" : [ \"Spring in Action\"]}}"));
}
@Test
public void testAddToSet() {
@@ -170,4 +188,100 @@ public class UpdateTests {
Update u = new Update().setOnInsert("size", 1).setOnInsert("count", 1);
assertThat(u.getUpdateObject().toString(), is("{ \"$setOnInsert\" : { \"size\" : 1 , \"count\" : 1}}"));
}
/**
* @see DATAMONGO-852
*/
@Test
public void testUpdateAffectsFieldShouldReturnTrueWhenMultiFieldOperationAddedForField() {
Update update = new Update().set("foo", "bar");
assertThat(update.modifies("foo"), is(true));
}
/**
* @see DATAMONGO-852
*/
@Test
public void testUpdateAffectsFieldShouldReturnFalseWhenMultiFieldOperationAddedForField() {
Update update = new Update().set("foo", "bar");
assertThat(update.modifies("oof"), is(false));
}
/**
* @see DATAMONGO-852
*/
@Test
public void testUpdateAffectsFieldShouldReturnTrueWhenSingleFieldOperationAddedForField() {
Update update = new Update().pullAll("foo", new Object[] { "bar" });
assertThat(update.modifies("foo"), is(true));
}
/**
* @see DATAMONGO-852
*/
@Test
public void testUpdateAffectsFieldShouldReturnFalseWhenSingleFieldOperationAddedForField() {
Update update = new Update().pullAll("foo", new Object[] { "bar" });
assertThat(update.modifies("oof"), is(false));
}
/**
* @see DATAMONGO-852
*/
@Test
public void testUpdateAffectsFieldShouldReturnFalseWhenCalledOnEmptyUpdate() {
assertThat(new Update().modifies("foo"), is(false));
}
/**
* @see DATAMONGO-852
*/
@Test
public void testUpdateAffectsFieldShouldReturnTrueWhenUpdateWithKeyCreatedFromDbObject() {
Update update = new Update().set("foo", "bar");
Update clone = Update.fromDBObject(update.getUpdateObject());
assertThat(clone.modifies("foo"), is(true));
}
/**
* @see DATAMONGO-852
*/
@Test
public void testUpdateAffectsFieldShouldReturnFalseWhenUpdateWithoutKeyCreatedFromDbObject() {
Update update = new Update().set("foo", "bar");
Update clone = Update.fromDBObject(update.getUpdateObject());
assertThat(clone.modifies("oof"), is(false));
}
/**
* @see DATAMONGO-853
*/
@Test(expected = IllegalArgumentException.class)
public void testAddingMultiFieldOperationThrowsExceptionWhenCalledWithNullKey() {
new Update().addMultiFieldOperation("$op", null, "exprected to throw IllegalArgumentException.");
}
/**
* @see DATAMONGO-853
*/
@Test(expected = IllegalArgumentException.class)
public void testAddingSingleFieldOperationThrowsExceptionWhenCalledWithNullKey() {
new Update().addFieldOperation("$op", null, "exprected to throw IllegalArgumentException.");
}
/**
* @see DATAMONGO-853
*/
@Test(expected = IllegalArgumentException.class)
public void testCreatingUpdateWithNullKeyThrowsException() {
Update.update(null, "value");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 the original author or authors.
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
public abstract class AbstractPersonRepositoryIntegrationTests {
@@ -738,4 +739,91 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
assertThat(result.size(), is(1));
assertThat(result.get(0), is(dave));
}
/**
* @see DATAMONGO-871
*/
@Test
public void findsPersonsByFirstnameAsArray() {
Person[] result = repository.findByThePersonsFirstnameAsArray("Leroi");
assertThat(result, is(arrayWithSize(1)));
assertThat(result, is(arrayContaining(leroi)));
}
/**
* @see DATAMONGO-821
*/
@Test
public void findUsingAnnotatedQueryOnDBRef() {
operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class);
User user = new User();
user.username = "Terria";
operations.save(user);
alicia.creator = user;
repository.save(alicia);
Page<Person> result = repository.findByHavingCreator(new PageRequest(0, 100));
assertThat(result.getNumberOfElements(), is(1));
assertThat(result.getContent().get(0), is(alicia));
}
/**
* @see DATAMONGO-893
*/
@Test
public void findByNestedPropertyInCollectionShouldFindMatchingDocuments() {
Person p = new Person("Mary", "Poppins");
Address adr = new Address("some", "2", "where");
p.setAddress(adr);
repository.save(p);
Page<Person> result = repository.findByAddressIn(Arrays.asList(adr), new PageRequest(0, 10));
assertThat(result.getContent(), hasSize(1));
}
/**
* @see DATAMONGO-745
*/
@Test
public void findByCustomQueryFirstnamesInListAndLastname() {
repository.save(new Person("foo", "bar"));
repository.save(new Person("bar", "bar"));
repository.save(new Person("fuu", "bar"));
repository.save(new Person("notfound", "bar"));
Page<Person> result = repository.findByCustomQueryFirstnamesAndLastname(Arrays.asList("bar", "foo", "fuu"), "bar",
new PageRequest(0, 2));
assertThat(result.getContent(), hasSize(2));
assertThat(result.getTotalPages(), is(2));
assertThat(result.getTotalElements(), is(3L));
}
/**
* @see DATAMONGO-745
*/
@Test
public void findByCustomQueryLastnameAndStreetInList() {
repository.save(new Person("foo", "bar").withAddress(new Address("street1", "1", "SB")));
repository.save(new Person("bar", "bar").withAddress(new Address("street2", "1", "SB")));
repository.save(new Person("fuu", "bar").withAddress(new Address("street1", "2", "RGB")));
repository.save(new Person("notfound", "notfound"));
Page<Person> result = repository.findByCustomQueryLastnameAndAddressStreetInList("bar",
Arrays.asList("street1", "street2"), new PageRequest(0, 2));
assertThat(result.getContent(), hasSize(2));
assertThat(result.getTotalPages(), is(2));
assertThat(result.getTotalElements(), is(3L));
}
}

View File

@@ -261,6 +261,12 @@ public class Person extends Contact {
return this.getId().equals(that.getId());
}
public Person withAddress(Address address) {
this.address = address;
return this;
}
/*
* (non-Javadoc)
*

View File

@@ -37,6 +37,7 @@ import org.springframework.data.querydsl.QueryDslPredicateExecutor;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public interface PersonRepository extends MongoRepository<Person, String>, QueryDslPredicateExecutor<Person> {
@@ -70,6 +71,12 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
@Query(value = "{ 'firstname' : ?0 }", fields = "{ 'firstname': 1, 'lastname': 1}")
List<Person> findByThePersonsFirstname(String firstname);
/**
* @see DATAMONGO-871
*/
@Query(value = "{ 'firstname' : ?0 }")
Person[] findByThePersonsFirstnameAsArray(String firstname);
/**
* Returns all {@link Person}s with a firstname matching the given one (*-wildcard supported).
*
@@ -245,4 +252,26 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
*/
List<Person> findByFirstnameContainingIgnoreCase(String firstName);
/**
* @see DATAMONGO-821
*/
@Query("{ creator : { $exists : true } }")
Page<Person> findByHavingCreator(Pageable page);
/**
* @see DATAMONGO-893
*/
Page<Person> findByAddressIn(List<Address> address, Pageable page);
/**
* @see DATAMONGO-745
*/
@Query("{firstname:{$in:?0}, lastname:?1}")
Page<Person> findByCustomQueryFirstnamesAndLastname(List<String> firstnames, String lastname, Pageable page);
/**
* @see DATAMONGO-745
*/
@Query("{lastname:?0, address.street:{$in:?1}}")
Page<Person> findByCustomQueryLastnameAndAddressStreetInList(String lastname, List<String> streetNames, Pageable page);
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.repository.query;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.core.RepositoryMetadata;
import com.mongodb.BasicDBObjectBuilder;
/**
* Unit tests for {@link PartTreeMongoQuery}.
*
* @author Christoph Strobl
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class PartTreeMongoQueryUnitTests {
@Mock RepositoryMetadata metadataMock;
@Mock MongoOperations mongoOperationsMock;
MongoMappingContext mappingContext;
public @Rule ExpectedException exception = ExpectedException.none();
@Before
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setUp() {
when(metadataMock.getDomainType()).thenReturn((Class) Person.class);
when(metadataMock.getReturnedDomainClass(Matchers.any(Method.class))).thenReturn((Class) Person.class);
mappingContext = new MongoMappingContext();
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mock(MongoDbFactory.class));
MongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
when(mongoOperationsMock.getConverter()).thenReturn(converter);
}
/**
* @see DATAMOGO-952
*/
@Test
public void rejectsInvalidFieldSpecification() {
exception.expect(IllegalStateException.class);
exception.expectMessage("findByLastname");
deriveQueryFromMethod("findByLastname", new Object[] { "foo" });
}
/**
* @see DATAMOGO-952
*/
@Test
public void singleFieldJsonIncludeRestrictionShouldBeConsidered() {
org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findByFirstname",
new Object[] { "foo" });
assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 1).get()));
}
/**
* @see DATAMOGO-952
*/
@Test
public void multiFieldJsonIncludeRestrictionShouldBeConsidered() {
org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findByFirstnameAndLastname",
new Object[] { "foo", "bar" });
assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 1).add("lastname", 1).get()));
}
/**
* @see DATAMOGO-952
*/
@Test
public void multiFieldJsonExcludeRestrictionShouldBeConsidered() {
org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findPersonByFirstnameAndLastname",
new Object[] { "foo", "bar" });
assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 0).add("lastname", 0).get()));
}
private org.springframework.data.mongodb.core.query.Query deriveQueryFromMethod(String method, Object[] args) {
Class<?>[] types = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
types[i] = args[i].getClass();
}
PartTreeMongoQuery partTreeQuery = createQueryForMethod(method, types);
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(partTreeQuery.getQueryMethod(), args);
return partTreeQuery.createQuery(new ConvertingParameterAccessor(mongoOperationsMock.getConverter(), accessor));
}
private PartTreeMongoQuery createQueryForMethod(String methodName, Class<?>... paramTypes) {
try {
Method method = Repo.class.getMethod(methodName, paramTypes);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadataMock, mappingContext);
return new PartTreeMongoQuery(queryMethod, mongoOperationsMock);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(e.getMessage(), e);
} catch (SecurityException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
interface Repo extends MongoRepository<Person, Long> {
@Query(fields = "firstname")
Person findByLastname(String lastname);
@Query(fields = "{ 'firstname' : 1 }")
Person findByFirstname(String lastname);
@Query(fields = "{ 'firstname' : 1, 'lastname' : 1 }")
Person findByFirstnameAndLastname(String firstname, String lastname);
@Query(fields = "{ 'firstname' : 0, 'lastname' : 0 }")
Person findPersonByFirstnameAndLastname(String firstname, String lastname);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 the original author or authors.
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@ import com.mongodb.DBObject;
* Unit tests for {@link StringBasedMongoQuery}.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class StringBasedMongoQueryUnitTests {
@@ -126,6 +127,19 @@ public class StringBasedMongoQueryUnitTests {
assertThat(query.getQueryObject().get("address"), is(nullValue()));
}
/**
* @see DATAMONGO-821
*/
@Test
public void bindsDbrefCorrectly() throws Exception {
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByHavingSizeFansNotZero");
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] {});
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
assertThat(query.getQueryObject(), is(new BasicQuery("{ fans : { $not : { $size : 0 } } }").getQueryObject()));
}
private StringBasedMongoQuery createQueryForMethod(String name, Class<?>... parameters) throws Exception {
Method method = SampleRepository.class.getMethod(name, parameters);
@@ -143,5 +157,9 @@ public class StringBasedMongoQueryUnitTests {
@Query("{ 'lastname' : ?0, 'address' : ?1 }")
Person findByLastnameAndAddress(String lastname, Address address);
@Query("{ fans : { $not : { $size : 0 } } }")
Person findByHavingSizeFansNotZero();
}
}

View File

@@ -28,6 +28,7 @@ import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.repository.ContactRepository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.util.ReflectionTestUtils;
@@ -71,6 +72,7 @@ public class MongoRepositoryFactoryBeanUnitTests {
factoryBean.setLazyInit(true);
factoryBean.setMongoOperations(operations);
factoryBean.setRepositoryInterface(ContactRepository.class);
factoryBean.afterPropertiesSet();
RepositoryFactorySupport factory = factoryBean.createRepositoryFactory();

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation="http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<mongo:db-factory id="factory" />
<bean class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg>
<mongo:mapping-converter />
</constructor-arg>
</bean>
</beans>

View File

@@ -1,6 +1,6 @@
Bundle-SymbolicName: org.springframework.data.mongodb
Bundle-Name: Spring Data MongoDB Support
Bundle-Vendor: SpringSource
Bundle-Vendor: Pivotal Software, Inc.
Bundle-ManifestVersion: 2
Import-Package:
sun.reflect;version="0";resolution:=optional
@@ -8,7 +8,7 @@ Export-Template:
org.springframework.data.mongodb.*;version="${project.version}"
Import-Template:
com.google.common.base.*;version="[11.0.0,14.0.0)";resolution:=optional,
com.mongodb.*;version="0",
com.mongodb.*;version="${mongo-osgi:[=.=.=,+1.0.0)}",
com.mysema.query.*;version="[2.1.1, 3.0.0)";resolution:=optional,
javax.annotation.processing.*;version="0",
javax.enterprise.*;version="${cdi:[=.=.=,+1.0.0)}";resolution:=optional,

View File

@@ -34,15 +34,28 @@
<firstname>Thomas</firstname>
<surname>Darimont</surname>
</author>
<author>
<firstname>Christoph</firstname>
<surname>Strobl</surname>
</author>
</authorgroup>
<legalnotice>
<para>Copies of this document may be made for your own use and for
distribution to others, provided that you do not charge any fee for such
copies and further provided that each copy contains this Copyright
Notice, whether distributed in print or electronically.
</para>
distribution to others, provided that you do not charge any fee for such
copies and further provided that each copy contains this Copyright
Notice, whether distributed in print or electronically.</para>
</legalnotice>
<pubdate />
<copyright>
<year>2008-2014</year>
<holder> The original authors.</holder>
</copyright>
<productname>Spring Data MongoDB - Reference Documentation</productname>
</bookinfo>
<toc/>
@@ -53,10 +66,9 @@
<title>Introduction</title>
<xi:include href="introduction/introduction.xml"/>
<xi:include href="introduction/why-sd-doc.xml"/>
<xi:include href="introduction/requirements.xml"/>
<xi:include href="introduction/getting-started.xml"/>
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.7.0.RC1/src/docbkx/repositories.xml">
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.7.3.RELEASE/src/docbkx/repositories.xml">
<xi:fallback href="../../../spring-data-commons/src/docbkx/repositories.xml" />
</xi:include>
</part>
@@ -76,10 +88,10 @@
<part id="appendix">
<title>Appendix</title>
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.7.0.RC1/src/docbkx/repository-namespace-reference.xml">
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.7.3.RELEASE/src/docbkx/repository-namespace-reference.xml">
<xi:fallback href="../../../spring-data-commons/src/docbkx/repository-namespace-reference.xml" />
</xi:include>
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.7.0.RC1/src/docbkx/repository-query-keywords-reference.xml">
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.7.3.RELEASE/src/docbkx/repository-query-keywords-reference.xml">
<xi:fallback href="../../../spring-data-commons/src/docbkx/repository-query-keywords-reference.xml" />
</xi:include>
</part>

View File

@@ -19,7 +19,7 @@
<title>Community Forum</title>
<para>The Spring Data <ulink
url="http://forum.springframework.org/forumdisplay.php?f=80">forum
url="http://forum.spring.io/forum/spring-projects/data">forum
</ulink> is a message board for all Spring Data (not just Document)
users to share information and help each other. Note that registration
is needed <emphasis>only</emphasis> for posting.</para>
@@ -30,7 +30,7 @@
<para>Professional, from-the-source support, with guaranteed response
time, is available from <ulink
url="http://www.springsource.com">SpringSource</ulink>, the company
url="http://gopivotal.com/">Pivotal Sofware, Inc.</ulink>, the company
behind Spring Data and Spring.</para>
</section>
</section>

View File

@@ -62,21 +62,26 @@
<itemizedlist>
<listitem>
<para>The <ulink url="http://www.mongodb.org/#">online shell</ulink>
<para>The <ulink url="http://docs.mongodb.org/manual/">manual</ulink> introduces MongoDB and contains links to
getting started guides, reference documentation and tutorials.</para>
</listitem>
<listitem>
<para>The <ulink url="http://try.mongodb.org/">online shell</ulink>
provides a convenient way to interact with a MongoDB instance in
combination with the online <ulink type=""
url="http://www.mongodb.org/display/DOCS/Tutorial">tutorial.</ulink></para>
url="http://docs.mongodb.org/manual/tutorial/getting-started/">tutorial.</ulink></para>
</listitem>
<listitem>
<para>MongoDB <ulink
url="http://www.mongodb.org/display/DOCS/Java+Language+Center">Java
url="http://docs.mongodb.org/ecosystem/drivers/java/">Java
Language Center</ulink></para>
</listitem>
<listitem>
<para>Several <ulink
url="http://www.mongodb.org/display/DOCS/Books">books</ulink>
url="http://www.mongodb.org/books">books</ulink>
available for purchase</para>
</listitem>

View File

@@ -1,17 +1,14 @@
<chapter id="requirements">
<title>Requirements</title>
<para>Spring Data Document 1.x binaries requires JDK level 6.0 and above,
<para>Spring Data MongoDB 1.x binaries requires JDK level 6.0 and above,
and
<ulink url="https://spring.io/docs">Spring Framework</ulink>
3.0.x and above.
3.2.x and above.
</para>
<para>
In terms of document stores,
<ulink url="http://www.mongodb.org/">MongoDB</ulink>
preferably version 1.6.5
or later or
<ulink url="http://couchdb.apache.org/">CouchDB</ulink>
1.0.1 or later are required.
preferably version 2.4.
</para>
</chapter>

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE preface PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="why-Spring Data-Document">
<title>Why Spring Data - Document?</title>
<para>The Spring Framework is the leading full-stack Java/JEE application
framework. It provides a lightweight container and a non-invasive
programming model enabled by the use of dependency injection, AOP, and
portable service abstractions.</para>
<para><ulink url="http://en.wikipedia.org/wiki/NoSQL">NoSQL</ulink> storages
provide an alternative to classical RDBMS for horizontal scalability and
speed. In terms of implementation, Document stores represent one of the most
popular types of stores in the NoSQL space. The document database supported
by Spring Data are MongoDB and CouchDB, though just MongoDB integration has
been released to date.</para>
<para>The goal of the Spring Data Document (or DATADOC) framework is to
provide an extension to the Spring programming model that supports writing
applications that use Document databases. The Spring framework has always
promoted a POJO programming model with a strong emphasis on portability and
productivity. These values are caried over into Spring Data Document.
</para>
<para>Notable features that are used in Spring Data Document from the Spring
framework are the Features that particular, features from the Spring
framework that are used are the Conversion Service, JMX Exporters, portable
Data Access Exception hierarchy, Spring Expression Language, and Java based
IoC container configuration. The programming model follows the familiar
Spring 'template' style, so if you are familar with Spring template classes
such as JdbcTemplate, JmsTemplate, RestTemplate, you will feel right at
home. For example, MongoTemplate removes much of the boilerplate code you
would have to write when using the MongoDB driver to save POJOs as well as a
rich java based query interface to retrieve POJOs. The programming model
also offers a new Repository approach in which the Spring container will
provide an implementation of a Repository based soley off an interface
definition which can also include custom finder methods.</para>
</chapter>

View File

@@ -8,7 +8,7 @@
data stores can be of different types. One might be relational while the
other a document store. For this use case we have created a separate module
in the MongoDB support that handles what we call cross-store support. The
current implemenatation is based on JPA as the driver for the relational
current implementation is based on JPA as the driver for the relational
database and we allow select fields in the Entities to be stored in a Mongo
database. In addition to allowing you to store your data in two stores we
also coordinate persistence operations for the non-transactional MongoDB
@@ -186,14 +186,14 @@
<para>We are assuming that you have a working JPA application so we will
only cover the additional steps needed to persist part of your Entity in
your Mongo database. First you need to identify the field you want
persited. It should be a domain class and follow the general rules for the
persisted. It should be a domain class and follow the general rules for the
Mongo mapping support covered in previous chapters. The field you want
persisted in MongoDB should be annotated using the
<classname>@RelatedDocument</classname> annotation. That is really all you
need to do!. The cross-store aspects take care of the rest. This includes
marking the field with @Transient so it won't be persisted using JPA,
keeping track of any changes made to the field value and writing them to
the database on succesfull transaction completion, loading the document
the database on successful transaction completion, loading the document
from MongoDB the first time the value is used in your application. Here is
an example of a simple Entity that has a field annotated with
@RelatedEntity.</para>

View File

@@ -4,7 +4,7 @@
<chapter id="mapping-chapter">
<title>Mapping</title>
<para>Rich maping support is provided by the
<para>Rich mapping support is provided by the
<classname>MongoMappingConverter</classname>.
<classname>MongoMappingConverter</classname> has a rich metadata model that
provides a full feature set of functionality to map domain objects to
@@ -60,13 +60,13 @@
<listitem>
<para>You can have a single non-zero argument constructor whose
constructor argument names match top level field names of document,
that constructor will be used. Otherewise the zero arg constructor
will be used. if there is more than one non-zero argument constructor
an exception will be thrown.</para>
that constructor will be used. Otherwise the zero arg constructor will
be used. if there is more than one non-zero argument constructor an
exception will be thrown.</para>
</listitem>
</itemizedlist>
<section>
<section id="mapping.conventions.id-field">
<title>How the '_id' field is handled in the mapping layer</title>
<para>MongoDB requires that you have an '_id' field for all documents.
@@ -104,7 +104,7 @@
the Java class it will be converted to and stored as an ObjectId if
possible. ObjectId as a field type is also valid. If you specify a
value for 'id' in your application, the conversion to an ObjectId is
delected to the MongoDBdriver. If the specified 'id' value cannot be
detected to the MongoDBdriver. If the specified 'id' value cannot be
converted to an ObjectId, then the value will be stored as is in the
document's _id field.</para>
</listitem>
@@ -172,12 +172,14 @@ public class GeoSpatialAppConfig extends AbstractMongoConfiguration {
// the following are optional
@Bean
@Override
protected void afterMappingMongoConverterCreation(MappingMongoConverter converter) {
Set&lt;Converter&lt;?, ?&gt;&gt; converterList = new HashSet&lt;Converter&lt;?, ?&gt;&gt;();
public CustomConversions customConversions() throws Exception {
List&lt;Converter&lt;?, ?&gt;&gt; converterList = new ArrayList&lt;Converter&lt;?, ?&gt;&gt;();
converterList.add(new org.springframework.data.mongodb.test.PersonReadConverter());
converterList.add(new org.springframework.data.mongodb.test.PersonWriteConverter());
converter.setCustomConverters(converterList);
return new CustomConversions(converterList);
}
@Bean
@@ -454,7 +456,7 @@ public class Person&lt;T extends Address&gt; {
<section id="mapping-custom-object-construction">
<title>Customized Object Construction</title>
<para>The Mapping Subsystem allows the customization of the object
<para>The mapping subsystem allows the customization of the object
construction by annotating a constructor with the
<literal>@PersistenceConstructor</literal> annotation. The values to be
used for the constructor parameters are resolved in the following
@@ -487,9 +489,9 @@ public class Person&lt;T extends Address&gt; {
<programlisting language="java">class OrderItem {
@Id String id;
int quantity;
double unitPrice;
private @Id String id;
private int quantity;
private double unitPrice;
OrderItem(String id, @Value("#root.qty ?: 0") int quantity, double unitPrice) {
this.id = id;
@@ -642,20 +644,17 @@ public class Person {
3 Type Conversion</ulink>.</para>
</note>
<para>The <methodname>setConverters</methodname> method on
<classname>SimpleMongoConverter</classname> and
<classname>MappingMongoConverter</classname> should be used for this
purpose. The method
<methodname>afterMappingMongoConverterCreation</methodname> in
<classname>AbstractMongoConfiguration</classname> can be overriden to
configure a MappingMongoConverter. The examples <link
linkend="???">here</link> at the begining of this chapter show how to
perform the configuration using Java and XML.</para>
<para>The method <methodname>customConversions</methodname> in
<classname>AbstractMongoConfiguration</classname> can be used to
configure Converters. The examples <link
linkend="mapping-configuration">here</link> at the begining of this
chapter show how to perform the configuration using Java and XML.</para>
<para>Below is an example of a Spring Converter implementation that
converts from a DBObject to a Person POJO.</para>
<programlisting language="java">public class PersonReadConverter implements Converter&lt;DBObject, Person&gt; {
<programlisting language="java">@ReadingConverter
public class PersonReadConverter implements Converter&lt;DBObject, Person&gt; {
public Person convert(DBObject source) {
Person p = new Person((ObjectId) source.get("_id"), (String) source.get("name"));
@@ -668,7 +667,8 @@ public class Person {
<para>Here is an example that converts from a Person to a
DBObject.</para>
<programlisting language="java">public class PersonWriteConverter implements Converter&lt;Person, DBObject&gt; {
<programlisting language="java">@WritingConverter
public class PersonWriteConverter implements Converter&lt;Person, DBObject&gt; {
public DBObject convert(Person source) {
DBObject dbo = new BasicDBObject();
@@ -679,8 +679,6 @@ public class Person {
}
}</programlisting>
<para/>
</section>
</section>
</chapter>

View File

@@ -225,6 +225,15 @@ public class PersonRepositoryTests {
<entry><code>{"age" : {"$gt" : age}}</code></entry>
</row>
<row>
<entry><literal>GreaterThanEqual</literal></entry>
<entry><methodname>findByAgeGreaterThanEqual(int
age)</methodname></entry>
<entry><code>{"age" : {"$gte" : age}}</code></entry>
</row>
<row>
<entry><literal>LessThan</literal></entry>
@@ -234,6 +243,15 @@ public class PersonRepositoryTests {
<entry><code>{"age" : {"$lt" : age}}</code></entry>
</row>
<row>
<entry><literal>LessThanEqual</literal></entry>
<entry><methodname>findByAgeLessThanEqual(int
age)</methodname></entry>
<entry><code>{"age" : {"$lte" : age}}</code></entry>
</row>
<row>
<entry><literal>Between</literal></entry>
@@ -243,6 +261,24 @@ public class PersonRepositoryTests {
<entry><code>{"age" : {"$gt" : from, "$lt" : to}}</code></entry>
</row>
<row>
<entry><literal>In</literal></entry>
<entry><methodname>findByAgeIn(Collection ages)
</methodname></entry>
<entry><code>{"age" : {"$in" : [ages...]}}</code></entry>
</row>
<row>
<entry><literal>NotIn</literal></entry>
<entry><methodname>findByAgeNotIn(Collection ages)
</methodname></entry>
<entry><code>{"age" : {"$nin" : [ages...]}}</code></entry>
</row>
<row>
<entry><literal>IsNotNull</literal>,
<literal>NotNull</literal></entry>
@@ -358,7 +394,7 @@ public class PersonRepositoryTests {
</tgroup>
</table></para>
<section>
<section id="mongodb.repositories.queries.geo-spatial">
<title>Geo-spatial repository queries</title>
<para>As you've just seen there are a few keywords triggering
@@ -418,7 +454,7 @@ Distance distance = new Distance(200, Metrics.KILOMETERS);
</simplesect>
</section>
<section>
<section id="mongodb.repositories.queries.json-based">
<title>MongoDB JSON based query methods and field restriction</title>
<para>By adding the annotation
@@ -452,7 +488,7 @@ Distance distance = new Distance(200, Metrics.KILOMETERS);
set and its value will therefore be null.</para>
</section>
<section>
<section id="mongodb.repositories.queries.type-safe">
<title>Type-safe Query methods</title>
<para>MongoDB repository support integrates with the <ulink
@@ -529,7 +565,7 @@ Page&lt;Person&gt; page = repository.findAll(person.lastname.contains("a"),
</programlisting>
<para>To use this in your repository implementation, simply inherit from
it in additiion to other repository interfaces. This is shown
it in addition to other repository interfaces. This is shown
below</para>
<programlisting lang="" language="java">public interface PersonRepository extends MongoRepository&lt;Person, String&gt;, QueryDslPredicateExecutor&lt;Person&gt; {
@@ -543,12 +579,12 @@ Page&lt;Person&gt; page = repository.findAll(person.lastname.contains("a"),
</section>
</section>
<section>
<section id="mongodb.repositories.misc">
<title>Miscellaneous</title>
<para/>
<section>
<section id="mongodb.repositories.misc.cdi-integration">
<title>CDI Integration</title>
<para>Instances of the repository interfaces are usually created by a

View File

@@ -44,8 +44,8 @@
</listitem>
<listitem>
<para>Automatic implementatin of Repository interfaces including support
for custom finder methods.</para>
<para>Automatic implementation of Repository interfaces including
support for custom finder methods.</para>
</listitem>
<listitem>
@@ -81,20 +81,20 @@
<title>Getting Started</title>
<para>Spring MongoDB support requires MongoDB 1.4 or higher and Java SE 5
or higher. The latest production release (2.0.x as of this writing) is
or higher. The latest production release (2.4.9 as of this writing) is
recommended. An easy way to bootstrap setting up a working environment is
to create a Spring based project in <ulink
url="http://www.springsource.com/developer/sts">STS</ulink>.</para>
url="http://spring.io/tools/sts">STS</ulink>.</para>
<para>First you need to set up a running Mongodb server. Refer to the
<ulink url="http://www.mongodb.org/display/DOCS/Quickstart">Mongodb Quick
Start guide</ulink> for an explanation on how to startup a MongoDB
<ulink url="http://docs.mongodb.org/manual/core/introduction/">Mongodb
Quick Start guide</ulink> for an explanation on how to startup a MongoDB
instance. Once installed starting MongoDB is typically a matter of
executing the following command:
<literal>MONGO_HOME/bin/mongod</literal></para>
<para>To create a Spring project in STS go to File -&gt; New -&gt; Spring
Template Project -&gt; Simple Spring Utility Project --&gt; press Yes when
Template Project -&gt; Simple Spring Utility Project -&gt; press Yes when
prompted. Then enter a project and a package name such as
org.spring.mongodb.example.</para>
@@ -107,14 +107,14 @@
&lt;dependency&gt;
&lt;groupId&gt;org.springframework.data&lt;/groupId&gt;
&lt;artifactId&gt;spring-data-mongodb&lt;/artifactId&gt;
&lt;version&gt;1.1.0.RELEASE&lt;/version&gt;
&lt;version&gt;1.4.2.RELEASE&lt;/version&gt;
&lt;/dependency&gt;
&lt;/dependencies&gt;</programlisting>
<para>Also change the version of Spring in the pom.xml to be</para>
<programlisting lang="" language="xml">&lt;spring.framework.version&gt;3.1.2.RELEASE&lt;/spring.framework.version&gt;</programlisting>
<programlisting lang="" language="xml">&lt;spring.framework.version&gt;3.2.8.RELEASE&lt;/spring.framework.version&gt;</programlisting>
<para>You will also need to add the location of the Spring Milestone
repository for maven to your pom.xml which is at the same level of your
@@ -124,7 +124,7 @@
&lt;repository&gt;
&lt;id&gt;spring-milestone&lt;/id&gt;
&lt;name&gt;Spring Maven MILESTONE Repository&lt;/name&gt;
&lt;url&gt;http://repo.springsource.org/libs-milestone&lt;/url&gt;
&lt;url&gt;http://repo.spring.io/libs-milestone&lt;/url&gt;
&lt;/repository&gt;
&lt;/repositories&gt;</programlisting>
@@ -310,7 +310,7 @@ public class AppConfig {
classes annoated with the <literal>@Repository</literal> annotation.
This hierarchy and use of <literal>@Repository</literal> is described in
<ulink
url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/dao.html">Spring's
url="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/dao.html">Spring's
DAO support features</ulink>.</para>
<para>An example of a Java based bean metadata that supports exception
@@ -434,7 +434,7 @@ public class AppConfig {
DB getDb(String dbName) throws DataAccessException;
}</programlisting>
<para>The following sections show how you can use the contiainer with
<para>The following sections show how you can use the container with
either Java or the XML based metadata to configure an instance of the
<classname>MongoDbFactory</classname> interface. In turn, you can use
the <classname>MongoDbFactory</classname> instance to configure
@@ -970,7 +970,7 @@ DEBUG work.data.mongodb.core.MongoTemplate: 376 - Dropped collection [database.p
functionality</para>
</note>
<para>The query stynax used in the example is explained in more detail in
<para>The query syntax used in the example is explained in more detail in
the section <link linkend="mongo.query">Querying Documents</link>.</para>
<section id="mongo-template.id-handling">
@@ -1079,7 +1079,7 @@ mongoTemplate.save(sample);
</example>
<para>As you can see we store the type information for the actual root
class persistet as well as for the nested type as it is complex and a
class persistent as well as for the nested type as it is complex and a
subtype of <classname>Contact</classname>. So if you're now using
<methodname>mongoTemplate.findAll(Object.class, "sample")</methodname>
we are able to find out that the document stored shall be a
@@ -1197,8 +1197,8 @@ class SampleMongoConfiguration extends AbstractMongoConfiguration {
overriden using mapping metadata.</para>
<para>When inserting or saving, if the Id property is not set, the
assumption is that its value will be autogenerated by the database. As
such, for autogeneration of an ObjectId to succeed the type of the Id
assumption is that its value will be auto-generated by the database. As
such, for auto-generation of an ObjectId to succeed the type of the Id
property/field in your class must be either a
<classname>String</classname>, <classname>ObjectId</classname>, or
<classname>BigInteger</classname>.</para>
@@ -1284,14 +1284,14 @@ Person qp = mongoTemplate.findOne(query(where("age").is(33)), Person.class);
<listitem>
<para><emphasis role="bold">insertAll</emphasis> Takes a
<literal>Collection </literal>of objects as the first parameter.
This method ispects each object and inserts it to the
This method inspects each object and inserts it to the
appropriate collection based on the rules specified
above.</para>
</listitem>
<listitem>
<para><emphasis role="bold">save</emphasis> Save the object
ovewriting any object that might exist with the same id.</para>
overwriting any object that might exist with the same id.</para>
</listitem>
</itemizedlist></para>
</section>
@@ -1371,7 +1371,7 @@ import static org.springframework.data.mongodb.core.query.Update;
<title>Methods for the Update class</title>
<para>The Update class can be used with a little 'syntax sugar' as its
methods are meant to be chained together and you can kickstart the
methods are meant to be chained together and you can kick-start the
creation of a new Update instance via the static method
<literal>public static Update update(String key, Object
value)</literal> and using static imports.</para>
@@ -1450,7 +1450,7 @@ import static org.springframework.data.mongodb.core.query.Update;
<section id="mongo-template.upserts">
<title>Upserting documents in a collection</title>
<para>Related to perfomring an <methodname>updateFirst</methodname>
<para>Related to performing an <methodname>updateFirst</methodname>
operations, you can also perform an upsert operation which will perform
an insert if no document is found that matches the query. The document
that is inserted is a combination of the query document and the update
@@ -1459,7 +1459,7 @@ import static org.springframework.data.mongodb.core.query.Update;
<programlisting>template.upsert(query(where("ssn").is(1111).and("firstName").is("Joe").and("Fraizer").is("Update")), update("address", addr), Person.class);</programlisting>
</section>
<section>
<section id="mongo-template.find-and-upsert">
<title>Finding and Upserting documents in a collection</title>
<para>The <methodname>findAndModify(…)</methodname> method on
@@ -1617,7 +1617,7 @@ List&lt;Person&gt; result = mongoTemplate.find(query(where("age").lt(50)
</emphasis> <literal>(String key) </literal>Adds a chained
<classname>Criteria</classname> with the specified
<literal>key</literal> to the current
<classname>Criteria</classname> and retuns the newly created
<classname>Criteria</classname> and returns the newly created
one</para>
</listitem>
@@ -2029,7 +2029,7 @@ GeoResults&lt;Restaurant&gt; = operations.geoNear(query, Restaurant.class);</pro
methods on MongoOperations to simplify the creation and execution of
Map-Reduce operations. It can convert the results of a Map-Reduce
operation to a POJO also integrates with Spring's <ulink
url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/resources.html">Resource
url="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/resources.html">Resource
abstraction</ulink> abstraction. This will let you place your JavaScript
files on the file system, classpath, http server or any other Spring
Resource implementation and then reference the JavaScript resources via an
@@ -2143,7 +2143,7 @@ MapReduceResults&lt;ValueObject&gt; results = mongoOperations.mapReduce(query, "
providing methods on MongoOperations to simplify the creation and
execution of group operations. It can convert the results of the group
operation to a POJO and also integrates with Spring's <ulink
url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/resources.html">Resource
url="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/resources.html">Resource
abstraction</ulink> abstraction. This will let you place your JavaScript
files on the file system, classpath, http server or any other Spring
Resource implementation and then reference the JavaScript resources via an
@@ -2156,7 +2156,7 @@ MapReduceResults&lt;ValueObject&gt; results = mongoOperations.mapReduce(query, "
<title>Example Usage</title>
<para>In order to understand how group operations work the following
example is used, which is somewhat artifical. For a more realistic
example is used, which is somewhat artificial. For a more realistic
example consult the book 'MongoDB - The definitive guide'. A collection
named "group_test_collection" created with the following rows.</para>
@@ -2232,7 +2232,7 @@ MapReduceResults&lt;ValueObject&gt; results = mongoOperations.mapReduce(query, "
}
}</programlisting>
<para>You can also obtain tha raw result as a
<para>You can also obtain the raw result as a
<classname>DbObject</classname> by calling the method
<methodname>getRawResults</methodname> on the
<classname>GroupByResults</classname> class.</para>
@@ -2318,9 +2318,9 @@ GroupByResults&lt;XObject&gt; results = mongoTemplate.group(where("x").gt(0),
<para><classname>AggregationResults</classname> is the container for
the result of an aggregate operation. It provides access to the raw
aggreation result in the form of an <classname>DBObject</classname>,
to the mapped objects and information which performed the
aggregation.</para>
aggregation result in the form of an
<classname>DBObject</classname>, to the mapped objects and
information which performed the aggregation.</para>
</listitem>
</itemizedlist>
@@ -2344,8 +2344,8 @@ List&lt;OutputType&gt; mappedResult = results.getMappedResults();
<classname>MongoTemplate</classname> will derive the name of the input
collection from this class. Otherwise if you don't not specify an input
class you must provide the name of the input collection explicitly. If
an input-class and an explicity input-collection is provided the latter
takes precedence.</para>
an input-class and an input-collection is provided the latter takes
precedence.</para>
</section>
<section id="mongo.aggregation.supported-aggregation-operations">
@@ -2460,7 +2460,7 @@ project("a","b").and("foo").as("bar") // will generate {$project: {a: 1, b: 1, b
documentation.</para>
<section id="mongo.aggregation.projection.expressions">
<title>Spring Expresison Support in Projection Expressions</title>
<title>Spring Expression Support in Projection Expressions</title>
<para>As of Version 1.4.0 we support the use of SpEL expression in
projection expressions via the <methodname>andExpression</methodname>
@@ -2471,7 +2471,7 @@ project("a","b").and("foo").as("bar") // will generate {$project: {a: 1, b: 1, b
calculations.</para>
<example>
<title>Complex calulations with SpEL expressions</title>
<title>Complex calculations with SpEL expressions</title>
<para>The following SpEL expression:</para>
@@ -2499,14 +2499,14 @@ project("a","b").and("foo").as("bar") // will generate {$project: {a: 1, b: 1, b
<section id="mongo.aggregation.examples">
<title>Aggregation Framework Examples</title>
<para>The follwing examples demonstrate the usage patterns for the
<para>The following examples demonstrate the usage patterns for the
MongoDB Aggregation Framework with Spring Data MongoDB.</para>
<example id="mongo.aggregation.examples.example1">
<title>Aggregation Framework Example 1</title>
<para>In this introductory example we want to aggregate a list of tags
to get the occurence count of a particular tag from a MongoDB
to get the occurrence count of a particular tag from a MongoDB
collection called <code>"tags"</code> sorted by the occurrence count
in descending order. This example demonstrates the usage of grouping,
sorting, projections (selection) and unwinding (result
@@ -2555,7 +2555,7 @@ List&lt;TagCount&gt; tagCount = results.getMappedResults();</programlisting>
<listitem>
<para>In the forth step we use the <methodname>group</methodname>
operation to define a group for each <code>"tags"</code>-value for
which we aggregate the occurence count via the
which we aggregate the occurrence count via the
<methodname>count</methodname> aggregation operator and collect the
result in a new field called <code>"n"</code>.</para>
</listitem>
@@ -2569,7 +2569,7 @@ List&lt;TagCount&gt; tagCount = results.getMappedResults();</programlisting>
<listitem>
<para>As the sixth step we sort the resulting list of tags by their
occurence count in descending order via the
occurrence count in descending order via the
<methodname>sort</methodname> operation.</para>
</listitem>
@@ -2687,7 +2687,7 @@ ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0);
<para>As the forth step we select the <code>"state"</code> field
from the previous <methodname>group</methodname> operation. Note
that <code>"state"</code> again implicitly references an group-id
field. As we do not want an implict generated id to appear, we
field. As we do not want an implicit generated id to appear, we
exclude the id from the previous operation via
<code>and(previousOperation()).exclude()</code>. As we want to
populate the nested <classname>City</classname> structures in our
@@ -2885,7 +2885,7 @@ List&lt;DBObject&gt; resultList = result.getMappedResults();</programlisting>
<note>
<para>For more information on the Spring type conversion service see the
reference docs <ulink
url="http://static.springsource.org/spring/docs/3.0.x/reference/validation.html#core-convert">here</ulink>.</para>
url="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert">here</ulink>.</para>
</note>
<section id="mongo.custom-converters.writer">
@@ -2916,7 +2916,7 @@ public class PersonWriteConverter implements Converter&lt;Person, DBObject&gt; {
<section id="mongo.custom-converters.reader">
<title>Reading using a Spring Converter</title>
<para>An example implemention of a Converter that converts from a
<para>An example implementation of a Converter that converts from a
DBObject ot a Person object is shownn below</para>
<programlisting language="java">public class PersonReadConverter implements Converter&lt;DBObject, Person&gt; {
@@ -2968,7 +2968,7 @@ public class PersonWriteConverter implements Converter&lt;Person, DBObject&gt; {
&lt;/mongo:mapping-converter&gt;</programlisting>
</section>
<section>
<section id="mongo.converter-disambiguation">
<title>Converter disambiguation</title>
<para>Generally we inspect the <interfacename>Converter</interfacename>
@@ -2999,12 +2999,12 @@ class MyConverter implements Converter&lt;String, Person&gt; { … }</programlis
</section>
<section id="mongo-template.index-and-collections">
<title>Index and Collection managment</title>
<title>Index and Collection management</title>
<para><classname>MongoTemplate</classname> provides a few methods for
managing indexes and collections. These are collected into a helper
interface called <interfacename>IndexOperations</interfacename>. You
access these operations by calilng the method
access these operations by calling the method
<methodname>indexOps</methodname> and pass in either the collection name
or the <literal>java.lang.Class</literal> of your entity (the collection
name will be derived from the .class either by name or via annotation
@@ -3244,11 +3244,11 @@ mongoTemplate.dropCollection("MyNewCollection"); </programlisting>
interface.</para>
<para>The motivation behind mapping to Spring's <ulink
url="http://static.springsource.org/spring/docs/3.0.x/reference/dao.html#dao-exceptions">consistent
url="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/dao.html#dao-exceptions">consistent
data access exception hierarchy</ulink> is that you are then able to write
portable and descriptive exception handling code without resorting to
coding against <ulink
url="http://www.mongodb.org/display/DOCS/Error+Codes">MongoDB error
url="http://www.mongodb.org/about/contributors/error-codes/">MongoDB error
codes</ulink>. All of Spring's data access exceptions are inherited from
the root <classname>DataAccessException</classname> class so you can be
sure that you will be able to catch all database related exception within
@@ -3390,8 +3390,8 @@ mongoTemplate.dropCollection("MyNewCollection"); </programlisting>
&lt;/beans&gt;</programlisting>
</example>
<para>You can no get the template injected and perform storing and
retrieving operations to it.</para>
<para>The template can now be injected and used to perform storage and
retrieval operations.</para>
<example>
<title>Using GridFsTemplate to store files</title>

View File

@@ -1,6 +1,195 @@
Spring Data MongoDB Changelog
=============================
Changes in version 1.4.3.RELEASE (2014-06-18)
---------------------------------------------
* DATAMONGO-955 - Release 1.4.3.
* DATAMONGO-953 - Update object should have a proper equals/hashcode/toString.
* DATAMONGO-952 - @Query annotation does not work with only field restrictions.
* DATAMONGO-948 - Assertion error in MongoTemplate.getMappedSortObject.
* DATAMONGO-938 - Exception when creating geo within Criteria using MapReduce.
* DATAMONGO-924 - Aggregation not working with as() method in project() pipeline operator.
* DATAMONGO-920 - Fix debug messages for delete events in AbstractMongoEventListener.
* DATAMONGO-917 - DefaultDbRefResolver throws NPE when bundled into an uberjar.
* DATAMONGO-914 - Improve resolving of LazyLoading proxies for classes that override equals/hashcode.
* DATAMONGO-913 - Can't query using lazy DBRef objects.
* DATAMONGO-912 - Aggregation#project followed by Aggregation#match with custom converter causes IllegalArgumentException.
* DATAMONGO-898 - MapReduce seems not to work when javascript not being escaped.
* DATAMONGO-847 - Allow usage of Criteria within Update.
* DATAMONGO-745 - @Query($in) and Pageable in result Page total = 0.
* DATAMONGO-647 - Using "OrderBy" in "query by method name" ignores the @Field annotation for field alias.
Changes in version 1.5.0.RELEASE (2014-05-20)
---------------------------------------------
* DATAMONGO-936 - Release 1.5 GA.
* DATAMONGO-929 - Index key should be the properties dot path when creating index using @Indexed / @CompoundIndex.
* DATAMONGO-928 - Error when using field-naming-strategy-ref.
* DATAMONGO-926 - Stack Overflow Error with 1.5.0.RC1 Release.
* DATAMONGO-925 - MappingMongoConverterParser is incorrectly rejecting field-naming-strategy-ref XML configuration.
* DATAMONGO-647 - Using "OrderBy" in "query by method name" ignores the @Field annotation for field alias.
* DATAMONGO-367 - @Indexed field in embedded Object creates new collection.
Changes in version 1.5.0.RC1 (2014-05-02)
-----------------------------------------
* DATAMONGO-924 - Aggregation not working with as() method in project() pipeline operator.
* DATAMONGO-921 - Upgrade to MongoDB Java driver 2.12.1.
* DATAMONGO-920 - Fix debug messages for delete events in AbstractMongoEventListener.
* DATAMONGO-919 - Release 1.5 RC1.
* DATAMONGO-917 - DefaultDbRefResolver throws NPE when bundled into an uberjar.
* DATAMONGO-914 - Improve resolving of LazyLoading proxies for classes that override equals/hashcode.
* DATAMONGO-913 - Can't query using lazy DBRef objects.
* DATAMONGO-912 - Aggregation#project followed by Aggregation#match with custom converter causes IllegalArgumentException.
* DATAMONGO-910 - Upgrade to latest MongoDB Java driver (2.12).
* DATAMONGO-909 - @CompoundIndex on inherited entity classes.
* DATAMONGO-908 - Nested field references in group operations broken.
* DATAMONGO-907 - Assert compatibility with mongodb 2.6.0.
* DATAMONGO-905 - Remove obsolete CGLib dependency from cross store module.
* DATAMONGO-901 - MongoRepositoryConfigurationExtension fails to invoke super method.
* DATAMONGO-899 - Overhaul automatic index creation.
* DATAMONGO-898 - MapReduce seems not to work when javascript not being escaped.
* DATAMONGO-897 - FindAndUpdate broken when using @DbRef and interface as target.
* DATAMONGO-896 - Assert compatibility with latest MongoDB Java driver.
* DATAMONGO-895 - Use most specific type for checks against values in DBObjects.
* DATAMONGO-893 - Mapping Convertor does not remove "_class" property on collection of embedded objects.
* DATAMONGO-892 - <mongo:mapping-converter> can't be configured as nested bean definition.
* DATAMONGO-888 - Mapping is not applied to SortObject during queries.
* DATAMONGO-866 - Add new field naming strategy and make it configurable through XML/Java config.
* DATAMONGO-847 - Allow usage of Criteria within Update.
* DATAMONGO-827 - @Indexed and @CompundIndex cannot be created without giving index name.
Changes in version 1.4.2.RELEASE (2014-04-15)
---------------------------------------------
** Fix
* [DATAMONGO-880] - Improved handling of persistence of lazy-loaded DBRefs.
* [DATAMONGO-884] - Improved handling for Object methods in LazyLoadingInterceptor.
* [DATAMONGO-887] - Added unit tests to verify TreeMaps can be converted.
* [DATAMONGO-888] - Sorting now considers mapping information.
* [DATAMONGO-890] - Fixed Point.toString().
* [DATAMONGO-892] - Reject nested MappingMongoConverter declarations in XML.
* [DATAMONGO-893] - Converter must not write "_class" information for know types.
* [DATAMONGO-897] - Fixed potential NullPointerException in QueryMapper.
* [DATAMONGO-908] - Support for nested field references in group operations.
** Improvement
* [DATAMONGO-881] - Allow custom conversions to override default conversions.
** Task
* [DATAMONGO-895] - Use most specific type for checks against values in DBObjects.
* [DATAMONGO-896] - Assert compatibility with latest MongoDB Java driver.
* [DATAMONGO-905] - Removed obsolete dependency to CGLib from cross-store support.
* [DATAMONGO-907] - Assert compatibility with mongodb 2.6.
* [DATAMONGO-911] - Release 1.4.2
Changes in version 1.5.0.M1 (2014-03-31)
----------------------------------------
** Fix
* [DATAMONGO-471] - Update operation $addToSet does not support adding a list with $each.
* [DATAMONGO-773] - Spring Data MongoDB projection search on @DBref fields.
* [DATAMONGO-821] - MappingException for $size queries on subcollections containing dbrefs.
* [DATAMONGO-829] - NearQuery, when used in conjunction with a Query, it sets num=0, unless Query specifies otherwise.
* [DATAMONGO-833] - EnumSet is not handled correctly.
* [DATAMONGO-843] - Unable to use @EnableMongoAuditing annotation in Java config.
* [DATAMONGO-862] - Update Array Field Using Positional Operator ($) Does Not Work.
* [DATAMONGO-863] - QueryMapper.getMappedValue Fails To Handle Arrays Mapped To $in.
* [DATAMONGO-868] - findAndModify method does not increment @Version field.
* [DATAMONGO-871] - Declarative query method with array return type causes NPE.
* [DATAMONGO-877] - AbstractMongoConfiguration.getMappingBasePackage() throws NullPointerException if config class resides in default package.
* [DATAMONGO-880] - Error when trying to persist an object containing a DBRef which was lazy loaded.
* [DATAMONGO-884] - Potential NullPointerException for lazy DBRefs.
* [DATAMONGO-887] - Repository not instantiated when entity contains field of type TreeMap.
* [DATAMONGO-890] - Point class toString method is confusing.
** Improvement
* [DATAMONGO-809] - Make filename optional in GridFsOperations doc and GridFsTemplate implementation.
* [DATAMONGO-858] - Add support for common geospatial structures.
* [DATAMONGO-865] - Adjust test dependencies to avoid ClassNotFoundException during test runs.
* [DATAMONGO-881] - Cannot override default converters in CustomConversions.
* [DATAMONGO-882] - Adapt to removal of obsolete generics in BeanWrapper.
** New Feature
* [DATAMONGO-566] - Provide support for removeBy… / deleteBy… methods like for findBy… on repository interfaces.
* [DATAMONGO-870] - Add support for sliced query method execution.
** Task
* [DATAMONGO-876] - Adapt to changes introduced for property access configuration.
* [DATAMONGO-883] - Update auditing configuration to enable auditing annotations on accessors.
* [DATAMONGO-859] - Release 1.5 M1.
Changes in version 1.4.1.RELEASE (2014-03-13)
---------------------------------------------
** Fix
* [DATAMONGO-773] - Verify that @DBRef fields can be included in query.
* [DATAMONGO-821] - Fixed handling of keyword expressions for DBRefs.
* [DATAMONGO-829] - NearQuery should not default 'num' to zero.
* [DATAMONGO-833] - Support for EnumSet and EnumMap in MappingMongoConverter.
* [DATAMONGO-843] - Back-port of defaulting of the MappingContext for auditing.
* [DATAMONGO-862] - Fixed handling of unmapped paths for updates.
* [DATAMONGO-863] - UpdateMapper doesn't convert raw DBObjects anymore.
* [DATAMONGO-868] - MongoTemplate.findAndModify(…) increases version if not handled manually.
* [DATAMONGO-871] - Add support for arrays as query method return types.
* [DATAMONGO-877] - Added guard against null-package in AbstractMappingConfiguration.
** Improvement
* [DATAMONGO-865] - Adjust test dependencies to avoid ClassNotFoundException during test runs.
Changes in version 1.3.5.RELEASE (2014-03-10)
---------------------------------------------
** Fix
* [DATAMONGO-829] - NearQuery, when used in conjunction with a Query, no longer sets num=0, unless Query specifies otherwise.
* [DATAMONGO-871] - Repository queries support array return type.
** Improvement
* [DATAMONGO-865] - Avoid ClassNotFoundException during test runs.
Changes in version 1.4.0.RELEASE (2014-02-24)
---------------------------------------------
** Fix
* [DATAMONGO-354] - MongoTemplate should support multiple $pushAll in one update.
* [DATAMONGO-404] - Removing a DBRef using pull does not work.
* [DATAMONGO-410] - Update with pushAll should recognize defined Converter.
* [DATAMONGO-812] - $pushAll is deprecated since mongodb 2.4 move to $push $each.
* [DATAMONGO-830] - Fix NPE during cache warmup in CustomConversions.
* [DATAMONGO-838] - Support for refering to expression based field in group operation.
* [DATAMONGO-840] - Support for nested MongoDB field references in SpEL expressions within Projections.
* [DATAMONGO-842] - Fix documentation error in GRIDFS section.
* [DATAMONGO-852] - Increase version for update should traverse DBObject correctly in order to find version property.
** Improvement
* [DATAMONGO-468] - Simplification for updates of DBRef fields with mongoTemplate.
* [DATAMONGO-849] - Documentation on github should not reference invalid class.
** Task
* [DATAMONGO-848] - Ensure compatibility with Mongo Java driver 2.12.
* [DATAMONGO-853] - Update no longer allows null keys.
* [DATAMONGO-856] - Update documentation.
Changes in version 1.3.4.RELEASE (2014-02-17)
---------------------------------------------
** Bug
* [DATAMONGO-407] - Collection with generics losing element type after $set update
* [DATAMONGO-410] - Update with pushAll doesnt recognize defined Converter
* [DATAMONGO-686] - ClassCastException while reusing Query object
* [DATAMONGO-805] - Excluding DBRef field in a query causes a MappingException
* [DATAMONGO-807] - using findAndModify removes the _class field of encapsulated classes, causing MappingInstantiationException
* [DATAMONGO-808] - Spring data mongoDB not working with IPv6 address directly
* [DATAMONGO-811] - updateFirst methods do not increment @Version field
* [DATAMONGO-816] - Unable to execute query with DocumentCallbackHandler when query contains Criteria with enums.
* [DATAMONGO-828] - UpdateFirst throws OptimisticLockingFailureException when updating document that does not exist
* [DATAMONGO-830] - NPE during cache warmup in CustomConversions
* [DATAMONGO-842] - Documentation error in GRIDFS section
** Improvement
* [DATAMONGO-813] - GridFsTemplate.getResource(location) throws NPE if don't find file
** Task
* [DATAMONGO-824] - Add contribution guidelines
* [DATAMONGO-846] - Release 1.3.4
Changes in version 1.4.0.RC1 (2014-01-29)
---------------------------------------------

View File

@@ -1,5 +1,5 @@
Spring Data MongoDB 1.4.0 RC1
Copyright (c) [2010-2014] Pivotal Inc.
Spring Data MongoDB 1.4.3
Copyright (c) [2010-2014] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
You may not use this product except in compliance with the License.
@@ -7,4 +7,4 @@ You may not use this product except in compliance with the License.
This product may include a number of subcomponents with
separate copyright notices and license terms. Your use of the source
code for the these subcomponents is subject to the terms and
conditions of the subcomponent's license, as noted in the LICENSE file.
conditions of the subcomponent's license, as noted in the LICENSE file.

View File

@@ -1,5 +1,5 @@
SPRING DATA MongoDB 1.4.0.RC1
-----------------------------
Spring Data MongoDB 1.4.2.RELEASE
---------------------------------
Spring Data MongoDB is released under the terms of the Apache Software License Version 2.0 (see license.txt).
@@ -13,5 +13,5 @@ The reference manual and javadoc are located in the 'docs' directory.
ADDITIONAL RESOURCES:
Spring Data Homepage: http://www.springsource.org/spring-data
Spring Data Forum: http://forum.springsource.org/forumdisplay.php?f=80
Spring Data Homepage: http://projects.spring.io/spring-data
Spring Data Forum: http://forum.spring.io/forum/spring-projects/data/nosql