Compare commits

...

74 Commits

Author SHA1 Message Date
Spring Buildmaster
b2d59f2539 DATAMONGO-1007 - Release version 1.5.2 (Dijkstra SR2). 2014-07-28 02:14:12 -07:00
Oliver Gierke
1cc2830e95 DATAMONGO-1007 - Prepare 1.5.2.RELEASE (Dijkstra SR2). 2014-07-28 10:43:09 +02:00
Oliver Gierke
9a4d6f6fb7 DATAMONGO-1007 - Updated changelog. 2014-07-28 10:19:43 +02:00
Oliver Gierke
e96db8b69b DATAMONGO-981 - Updated changelog. 2014-07-25 06:20:22 +02:00
Christoph Strobl
13ce75779f DATAMONGO-1001 - Fix saving lazy loaded object.
We now resolve the target type for CGLib-proxied objects and initialize lazy loaded ones before saving. As it turns out CustomConversions already knows how to deal with proxies correctly. Ee added an explicit test to assert that.

Original pull request: #208.
2014-07-24 13:42:05 +02:00
Oliver Gierke
cc785ecf4f DATAMONGO-1002 - Update.toString() now uses SerializationUtils.
A simple call of toString() on a DBObject might result in an exception if the DBObject contains objects that are non-native MongoDB types (i.e. types that need to be converted prior to persistence).

We now use SerializationUtils.serializeToJsonSafely(…) to avoid exceptions.
2014-07-23 12:35:39 +02:00
Thomas Darimont
41fe1809da DATAMONGO-995 - Improve support of quote handling for custom query parameters.
Introduced ParameterBindingParser which exposes parameter references in query strings as ParameterBindings. This allows us to detect whether a parameter reference in a query string is already quoted avoiding wrongly double-quoting the parameter value.

Original pull request: #185.
Related ticket: DATAMONGO-420.
2014-07-21 20:16:53 +02:00
Thomas Darimont
fd5d39f4d9 DATAMONGO-420 - Improve support of quote handling for custom query parameters.
Introduced ParameterBindingParser which exposes parameter references in query strings as ParameterBindings. This allows us to detect whether a parameter reference in a query string is already quoted avoiding wrongly double-quoting the parameter value.

Original pull request: #185.
2014-07-17 15:27:53 +02:00
Oliver Gierke
71d97ff53a DATAMONGO-987 - Some polishing in MappingMongoConverter.
Let getValueInternal(…) use the provided SpELExpressionEvaluator instead of relying on the MongoDbPropertyValueProvider to create a new one. Removed the obsolete constructor in MongoDbPropertyValueProvider.
2014-07-17 15:18:55 +02:00
Thomas Darimont
f2f3faef08 DATAMONGO-987 - Avoid creation of lazy-loading proxies for null-values.
We now avoid creating a lazy-loading proxy if we detect that the property-value in the backing DbObject for a @Lazy(true) annotated field is null.

Original pull request: #207.
2014-07-17 15:18:47 +02:00
Thomas Darimont
b1a488098b DATAMONGO-989 - MatchOperation should accept CriteriaDefinition.
Added additional constructor that accepts CriteriaDefinition to not force clients to extends Criteria. We deprecated the original constructor and delegate to the new one. We keep the old one to remain binary compatible.

Original pull request: #206.
2014-07-17 09:01:48 +02:00
Christoph Strobl
fd18a8b82c DATAMONGO-983 - Remove links to forum.spring.io.
Replace forum links with those to stackoverflow.

Original Pull Request: #205
2014-07-10 07:08:43 +02:00
Christoph Strobl
761d2748c5 DATAMONGO-969 - Fixed nested id handling in SpringDataMongodbSerializer.
SpringDataMongodbSerializer now defensively triggers mapping of the DBObject created by the default serializer. This asserts that ids buried in nested structures like { "_id" : { "$in" : ["x", "y"] } } are converted correctly.

Original pull request: #202.
2014-07-09 21:47:03 +02:00
Christoph Strobl
e2f3966763 DATAMONGO-972 - Querydsl integration now handles references correctly.
SpringDataMongodbSerializer now overrides the necessary methods to create the appropriate DBRef objects when serializing data via Querydsl.

We currently disable the test case as it the fix taking effect requires Querydsl 3.4.1 which unfortunately breaks Java 6 compatibility. We include the fix nonetheless to allow users on Java 7 to potentially use the latest Querydsl.

Original pull request: #203.
Related tickets: querydsl/querydsl#803.
2014-07-09 13:47:25 +02:00
Oliver Gierke
2b3e5461c2 DATAMONGO-982 - Added build profiles to build against next MongoDB driver versions.
Added build profile for MongoDB Java driver versions 2.12.3-SNAPSHOT and 3.0.0-SNAPSHOT. Added another property to be able to build manifests correctly as the snapshot versions aren't valid OSGi versions.

Adapted MongoExceptionTranslator to convert the new Exceptions being thrown for server timeouts and the deprecated values we currently handle.
2014-07-08 17:34:48 +02:00
Christoph Strobl
ca4db459c4 DATAMONGO-978 - Derived delete query should pass on type information.
We now pass on type information for derived delete queries to the according delete operation. This propagates the information correctly to the according Before and After events.

Before this change the type would have been set to null in case of non collection like method return type.

Original pull request: #199.
2014-07-03 14:19:21 +02:00
Oliver Gierke
e0d0a5dc31 DATAMONGO-971 - After release cleanups. 2014-06-30 16:47:27 +02:00
Spring Buildmaster
18761a39c4 DATAMONGO-971 - Prepare next development iteration. 2014-06-30 07:02:21 -07:00
Spring Buildmaster
ad25751dbb DATAMONGO-971 - Release version 1.5.1.RELEASE (Dijkstra SR1). 2014-06-30 07:02:16 -07:00
Oliver Gierke
879ca6d149 DATAMONGO-971 - Prepare 1.5.1.RELEASE (Dijkstra SR1). 2014-06-30 15:01:20 +02:00
Oliver Gierke
8a40cf421c DATAMONGO-971 - Updated changelog. 2014-06-30 15:01:02 +02:00
Oliver Gierke
fbbb7b6bf7 DATAMONGO-955 - Updated changelog. 2014-06-30 10:56:34 +02:00
Christoph Strobl
0596403081 DATAMONGO-962 - Cycle guard should respect full path.
We now check on intersections of given path and existing to not only check types and contained property names but also properties full path which must not be present in already traversed paths.

Additionally we’ll now catch any CyclicPropertyReferenceExceptions on the root level to prevent cycle detection interfering with application startup.

Original pull request: #197.
2014-06-27 19:26:51 +02:00
Oliver Gierke
d9eb18df4d DATAMONGO-970 - MongoTemplate.remove(…) now correctly builds query for DBObjects.
If a DBObject was handed into MongoTemplate.remove(…) we previously failed to look up the id value to create a by-id-query. This commit adds explicit handling of DBObjects by looking up their _id field to obtain the id value.
2014-06-27 16:13:17 +02:00
Christoph Strobl
132e4a9839 DATAMONGO-963 - @CompoundIndex should treat expireAfterSeconds correctly.
We added an additional check on the fields used as key, so that TTL is ignored for CompoundIndex with more than one field (which effectively renders it useless on @CompoundIndex at all).

Prior to this change potentially invalid index structures would have been created for e.g. @CompoundIndex(def = "{'foo': 1, 'bar': 1}", expireAfterSeconds=10) leading to MongoDB not being able to clean up the indexes (logs: "ERROR: key for ttl index can only have 1 field")

This fix is related to https://jira.mongodb.org/browse/SERVER-10075.

Original pull request: #196.
2014-06-25 15:54:24 +02:00
Thomas Darimont
4acf8caac1 DATAMONGO-953 - Add equals(…)/hashCode()/toString() to Update.
We now use the underlying updateObject to implement appropriate equals(…)/hashCode() and toString() methods.

Original pull request: #192.
2014-06-25 12:40:44 +02:00
Christoph Strobl
9de3c88e6b 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:25 +02:00
Christoph Strobl
6115c9562b DATAMONGO-949 - CycleGuard should only match properties in word boundaries.
We modified the regular expression used for cycle detection to match on the exact property name within the inspected path using word boundaries. This fix prevents sub sequences of an existing property (like ‘sub’ would have matched ‘substr’) from being matched.

Along the way we fixed the (false) assertion in one of the tests, as we create the +1 cycle reference index before actually breaking the operation.
2014-06-18 08:32:40 +02:00
Christoph Strobl
963a222616 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:20:55 +02:00
Thomas Darimont
7a2de49ac1 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:20:53 +02:00
Thomas Darimont
8380806d7a 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:46 +02:00
Oliver Gierke
e60479e47a DATAMONGO-936 - Prepare next development iteration. 2014-05-22 12:22:35 +02:00
Spring Buildmaster
c7e65cbc40 DATAMONGO-936 - Release version 1.5.0.RELEASE. 2014-05-20 09:35:35 -07:00
Oliver Gierke
b8e02efb04 DATAMONGO-936 - Prepare 1.5 GA.
Removed obsolete readme.txt.
2014-05-20 17:14:04 +02:00
Oliver Gierke
c7f20fb836 DATAMONGO-936 - Prepare 1.5 GA. 2014-05-20 16:57:34 +02:00
Oliver Gierke
28a0202ef4 DATAMONGO-936 - Updated changelog. 2014-05-20 16:26:20 +02:00
Christoph Strobl
164e947045 DATAMONGO-926 - Avoid StackOverflowError while resolving index structures.
We now guard cyclic non transient, non DBRef property references while inspecting domain types for potentially index structures. To do so we check on the properties path and owning type to determine potential cycles. If found we log a warn message pointing to path, entity and property potentially causing problems and skip processing for this path.

Original pull request: #180.
2014-05-19 19:09:40 +02:00
Christoph Strobl
7e65c0c87d DATAMONGO-367 - Nested @Indexed should not trigger creation of separate collection.
The issue has been solved along with DATAMONGO-888 (Pull Request: #162). We have created additional tests to explicitly check it has truly been fixed.
2014-05-19 17:19:28 +02:00
Christoph Strobl
9c1f753f17 DATAMONGO-929 - Use property path for keys of Indexed and CompoundIndex.
Index creation failed for @Indexed and @CompoundIndex as the resolved dotPath was not used for creation. We now not only resolve the dotPath but also use it within the key for index definition. In case of a nested compound index the key definition is enhanced by the provided path.

When leaving the key definition empty for nested compound index we'll create an index for the whole nested document. Trying to create a compound index on root level not providing key information leads to InvalidDataApiUsageException.

Original pull request: #179.
2014-05-19 17:14:36 +02:00
Oliver Gierke
0f821eb52d DATAMONGO-925, DATAMONGO-928 - Polishing.
Only reject attribute setup if abbreviation is activated and a custom strategy is configured. Additional test cases for the rejection case and a custom, over-configuration (explicitly setting abbreviation to false, which is the default anyway).

Related pull request: #177.
2014-05-19 17:07:50 +02:00
Ryan Tenney
e3aadd63ab DATAMONGO-928 - Removed explicit default value for abbreviate-field-names from namespace XSD.
The default for boolean attributes leaks into the evaluation of XML namespace attributes which causes us being unable to detect whether two attributes have been set in a conflicting way.

Fix the documentation on the field-naming-strategy-ref attribute.

Original pull request: #183.
Related pull request: #177.
Related ticket: DATAMONGO-925.
2014-05-19 16:44:03 +02:00
Christoph Strobl
aa06d520df 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:38:06 +02:00
Oliver Gierke
4fa1d4ba97 DATAMONGO-919 - After release cleanups. 2014-05-02 15:45:06 +02:00
Spring Buildmaster
ba9f11b345 DATAMONGO-919 - Prepare next development iteration. 2014-05-02 05:58:16 -07:00
Spring Buildmaster
4777dd2e5e DATAMONGO-919 - Release version 1.5.0.RC1. 2014-05-02 05:58:13 -07:00
Oliver Gierke
64b4591b72 DATAMONGO-919 - Prepare 1.5.0.RC1.
Upgraded to Spring Data Parent 1.4.0.RC1 and Spring Data Commons 1.8.0.RC1. Switched to milestone repository.
2014-05-02 14:50:48 +02:00
Oliver Gierke
bac5961fd8 DATAMONGO-919 - Updated changelog. 2014-05-02 14:50:48 +02:00
Thomas Darimont
b3cac862d8 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:46:13 +02:00
Oliver Gierke
b8ab2ad539 DATAMNOG-919 - Forward port changelog entries from bugfix branch. 2014-05-02 12:48:18 +02:00
Christoph Strobl
618d5bd5e9 DATAMONGO-919 - Prepare Release 1.5.0.RC1
Upgrade AspectJ Maven plugin to recent version which enables building the module on java 8 using AspectJ 1.8.
2014-05-01 21:02:01 +02:00
Oliver Gierke
096c3278b3 DATAMONGO-92 - Upgraded to MongoDB Java driver 2.12.1. 2014-04-30 08:51:18 +02:00
Kim Toms
c9d9976c22 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:52:12 +02:00
Thomas Darimont
65497f93d4 DATAMONGO-827 - Allow index creation use mongodb auto generated names.
We now support letting MongoDB generate index names by introducing the attribute "useGeneratedName" to the @Indexed, @GeoSpatialIndex, @CompoundIndex annotations.
2014-04-28 19:58:56 +02:00
Christoph Strobl
af8a53bef6 DATAMONGO-909 - Compound index on inherited class should be created correctly.
With the overhaul of the index creation done in DATAMONGO-899 the CompoundIndex annotation is not longer just looked up at the concrete type but rather all its interfaces and super classes. So we just added an additional test to verify this behaviour.
2014-04-28 19:58:56 +02:00
Oliver Gierke
916b856e97 DATAMONGO-899 - Polished API of new index creation abstractions.
Removed the introduction of the IndexDefinition being collection aware again. The collection an index is created in is now held in the IndexDefinitionHolder. This is mostly due to the fact that the IndexDefinition implementations can be used with MongoTemplate and the index opoerations take a collection alongside the index definition.

Made the IndexResolver API package protected so that we can further change it going forward. We should think about deprecating the collectionName attributes on index annotations as it doesn't make too much sense to manually configure the collection name for the indexes as the collection is predefined through the domain type setting here. This would allow us to remove the entire collection handling code inside the IndexResolver implementation.

Turned IndexDefinitionHolder into a value object.

Original pull request: #168.
2014-04-28 19:58:50 +02:00
Christoph Strobl
7848da63f2 DATAMONGO-899 - Ensure proper creation of index structures for nested entities.
Index creation did not consider the properties path when creating the index. This lead to broken index creation when nesting entities that might require index structures.

Off now index creation traverses the entities property path for all top level entities (namely those holding the @Document annotation) and creates the index using the full property path.

This is a breaking change as having an entity to carry the @Document annotation has not been required by now.

Original Pull Request: #168
2014-04-28 16:50:26 +02:00
Christoph Strobl
f359a1d31a 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:13 +02:00
Thomas Darimont
72d645feae DATAMONGO-917 - Improve Spring 4.0 framework version detection to avoid NullPointerExceptions.
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:21:09 +02:00
Thomas Darimont
72fe382bba 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:07:57 +02:00
Thomas Darimont
d25e840cf5 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:29:33 +02:00
Thomas Darimont
df1775572a 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:00:56 +02:00
Christoph Strobl
86670cd49f 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:36:11 +02:00
Christoph Strobl
31a4bf906e 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:11:12 +02:00
Christoph Strobl
599291e8b7 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:11:00 +02:00
Thomas Darimont
f5a04fb9fb 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:55:57 +02:00
Oliver Gierke
88558b67c3 DATAMONGO-866 - Polishing for new field naming strategy configuration support.
Use the camel case split logic from Spring Data Commons (introduced for DATACMNS-486) in a common CamelCaseSplittingFieldNamingStrategy super class.

MappingMongoConverterParser now also rejects the configuration if both abbreviate-field-names and field-naming-strategy-ref are configured.
2014-04-10 18:36:37 +02:00
Ryan Tenney
d52cb255e0 DATAMONGO-866 - Add means to configure field naming strategy. 2014-04-10 18:03:55 +02:00
Oliver Gierke
6c214cbc37 DATAMONGO-910 - Upgraded to latest MongoDB Java driver 2.12.0.
Removed special mongo-osgi property as the driver version is now a valid OSGi version number.
2014-04-10 16:55:22 +02:00
Jeff Yemin
01012c1448 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:45 +02:00
Christoph Strobl
4c7befb910 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:43:51 +02:00
Christoph Strobl
b62669ec8f 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:33:34 +02:00
Oliver Gierke
0fb74caf9b 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 12:12:29 +02:00
Oliver Gierke
9623dac01f DATAMONGO-901 - Fixed regression of not registering type predicting post processor.
The changes for DATAMONGO-843 introduced a regression by skipping the registration of the RepositoryInterfaceAwareBeanPostProcessor. This can cause the wiring of repository bean definitions to fail depending on in which order the bean definitions get instantiated.

This change reintroduces the registration and adds an explicit test case for it.
2014-04-02 17:58:36 +02:00
Spring Buildmaster
695b27968c DATAMONGO-859 - Prepare next development iteration. 2014-03-31 17:10:40 +02:00
82 changed files with 5296 additions and 426 deletions

View File

@@ -11,7 +11,7 @@ For a comprehensive treatment of all the Spring Data MongoDB features, please re
* 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).
* for more detailed questions, use [Spring Data Mongodb on Stackoverflow](http://stackoverflow.com/questions/tagged/spring-data-mongodb).
If you are new to Spring as well as to Spring Data, look for information about [Spring projects](http://projects.spring.io/).
@@ -26,7 +26,7 @@ Add the Maven dependency:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.4.1.RELEASE</version>
<version>1.5.0.RELEASE</version>
</dependency>
```
@@ -36,7 +36,7 @@ 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.5.0.BUILD-SNAPSHOT</version>
<version>1.6.0.BUILD-SNAPSHOT</version>
</dependency>
<repository>
@@ -139,7 +139,7 @@ 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.spring.io/forum/spring-projects/data/nosql) by responding to questions and joining the debate.
* Get involved with the Spring community on Stackoverflow and help out on the [spring-data-mongodb](http://stackoverflow.com/questions/tagged/spring-data-mongodb) tag 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://spring.io/blog) to spring.io.

50
pom.xml
View File

@@ -2,10 +2,10 @@
<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.5.0.M1</version>
<version>1.5.2.RELEASE</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>1.4.0.M1</version>
<version>1.4.2.RELEASE</version>
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
</parent>
@@ -29,11 +29,11 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>1.8.0.M1</springdata.commons>
<mongo>2.11.4</mongo>
<mongo-osgi>${mongo}</mongo-osgi>
<springdata.commons>1.8.2.RELEASE</springdata.commons>
<mongo>2.12.1</mongo>
<mongo.osgi>2.12.1</mongo.osgi>
</properties>
<developers>
<developer>
<id>ogierke</id>
@@ -105,11 +105,35 @@
<profiles>
<profile>
<id>mongo-next</id>
<properties>
<mongo>2.12.0-rc0</mongo>
<mongo-osgi>2.12.0</mongo-osgi>
<mongo>2.12.3-SNAPSHOT</mongo>
</properties>
<repositories>
<repository>
<id>mongo-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
</profile>
<profile>
<id>mongo-3-next</id>
<properties>
<mongo>3.0.0-SNAPSHOT</mongo>
</properties>
<repositories>
<repository>
<id>mongo-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
</profile>
</profiles>
@@ -121,14 +145,14 @@
<version>${mongo}</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-libs-milestone</id>
<url>http://repo.spring.io/libs-milestone/</url>
<id>spring-libs-release</id>
<url>http://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-plugins-release</id>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.5.0.M1</version>
<version>1.5.2.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -48,7 +48,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.5.0.M1</version>
<version>1.5.2.RELEASE</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 -->
@@ -102,7 +98,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<version>1.6</version>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
@@ -130,7 +126,8 @@
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</aspectLibraries>
<complianceLevel>${source.level}</complianceLevel>
<source>${source.level}</source>
<target>${source.level}</target>
</configuration>

View File

@@ -2,12 +2,12 @@ Bundle-SymbolicName: org.springframework.data.mongodb.crossstore
Bundle-Name: Spring Data MongoDB Cross Store Support
Bundle-Vendor: Pivotal Software, Inc.
Bundle-ManifestVersion: 2
Import-Package:
Import-Package:
sun.reflect;version="0";resolution:=optional
Export-Template:
org.springframework.data.mongodb.crossstore.*;version="${project.version}"
Import-Template:
com.mongodb.*;version="${mongo-osgi:[=.=.=,+1.0.0)}",
Import-Template:
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.5.0.M1</version>
<version>1.5.2.RELEASE</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.5.0.M1</version>
<version>1.5.2.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -5,5 +5,5 @@ Bundle-ManifestVersion: 2
Import-Package:
sun.reflect;version="0";resolution:=optional
Import-Template:
com.mongodb.*;version="${mongo-osgi:[=.=.=,+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.5.0.M1</version>
<version>1.5.2.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

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.
@@ -37,7 +37,9 @@ import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.FieldNamingStrategy;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.PropertyNameFieldNamingStrategy;
import org.springframework.data.support.CachingIsNewStrategyFactory;
import org.springframework.data.support.IsNewStrategyFactory;
import org.springframework.util.ClassUtils;
@@ -51,6 +53,7 @@ import com.mongodb.Mongo;
* @author Mark Pollack
* @author Oliver Gierke
* @author Thomas Darimont
* @author Ryan Tenney
*/
@Configuration
public abstract class AbstractMongoConfiguration {
@@ -144,10 +147,7 @@ public abstract class AbstractMongoConfiguration {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setInitialEntitySet(getInitialEntitySet());
mappingContext.setSimpleTypeHolder(customConversions().getSimpleTypeHolder());
if (abbreviateFieldNames()) {
mappingContext.setFieldNamingStrategy(new CamelCaseAbbreviatingFieldNamingStrategy());
}
mappingContext.setFieldNamingStrategy(fieldNamingStrategy());
return mappingContext;
}
@@ -232,4 +232,15 @@ public abstract class AbstractMongoConfiguration {
protected boolean abbreviateFieldNames() {
return false;
}
/**
* Configures a {@link FieldNamingStrategy} on the {@link MongoMappingContext} instance created.
*
* @return
* @since 1.5
*/
protected FieldNamingStrategy fieldNamingStrategy() {
return abbreviateFieldNames() ? new CamelCaseAbbreviatingFieldNamingStrategy()
: PropertyNameFieldNamingStrategy.INSTANCE;
}
}

View File

@@ -30,6 +30,7 @@ import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.parsing.ReaderContext;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -71,6 +72,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 +85,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 : DEFAULT_CONVERTER_BEAN_NAME;
@@ -209,11 +214,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
mappingContextBuilder.addPropertyValue("simpleTypeHolder", simpleTypesDefinition);
}
String abbreviateFieldNames = element.getAttribute("abbreviate-field-names");
if ("true".equals(abbreviateFieldNames)) {
mappingContextBuilder.addPropertyValue("fieldNamingStrategy", new RootBeanDefinition(
CamelCaseAbbreviatingFieldNamingStrategy.class));
}
parseFieldNamingStrategy(element, parserContext.getReaderContext(), mappingContextBuilder);
ctxRef = converterId == null || DEFAULT_CONVERTER_BEAN_NAME.equals(converterId) ? MAPPING_CONTEXT_BEAN_NAME
: converterId + "." + MAPPING_CONTEXT_BEAN_NAME;
@@ -222,6 +223,34 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
return ctxRef;
}
private static void parseFieldNamingStrategy(Element element, ReaderContext context, BeanDefinitionBuilder builder) {
String abbreviateFieldNames = element.getAttribute("abbreviate-field-names");
String fieldNamingStrategy = element.getAttribute("field-naming-strategy-ref");
boolean fieldNamingStrategyReferenced = StringUtils.hasText(fieldNamingStrategy);
boolean abbreviationActivated = StringUtils.hasText(abbreviateFieldNames)
&& Boolean.parseBoolean(abbreviateFieldNames);
if (fieldNamingStrategyReferenced && abbreviationActivated) {
context.error("Field name abbreviation cannot be activated if a field-naming-strategy-ref is configured!",
element);
return;
}
Object value = null;
if ("true".equals(abbreviateFieldNames)) {
value = new RootBeanDefinition(CamelCaseAbbreviatingFieldNamingStrategy.class);
} else if (fieldNamingStrategyReferenced) {
value = new RuntimeBeanReference(fieldNamingStrategy);
}
if (value != null) {
builder.addPropertyValue("fieldNamingStrategy", value);
}
}
private BeanDefinition getCustomConversions(Element element, ParserContext parserContext) {
List<Element> customConvertersElements = DomUtils.getChildElementsByTagName(element, "custom-converters");

View File

@@ -23,11 +23,15 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
import com.mongodb.MongoCursorNotFoundException;
import com.mongodb.MongoException;
import com.mongodb.MongoException.CursorNotFound;
import com.mongodb.MongoException.DuplicateKey;
import com.mongodb.MongoException.Network;
import com.mongodb.MongoInternalException;
import com.mongodb.MongoServerSelectionException;
import com.mongodb.MongoSocketException;
import com.mongodb.MongoTimeoutException;
/**
* Simple {@link PersistenceExceptionTranslator} for Mongo. Convert the given runtime exception to an appropriate
@@ -47,21 +51,23 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
// Check for well-known MongoException subclasses.
// All other MongoExceptions
if (ex instanceof DuplicateKey) {
if (ex instanceof DuplicateKey || ex instanceof DuplicateKeyException) {
return new DuplicateKeyException(ex.getMessage(), ex);
}
if (ex instanceof Network) {
if (ex instanceof Network || ex instanceof MongoSocketException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
if (ex instanceof CursorNotFound) {
if (ex instanceof CursorNotFound || ex instanceof MongoCursorNotFoundException) {
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")) {
if (ex instanceof MongoServerSelectionException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
if (ex instanceof MongoTimeoutException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
@@ -69,6 +75,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
}
// All other MongoExceptions
if (ex instanceof MongoException) {
int code = ((MongoException) ex).getCode();

View File

@@ -115,6 +115,7 @@ import com.mongodb.WriteConcern;
import com.mongodb.WriteResult;
import com.mongodb.util.JSON;
import com.mongodb.util.JSONParseException;
/**
* Primary implementation of {@link MongoOperations}.
*
@@ -354,7 +355,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));
}
/**
@@ -532,7 +533,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) {
@@ -614,8 +615,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
@@ -626,8 +627,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) {
@@ -1068,17 +1070,22 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
/**
* Returns {@link Entry} containing the {@link MongoPersistentProperty} defining the {@literal id} as
* {@link Entry#getKey()} and the {@link Id}s property value as its {@link Entry#getValue()}.
* Returns {@link Entry} containing the field name of the id property as {@link Entry#getKey()} and the {@link Id}s
* property value as its {@link Entry#getValue()}.
*
* @param object
* @return
*/
private Map.Entry<MongoPersistentProperty, Object> extractIdPropertyAndValue(Object object) {
private Entry<String, Object> extractIdPropertyAndValue(Object object) {
Assert.notNull(object, "Id cannot be extracted from 'null'.");
Class<?> objectType = object.getClass();
if (object instanceof DBObject) {
return Collections.singletonMap(ID_FIELD, ((DBObject) object).get(ID_FIELD)).entrySet().iterator().next();
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(objectType);
MongoPersistentProperty idProp = entity == null ? null : entity.getIdProperty();
@@ -1088,7 +1095,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
Object idValue = BeanWrapper.create(object, mongoConverter.getConversionService())
.getProperty(idProp, Object.class);
return Collections.singletonMap(idProp, idValue).entrySet().iterator().next();
return Collections.singletonMap(idProp.getFieldName(), idValue).entrySet().iterator().next();
}
/**
@@ -1099,8 +1106,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
*/
private Query getIdQueryFor(Object object) {
Map.Entry<MongoPersistentProperty, Object> id = extractIdPropertyAndValue(object);
return new Query(where(id.getKey().getFieldName()).is(id.getValue()));
Entry<String, Object> id = extractIdPropertyAndValue(object);
return new Query(where(id.getKey()).is(id.getValue()));
}
/**
@@ -1114,7 +1121,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
Assert.notEmpty(objects, "Cannot create Query for empty collection.");
Iterator<?> it = objects.iterator();
Map.Entry<MongoPersistentProperty, Object> firstEntry = extractIdPropertyAndValue(it.next());
Entry<String, Object> firstEntry = extractIdPropertyAndValue(it.next());
ArrayList<Object> ids = new ArrayList<Object>(objects.size());
ids.add(firstEntry.getValue());
@@ -1123,7 +1130,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
ids.add(extractIdPropertyAndValue(it.next()).getValue());
}
return new Query(where(firstEntry.getKey().getFieldName()).in(ids));
return new Query(where(firstEntry.getKey()).in(ids));
}
private void assertUpdateableIdIfNotSet(Object entity) {
@@ -1453,13 +1460,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;
@@ -1941,6 +1948,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
/**
@@ -2134,9 +2151,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;
}
/*
@@ -2164,7 +2184,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

@@ -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.
@@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core.aggregation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject;
@@ -32,17 +33,29 @@ import com.mongodb.DBObject;
*/
public class MatchOperation implements AggregationOperation {
private final Criteria criteria;
private final CriteriaDefinition criteriaDefinition;
/**
* Creates a new {@link MatchOperation} for the given {@link Criteria}.
*
* @param criteria must not be {@literal null}.
* @deprecated Use {@link MatchOperation#MatchOperation(CriteriaDefinition)} instead. This constructor is scheduled
* for removal in the next versions.
*/
@Deprecated
public MatchOperation(Criteria criteria) {
this((CriteriaDefinition) criteria);
}
Assert.notNull(criteria, "Criteria must not be null!");
this.criteria = criteria;
/**
* Creates a new {@link MatchOperation} for the given {@link CriteriaDefinition}.
*
* @param criteriaDefinition must not be {@literal null}.
*/
public MatchOperation(CriteriaDefinition criteriaDefinition) {
Assert.notNull(criteriaDefinition, "Criteria must not be null!");
this.criteriaDefinition = criteriaDefinition;
}
/*
@@ -51,6 +64,6 @@ public class MatchOperation implements AggregationOperation {
*/
@Override
public DBObject toDBObject(AggregationOperationContext context) {
return new BasicDBObject("$match", context.getMappedObject(criteria.getCriteriaObject()));
return new BasicDBObject("$match", context.getMappedObject(criteriaDefinition.getCriteriaObject()));
}
}

View File

@@ -28,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;
@@ -57,7 +57,6 @@ import com.mongodb.DBRef;
*/
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;
@@ -138,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();
}
@@ -374,13 +373,27 @@ public class DefaultDbRefResolver implements DbRefResolver {
}
/**
* 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) {
@@ -390,9 +403,77 @@ public class DefaultDbRefResolver implements DbRefResolver {
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

@@ -56,6 +56,7 @@ import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import com.mongodb.BasicDBList;
@@ -253,8 +254,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return;
}
Object obj = getValueInternal(prop, dbo, evaluator, result);
wrapper.setProperty(prop, obj);
wrapper.setProperty(prop, getValueInternal(prop, dbo, evaluator, result));
}
});
@@ -263,18 +263,20 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
public void doWithAssociation(Association<MongoPersistentProperty> association) {
MongoPersistentProperty property = association.getInverse();
Object value = dbo.get(property.getName());
if (value == null) {
return;
}
DBRef dbref = value instanceof DBRef ? (DBRef) value : null;
Object obj = dbRefResolver.resolveDbRef(property, dbref, new DbRefResolverCallback() {
wrapper.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, new DbRefResolverCallback() {
@Override
public Object resolve(MongoPersistentProperty property) {
return getValueInternal(property, dbo, evaluator, parent);
}
});
wrapper.setProperty(property, obj);
}));
}
});
@@ -294,6 +296,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Assert.isTrue(annotation != null, "The referenced property has to be mapped with @DBRef!");
}
// @see DATAMONGO-913
if (object instanceof LazyLoadingProxy) {
return ((LazyLoadingProxy) object).toDBRef();
}
return createDBRef(object, referingProperty);
}
@@ -309,14 +316,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return;
}
boolean handledByCustomConverter = conversions.getCustomWriteTarget(obj.getClass(), DBObject.class) != null;
TypeInformation<? extends Object> type = ClassTypeInformation.from(obj.getClass());
Class<?> entityType = obj.getClass();
boolean handledByCustomConverter = conversions.getCustomWriteTarget(entityType, DBObject.class) != null;
TypeInformation<? extends Object> type = ClassTypeInformation.from(entityType);
if (!handledByCustomConverter && !(dbo instanceof BasicDBList)) {
typeMapper.writeType(type, dbo);
}
writeInternal(obj, dbo, type);
Object target = obj instanceof LazyLoadingProxy ? ((LazyLoadingProxy) obj).initialize() : obj;
writeInternal(target, dbo, type);
}
/**
@@ -332,7 +342,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return;
}
Class<?> customTarget = conversions.getCustomWriteTarget(obj.getClass(), DBObject.class);
Class<?> entityType = obj.getClass();
Class<?> customTarget = conversions.getCustomWriteTarget(entityType, DBObject.class);
if (customTarget != null) {
DBObject result = conversionService.convert(obj, DBObject.class);
@@ -340,17 +351,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return;
}
if (Map.class.isAssignableFrom(obj.getClass())) {
if (Map.class.isAssignableFrom(entityType)) {
writeMapInternal((Map<Object, Object>) obj, dbo, ClassTypeInformation.MAP);
return;
}
if (Collection.class.isAssignableFrom(obj.getClass())) {
if (Collection.class.isAssignableFrom(entityType)) {
writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (BasicDBList) dbo);
return;
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(obj.getClass());
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityType);
writeInternal(obj, dbo, entity);
addCustomTypeKeyIfNecessary(typeHint, obj, dbo);
}
@@ -677,10 +688,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
TypeInformation<?> actualType = type != null ? type.getActualType() : null;
Class<?> reference = actualType == null ? Object.class : actualType.getType();
Class<?> valueType = ClassUtils.getUserClass(value.getClass());
boolean notTheSameClass = !value.getClass().equals(reference);
boolean notTheSameClass = !valueType.equals(reference);
if (notTheSameClass) {
typeMapper.writeType(value.getClass(), dbObject);
typeMapper.writeType(valueType, dbObject);
}
}
@@ -786,11 +798,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
idMapper.convertId(id));
}
protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator eval,
protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator,
Object parent) {
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(dbo, spELContext, parent);
return provider.getPropertyValue(prop);
return new MongoDbPropertyValueProvider(dbo, evaluator, parent).getPropertyValue(prop);
}
/**
@@ -919,7 +930,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return getPotentiallyConvertedSimpleWrite(obj);
}
TypeInformation<?> typeHint = typeInformation == null ? null : ClassTypeInformation.OBJECT;
TypeInformation<?> typeHint = typeInformation == null ? ClassTypeInformation.OBJECT : typeInformation;
if (obj instanceof BasicDBList) {
return maybeConvertList((BasicDBList) obj, typeHint);
@@ -1007,17 +1018,27 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return dbObject;
}
/**
* {@link PropertyValueProvider} to evaluate a SpEL expression if present on the property or simply accesses the field
* of the configured source {@link DBObject}.
*
* @author Oliver Gierke
*/
private class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {
private final DBObjectAccessor source;
private final SpELExpressionEvaluator evaluator;
private final Object parent;
public MongoDbPropertyValueProvider(DBObject source, SpELContext factory, Object parent) {
this(source, new DefaultSpELExpressionEvaluator(source, factory), parent);
}
public MongoDbPropertyValueProvider(DBObject source, DefaultSpELExpressionEvaluator evaluator, Object parent) {
/**
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
* parent object.
*
* @param source must not be {@literal null}.
* @param evaluator must not be {@literal null}.
* @param parent can be {@literal null}.
*/
public MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, Object parent) {
Assert.notNull(source);
Assert.notNull(evaluator);

View File

@@ -250,14 +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
*/
protected boolean isAssociationConversionNecessary(Field documentField, Object value) {
return documentField.isAssociation() && value != null
&& (documentField.getProperty().getActualType().isAssignableFrom(value.getClass()) //
|| documentField.getPropertyEntity().getIdProperty().getActualType().isAssignableFrom(value.getClass()));
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);
}
/**
@@ -289,7 +306,7 @@ 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) {
@@ -594,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);
@@ -603,7 +635,7 @@ 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();
}
@@ -613,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);
}
/*

View File

@@ -25,6 +25,7 @@ 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;
@@ -79,10 +80,19 @@ public class UpdateMapper extends QueryMapper {
return createMapEntry(field, convertSimpleOrDBObject(rawValue, field.getPropertyEntity()));
}
if (!isUpdateModifier(rawValue)) {
return super.getMappedObjectForField(field, getMappedValue(field, rawValue));
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) {
@@ -99,7 +109,6 @@ public class UpdateMapper extends QueryMapper {
value = modificationOperations;
} else {
throw new IllegalArgumentException(String.format("Unable to map value of type '%s'!", rawValue.getClass()));
}
@@ -119,6 +128,10 @@ public class UpdateMapper extends QueryMapper {
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);

View File

@@ -36,11 +36,12 @@ public @interface CompoundIndex {
/**
* The actual index definition in JSON format. The keys of the JSON document are the fields to be indexed, the values
* define the index direction (1 for ascending, -1 for descending).
* define the index direction (1 for ascending, -1 for descending). <br />
* If left empty on nested document, the whole document will be indexed.
*
* @return
*/
String def();
String def() default "";
/**
* It does not actually make sense to use that attribute as the direction has to be defined in the {@link #def()}
@@ -78,6 +79,15 @@ public @interface CompoundIndex {
*/
String name() default "";
/**
* If set to {@literal true} then MongoDB will ignore the given index name and instead generate a new name. Defaults
* to {@literal false}.
*
* @return
* @since 1.5
*/
boolean useGeneratedName() default false;
/**
* The collection the index will be created in. Will default to the collection the annotated domain class will be
* stored in.
@@ -97,8 +107,11 @@ public @interface CompoundIndex {
/**
* Configures the number of seconds after which the collection should expire. Defaults to -1 for no expiry.
*
* @deprecated TTL cannot be defined for {@link CompoundIndex} having more than one field as key. Will be removed in
* 1.6.
* @see http://docs.mongodb.org/manual/tutorial/expire-data/
* @return
*/
@Deprecated
int expireAfterSeconds() default -1;
}

View File

@@ -0,0 +1,56 @@
/*
* 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.index;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* Index definition to span multiple keys.
*
* @author Christoph Strobl
* @since 1.5
*/
public class CompoundIndexDefinition extends Index {
private DBObject keys;
/**
* Creates a new {@link CompoundIndexDefinition} for the given keys.
*
* @param keys must not be {@literal null}.
*/
public CompoundIndexDefinition(DBObject keys) {
Assert.notNull(keys, "Keys must not be null!");
this.keys = keys;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.Index#getIndexKeys()
*/
@Override
public DBObject getIndexKeys() {
BasicDBObject dbo = new BasicDBObject();
dbo.putAll(this.keys);
dbo.putAll(super.getIndexKeys());
return dbo;
}
}

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.
@@ -25,6 +25,7 @@ import java.lang.annotation.Target;
*
* @author Jon Brisbin
* @author Laurent Canet
* @author Thomas Darimont
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@@ -37,6 +38,15 @@ public @interface GeoSpatialIndexed {
*/
String name() default "";
/**
* If set to {@literal true} then MongoDB will ignore the given index name and instead generate a new name. Defaults
* to {@literal false}.
*
* @return
* @since 1.5
*/
boolean useGeneratedName() default false;
/**
* Name of the collection in which to create the index.
*

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.
@@ -27,6 +27,7 @@ import com.mongodb.DBObject;
* @author Jon Brisbin
* @author Oliver Gierke
* @author Laurent Canet
* @author Christoph Strobl
*/
public class GeospatialIndex implements IndexDefinition {
@@ -57,8 +58,6 @@ public class GeospatialIndex implements IndexDefinition {
*/
public GeospatialIndex named(String name) {
Assert.hasText(name, "Name must have text!");
this.name = name;
return this;
}
@@ -151,12 +150,12 @@ public class GeospatialIndex implements IndexDefinition {
public DBObject getIndexOptions() {
if (name == null && min == null && max == null && bucketSize == null) {
if (!StringUtils.hasText(name) && min == null && max == null && bucketSize == null) {
return null;
}
DBObject dbo = new BasicDBObject();
if (name != null) {
if (StringUtils.hasText(name)) {
dbo.put("name", name);
}
@@ -186,6 +185,7 @@ public class GeospatialIndex implements IndexDefinition {
}
break;
}
return dbo;
}

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.
@@ -17,13 +17,20 @@ package org.springframework.data.mongodb.core.index;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.query.Order;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* @author Oliver Gierke
* @author Christoph Strobl
*/
@SuppressWarnings("deprecation")
public class Index implements IndexDefinition {
@@ -41,6 +48,10 @@ public class Index implements IndexDefinition {
private boolean sparse = false;
private boolean background = false;
private long expire = -1;
public Index() {}
public Index(String key, Direction direction) {
@@ -104,6 +115,44 @@ public class Index implements IndexDefinition {
return this;
}
/**
* Build the index in background (non blocking).
*
* @return
* @since 1.5
*/
public Index background() {
this.background = true;
return this;
}
/**
* Specifies TTL in seconds.
*
* @param value
* @return
* @since 1.5
*/
public Index expire(long value) {
return expire(value, TimeUnit.SECONDS);
}
/**
* Specifies TTL with given {@link TimeUnit}.
*
* @param value
* @param unit
* @return
* @since 1.5
*/
public Index expire(long value, TimeUnit unit) {
Assert.notNull(unit, "TimeUnit for expiration must not be null.");
this.expire = unit.toSeconds(value);
return this;
}
/**
* @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping
* @param duplicates
@@ -125,11 +174,9 @@ public class Index implements IndexDefinition {
}
public DBObject getIndexOptions() {
if (name == null && !unique) {
return null;
}
DBObject dbo = new BasicDBObject();
if (name != null) {
if (StringUtils.hasText(name)) {
dbo.put("name", name);
}
if (unique) {
@@ -141,6 +188,13 @@ public class Index implements IndexDefinition {
if (sparse) {
dbo.put("sparse", true);
}
if (background) {
dbo.put("background", true);
}
if (expire >= 0) {
dbo.put("expireAfterSeconds", expire);
}
return dbo;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011 by the original author(s).
* Copyright (c) 2011-2014 by the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,11 +20,11 @@ import com.mongodb.DBObject;
/**
* @author Jon Brisbin <jbrisbin@vmware.com>
* @author Christoph Strobl
*/
public interface IndexDefinition {
DBObject getIndexKeys();
DBObject getIndexOptions();
}

View File

@@ -0,0 +1,37 @@
/*
* 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.index;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder;
/**
* {@link IndexResolver} finds those {@link IndexDefinition}s to be created for a given class.
*
* @author Christoph Strobl
* @since 1.5
*/
interface IndexResolver {
/**
* Find and create {@link IndexDefinition}s for properties of given {@code type}. {@link IndexDefinition}s are created
* for properties and types with {@link Indexed}, {@link CompoundIndexes} or {@link GeoSpatialIndexed}.
*
* @param type
* @return Empty {@link Iterable} in case no {@link IndexDefinition} could be resolved for type.
*/
Iterable<? extends IndexDefinitionHolder> resolveIndexForClass(Class<?> type);
}

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.
@@ -27,6 +27,7 @@ import java.lang.annotation.Target;
* @author Oliver Gierke
* @author Philipp Schneider
* @author Johno Crawford
* @author Thomas Darimont
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@@ -63,6 +64,15 @@ public @interface Indexed {
*/
String name() default "";
/**
* If set to {@literal true} then MongoDB will ignore the given index name and instead generate a new name. Defaults
* to {@literal false}.
*
* @return
* @since 1.5
*/
boolean useGeneratedName() default false;
/**
* Colleciton name for index to be created on.
*

View File

@@ -22,19 +22,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.MappingContextEvent;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
/**
* Component that inspects {@link MongoPersistentEntity} instances contained in the given {@link MongoMappingContext}
@@ -45,6 +41,7 @@ import com.mongodb.util.JSON;
* @author Philipp Schneider
* @author Johno Crawford
* @author Laurent Canet
* @author Christoph Strobl
*/
public class MongoPersistentEntityIndexCreator implements
ApplicationListener<MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty>> {
@@ -54,21 +51,37 @@ public class MongoPersistentEntityIndexCreator implements
private final Map<Class<?>, Boolean> classesSeen = new ConcurrentHashMap<Class<?>, Boolean>();
private final MongoDbFactory mongoDbFactory;
private final MongoMappingContext mappingContext;
private final IndexResolver indexResolver;
/**
* Creats a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and
* {@link MongoDbFactory}.
*
* @param mappingContext must not be {@literal null}
* @param mongoDbFactory must not be {@literal null}
* @param mappingContext must not be {@literal null}.
* @param mongoDbFactory must not be {@literal null}.
*/
public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) {
this(mappingContext, mongoDbFactory, new MongoPersistentEntityIndexResolver(mappingContext));
}
/**
* Creats a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and
* {@link MongoDbFactory}.
*
* @param mappingContext must not be {@literal null}.
* @param mongoDbFactory must not be {@literal null}.
* @param indexResolver must not be {@literal null}.
*/
public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory,
IndexResolver indexResolver) {
Assert.notNull(mongoDbFactory);
Assert.notNull(mappingContext);
Assert.notNull(indexResolver);
this.mongoDbFactory = mongoDbFactory;
this.mappingContext = mappingContext;
this.indexResolver = indexResolver;
for (MongoPersistentEntity<?> entity : mappingContext.getPersistentEntities()) {
checkForIndexes(entity);
@@ -93,87 +106,36 @@ public class MongoPersistentEntityIndexCreator implements
}
}
protected void checkForIndexes(final MongoPersistentEntity<?> entity) {
final Class<?> type = entity.getType();
private void checkForIndexes(final MongoPersistentEntity<?> entity) {
Class<?> type = entity.getType();
if (!classesSeen.containsKey(type)) {
this.classesSeen.put(type, Boolean.TRUE);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Analyzing class " + type + " for index information.");
}
// Make sure indexes get created
if (type.isAnnotationPresent(CompoundIndexes.class)) {
CompoundIndexes indexes = type.getAnnotation(CompoundIndexes.class);
for (CompoundIndex index : indexes.value()) {
String indexColl = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection();
DBObject definition = (DBObject) JSON.parse(index.def());
ensureIndex(indexColl, index.name(), definition, index.unique(), index.dropDups(), index.sparse(),
index.background(), index.expireAfterSeconds());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Created compound index " + index);
}
}
}
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
public void doWithPersistentProperty(MongoPersistentProperty property) {
if (property.isAnnotationPresent(Indexed.class)) {
Indexed index = property.findAnnotation(Indexed.class);
String name = index.name();
if (!StringUtils.hasText(name)) {
name = property.getFieldName();
} else {
if (!name.equals(property.getName()) && index.unique() && !index.sparse()) {
// Names don't match, and sparse is not true. This situation will generate an error on the server.
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("The index name " + name + " doesn't match this property name: " + property.getName()
+ ". Setting sparse=true on this index will prevent errors when inserting documents.");
}
}
}
String collection = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection();
int direction = index.direction() == IndexDirection.ASCENDING ? 1 : -1;
DBObject definition = new BasicDBObject(property.getFieldName(), direction);
ensureIndex(collection, name, definition, index.unique(), index.dropDups(), index.sparse(),
index.background(), index.expireAfterSeconds());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Created property index " + index);
}
} else if (property.isAnnotationPresent(GeoSpatialIndexed.class)) {
GeoSpatialIndexed index = property.findAnnotation(GeoSpatialIndexed.class);
GeospatialIndex indexObject = new GeospatialIndex(property.getFieldName());
indexObject.withMin(index.min()).withMax(index.max());
indexObject.named(StringUtils.hasText(index.name()) ? index.name() : property.getName());
indexObject.typed(index.type()).withBucketSize(index.bucketSize())
.withAdditionalField(index.additionalField());
String collection = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection();
mongoDbFactory.getDb().getCollection(collection)
.ensureIndex(indexObject.getIndexKeys(), indexObject.getIndexOptions());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Created %s for entity %s in collection %s! ", indexObject, entity.getType(),
collection));
}
}
}
});
classesSeen.put(type, true);
checkForAndCreateIndexes(entity);
}
}
private void checkForAndCreateIndexes(MongoPersistentEntity<?> entity) {
if (entity.findAnnotation(Document.class) != null) {
for (IndexDefinitionHolder indexToCreate : indexResolver.resolveIndexForClass(entity.getType())) {
createIndex(indexToCreate);
}
}
}
private void createIndex(IndexDefinitionHolder indexDefinition) {
mongoDbFactory.getDb().getCollection(indexDefinition.getCollection())
.createIndex(indexDefinition.getIndexKeys(), indexDefinition.getIndexOptions());
}
/**
* Returns whether the current index creator was registered for the given {@link MappingContext}.
*
@@ -183,33 +145,4 @@ public class MongoPersistentEntityIndexCreator implements
public boolean isIndexCreatorFor(MappingContext<?, ?> context) {
return this.mappingContext.equals(context);
}
/**
* Triggers the actual index creation.
*
* @param collection the collection to create the index in
* @param name the name of the index about to be created
* @param indexDefinition the index definition
* @param unique whether it shall be a unique index
* @param dropDups whether to drop duplicates
* @param sparse sparse or not
* @param background whether the index will be created in the background
* @param expireAfterSeconds the time to live for documents in the collection
*/
protected void ensureIndex(String collection, String name, DBObject indexDefinition, boolean unique,
boolean dropDups, boolean sparse, boolean background, int expireAfterSeconds) {
DBObject opts = new BasicDBObject();
opts.put("name", name);
opts.put("dropDups", dropDups);
opts.put("sparse", sparse);
opts.put("unique", unique);
opts.put("background", background);
if (expireAfterSeconds != -1) {
opts.put("expireAfterSeconds", expireAfterSeconds);
}
mongoDbFactory.getDb().getCollection(collection).ensureIndex(indexDefinition, opts);
}
}

View File

@@ -0,0 +1,552 @@
/*
* 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.index;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mongodb.core.index.Index.Duplicates;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
/**
* {@link IndexResolver} implementation inspecting {@link MongoPersistentEntity} for {@link MongoPersistentEntity} to be
* indexed. <br />
* All {@link MongoPersistentProperty} of the {@link MongoPersistentEntity} are inspected for potential indexes by
* scanning related annotations.
*
* @author Christoph Strobl
* @since 1.5
*/
public class MongoPersistentEntityIndexResolver implements IndexResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoPersistentEntityIndexResolver.class);
private final MongoMappingContext mappingContext;
/**
* Create new {@link MongoPersistentEntityIndexResolver}.
*
* @param mappingContext must not be {@literal null}.
*/
public MongoPersistentEntityIndexResolver(MongoMappingContext mappingContext) {
Assert.notNull(mappingContext, "Mapping context must not be null in order to resolve index definitions");
this.mappingContext = mappingContext;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.IndexResolver#resolveIndexForClass(java.lang.Class)
*/
@Override
public List<IndexDefinitionHolder> resolveIndexForClass(Class<?> type) {
return resolveIndexForEntity(mappingContext.getPersistentEntity(type));
}
/**
* Resolve the {@link IndexDefinition}s for given {@literal root} entity by traversing {@link MongoPersistentProperty}
* scanning for index annotations {@link Indexed}, {@link CompoundIndex} and {@link GeospatialIndex}. The given
* {@literal root} has therefore to be annotated with {@link Document}.
*
* @param root must not be null.
* @return List of {@link IndexDefinitionHolder}. Will never be {@code null}.
* @throws IllegalArgumentException in case of missing {@link Document} annotation marking root entities.
*/
public List<IndexDefinitionHolder> resolveIndexForEntity(final MongoPersistentEntity<?> root) {
Assert.notNull(root, "Index cannot be resolved for given 'null' entity.");
Document document = root.findAnnotation(Document.class);
Assert.notNull(document, "Given entity is not collection root.");
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root.getType()));
final CycleGuard guard = new CycleGuard();
root.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
@Override
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
try {
if (persistentProperty.isEntity()) {
indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(),
persistentProperty.getFieldName(), root.getCollection(), guard));
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(
persistentProperty.getFieldName(), root.getCollection(), persistentProperty);
if (indexDefinitionHolder != null) {
indexInformation.add(indexDefinitionHolder);
}
} catch (CyclicPropertyReferenceException e) {
LOGGER.warn(e.getMessage());
}
}
});
return indexInformation;
}
/**
* Recursively resolve and inspect properties of given {@literal type} for indexes to be created.
*
* @param type
* @param path The {@literal "dot} path.
* @param collection
* @return List of {@link IndexDefinitionHolder} representing indexes for given type and its referenced property
* types. Will never be {@code null}.
*/
private List<IndexDefinitionHolder> resolveIndexForClass(final Class<?> type, final String path,
final String collection, final CycleGuard guard) {
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, type));
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
@Override
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
String propertyDotPath = (StringUtils.hasText(path) ? path + "." : "") + persistentProperty.getFieldName();
guard.protect(persistentProperty, path);
if (persistentProperty.isEntity()) {
try {
indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(), propertyDotPath,
collection, guard));
} catch (CyclicPropertyReferenceException e) {
LOGGER.warn(e.getMessage());
}
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(propertyDotPath,
collection, persistentProperty);
if (indexDefinitionHolder != null) {
indexInformation.add(indexDefinitionHolder);
}
}
});
return indexInformation;
}
private IndexDefinitionHolder createIndexDefinitionHolderForProperty(String dotPath, String collection,
MongoPersistentProperty persistentProperty) {
if (persistentProperty.isAnnotationPresent(Indexed.class)) {
return createIndexDefinition(dotPath, collection, persistentProperty);
} else if (persistentProperty.isAnnotationPresent(GeoSpatialIndexed.class)) {
return createGeoSpatialIndexDefinition(dotPath, collection, persistentProperty);
}
return null;
}
private List<IndexDefinitionHolder> potentiallyCreateCompoundIndexDefinitions(String dotPath, String collection,
Class<?> type) {
if (AnnotationUtils.findAnnotation(type, CompoundIndexes.class) == null
&& AnnotationUtils.findAnnotation(type, CompoundIndex.class) == null) {
return Collections.emptyList();
}
return createCompoundIndexDefinitions(dotPath, collection, type);
}
/**
* Create {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} for {@link CompoundIndexes} of given type.
*
* @param dotPath The properties {@literal "dot"} path representation from its document root.
* @param fallbackCollection
* @param type
* @return
*/
protected List<IndexDefinitionHolder> createCompoundIndexDefinitions(String dotPath, String fallbackCollection,
Class<?> type) {
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
CompoundIndexes indexes = AnnotationUtils.findAnnotation(type, CompoundIndexes.class);
if (indexes != null) {
for (CompoundIndex index : indexes.value()) {
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index));
}
}
CompoundIndex index = AnnotationUtils.findAnnotation(type, CompoundIndex.class);
if (index != null) {
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index));
}
return indexDefinitions;
}
@SuppressWarnings("deprecation")
protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, String fallbackCollection,
CompoundIndex index) {
CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition(resolveCompoundIndexKeyFromStringDefinition(
dotPath, index.def()));
if (!index.useGeneratedName()) {
indexDefinition.named(index.name());
}
if (index.unique()) {
indexDefinition.unique(index.dropDups() ? Duplicates.DROP : Duplicates.RETAIN);
}
if (index.sparse()) {
indexDefinition.sparse();
}
if (index.background()) {
indexDefinition.background();
}
int ttl = index.expireAfterSeconds();
if (ttl >= 0) {
if (indexDefinition.getIndexKeys().keySet().size() > 1) {
LOGGER.warn("TTL is not supported for compound index with more than one key. TTL={} will be ignored.", ttl);
} else {
indexDefinition.expire(ttl, TimeUnit.SECONDS);
}
}
String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection;
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}
private DBObject resolveCompoundIndexKeyFromStringDefinition(String dotPath, String keyDefinitionString) {
if (!StringUtils.hasText(dotPath) && !StringUtils.hasText(keyDefinitionString)) {
throw new InvalidDataAccessApiUsageException("Cannot create index on root level for empty keys.");
}
if (!StringUtils.hasText(keyDefinitionString)) {
return new BasicDBObject(dotPath, 1);
}
DBObject dbo = (DBObject) JSON.parse(keyDefinitionString);
if (!StringUtils.hasText(dotPath)) {
return dbo;
}
BasicDBObjectBuilder dboBuilder = new BasicDBObjectBuilder();
for (String key : dbo.keySet()) {
dboBuilder.add(dotPath + "." + key, dbo.get(key));
}
return dboBuilder.get();
}
/**
* Creates {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} out of {@link Indexed} for given
* {@link MongoPersistentProperty}.
*
* @param dotPath The properties {@literal "dot"} path representation from its document root.
* @param collection
* @param persitentProperty
* @return
*/
protected IndexDefinitionHolder createIndexDefinition(String dotPath, String fallbackCollection,
MongoPersistentProperty persitentProperty) {
Indexed index = persitentProperty.findAnnotation(Indexed.class);
String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection;
Index indexDefinition = new Index().on(dotPath,
IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC);
if (!index.useGeneratedName()) {
indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : dotPath);
}
if (index.unique()) {
indexDefinition.unique(index.dropDups() ? Duplicates.DROP : Duplicates.RETAIN);
}
if (index.sparse()) {
indexDefinition.sparse();
}
if (index.background()) {
indexDefinition.background();
}
if (index.expireAfterSeconds() >= 0) {
indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS);
}
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}
/**
* Creates {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} out of {@link GeoSpatialIndexed} for
* {@link MongoPersistentProperty}.
*
* @param dotPath The properties {@literal "dot"} path representation from its document root.
* @param collection
* @param persistentProperty
* @return
*/
protected IndexDefinitionHolder createGeoSpatialIndexDefinition(String dotPath, String fallbackCollection,
MongoPersistentProperty persistentProperty) {
GeoSpatialIndexed index = persistentProperty.findAnnotation(GeoSpatialIndexed.class);
String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection;
GeospatialIndex indexDefinition = new GeospatialIndex(dotPath);
indexDefinition.withBits(index.bits());
indexDefinition.withMin(index.min()).withMax(index.max());
if (!index.useGeneratedName()) {
indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : persistentProperty.getName());
}
indexDefinition.typed(index.type()).withBucketSize(index.bucketSize()).withAdditionalField(index.additionalField());
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}
/**
* {@link CycleGuard} holds information about properties and the paths for accessing those. This information is used
* to detect potential cycles within the references.
*
* @author Christoph Strobl
*/
static class CycleGuard {
private final Map<String, List<Path>> propertyTypeMap;
CycleGuard() {
this.propertyTypeMap = new LinkedHashMap<String, List<Path>>();
}
/**
* @param property The property to inspect
* @param path The path under which the property can be reached.
* @throws CyclicPropertyReferenceException in case a potential cycle is detected.
* @see Path#cycles(MongoPersistentProperty, String)
*/
void protect(MongoPersistentProperty property, String path) throws CyclicPropertyReferenceException {
String propertyTypeKey = createMapKey(property);
if (propertyTypeMap.containsKey(propertyTypeKey)) {
List<Path> paths = propertyTypeMap.get(propertyTypeKey);
for (Path existingPath : paths) {
if (existingPath.cycles(property, path)) {
paths.add(new Path(property, path));
throw new CyclicPropertyReferenceException(property.getFieldName(), property.getOwner().getType(),
existingPath.getPath());
}
}
paths.add(new Path(property, path));
} else {
ArrayList<Path> paths = new ArrayList<Path>();
paths.add(new Path(property, path));
propertyTypeMap.put(propertyTypeKey, paths);
}
}
private String createMapKey(MongoPersistentProperty property) {
return property.getOwner().getType().getSimpleName() + ":" + property.getFieldName();
}
/**
* Path defines the property and its full path from the document root. <br />
* A {@link Path} with {@literal spring.data.mongodb} would be created for the property {@code Three.mongodb}.
*
* <pre>
* <code>
* &#64;Document
* class One {
* Two spring;
* }
*
* class Two {
* Three data;
* }
*
* class Three {
* String mongodb;
* }
* </code>
* </pre>
*
* @author Christoph Strobl
*/
static class Path {
private final MongoPersistentProperty property;
private final String path;
Path(MongoPersistentProperty property, String path) {
this.property = property;
this.path = path;
}
public String getPath() {
return path;
}
/**
* Checks whether the given property is owned by the same entity and if it has been already visited by a subset of
* the current path. Given {@literal foo.bar.bar} cycles if {@literal foo.bar} has already been visited and
* {@code class Bar} contains a property of type {@code Bar}. The previously mentioned path would not cycle if
* {@code class Bar} contained a property of type {@code SomeEntity} named {@literal bar}.
*
* @param property
* @param path
* @return
*/
boolean cycles(MongoPersistentProperty property, String path) {
if (!property.getOwner().equals(this.property.getOwner())) {
return false;
}
return path.contains(this.path);
}
}
}
/**
* @author Christoph Strobl
* @since 1.5
*/
public static class CyclicPropertyReferenceException extends RuntimeException {
private static final long serialVersionUID = -3762979307658772277L;
private final String propertyName;
private final Class<?> type;
private final String dotPath;
public CyclicPropertyReferenceException(String propertyName, Class<?> type, String dotPath) {
this.propertyName = propertyName;
this.type = type;
this.dotPath = dotPath;
}
/*
* (non-Javadoc)
* @see java.lang.Throwable#getMessage()
*/
@Override
public String getMessage() {
return String.format("Found cycle for field '%s' in type '%s' for path '%s'", propertyName,
type != null ? type.getSimpleName() : "unknown", dotPath);
}
}
/**
* Implementation of {@link IndexDefinition} holding additional (property)path information used for creating the
* index. The path itself is the properties {@literal "dot"} path representation from its root document.
*
* @author Christoph Strobl
* @since 1.5
*/
public static class IndexDefinitionHolder implements IndexDefinition {
private final String path;
private final IndexDefinition indexDefinition;
private final String collection;
/**
* Create
*
* @param path
*/
public IndexDefinitionHolder(String path, IndexDefinition definition, String collection) {
this.path = path;
this.indexDefinition = definition;
this.collection = collection;
}
public String getCollection() {
return collection;
}
/**
* Get the {@literal "dot"} path used to create the index.
*
* @return
*/
public String getPath() {
return path;
}
/**
* Get the {@literal raw} {@link IndexDefinition}.
*
* @return
*/
public IndexDefinition getIndexDefinition() {
return indexDefinition;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys()
*/
@Override
public DBObject getIndexKeys() {
return indexDefinition.getIndexKeys();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexOptions()
*/
@Override
public DBObject getIndexOptions() {
return indexDefinition.getIndexOptions();
}
}
}

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,8 +15,6 @@
*/
package org.springframework.data.mongodb.core.mapping;
import java.util.Locale;
/**
* {@link FieldNamingStrategy} that abbreviates field names by using the very first letter of the camel case parts of
* the {@link MongoPersistentProperty}'s name.
@@ -24,23 +22,18 @@ import java.util.Locale;
* @since 1.3
* @author Oliver Gierke
*/
public class CamelCaseAbbreviatingFieldNamingStrategy implements FieldNamingStrategy {
public class CamelCaseAbbreviatingFieldNamingStrategy extends CamelCaseSplittingFieldNamingStrategy {
private static final String CAMEL_CASE_PATTERN = "(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])";
public CamelCaseAbbreviatingFieldNamingStrategy() {
super("");
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.FieldNamingStrategy#getFieldName(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty)
* @see org.springframework.data.mongodb.core.mapping.CamelCaseSplittingFieldNamingStrategy#preparePart(java.lang.String)
*/
public String getFieldName(MongoPersistentProperty property) {
String[] parts = property.getName().split(CAMEL_CASE_PATTERN);
StringBuilder builder = new StringBuilder();
for (String part : parts) {
builder.append(part.substring(0, 1).toLowerCase(Locale.US));
}
return builder.toString();
@Override
protected String preparePart(String part) {
return part.substring(0, 1);
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.mapping;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.util.ParsingUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Configurable {@link FieldNamingStrategy} that splits up camel-case property names and reconcatenates them using a
* configured delimiter. Individual parts of the name can be manipulated using {@link #preparePart(String)}.
*
* @author Oliver Gierke
* @since 1.5
*/
public class CamelCaseSplittingFieldNamingStrategy implements FieldNamingStrategy {
private final String delimiter;
/**
* Creates a new {@link CamelCaseSplittingFieldNamingStrategy}.
*
* @param delimiter must not be {@literal null}.
*/
public CamelCaseSplittingFieldNamingStrategy(String delimiter) {
Assert.notNull(delimiter, "Delimiter must not be null!");
this.delimiter = delimiter;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.FieldNamingStrategy#getFieldName(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty)
*/
@Override
public String getFieldName(MongoPersistentProperty property) {
List<String> parts = ParsingUtils.splitCamelCaseToLower(property.getName());
List<String> result = new ArrayList<String>();
for (String part : parts) {
String candidate = preparePart(part);
if (StringUtils.hasText(candidate)) {
result.add(candidate);
}
}
return StringUtils.collectionToDelimitedString(result, delimiter);
}
/**
* Callback to prepare the uncapitalized part obtained from the split up of the camel case source. Default
* implementation returns the part as is.
*
* @param part
* @return
*/
protected String preparePart(String part) {
return part;
}
}

View File

@@ -21,6 +21,7 @@ package org.springframework.data.mongodb.core.mapping;
* @see DocumentField
* @see PropertyNameFieldNamingStrategy
* @see CamelCaseAbbreviatingFieldNamingStrategy
* @see SnakeCaseFieldNamingStrategy
* @since 1.3
* @author Oliver Gierke
*/

View File

@@ -0,0 +1,34 @@
/*
* 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.
* 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.mapping;
/**
* {@link FieldNamingStrategy} that translates typical camel case Java property names to lower case JSON element names,
* separated by underscores.
*
* @since 1.5
* @author Ryan Tenney
* @author Oliver Gierke
*/
public class SnakeCaseFieldNamingStrategy extends CamelCaseSplittingFieldNamingStrategy {
/**
* Creates a new {@link SnakeCaseFieldNamingStrategy}.
*/
public SnakeCaseFieldNamingStrategy() {
super("_");
}
}

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

@@ -15,10 +15,11 @@
*/
package org.springframework.data.mongodb.core.query;
import static org.springframework.util.ObjectUtils.*;
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;
@@ -40,6 +41,7 @@ import com.mongodb.DBObject;
* @author Oliver Gierke
* @author Becca Gaspard
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class Update {
@@ -279,6 +281,7 @@ public class Update {
}
public DBObject getUpdateObject() {
DBObject dbo = new BasicDBObject();
for (String k : modifierOps.keySet()) {
dbo.put(k, modifierOps.get(k));
@@ -335,14 +338,52 @@ public class Update {
return StringUtils.startsWithIgnoreCase(key, "$");
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return getUpdateObject().hashCode();
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Update that = (Update) obj;
return this.getUpdateObject().equals(that.getUpdateObject());
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return SerializationUtils.serializeToJsonSafely(getUpdateObject());
}
/**
* Modifiers holds a distinct collection of {@link Modifier}
*
* @author Christoph Strobl
* @author Thomas Darimont
*/
public static class Modifiers {
private HashMap<String, Modifier> modifiers;
private Map<String, Modifier> modifiers;
public Modifiers() {
this.modifiers = new LinkedHashMap<String, Modifier>(1);
@@ -355,6 +396,33 @@ public class Update {
public void addModifier(Modifier modifier) {
this.modifiers.put(modifier.getKey(), modifier);
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return nullSafeHashCode(modifiers);
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Modifiers that = (Modifiers) obj;
return this.modifiers.equals(that.modifiers);
}
}
/**
@@ -379,6 +447,7 @@ public class Update {
* Implementation of {@link Modifier} representing {@code $each}.
*
* @author Christoph Strobl
* @author Thomas Darimont
*/
private static class Each implements Modifier {
@@ -399,6 +468,7 @@ public class Update {
}
Object[] convertedValues = new Object[values.length];
for (int i = 0; i < values.length; i++) {
convertedValues[i] = values[i];
}
@@ -406,21 +476,57 @@ public class Update {
return convertedValues;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.query.Update.Modifier#getKey()
*/
@Override
public String getKey() {
return "$each";
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.query.Update.Modifier#getValue()
*/
@Override
public Object getValue() {
return this.values;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return nullSafeHashCode(values);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null || getClass() != that.getClass()) {
return false;
}
return nullSafeEquals(values, ((Each) that).values);
}
}
/**
* Builder for creating {@code $push} modifiers
*
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class PushOperatorBuilder {
@@ -453,6 +559,50 @@ public class Update {
public Update value(Object value) {
return Update.this.push(key, value);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += 31 * result + getOuterType().hashCode();
result += 31 * result + nullSafeHashCode(key);
result += 31 * result + nullSafeHashCode(modifiers);
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PushOperatorBuilder that = (PushOperatorBuilder) obj;
if (!getOuterType().equals(that.getOuterType())) {
return false;
}
return nullSafeEquals(this.key, that.key) && nullSafeEquals(this.modifiers, that.modifiers);
}
private Update getOuterType() {
return Update.this;
}
}
/**
@@ -488,7 +638,5 @@ public class Update {
public Update value(Object value) {
return Update.this.addToSet(this.key, value);
}
}
}

View File

@@ -105,6 +105,8 @@ public class MongoRepositoryConfigurationExtension extends RepositoryConfigurati
@Override
public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) {
super.registerBeansForRoot(registry, configurationSource);
if (!registry.containsBeanDefinition(BeanNames.MAPPING_CONTEXT_BEAN_NAME)) {
RootBeanDefinition definition = new RootBeanDefinition(MongoMappingContext.class);

View File

@@ -391,7 +391,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
return operations.findAllAndRemove(query, metadata.getJavaType());
}
WriteResult writeResult = operations.remove(query, metadata.getCollectionName());
WriteResult writeResult = operations.remove(query, metadata.getJavaType(), metadata.getCollectionName());
return writeResult != null ? writeResult.getN() : 0L;
}
}

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.
@@ -67,7 +71,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

@@ -15,6 +15,9 @@
*/
package org.springframework.data.mongodb.repository.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -23,7 +26,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.StringUtils;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
/**
@@ -31,17 +36,20 @@ import com.mongodb.util.JSON;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class StringBasedMongoQuery extends AbstractMongoQuery {
private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!";
private static final Pattern PLACEHOLDER = Pattern.compile("\\?(\\d+)");
private static final Logger LOG = LoggerFactory.getLogger(StringBasedMongoQuery.class);
private static final ParameterBindingParser PARSER = ParameterBindingParser.INSTANCE;
private final String query;
private final String fieldSpec;
private final boolean isCountQuery;
private final boolean isDeleteQuery;
private final List<ParameterBinding> queryParameterBindings;
private final List<ParameterBinding> fieldSpecParameterBindings;
/**
* Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod} and {@link MongoOperations}.
@@ -65,7 +73,11 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
super(method, mongoOperations);
this.query = query;
this.queryParameterBindings = PARSER.parseParameterBindingsFrom(query);
this.fieldSpec = method.getFieldSpecification();
this.fieldSpecParameterBindings = PARSER.parseParameterBindingsFrom(method.getFieldSpecification());
this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false;
@@ -81,12 +93,12 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
@Override
protected Query createQuery(ConvertingParameterAccessor accessor) {
String queryString = replacePlaceholders(query, accessor);
String queryString = replacePlaceholders(query, accessor, queryParameterBindings);
Query query = null;
if (fieldSpec != null) {
String fieldString = replacePlaceholders(fieldSpec, accessor);
String fieldString = replacePlaceholders(fieldSpec, accessor, fieldSpecParameterBindings);
query = new BasicQuery(queryString, fieldString);
} else {
query = new BasicQuery(queryString);
@@ -119,21 +131,173 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
return this.isDeleteQuery;
}
private String replacePlaceholders(String input, ConvertingParameterAccessor accessor) {
/**
* Replaced the parameter place-holders with the actual parameter values from the given {@link ParameterBinding}s.
*
* @param input
* @param accessor
* @param bindings
* @return
*/
private String replacePlaceholders(String input, ConvertingParameterAccessor accessor, List<ParameterBinding> bindings) {
Matcher matcher = PLACEHOLDER.matcher(input);
String result = input;
while (matcher.find()) {
String group = matcher.group();
int index = Integer.parseInt(matcher.group(1));
result = result.replace(group, getParameterWithIndex(accessor, index));
if (bindings.isEmpty()) {
return input;
}
return result;
StringBuilder result = new StringBuilder(input);
for (ParameterBinding binding : bindings) {
String parameter = binding.getParameter();
int idx = result.indexOf(parameter);
if (idx != -1) {
result.replace(idx, idx + parameter.length(), getParameterValueForBinding(accessor, binding));
}
}
return result.toString();
}
private String getParameterWithIndex(ConvertingParameterAccessor accessor, int index) {
return JSON.serialize(accessor.getBindableValue(index));
/**
* Returns the serialized value to be used for the given {@link ParameterBinding}.
*
* @param accessor
* @param binding
* @return
*/
private String getParameterValueForBinding(ConvertingParameterAccessor accessor, ParameterBinding binding) {
Object value = accessor.getBindableValue(binding.getParameterIndex());
if (value instanceof String && binding.isQuoted()) {
return (String) value;
}
return JSON.serialize(value);
}
/**
* A parser that extracts the parameter bindings from a given query string.
*
* @author Thomas Darimont
*/
private static enum ParameterBindingParser {
INSTANCE;
private static final String PARAMETER_PREFIX = "_param_";
private static final String PARSEABLE_PARAMETER = "\"" + PARAMETER_PREFIX + "$1\"";
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)");
private static final Pattern PARSEABLE_BINDING_PATTERN = Pattern.compile("\"?" + PARAMETER_PREFIX + "(\\d+)\"?");
private final static int PARAMETER_INDEX_GROUP = 1;
/**
* Returns a list of {@link ParameterBinding}s found in the given {@code input} or an
* {@link Collections#emptyList()}.
*
* @param input
* @return
*/
public List<ParameterBinding> parseParameterBindingsFrom(String input) {
if (!StringUtils.hasText(input)) {
return Collections.emptyList();
}
List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
String parseableInput = makeParameterReferencesParseable(input);
collectParameterReferencesIntoBindings(bindings, JSON.parse(parseableInput));
return bindings;
}
private String makeParameterReferencesParseable(String input) {
Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(input);
String parseableInput = matcher.replaceAll(PARSEABLE_PARAMETER);
return parseableInput;
}
private void collectParameterReferencesIntoBindings(List<ParameterBinding> bindings, Object value) {
if (value instanceof String) {
String string = ((String) value).trim();
Matcher valueMatcher = PARSEABLE_BINDING_PATTERN.matcher(string);
while (valueMatcher.find()) {
int paramIndex = Integer.parseInt(valueMatcher.group(PARAMETER_INDEX_GROUP));
boolean quoted = (string.startsWith("'") && string.endsWith("'"))
|| (string.startsWith("\"") && string.endsWith("\""));
bindings.add(new ParameterBinding(paramIndex, quoted));
}
} else if (value instanceof Pattern) {
String string = ((Pattern) value).toString().trim();
Matcher valueMatcher = PARSEABLE_BINDING_PATTERN.matcher(string);
while (valueMatcher.find()) {
int paramIndex = Integer.parseInt(valueMatcher.group(PARAMETER_INDEX_GROUP));
/*
* The pattern is used as a direct parameter replacement, e.g. 'field': ?1,
* therefore we treat it as not quoted to remain backwards compatible.
*/
boolean quoted = !string.equals(PARAMETER_PREFIX + paramIndex);
bindings.add(new ParameterBinding(paramIndex, quoted));
}
} else if (value instanceof DBObject) {
DBObject dbo = (DBObject) value;
for (String field : dbo.keySet()) {
collectParameterReferencesIntoBindings(bindings, dbo.get(field));
}
}
}
}
/**
* A generic parameter binding with name or position information.
*
* @author Thomas Darimont
*/
private static class ParameterBinding {
private final int parameterIndex;
private final boolean quoted;
/**
* Creates a new {@link ParameterBinding} with the given {@code parameterIndex} and {@code quoted} information.
*
* @param parameterIndex
* @param quoted whether or not the parameter is already quoted.
*/
public ParameterBinding(int parameterIndex, boolean quoted) {
this.parameterIndex = parameterIndex;
this.quoted = quoted;
}
public boolean isQuoted() {
return quoted;
}
public int getParameterIndex() {
return parameterIndex;
}
public String getParameter() {
return "?" + parameterIndex;
}
}
}

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.
@@ -25,7 +25,10 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.util.Assert;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
import com.mysema.query.mongodb.MongodbSerializer;
import com.mysema.query.types.Constant;
import com.mysema.query.types.Operation;
import com.mysema.query.types.Path;
import com.mysema.query.types.PathMetadata;
import com.mysema.query.types.PathType;
@@ -34,9 +37,12 @@ import com.mysema.query.types.PathType;
* Custom {@link MongodbSerializer} to take mapping information into account when building keys for constraints.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
class SpringDataMongodbSerializer extends MongodbSerializer {
private final String ID_KEY = "_id";
private final MongoConverter converter;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final QueryMapper mapper;
@@ -44,7 +50,7 @@ class SpringDataMongodbSerializer extends MongodbSerializer {
/**
* Creates a new {@link SpringDataMongodbSerializer} for the given {@link MappingContext}.
*
* @param mappingContext
* @param mappingContext must not be {@literal null}.
*/
public SpringDataMongodbSerializer(MongoConverter converter) {
@@ -80,10 +86,63 @@ class SpringDataMongodbSerializer extends MongodbSerializer {
@Override
protected DBObject asDBObject(String key, Object value) {
if ("_id".equals(key)) {
return super.asDBObject(key, mapper.convertId(value));
if (ID_KEY.equals(key)) {
return mapper.getMappedObject(super.asDBObject(key, value), null);
}
return super.asDBObject(key, value instanceof Pattern ? value : converter.convertToMongoType(value));
}
/*
* (non-Javadoc)
* @see com.mysema.query.mongodb.MongodbSerializer#isReference(com.mysema.query.types.Path)
*/
@Override
protected boolean isReference(Path<?> path) {
MongoPersistentProperty property = getPropertyFor(path);
return property == null ? false : property.isAssociation();
}
/*
* (non-Javadoc)
* @see com.mysema.query.mongodb.MongodbSerializer#asReference(java.lang.Object)
*/
@Override
protected DBRef asReference(Object constant) {
return converter.toDBRef(constant, null);
}
/*
* (non-Javadoc)
* @see com.mysema.query.mongodb.MongodbSerializer#asReference(com.mysema.query.types.Operation, int)
*/
@Override
protected DBRef asReference(Operation<?> expr, int constIndex) {
for (Object arg : expr.getArgs()) {
if (arg instanceof Path) {
MongoPersistentProperty property = getPropertyFor((Path<?>) arg);
Object constant = ((Constant<?>) expr.getArg(constIndex)).getConstant();
return converter.toDBRef(constant, property);
}
}
return super.asReference(expr, constIndex);
}
private MongoPersistentProperty getPropertyFor(Path<?> path) {
Path<?> parent = path.getMetadata().getParent();
if (parent == null) {
return null;
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(parent.getType());
return entity != null ? entity.getPersistentProperty(path.getMetadata().getName()) : null;
}
}

View File

@@ -3,4 +3,5 @@ http\://www.springframework.org/schema/data/mongo/spring-mongo-1.1.xsd=org/sprin
http\://www.springframework.org/schema/data/mongo/spring-mongo-1.2.xsd=org/springframework/data/mongodb/config/spring-mongo-1.2.xsd
http\://www.springframework.org/schema/data/mongo/spring-mongo-1.3.xsd=org/springframework/data/mongodb/config/spring-mongo-1.3.xsd
http\://www.springframework.org/schema/data/mongo/spring-mongo-1.4.xsd=org/springframework/data/mongodb/config/spring-mongo-1.4.xsd
http\://www.springframework.org/schema/data/mongo/spring-mongo.xsd=org/springframework/data/mongodb/config/spring-mongo-1.4.xsd
http\://www.springframework.org/schema/data/mongo/spring-mongo-1.5.xsd=org/springframework/data/mongodb/config/spring-mongo-1.5.xsd
http\://www.springframework.org/schema/data/mongo/spring-mongo.xsd=org/springframework/data/mongodb/config/spring-mongo-1.5.xsd

View File

@@ -0,0 +1,657 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://www.springframework.org/schema/data/mongo"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:repository="http://www.springframework.org/schema/data/repository"
targetNamespace="http://www.springframework.org/schema/data/mongo"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/context" />
<xsd:import namespace="http://www.springframework.org/schema/data/repository"
schemaLocation="http://www.springframework.org/schema/data/repository/spring-repository.xsd" />
<xsd:element name="mongo" type="mongoType">
<xsd:annotation>
<xsd:documentation source="org.springframework.data.mongodb.core.MongoFactoryBean"><![CDATA[
Defines a Mongo instance used for accessing MongoDB'.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="com.mongodb.Mongo"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:element name="db-factory">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines a MongoDbFactory for connecting to a specific database
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the mongo definition (by default "mongoDbFactory").]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mongo-ref" type="mongoRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a Mongo instance. If not configured a default com.mongodb.Mongo instance will be created.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="dbname" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the database to connect to. Default is 'db'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="authentication-dbname" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the authentication database to connect to. Default is 'db'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="port" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The port to connect to MongoDB server. Default is 27017
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="host" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The host to connect to a MongoDB server. Default is localhost
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="username" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The username to use when connecting to a MongoDB server.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="password" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The password to use when connecting to a MongoDB server.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="uri" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The Mongo URI string.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="write-concern">
<xsd:annotation>
<xsd:documentation>
The WriteConcern that will be the default value used when asking the MongoDbFactory for a DB object
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="writeConcernEnumeration xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:attributeGroup name="mongo-repository-attributes">
<xsd:attribute name="mongo-template-ref" type="mongoTemplateRef" default="mongoTemplate">
<xsd:annotation>
<xsd:documentation>
The reference to a MongoTemplate. Will default to 'mongoTemplate'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="create-query-indexes" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation>
Enables creation of indexes for queries that get derived from the method name
and thus reference domain class properties. Defaults to false.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:element name="repositories">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="repository:repositories">
<xsd:attributeGroup ref="mongo-repository-attributes"/>
<xsd:attributeGroup ref="repository:repository-attributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="mapping-converter">
<xsd:annotation>
<xsd:documentation><![CDATA[Defines a MongoConverter for getting rich mapping functionality.]]></xsd:documentation>
<xsd:appinfo>
<tool:exports type="org.springframework.data.mongodb.core.convert.MappingMongoConverter" />
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="custom-converters" minOccurs="0">
<xsd:annotation>
<xsd:documentation><![CDATA[
Top-level element that contains one or more custom converters to be used for mapping
domain objects to and from Mongo's DBObject]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="converter" type="customConverterType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="base-package" type="xsd:string" />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the MappingMongoConverter instance (by default "mappingConverter").]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="base-package" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The base package in which to scan for entities annotated with @Document
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="db-factory-ref" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
The reference to a DbFactory.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mongodb.MongoDbFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type-mapper-ref" type="typeMapperRef" use="optional">
<xsd:annotation>
<xsd:documentation>
The reference to a MongoTypeMapper to be used by this MappingMongoConverter.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapping-context-ref" type="mappingContextRef" use="optional">
<xsd:annotation>
<xsd:documentation source="org.springframework.data.mapping.model.MappingContext">
The reference to a MappingContext. Will default to 'mappingContext'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="disable-validation" use="optional">
<xsd:annotation>
<xsd:documentation source="org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener">
Disables JSR-303 validation on MongoDB documents before they are saved. By default it is set to false.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="abbreviate-field-names" use="optional">
<xsd:annotation>
<xsd:documentation source="org.springframework.data.mongodb.core.mapping.CamelCaseAbbreviatingFieldNamingStrategy">
Enables abbreviating the field names for domain class properties to the
first character of their camel case names, e.g. fooBar -> fb. Defaults to false.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="field-naming-strategy-ref" type="fieldNamingStrategyRef" use="optional">
<xsd:annotation>
<xsd:documentation source="org.springframework.data.mongodb.core.mapping.FieldNamingStrategy">
The reference to a FieldNamingStrategy.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="jmx">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines a JMX Model MBeans for monitoring a MongoDB server'.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="mongo-ref" type="mongoRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the Mongo object that determines what server to monitor. (by default "mongo").]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="auditing">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.data.mongodb.core.mapping.event.AuditingEventListener" />
<tool:exports type="org.springframework.data.auditing.IsNewAwareAuditingHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:attributeGroup ref="repository:auditing-attributes" />
<xsd:attribute name="mapping-context-ref" type="mappingContextRef" />
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="typeMapperRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mongodb.core.convert.MongoTypeMapper"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="mappingContextRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mapping.model.MappingContext"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="fieldNamingStrategyRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mongodb.core.mapping.FieldNamingStrategy"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="mongoTemplateRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mongodb.core.MongoTemplate"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="mongoRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mongodb.core.MongoFactoryBean"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="sslSocketFactoryRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="javax.net.ssl.SSLSocketFactory"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="writeConcernEnumeration">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="NONE" />
<xsd:enumeration value="NORMAL" />
<xsd:enumeration value="SAFE" />
<xsd:enumeration value="FSYNC_SAFE" />
<xsd:enumeration value="REPLICAS_SAFE" />
<xsd:enumeration value="JOURNAL_SAFE" />
<xsd:enumeration value="MAJORITY" />
</xsd:restriction>
</xsd:simpleType>
<!-- MLP
<xsd:attributeGroup name="writeConcern">
<xsd:attribute name="write-concern">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="NONE" />
<xsd:enumeration value="NORMAL" />
<xsd:enumeration value="SAFE" />
<xsd:enumeration value="FSYNC_SAFE" />
<xsd:enumeration value="REPLICA_SAFE" />
<xsd:enumeration value="JOURNAL_SAFE" />
<xsd:enumeration value="MAJORITY" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
-->
<xsd:complexType name="mongoType">
<xsd:sequence minOccurs="0" maxOccurs="1">
<xsd:element name="options" type="optionsType">
<xsd:annotation>
<xsd:documentation><![CDATA[
The Mongo driver options
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="com.mongodb.MongoOptions"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="write-concern">
<xsd:annotation>
<xsd:documentation>
The WriteConcern that will be the default value used when asking the MongoDbFactory for a DB object
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="writeConcernEnumeration xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<!-- MLP
<xsd:attributeGroup ref="writeConcern" />
-->
<xsd:attribute name="id" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the mongo definition (by default "mongo").]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="port" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The port to connect to MongoDB server. Default is 27017
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="host" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The host to connect to a MongoDB server. Default is localhost
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="replica-set" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The comma delimited list of host:port entries to use for replica set/pairs.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="optionsType">
<xsd:attribute name="connections-per-host" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The number of connections allowed per host. Will block if run out. Default is 10. System property MONGO.POOLSIZE can override
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="threads-allowed-to-block-for-connection-multiplier" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The multiplier for connectionsPerHost for # of threads that can block. Default is 5.
If connectionsPerHost is 10, and threadsAllowedToBlockForConnectionMultiplier is 5,
then 50 threads can block more than that and an exception will be thrown.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-wait-time" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The max wait time of a blocking thread for a connection. Default is 12000 ms (2 minutes)
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="connect-timeout" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The connect timeout in milliseconds. 0 is default and infinite.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="socket-timeout" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The socket timeout. 0 is default and infinite.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="socket-keep-alive" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The keep alive flag, controls whether or not to have socket keep alive timeout. Defaults to false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-connect-retry" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
This controls whether or not on a connect, the system retries automatically. Default is false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-auto-connect-retry-time" type="xsd:long">
<xsd:annotation>
<xsd:documentation><![CDATA[
The maximum amount of time in millisecons to spend retrying to open connection to the same server. Default is 0, which means to use the default 15s if autoConnectRetry is on.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="write-number" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
This specifies the number of servers to wait for on the write operation, and exception raising behavior. The 'w' option to the getlasterror command. Defaults to 0.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="write-timeout" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
This controls timeout for write operations in milliseconds. The 'wtimeout' option to the getlasterror command. Defaults to 0 (indefinite). Greater than zero is number of milliseconds to wait.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="write-fsync" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
This controls whether or not to fsync. The 'fsync' option to the getlasterror command. Defaults to false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="slave-ok" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
This controls if the driver is allowed to read from secondaries or slaves. Defaults to false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ssl" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
This controls if the driver should us an SSL connection. Defaults to false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ssl-socket-factory-ref" type="sslSocketFactoryRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SSLSocketFactory to use for the SSL connection. If none is configured here, SSLSocketFactory#getDefault() will be used.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:group name="beanElementGroup">
<xsd:choice>
<xsd:element ref="beans:bean"/>
<xsd:element ref="beans:ref"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="customConverterType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Element defining a custom converterr.
]]></xsd:documentation>
</xsd:annotation>
<xsd:group ref="beanElementGroup" minOccurs="0" maxOccurs="1"/>
<xsd:attribute name="ref" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A reference to a custom converter.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref"/>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:simpleType name="converterRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mongodb.core.convert.MongoConverter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:element name="template">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines a MongoDbFactory for connecting to a specific database
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the mongo definition (by default "mongoDbFactory").]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="converter-ref" type="converterRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a Mongoconverter instance.
]]>
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mongodb.core.convert.MongoConverter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="db-factory-ref" type="xsd:string"
use="optional">
<xsd:annotation>
<xsd:documentation>
The reference to a DbFactory.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to
type="org.springframework.data.mongodb.MongoDbFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="write-concern">
<xsd:annotation>
<xsd:documentation>
The WriteConcern that will be the default value used when asking the MongoDbFactory for a DB object
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="writeConcernEnumeration xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="gridFsTemplate">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines a MongoDbFactory for connecting to a specific database
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the mongo definition (by default "mongoDbFactory").]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="converter-ref" type="converterRef" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The reference to a Mongoconverter instance.
]]>
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mongodb.core.convert.MongoConverter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="db-factory-ref" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
The reference to a DbFactory.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mongodb.MongoDbFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="bucket" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The GridFs bucket string.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -21,9 +21,13 @@ 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.config.BeanReference;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.convert.TypeDescriptor;
@@ -45,17 +49,14 @@ import com.mongodb.DBObject;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Ryan Tenney
*/
public class MappingMongoConverterParserIntegrationTests {
DefaultListableBeanFactory factory;
@Rule public ExpectedException exception = ExpectedException.none();
@Before
public void setUp() {
factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("namespace/converter.xml"));
}
DefaultListableBeanFactory factory;
/**
* @see DATAMONGO-243
@@ -63,6 +64,7 @@ public class MappingMongoConverterParserIntegrationTests {
@Test
public void allowsDbFactoryRefAttribute() {
loadValidConfiguration();
factory.getBeanDefinition("converter");
factory.getBean("converter");
}
@@ -73,6 +75,7 @@ public class MappingMongoConverterParserIntegrationTests {
@Test
public void hasCustomTypeMapper() {
loadValidConfiguration();
MappingMongoConverter converter = factory.getBean("converter", MappingMongoConverter.class);
MongoTypeMapper customMongoTypeMapper = factory.getBean(CustomMongoTypeMapper.class);
@@ -85,6 +88,7 @@ public class MappingMongoConverterParserIntegrationTests {
@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));
@@ -96,6 +100,7 @@ public class MappingMongoConverterParserIntegrationTests {
@Test
public void activatesAbbreviatingPropertiesCorrectly() {
loadValidConfiguration();
BeanDefinition definition = factory.getBeanDefinition("abbreviatingConverter.mongoMappingContext");
Object value = definition.getPropertyValues().getPropertyValue("fieldNamingStrategy").getValue();
@@ -104,6 +109,76 @@ public class MappingMongoConverterParserIntegrationTests {
assertThat(strategy.getBeanClassName(), is(CamelCaseAbbreviatingFieldNamingStrategy.class.getName()));
}
/**
* @see DATAMONGO-866
*/
@Test
public void rejectsInvalidFieldNamingStrategyConfiguration() {
exception.expect(BeanDefinitionParsingException.class);
exception.expectMessage("abbreviation");
exception.expectMessage("field-naming-strategy-ref");
BeanDefinitionRegistry factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-invalid.xml"));
}
/**
* @see DATAMONGO-892
*/
@Test
public void shouldThrowBeanDefinitionParsingExceptionIfConverterDefinedAsNestedBean() {
exception.expect(BeanDefinitionParsingException.class);
exception.expectMessage("Mongo Converter must not be defined as nested bean.");
loadNestedBeanConfiguration();
}
/**
* @see DATAMONGO-925, DATAMONGO-928
*/
@Test
public void shouldSupportCustomFieldNamingStrategy() {
assertStrategyReferenceSetFor("mappingConverterWithCustomFieldNamingStrategy");
}
/**
* @see DATAMONGO-925, DATAMONGO-928
*/
@Test
public void shouldNotFailLoadingConfigIfAbbreviationIsDisabledAndStrategySet() {
assertStrategyReferenceSetFor("mappingConverterWithCustomFieldNamingStrategyAndAbbreviationDisabled");
}
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));
}
private static void assertStrategyReferenceSetFor(String beanId) {
BeanDefinitionRegistry factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-custom-fieldnamingstrategy.xml"));
BeanDefinition definition = reader.getRegistry().getBeanDefinition(beanId.concat(".mongoMappingContext"));
BeanReference value = (BeanReference) definition.getPropertyValues().getPropertyValue("fieldNamingStrategy")
.getValue();
assertThat(value.getBeanName(), is("customFieldNamingStrategy"));
}
@Component
public static class SampleConverter implements Converter<Person, DBObject> {
public DBObject convert(Person source) {

View File

@@ -34,7 +34,6 @@ 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.Before;
@@ -187,6 +186,8 @@ public class MongoTemplateTests {
template.dropCollection(DocumentWithCollectionOfSimpleType.class);
template.dropCollection(DocumentWithMultipleCollections.class);
template.dropCollection(DocumentWithDBRefCollection.class);
template.dropCollection(SomeContent.class);
template.dropCollection(SomeTemplate.class);
}
@Test
@@ -299,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);
@@ -310,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
@@ -2515,6 +2511,37 @@ public class MongoTemplateTests {
assertThat(template.getDb().getCollection("sample").find(new BasicDBObject("field", "data")).count(), is(1));
}
/**
* @see DATAMONGO-1001
*/
@Test
public void shouldAllowSavingOfLazyLoadedDbRefs() {
template.dropCollection(SomeTemplate.class);
template.dropCollection(SomeMessage.class);
template.dropCollection(SomeContent.class);
SomeContent content = new SomeContent();
content.id = "content-1";
content.text = "spring";
template.save(content);
SomeTemplate tmpl = new SomeTemplate();
tmpl.id = "template-1";
tmpl.content = content; // @DBRef(lazy=true) tmpl.content
template.save(tmpl);
SomeTemplate savedTmpl = template.findById(tmpl.id, SomeTemplate.class);
SomeContent loadedContent = savedTmpl.getContent();
loadedContent.setText("data");
template.save(loadedContent);
assertThat(template.findById(content.id, SomeContent.class).getText(), is("data"));
}
/**
* @see DATAMONGO-880
*/
@@ -2599,6 +2626,167 @@ public class MongoTemplateTests {
assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3));
}
/**
* @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()));
}
/**
* @see DATAMONGO-970
*/
@Test
public void insertsAndRemovesBasicDbObjectCorrectly() {
BasicDBObject object = new BasicDBObject("key", "value");
template.insert(object, "collection");
assertThat(object.get("_id"), is(notNullValue()));
assertThat(template.findAll(DBObject.class, "collection"), hasSize(1));
template.remove(object, "collection");
assertThat(template.findAll(DBObject.class, "collection"), hasSize(0));
}
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;
@@ -2793,6 +2981,20 @@ public class MongoTemplateTests {
String id;
String text;
String name;
public String getName() {
return name;
}
public void setText(String text) {
this.text = text;
}
public String getId() {
return id;
}
public String getText() {
return text;

View File

@@ -42,6 +42,7 @@ 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;
@@ -50,11 +51,13 @@ 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.BasicQuery;
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;
@@ -235,7 +238,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
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", 1)));
Assert.assertThat(captor.getValue().get("$inc"), Is.<Object> is(new BasicDBObject("version", 1L)));
}
/**
@@ -329,6 +332,26 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(collection, never()).remove(Mockito.any(DBObject.class));
}
/**
* @see DATAMONGO-948
*/
@Test
public void sortShouldBeTakenAsIsWhenExecutingQueryWithoutSpecificTypeInformation() {
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) throws MongoException, DataAccessException {
// nothing to do - just a test
}
});
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;

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;
@@ -822,6 +823,40 @@ public class AggregationTests {
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()));
@@ -938,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

@@ -179,4 +179,49 @@ public class AggregationUnitTests {
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

@@ -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

@@ -1,3 +1,18 @@
/*
* 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
*
* 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 static org.hamcrest.Matchers.*;
@@ -15,6 +30,7 @@ import org.bson.types.Binary;
import org.bson.types.ObjectId;
import org.joda.time.DateTime;
import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
@@ -26,6 +42,7 @@ import com.mongodb.DBRef;
* Unit tests for {@link CustomConversions}.
*
* @author Oliver Gierke
* @auhtor Christoph Strobl
*/
public class CustomConversionsUnitTests {
@@ -197,6 +214,35 @@ public class CustomConversionsUnitTests {
assertThat(conversionService.convert(new DateTime(), Date.class), is(new Date(0)));
}
/**
* @see DATAMONGO-1001
*/
@Test
public void shouldSelectPropertCustomWriteTargetForCglibProxiedType() {
CustomConversions conversions = new CustomConversions(Arrays.asList(FormatToStringConverter.INSTANCE));
assertThat(conversions.getCustomWriteTarget(createProxyTypeFor(Format.class)), is(typeCompatibleWith(String.class)));
}
/**
* @see DATAMONGO-1001
*/
@Test
public void shouldSelectPropertCustomReadTargetForCglibProxiedType() {
CustomConversions conversions = new CustomConversions(Arrays.asList(CustomObjectToStringConverter.INSTANCE));
assertThat(conversions.hasCustomReadTarget(createProxyTypeFor(Object.class), String.class), is(true));
}
private static Class<?> createProxyTypeFor(Class<?> type) {
ProxyFactory factory = new ProxyFactory();
factory.setProxyTargetClass(true);
factory.setTargetClass(type);
return factory.getProxy().getClass();
}
enum FormatToStringConverter implements Converter<Format, String> {
INSTANCE;
@@ -251,4 +297,15 @@ public class CustomConversionsUnitTests {
return new Date(0);
}
}
enum CustomObjectToStringConverter implements Converter<Object, String> {
INSTANCE;
@Override
public String convert(Object source) {
return source != null ? source.toString() : null;
}
}
}

View File

@@ -429,6 +429,27 @@ public class DbRefMappingMongoConverterUnitTests {
assertProxyIsResolved(result.dbRefEqualsAndHashcodeObjectMethodOverride2, true);
}
/**
* @see DATAMONGO-987
*/
@Test
public void shouldNotGenerateLazyLoadingProxyForNullValues() {
DBObject dbo = new BasicDBObject();
ClassWithLazyDbRefs lazyDbRefs = new ClassWithLazyDbRefs();
lazyDbRefs.id = "42";
converter.write(lazyDbRefs, dbo);
ClassWithLazyDbRefs result = converter.read(ClassWithLazyDbRefs.class, dbo);
assertThat(result.id, is(lazyDbRefs.id));
assertThat(result.dbRefToInterface, is(nullValue()));
assertThat(result.dbRefToConcreteCollection, is(nullValue()));
assertThat(result.dbRefToConcreteType, is(nullValue()));
assertThat(result.dbRefToConcreteTypeWithPersistenceConstructor, is(nullValue()));
assertThat(result.dbRefToConcreteTypeWithPersistenceConstructorWithoutDefaultConstructor, is(nullValue()));
}
private Object transport(Object result) {
return SerializationUtils.deserialize(SerializationUtils.serialize(result));
}

View File

@@ -49,6 +49,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.converter.Converter;
@@ -1398,6 +1399,7 @@ public class MappingMongoConverterUnitTests {
/**
* @see DATAMONGO-812
* @see DATAMONGO-893
*/
@Test
public void convertsListToBasicDBListAndRetainsTypeInformationForComplexObjects() {
@@ -1407,7 +1409,7 @@ public class MappingMongoConverterUnitTests {
address.street = "Foo";
Object result = converter.convertToMongoType(Collections.singletonList(address),
ClassTypeInformation.from(Address.class));
ClassTypeInformation.from(InterfaceType.class));
assertThat(result, is(instanceOf(BasicDBList.class)));
@@ -1806,6 +1808,37 @@ public class MappingMongoConverterUnitTests {
assertThat(result.shape, is((Shape) sphere));
}
/**
* @see DATAMONGO-1001
*/
@Test
public void shouldWriteCglibProxiedClassTypeInformationCorrectly() {
ProxyFactory factory = new ProxyFactory();
factory.setTargetClass(GenericType.class);
factory.setProxyTargetClass(true);
GenericType<?> proxied = (GenericType<?>) factory.getProxy();
BasicDBObject dbo = new BasicDBObject();
converter.write(proxied, dbo);
assertThat(dbo.get("_class"), is((Object) GenericType.class.getName()));
}
/**
* @see DATAMONGO-1001
*/
@Test
public void shouldUseTargetObjectOfLazyLoadingProxyWhenWriting() {
LazyLoadingProxy mock = mock(LazyLoadingProxy.class);
BasicDBObject dbo = new BasicDBObject();
converter.write(mock, dbo);
verify(mock, times(1)).initialize();
}
static class GenericType<T> {
T content;
}
@@ -1833,7 +1866,11 @@ public class MappingMongoConverterUnitTests {
abstract void method();
}
static class Address {
static interface InterfaceType {
}
static class Address implements InterfaceType {
String street;
String city;
}

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;
@@ -568,10 +572,53 @@ public class QueryMapperUnitTests {
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;

View File

@@ -26,6 +26,7 @@ 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;
@@ -37,12 +38,16 @@ 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;
@@ -452,6 +457,115 @@ public class UpdateMapperUnitTests {
}
/**
* @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 {

View File

@@ -15,9 +15,10 @@
*/
package org.springframework.data.mongodb.core.geo;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Map;
import org.junit.Before;
@@ -27,10 +28,13 @@ import org.springframework.dao.DataAccessException;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.config.AbstractIntegrationTests;
import org.springframework.data.mongodb.core.CollectionCallback;
import org.springframework.data.mongodb.core.IndexOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.WriteResultChecking;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.mapping.Document;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
@@ -43,6 +47,7 @@ import com.mongodb.WriteConcern;
* @author Laurent Canet
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class GeoSpatialIndexTests extends AbstractIntegrationTests {
@@ -97,6 +102,29 @@ public class GeoSpatialIndexTests extends AbstractIntegrationTests {
}
}
/**
* @see DATAMONGO-827
*/
@Test
public void useGeneratedNameShouldGenerateAnIndexName() {
try {
GeoSpatialEntity2dWithGeneratedIndex geo = new GeoSpatialEntity2dWithGeneratedIndex(45.2, 4.6);
template.save(geo);
IndexOperations indexOps = template.indexOps(GeoSpatialEntity2dWithGeneratedIndex.class);
List<IndexInfo> indexInfo = indexOps.getIndexInfo();
assertThat(indexInfo, hasSize(2));
assertThat(indexInfo.get(1), is(notNullValue()));
assertThat(indexInfo.get(1).getName(), is("location_2d"));
} finally {
template.dropCollection(GeoSpatialEntity2D.class);
}
}
/**
* Returns whether an index with the given name exists for the given entity type.
*
@@ -129,6 +157,7 @@ public class GeoSpatialIndexTests extends AbstractIntegrationTests {
});
}
@Document
static class GeoSpatialEntity2D {
public String id;
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2D) public org.springframework.data.geo.Point location;
@@ -138,6 +167,7 @@ public class GeoSpatialIndexTests extends AbstractIntegrationTests {
}
}
@Document
static class GeoSpatialEntityHaystack {
public String id;
public String name;
@@ -154,6 +184,7 @@ public class GeoSpatialIndexTests extends AbstractIntegrationTests {
double coordinates[];
}
@Document
static class GeoSpatialEntity2DSphere {
public String id;
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) public GeoJsonPoint location;
@@ -163,4 +194,15 @@ public class GeoSpatialIndexTests extends AbstractIntegrationTests {
this.location.coordinates = new double[] { x, y };
}
}
@Document
static class GeoSpatialEntity2dWithGeneratedIndex {
public String id;
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2D, useGeneratedName = true) public Point location;
public GeoSpatialEntity2dWithGeneratedIndex(double x, double y) {
this.location = new Point(x, y);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011 by the original author(s).
* Copyright (c) 2011-2014 by the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@
*/
package org.springframework.data.mongodb.core.index;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Test;
@@ -25,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.CollectionCallback;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -38,13 +38,13 @@ import com.mongodb.MongoException;
* Integration tests for index handling.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
public class IndexingIntegrationTests {
@Autowired
MongoOperations operations;
@Autowired MongoOperations operations;
@After
public void tearDown() {
@@ -61,11 +61,10 @@ public class IndexingIntegrationTests {
assertThat(hasIndex("_firstname", IndexedPerson.class), is(true));
}
@Document
class IndexedPerson {
@Field("_firstname")
@Indexed
String firstname;
@Field("_firstname") @Indexed String firstname;
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* 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.
@@ -17,22 +17,31 @@ package org.springframework.data.mongodb.core.index;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.Collections;
import java.util.Date;
import org.hamcrest.core.IsEqual;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.data.geo.Point;
import org.springframework.data.mapping.context.MappingContextEvent;
import org.springframework.data.mongodb.MongoDbFactory;
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;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
/**
@@ -41,24 +50,46 @@ import com.mongodb.DBObject;
* @author Oliver Gierke
* @author Philipp Schneider
* @author Johno Crawford
* @author Christoph Strobl
* @author Thomas Darimont
*/
@RunWith(MockitoJUnitRunner.class)
public class MongoPersistentEntityIndexCreatorUnitTests {
@Mock MongoDbFactory factory;
@Mock ApplicationContext context;
private @Mock MongoDbFactory factory;
private @Mock ApplicationContext context;
private @Mock DB db;
private @Mock DBCollection collection;
ArgumentCaptor<DBObject> keysCaptor;
ArgumentCaptor<DBObject> optionsCaptor;
ArgumentCaptor<String> collectionCaptor;
@Before
public void setUp() {
keysCaptor = ArgumentCaptor.forClass(DBObject.class);
optionsCaptor = ArgumentCaptor.forClass(DBObject.class);
collectionCaptor = ArgumentCaptor.forClass(String.class);
when(factory.getDb()).thenReturn(db);
when(db.getCollection(collectionCaptor.capture())).thenReturn(collection);
doNothing().when(collection).createIndex(keysCaptor.capture(), optionsCaptor.capture());
}
@Test
public void buildsIndexDefinitionUsingFieldName() {
MongoMappingContext mappingContext = prepareMappingContext(Person.class);
DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(creator.indexDefinition, is(notNullValue()));
assertThat(creator.indexDefinition.keySet(), hasItem("fieldname"));
assertThat(creator.name, is("indexName"));
assertThat(creator.background, is(false));
assertThat(creator.expireAfterSeconds, is(-1));
new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(keysCaptor.getValue(), is(notNullValue()));
assertThat(keysCaptor.getValue().keySet(), hasItem("fieldname"));
assertThat(optionsCaptor.getValue().get("name").toString(), is("indexName"));
assertThat(optionsCaptor.getValue().get("background"), nullValue());
assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), nullValue());
}
@Test
@@ -67,7 +98,7 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
MongoMappingContext mappingContext = new MongoMappingContext();
MongoMappingContext personMappingContext = prepareMappingContext(Person.class);
DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory);
MongoPersistentEntityIndexCreator creator = new MongoPersistentEntityIndexCreator(mappingContext, factory);
MongoPersistentEntity<?> entity = personMappingContext.getPersistentEntity(Person.class);
MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty> event = new MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty>(
@@ -75,7 +106,7 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
creator.onApplicationEvent(event);
assertThat(creator.indexDefinition, is(nullValue()));
verifyZeroInteractions(collection);
}
/**
@@ -87,7 +118,7 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.initialize();
MongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory);
MongoPersistentEntityIndexCreator creator = new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(creator.isIndexCreatorFor(mappingContext), is(true));
assertThat(creator.isIndexCreatorFor(new MongoMappingContext()), is(false));
}
@@ -99,12 +130,13 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
public void triggersBackgroundIndexingIfConfigured() {
MongoMappingContext mappingContext = prepareMappingContext(AnotherPerson.class);
DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory);
new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(creator.indexDefinition, is(notNullValue()));
assertThat(creator.indexDefinition.keySet(), hasItem("lastname"));
assertThat(creator.name, is("lastname"));
assertThat(creator.background, is(true));
assertThat(keysCaptor.getValue(), is(notNullValue()));
assertThat(keysCaptor.getValue().keySet(), hasItem("lastname"));
assertThat(optionsCaptor.getValue().get("name").toString(), is("lastname"));
assertThat(optionsCaptor.getValue().get("background"), IsEqual.<Object> equalTo(true));
assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), nullValue());
}
/**
@@ -114,11 +146,69 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
public void expireAfterSecondsIfConfigured() {
MongoMappingContext mappingContext = prepareMappingContext(Milk.class);
DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory);
new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(creator.indexDefinition, is(notNullValue()));
assertThat(creator.indexDefinition.keySet(), hasItem("expiry"));
assertThat(creator.expireAfterSeconds, is(60));
assertThat(keysCaptor.getValue(), is(notNullValue()));
assertThat(keysCaptor.getValue().keySet(), hasItem("expiry"));
assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), IsEqual.<Object> equalTo(60L));
}
/**
* @see DATAMONGO-899
*/
@Test
public void createsNotNestedGeoSpatialIndexCorrectly() {
MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class);
new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(keysCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("company.address.location", "2d").get()));
assertThat(optionsCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("name", "location").add("min", -180)
.add("max", 180).add("bits", 26).get()));
}
/**
* @see DATAMONGO-827
*/
@Test
public void autoGeneratedIndexNameShouldGenerateNoName() {
MongoMappingContext mappingContext = prepareMappingContext(EntityWithGeneratedIndexName.class);
new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(keysCaptor.getValue().containsField("name"), is(false));
assertThat(keysCaptor.getValue().keySet(), hasItem("lastname"));
assertThat(optionsCaptor.getValue(), is(new BasicDBObjectBuilder().get()));
}
/**
* @see DATAMONGO-367
*/
@Test
public void indexCreationShouldNotCreateNewCollectionForNestedGeoSpatialIndexStructures() {
MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class);
new MongoPersistentEntityIndexCreator(mappingContext, factory);
ArgumentCaptor<String> collectionNameCapturer = ArgumentCaptor.forClass(String.class);
verify(db, times(1)).getCollection(collectionNameCapturer.capture());
assertThat(collectionNameCapturer.getValue(), equalTo("wrapper"));
}
/**
* @see DATAMONGO-367
*/
@Test
public void indexCreationShouldNotCreateNewCollectionForNestedIndexStructures() {
MongoMappingContext mappingContext = prepareMappingContext(IndexedDocumentWrapper.class);
new MongoPersistentEntityIndexCreator(mappingContext, factory);
ArgumentCaptor<String> collectionNameCapturer = ArgumentCaptor.forClass(String.class);
verify(db, times(1)).getCollection(collectionNameCapturer.capture());
assertThat(collectionNameCapturer.getValue(), equalTo("indexedDocumentWrapper"));
}
private static MongoMappingContext prepareMappingContext(Class<?> type) {
@@ -130,41 +220,63 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
return mappingContext;
}
@Document
static class Person {
@Indexed(name = "indexName") @Field("fieldname") String field;
@Indexed(name = "indexName")//
@Field("fieldname")//
String field;
}
@Document
static class AnotherPerson {
@Indexed(background = true) String lastname;
}
@Document
static class Milk {
@Indexed(expireAfterSeconds = 60) Date expiry;
}
static class DummyMongoPersistentEntityIndexCreator extends MongoPersistentEntityIndexCreator {
@Document
static class Wrapper {
String id;
Company company;
}
static class Company {
DBObject indexDefinition;
String name;
boolean background;
int expireAfterSeconds;
Address address;
}
public DummyMongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) {
super(mappingContext, mongoDbFactory);
}
static class Address {
@Override
protected void ensureIndex(String collection, String name, DBObject indexDefinition, boolean unique,
boolean dropDups, boolean sparse, boolean background, int expireAfterSeconds) {
String street;
String city;
this.name = name;
this.indexDefinition = indexDefinition;
this.background = background;
this.expireAfterSeconds = expireAfterSeconds;
}
@GeoSpatialIndexed Point location;
}
@Document
static class IndexedDocumentWrapper {
IndexedDocument indexedDocument;
}
static class IndexedDocument {
@Indexed String indexedValue;
}
@Document
class EntityWithGeneratedIndexName {
@Indexed(useGeneratedName = true, name = "ignored") String lastname;
}
}

View File

@@ -0,0 +1,704 @@
/*
* 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.index;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import static org.hamcrest.collection.IsEmptyCollection.*;
import static org.hamcrest.core.IsEqual.*;
import static org.hamcrest.core.IsInstanceOf.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.List;
import org.hamcrest.collection.IsEmptyCollection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.CompoundIndexResolutionTests;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.GeoSpatialIndexResolutionTests;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.IndexResolutionTests;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.MixedIndexResolutionTests;
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;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntityTestDummy.MongoPersistentEntityDummyBuilder;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import com.mongodb.BasicDBObjectBuilder;
/**
* @author Christoph Strobl
*/
@RunWith(Suite.class)
@SuiteClasses({ IndexResolutionTests.class, GeoSpatialIndexResolutionTests.class, CompoundIndexResolutionTests.class,
MixedIndexResolutionTests.class })
public class MongoPersistentEntityIndexResolverUnitTests {
/**
* Test resolution of {@link Indexed}.
*
* @author Christoph Strobl
*/
public static class IndexResolutionTests {
/**
* @see DATAMONGO-899
*/
@Test
public void indexPathOnLevelZeroIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelZero.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("indexedProperty", "Zero", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void indexPathOnLevelOneIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelOne.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("zero.indexedProperty", "One", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void depplyNestedIndexPathIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelTwo.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("one.zero.indexedProperty", "Two", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void resolvesIndexPathNameForNamedPropertiesCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelOneWithExplicitlyNamedField.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("customZero.customFieldName", "indexOnLevelOneWithExplicitlyNamedField",
indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void resolvesIndexDefinitionCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelZero.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
assertThat(indexDefinition.getIndexOptions(), equalTo(new BasicDBObjectBuilder().add("name", "indexedProperty")
.get()));
}
/**
* @see DATAMONGO-899
*/
@Test
public void resolvesIndexDefinitionOptionsCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(WithOptionsOnIndexedProperty.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
assertThat(indexDefinition.getIndexOptions(),
equalTo(new BasicDBObjectBuilder().add("name", "indexedProperty").add("unique", true).add("dropDups", true)
.add("sparse", true).add("background", true).add("expireAfterSeconds", 10L).get()));
}
/**
* @see DATAMONGO-899
*/
@Test
public void resolvesIndexCollectionNameCorrectlyWhenDefinedInAnnotation() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(WithOptionsOnIndexedProperty.class);
assertThat(indexDefinitions.get(0).getCollection(), equalTo("CollectionOverride"));
}
@Document(collection = "Zero")
static class IndexOnLevelZero {
@Indexed String indexedProperty;
}
@Document(collection = "One")
static class IndexOnLevelOne {
IndexOnLevelZero zero;
}
@Document(collection = "Two")
static class IndexOnLevelTwo {
IndexOnLevelOne one;
}
@Document(collection = "WithOptionsOnIndexedProperty")
static class WithOptionsOnIndexedProperty {
@Indexed(background = true, collection = "CollectionOverride", direction = IndexDirection.DESCENDING,
dropDups = true, expireAfterSeconds = 10, sparse = true, unique = true)//
String indexedProperty;
}
@Document
static class IndexOnLevelOneWithExplicitlyNamedField {
@Field("customZero") IndexOnLevelZeroWithExplicityNamedField zero;
}
static class IndexOnLevelZeroWithExplicityNamedField {
@Indexed @Field("customFieldName") String namedProperty;
}
}
/**
* Test resolution of {@link GeoSpatialIndexed}.
*
* @author Christoph Strobl
*/
public static class GeoSpatialIndexResolutionTests {
/**
* @see DATAMONGO-899
*/
@Test
public void geoSpatialIndexPathOnLevelZeroIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(GeoSpatialIndexOnLevelZero.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("geoIndexedProperty", "Zero", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void geoSpatialIndexPathOnLevelOneIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(GeoSpatialIndexOnLevelOne.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("zero.geoIndexedProperty", "One", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void depplyNestedGeoSpatialIndexPathIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(GeoSpatialIndexOnLevelTwo.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("one.zero.geoIndexedProperty", "Two", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void resolvesIndexDefinitionOptionsCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(WithOptionsOnGeoSpatialIndexProperty.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
assertThat(
indexDefinition.getIndexOptions(),
equalTo(new BasicDBObjectBuilder().add("name", "location").add("min", 1).add("max", 100).add("bits", 2).get()));
}
@Document(collection = "Zero")
static class GeoSpatialIndexOnLevelZero {
@GeoSpatialIndexed Point geoIndexedProperty;
}
@Document(collection = "One")
static class GeoSpatialIndexOnLevelOne {
GeoSpatialIndexOnLevelZero zero;
}
@Document(collection = "Two")
static class GeoSpatialIndexOnLevelTwo {
GeoSpatialIndexOnLevelOne one;
}
@Document(collection = "WithOptionsOnGeoSpatialIndexProperty")
static class WithOptionsOnGeoSpatialIndexProperty {
@GeoSpatialIndexed(collection = "CollectionOverride", bits = 2, max = 100, min = 1,
type = GeoSpatialIndexType.GEO_2D)//
Point location;
}
}
/**
* Test resolution of {@link CompoundIndexes}.
*
* @author Christoph Strobl
*/
public static class CompoundIndexResolutionTests {
/**
* @see DATAMONGO-899
*/
@Test
public void compoundIndexPathOnLevelZeroIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelZero.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void compoundIndexOptionsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelZero.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
assertThat(indexDefinition.getIndexOptions(), equalTo(new BasicDBObjectBuilder().add("name", "compound_index")
.add("unique", true).add("dropDups", true).add("sparse", true).add("background", true).get()));
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
}
/**
* @see DATAMONGO-909
*/
@Test
public void compoundIndexOnSuperClassResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexDefinedOnSuperClass.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
assertThat(indexDefinition.getIndexOptions(), equalTo(new BasicDBObjectBuilder().add("name", "compound_index")
.add("unique", true).add("dropDups", true).add("sparse", true).add("background", true).get()));
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
}
/**
* @see DATAMONGO-827
*/
@Test
public void compoundIndexDoesNotSpecifyNameWhenUsingGenerateName() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(ComountIndexWithAutogeneratedName.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
assertThat(
indexDefinition.getIndexOptions(),
equalTo(new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).add("sparse", true)
.add("background", true).get()));
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
}
/**
* @see DATAMONGO-929
*/
@Test
public void compoundIndexPathOnLevelOneIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelOne.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection(new String[] { "zero.foo", "zero.bar" }, "CompoundIndexOnLevelOne",
indexDefinitions.get(0));
}
/**
* @see DATAMONGO-929
*/
@Test
public void emptyCompoundIndexPathOnLevelOneIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelOneWithEmptyIndexDefinition.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection(new String[] { "zero" }, "CompoundIndexOnLevelZeroWithEmptyIndexDef",
indexDefinitions.get(0));
}
/**
* @see DATAMONGO-929
*/
@Test
public void singleCompoundIndexPathOnLevelZeroIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(SingleCompoundIndex.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-963
*/
@Test
public void compoundIndexShouldIncludeTTLWhenConsistingOfOnlyOneKey() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexWithOnlyOneKeyAndTTL.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
assertThat(
indexDefinition.getIndexOptions(),
equalTo(new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).add("sparse", true)
.add("background", true).add("expireAfterSeconds", 10L).get()));
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).get()));
}
@Document(collection = "CompoundIndexOnLevelOne")
static class CompoundIndexOnLevelOne {
CompoundIndexOnLevelZero zero;
}
@Document(collection = "CompoundIndexOnLevelZeroWithEmptyIndexDef")
static class CompoundIndexOnLevelOneWithEmptyIndexDefinition {
CompoundIndexOnLevelZeroWithEmptyIndexDef zero;
}
@Document(collection = "CompoundIndexOnLevelZero")
@CompoundIndexes({ @CompoundIndex(name = "compound_index", def = "{'foo': 1, 'bar': -1}", background = true,
dropDups = true, expireAfterSeconds = 10, sparse = true, unique = true) })
static class CompoundIndexOnLevelZero {}
@CompoundIndexes({ @CompoundIndex(name = "compound_index", background = true, dropDups = true,
expireAfterSeconds = 10, sparse = true, unique = true) })
static class CompoundIndexOnLevelZeroWithEmptyIndexDef {}
@Document(collection = "CompoundIndexOnLevelZero")
@CompoundIndex(name = "compound_index", def = "{'foo': 1, 'bar': -1}", background = true, dropDups = true,
expireAfterSeconds = 10, sparse = true, unique = true)
static class SingleCompoundIndex {}
static class IndexDefinedOnSuperClass extends CompoundIndexOnLevelZero {
}
@Document(collection = "ComountIndexWithAutogeneratedName")
@CompoundIndexes({ @CompoundIndex(useGeneratedName = true, def = "{'foo': 1, 'bar': -1}", background = true,
dropDups = true, expireAfterSeconds = 10, sparse = true, unique = true) })
static class ComountIndexWithAutogeneratedName {
}
@Document(collection = "CompoundIndexWithOnlyOneKeyAndTTL")
@CompoundIndex(def = "{'foo': 1}", background = true, dropDups = true, expireAfterSeconds = 10, sparse = true,
unique = true)
static class CompoundIndexWithOnlyOneKeyAndTTL {
}
}
public static class MixedIndexResolutionTests {
/**
* @see DATAMONGO-899
*/
@Test
public void multipleIndexesResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(MixedIndexRoot.class);
assertThat(indexDefinitions, hasSize(2));
assertThat(indexDefinitions.get(0).getIndexDefinition(), instanceOf(Index.class));
assertThat(indexDefinitions.get(1).getIndexDefinition(), instanceOf(GeospatialIndex.class));
}
/**
* @see DATAMONGO-899
*/
@Test
public void cyclicPropertyReferenceOverDBRefShouldNotBeTraversed() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(Inner.class);
assertThat(indexDefinitions, hasSize(1));
assertThat(indexDefinitions.get(0).getIndexDefinition().getIndexKeys(),
equalTo(new BasicDBObjectBuilder().add("outer", 1).get()));
}
/**
* @see DATAMONGO-899
*/
@Test
public void associationsShouldNotBeTraversed() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(Outer.class);
assertThat(indexDefinitions, empty());
}
/**
* @see DATAMONGO-926
*/
@Test
public void shouldNotRunIntoStackOverflow() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleStartingInBetween.class);
assertThat(indexDefinitions, hasSize(1));
}
/**
* @see DATAMONGO-926
*/
@Test
public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelZero() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleLevelZero.class);
assertIndexPathAndCollection("indexedProperty", "cycleLevelZero", indexDefinitions.get(0));
assertIndexPathAndCollection("cyclicReference.indexedProperty", "cycleLevelZero", indexDefinitions.get(1));
assertThat(indexDefinitions, hasSize(2));
}
/**
* @see DATAMONGO-926
*/
@Test
public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelOne() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleOnLevelOne.class);
assertIndexPathAndCollection("reference.indexedProperty", "cycleOnLevelOne", indexDefinitions.get(0));
assertThat(indexDefinitions, hasSize(1));
}
/**
* @see DATAMONGO-926
*/
@Test
public void indexBeResolvedCorrectlyWhenPropertiesOfDifferentTypesAreNamedEqually() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(NoCycleButIdenticallyNamedProperties.class);
assertIndexPathAndCollection("foo", "noCycleButIdenticallyNamedProperties", indexDefinitions.get(0));
assertIndexPathAndCollection("reference.foo", "noCycleButIdenticallyNamedProperties", indexDefinitions.get(1));
assertIndexPathAndCollection("reference.deep.foo", "noCycleButIdenticallyNamedProperties",
indexDefinitions.get(2));
assertThat(indexDefinitions, hasSize(3));
}
/**
* @see DATAMONGO-949
*/
@Test
public void shouldNotDetectCycleInSimilarlyNamedProperties() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(SimilarityHolingBean.class);
assertIndexPathAndCollection("norm", "similarityHolingBean", indexDefinitions.get(0));
assertThat(indexDefinitions, hasSize(1));
}
/**
* @see DATAMONGO-962
*/
@Test
public void shouldDetectSelfCycleViaCollectionTypeCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(SelfCyclingViaCollectionType.class);
assertThat(indexDefinitions, IsEmptyCollection.empty());
}
/**
* @see DATAMONGO-962
*/
@Test
public void shouldNotDetectCycleWhenTypeIsUsedMoreThanOnce() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(MultipleObjectsOfSameType.class);
assertThat(indexDefinitions, IsEmptyCollection.empty());
}
/**
* @see DATAMONGO-962
*/
@Test
public void shouldCatchCyclicReferenceExceptionOnRoot() {
Document documentDummy = new Document() {
@Override
public Class<? extends Annotation> annotationType() {
return Document.class;
}
@Override
public String collection() {
return null;
}
};
MongoPersistentProperty propertyMock = mock(MongoPersistentProperty.class);
when(propertyMock.isEntity()).thenReturn(true);
when(propertyMock.getActualType()).thenThrow(
new MongoPersistentEntityIndexResolver.CyclicPropertyReferenceException("foo", Object.class, "bar"));
MongoPersistentEntity<SelfCyclingViaCollectionType> dummy = MongoPersistentEntityDummyBuilder
.forClass(SelfCyclingViaCollectionType.class).withCollection("foo").and(propertyMock)
.and(documentDummy).build();
new MongoPersistentEntityIndexResolver(prepareMappingContext(SelfCyclingViaCollectionType.class))
.resolveIndexForEntity(dummy);
}
@Document
static class MixedIndexRoot {
@Indexed String first;
NestedGeoIndex nestedGeo;
}
static class NestedGeoIndex {
@GeoSpatialIndexed Point location;
}
@Document
static class Outer {
@DBRef Inner inner;
}
@Document
static class Inner {
@Indexed Outer outer;
}
@Document
static class CycleLevelZero {
@Indexed String indexedProperty;
CycleLevelZero cyclicReference;
}
@Document
static class CycleOnLevelOne {
CycleOnLevelOneReferenced reference;
}
static class CycleOnLevelOneReferenced {
@Indexed String indexedProperty;
CycleOnLevelOne cyclicReference;
}
@Document
public static class CycleStartingInBetween {
CycleOnLevelOne referenceToCycleStart;
}
@Document
static class NoCycleButIdenticallyNamedProperties {
@Indexed String foo;
NoCycleButIdenticallyNamedPropertiesNested reference;
}
static class NoCycleButIdenticallyNamedPropertiesNested {
@Indexed String foo;
NoCycleButIndenticallNamedPropertiesDeeplyNested deep;
}
static class NoCycleButIndenticallNamedPropertiesDeeplyNested {
@Indexed String foo;
}
@Document
static class SimilarityHolingBean {
@Indexed @Field("norm") String normalProperty;
@Field("similarityL") private List<SimilaritySibling> listOfSimilarilyNamedEntities = null;
}
static class SimilaritySibling {
@Field("similarity") private String similarThoughNotEqualNamedProperty;
}
@Document
static class MultipleObjectsOfSameType {
SelfCyclingViaCollectionType cycleOne;
SelfCyclingViaCollectionType cycleTwo;
}
@Document
static class SelfCyclingViaCollectionType {
List<SelfCyclingViaCollectionType> cyclic;
}
}
private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?> type) {
MongoMappingContext mappingContext = prepareMappingContext(type);
MongoPersistentEntityIndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext);
return resolver.resolveIndexForEntity(mappingContext.getPersistentEntity(type));
}
private static MongoMappingContext prepareMappingContext(Class<?> type) {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setInitialEntitySet(Collections.singleton(type));
mappingContext.initialize();
return mappingContext;
}
private static void assertIndexPathAndCollection(String expectedPath, String expectedCollection,
IndexDefinitionHolder holder) {
assertIndexPathAndCollection(new String[] { expectedPath }, expectedCollection, holder);
}
private static void assertIndexPathAndCollection(String[] expectedPaths, String expectedCollection,
IndexDefinitionHolder holder) {
for (String expectedPath : expectedPaths) {
assertThat(holder.getIndexDefinition().getIndexKeys().containsField(expectedPath), equalTo(true));
}
assertThat(holder.getCollection(), equalTo(expectedCollection));
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.index;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
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.data.mongodb.core.index.MongoPersistentEntityIndexResolver.CycleGuard.Path;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
/**
* Unit tests for {@link Path}.
*
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class PathUnitTests {
@Mock MongoPersistentEntity<?> entityMock;
@Before
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setUp() {
when(entityMock.getType()).thenReturn((Class) Object.class);
}
/**
* @see DATAMONGO-962
*/
@Test
public void shouldIdentifyCycleForOwnerOfSameTypeAndMatchingPath() {
MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo");
assertThat(new Path(property, "foo.bar").cycles(property, "foo.bar.bar"), is(true));
}
/**
* @see DATAMONGO-962
*/
@Test
@SuppressWarnings("rawtypes")
public void shouldAllowMatchingPathForDifferentOwners() {
MongoPersistentProperty existing = createPersistentPropertyMock(entityMock, "foo");
MongoPersistentEntity entityOfDifferentType = Mockito.mock(MongoPersistentEntity.class);
when(entityOfDifferentType.getType()).thenReturn(String.class);
MongoPersistentProperty toBeVerified = createPersistentPropertyMock(entityOfDifferentType, "foo");
assertThat(new Path(existing, "foo.bar").cycles(toBeVerified, "foo.bar.bar"), is(false));
}
/**
* @see DATAMONGO-962
*/
@Test
public void shouldAllowEqaulPropertiesOnDifferentPaths() {
MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo");
assertThat(new Path(property, "foo.bar").cycles(property, "foo2.bar.bar"), is(false));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private MongoPersistentProperty createPersistentPropertyMock(MongoPersistentEntity owner, String fieldname) {
MongoPersistentProperty property = Mockito.mock(MongoPersistentProperty.class);
when(property.getOwner()).thenReturn(owner);
when(property.getFieldName()).thenReturn(fieldname);
return property;
}
}

View File

@@ -0,0 +1,212 @@
/*
* 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.mapping;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.SimpleAssociationHandler;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.util.TypeInformation;
/**
* Trivial dummy implementation of {@link MongoPersistentEntity} to be used in tests.
*
* @author Christoph Strobl
* @param <T>
*/
public class MongoPersistentEntityTestDummy<T> implements MongoPersistentEntity<T> {
private Map<Class<?>, Annotation> annotations = new HashMap<Class<?>, Annotation>();
private Collection<MongoPersistentProperty> properties = new ArrayList<MongoPersistentProperty>();
private String collection;
private String name;
private Class<T> type;
@Override
public String getName() {
return name;
}
@Override
public PreferredConstructor<T, MongoPersistentProperty> getPersistenceConstructor() {
return null;
}
@Override
public boolean isConstructorArgument(PersistentProperty<?> property) {
return false;
}
@Override
public boolean isIdProperty(PersistentProperty<?> property) {
return property != null ? property.isIdProperty() : false;
}
@Override
public boolean isVersionProperty(PersistentProperty<?> property) {
return property != null ? property.isIdProperty() : false;
}
@Override
public MongoPersistentProperty getIdProperty() {
return getPersistentProperty(Id.class);
}
@Override
public MongoPersistentProperty getVersionProperty() {
return getPersistentProperty(Version.class);
}
@Override
public MongoPersistentProperty getPersistentProperty(String name) {
for (MongoPersistentProperty p : this.properties) {
if (p.getName().equals(name)) {
return p;
}
}
return null;
}
@Override
public MongoPersistentProperty getPersistentProperty(Class<? extends Annotation> annotationType) {
for (MongoPersistentProperty p : this.properties) {
if (p.isAnnotationPresent(annotationType)) {
return p;
}
}
return null;
}
@Override
public boolean hasIdProperty() {
return false;
}
@Override
public boolean hasVersionProperty() {
return getVersionProperty() != null;
}
@Override
public Class<T> getType() {
return this.type;
}
@Override
public Object getTypeAlias() {
return null;
}
@Override
public TypeInformation<T> getTypeInformation() {
return null;
}
@Override
public void doWithProperties(PropertyHandler<MongoPersistentProperty> handler) {
for (MongoPersistentProperty p : this.properties) {
handler.doWithPersistentProperty(p);
}
}
@Override
public void doWithProperties(SimplePropertyHandler handler) {
for (MongoPersistentProperty p : this.properties) {
handler.doWithPersistentProperty(p);
}
}
@Override
public void doWithAssociations(AssociationHandler<MongoPersistentProperty> handler) {
}
@Override
public void doWithAssociations(SimpleAssociationHandler handler) {
}
@SuppressWarnings("unchecked")
@Override
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
return (A) this.annotations.get(annotationType);
}
@Override
public String getCollection() {
return this.collection;
}
/**
* Simple builder to create {@link MongoPersistentEntityTestDummy} with defined properties.
*
* @author Christoph Strobl
* @param <T>
*/
public static class MongoPersistentEntityDummyBuilder<T> {
private MongoPersistentEntityTestDummy<T> instance;
private MongoPersistentEntityDummyBuilder(Class<T> type) {
this.instance = new MongoPersistentEntityTestDummy<T>();
this.instance.type = type;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static <T> MongoPersistentEntityDummyBuilder<T> forClass(Class<T> type) {
return new MongoPersistentEntityDummyBuilder(type);
}
public MongoPersistentEntityDummyBuilder<T> withName(String name) {
this.instance.name = name;
return this;
}
public MongoPersistentEntityDummyBuilder<T> and(MongoPersistentProperty property) {
this.instance.properties.add(property);
return this;
}
public MongoPersistentEntityDummyBuilder<T> withCollection(String collection) {
this.instance.collection = collection;
return this;
}
public MongoPersistentEntityDummyBuilder<T> and(Annotation annotation) {
this.instance.annotations.put(annotation.annotationType(), annotation);
return this;
}
public MongoPersistentEntityTestDummy<T> build() {
return this.instance;
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.
* 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.mapping;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Unit tests for {@link SnakeCaseFieldNamingStrategy}.
*
* @author Ryan Tenney
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class SnakeCaseFieldNamingStrategyUnitTests {
FieldNamingStrategy strategy = new SnakeCaseFieldNamingStrategy();
@Mock MongoPersistentProperty property;
/**
* @see DATAMONGO-866
*/
@Test
public void rendersSnakeCaseFieldNames() {
assertFieldNameForPropertyName("fooBar", "foo_bar");
assertFieldNameForPropertyName("FooBar", "foo_bar");
assertFieldNameForPropertyName("foo_bar", "foo_bar");
assertFieldNameForPropertyName("FOO_BAR", "foo_bar");
}
private void assertFieldNameForPropertyName(String propertyName, String fieldName) {
when(property.getName()).thenReturn(propertyName);
assertThat(strategy.getFieldName(property), is(fieldName));
}
}

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.
@@ -32,6 +32,7 @@ import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Box;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
@@ -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

@@ -21,6 +21,7 @@ import static org.junit.Assert.*;
import java.util.Collections;
import java.util.Map;
import org.joda.time.DateTime;
import org.junit.Test;
/**
@@ -30,6 +31,7 @@ import org.junit.Test;
* @author Thomas Risberg
* @author Becca Gaspard
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class UpdateTests {
@@ -284,4 +286,71 @@ public class UpdateTests {
public void testCreatingUpdateWithNullKeyThrowsException() {
Update.update(null, "value");
}
/**
* @see DATAMONGO-953
*/
@Test
public void testEquality() {
Update actualUpdate = new Update() //
.inc("size", 1) //
.set("nl", null) //
.set("directory", "/Users/Test/Desktop") //
.push("authors", Collections.singletonMap("name", "Sven")) //
.pop("authors", Update.Position.FIRST) //
.set("foo", "bar");
Update expectedUpdate = new Update() //
.inc("size", 1) //
.set("nl", null) //
.set("directory", "/Users/Test/Desktop") //
.push("authors", Collections.singletonMap("name", "Sven")) //
.pop("authors", Update.Position.FIRST) //
.set("foo", "bar");
assertThat(actualUpdate, is(equalTo(actualUpdate)));
assertThat(actualUpdate.hashCode(), is(equalTo(actualUpdate.hashCode())));
assertThat(actualUpdate, is(equalTo(expectedUpdate)));
assertThat(actualUpdate.hashCode(), is(equalTo(expectedUpdate.hashCode())));
}
/**
* @see DATAMONGO-953
*/
@Test
public void testToString() {
Update actualUpdate = new Update() //
.inc("size", 1) //
.set("nl", null) //
.set("directory", "/Users/Test/Desktop") //
.push("authors", Collections.singletonMap("name", "Sven")) //
.pop("authors", Update.Position.FIRST) //
.set("foo", "bar");
Update expectedUpdate = new Update() //
.inc("size", 1) //
.set("nl", null) //
.set("directory", "/Users/Test/Desktop") //
.push("authors", Collections.singletonMap("name", "Sven")) //
.pop("authors", Update.Position.FIRST) //
.set("foo", "bar");
assertThat(actualUpdate.toString(), is(equalTo(expectedUpdate.toString())));
assertThat(actualUpdate.toString(), is("{ \"$inc\" : { \"size\" : 1} ," //
+ " \"$set\" : { \"nl\" : null , \"directory\" : \"/Users/Test/Desktop\" , \"foo\" : \"bar\"} , " //
+ "\"$push\" : { \"authors\" : { \"name\" : \"Sven\"}} " //
+ ", \"$pop\" : { \"authors\" : -1}}")); //
}
/**
* @see DATAMONGO-1002
*/
@Test
public void toStringWorksForUpdateWithComplexObject() {
Update update = new Update().addToSet("key", new DateTime());
assertThat(update.toString(), is(notNullValue()));
}
}

View File

@@ -24,6 +24,7 @@ import java.util.HashSet;
import java.util.List;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -863,4 +864,89 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
public void deleteByUsingAnnotatedQueryShouldReturnNumberOfDocumentsRemovedIfReturnTypeIsLong() {
assertThat(repository.removePersonByLastnameUsingAnnotatedQuery("Beauford"), is(1L));
}
/**
* @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));
}
/**
* Ignored for now as this requires Querydsl 3.4.1 to succeed.
*
* @see DATAMONGO-972
*/
@Test
@Ignore
public void shouldExecuteFindOnDbRefCorrectly() {
operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class);
User user = new User();
user.setUsername("Valerie Matthews");
operations.save(user);
dave.setCreator(user);
operations.save(dave);
assertThat(repository.findOne(QPerson.person.creator.eq(user)), is(dave));
}
/**
* @see DATAMONGO-969
*/
@Test
public void shouldFindPersonsWhenUsingQueryDslPerdicatedOnIdProperty() {
assertThat(repository.findAll(person.id.in(Arrays.asList(dave.id, carter.id))), containsInAnyOrder(dave, carter));
}
}

View File

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

View File

@@ -286,4 +286,20 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
@Query(value = "{ 'lastname' : ?0 }", delete = true)
Long removePersonByLastnameUsingAnnotatedQuery(String lastname);
/**
* @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

@@ -15,9 +15,12 @@
*/
package org.springframework.data.mongodb.repository.config;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoOperations;
@@ -49,9 +52,25 @@ public class MongoRepositoriesRegistrarIntegrationTests {
}
@Autowired PersonRepository personRepository;
@Autowired ApplicationContext context;
@Test
public void testConfiguration() {
}
/**
* @see DATAMONGO-901
*/
@Test
public void registersTypePredictingPostProcessor() {
for (String name : context.getBeanDefinitionNames()) {
if (name.startsWith("org.springframework.data.repository.core.support.RepositoryInterfaceAwareBeanPostProcessor")) {
return;
}
}
fail("Expected to find a bean with name starting with RepositoryInterfaceAwareBeanPostProcessor");
}
}

View File

@@ -54,7 +54,7 @@ import com.mongodb.WriteResult;
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class AbstracMongoQueryUnitTests {
public class AbstractMongoQueryUnitTests {
@Mock RepositoryMetadata metadataMock;
@Mock MongoOperations mongoOperationsMock;
@@ -88,7 +88,8 @@ public class AbstracMongoQueryUnitTests {
createQueryForMethod("deletePersonByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" });
verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), Matchers.eq("persons"));
verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), Matchers.eq(Person.class),
Matchers.eq("persons"));
verify(this.mongoOperationsMock, times(0)).find(Matchers.any(Query.class), Matchers.any(Class.class),
Matchers.anyString());
}
@@ -122,19 +123,21 @@ public class AbstracMongoQueryUnitTests {
/**
* @see DATAMONGO-566
* @see DATAMONGO-978
*/
@Test
public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() {
when(writeResultMock.getN()).thenReturn(100);
when(this.mongoOperationsMock.remove(Matchers.any(Query.class), Matchers.eq("persons")))
when(this.mongoOperationsMock.remove(Matchers.any(Query.class), Matchers.eq(Person.class), Matchers.eq("persons")))
.thenReturn(writeResultMock);
MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class);
query.setDeleteQuery(true);
assertThat(query.execute(new Object[] { "fake" }), is((Object) 100L));
verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), Matchers.eq("persons"));
verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), Matchers.eq(Person.class),
Matchers.eq("persons"));
}
private MongoQueryFake createQueryForMethod(String methodName, Class<?>... paramTypes) {

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

@@ -20,6 +20,9 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
@@ -46,6 +49,7 @@ import com.mongodb.DBObject;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
*/
@RunWith(MockitoJUnitRunner.class)
public class StringBasedMongoQueryUnitTests {
@@ -158,6 +162,99 @@ public class StringBasedMongoQueryUnitTests {
createQueryForMethod("invalidMethod", String.class);
}
/**
* @see DATAMONGO-420
*/
@Test
public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception {
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] {
new BasicDBObject("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1) });
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByParameterizedCriteriaAndFields", DBObject.class,
Map.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
assertThat(query.getQueryObject(),
is(new BasicQuery("{ \"firstname\": \"first\", \"lastname\": \"last\"}").getQueryObject()));
assertThat(query.getFieldsObject(), is(new BasicQuery(null, "{ \"lastname\": 1}").getFieldsObject()));
}
/**
* @see DATAMONGO-420
*/
@Test
public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQuoting() throws Exception {
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { "fun" });
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByTitleBeginsWithExplicitQuoting", String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
assertThat(query.getQueryObject(), is(new BasicQuery("{title: {$regex: '^fun', $options: 'i'}}").getQueryObject()));
}
/**
* @see DATAMONGO-995, DATAMONGO-420
*/
@Test
public void shouldParseQueryWithParametersInExpression() throws Exception {
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { 1, 2, 3, 4 });
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithParametersInExpression", int.class,
int.class, int.class, int.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
assertThat(query.getQueryObject(), is(new BasicQuery(
"{$where: 'return this.date.getUTCMonth() == 3 && this.date.getUTCDay() == 4;'}").getQueryObject()));
}
/**
* @see DATAMONGO-995, DATAMONGO-420
*/
@Test
public void bindsSimplePropertyAlreadyQuotedCorrectly() throws Exception {
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews");
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}");
assertThat(query.getQueryObject(), is(reference.getQueryObject()));
}
/**
* @see DATAMONGO-995, DATAMONGO-420
*/
@Test
public void bindsSimplePropertyAlreadyQuotedWithRegexCorrectly() throws Exception {
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*");
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}");
assertThat(query.getQueryObject(), is(reference.getQueryObject()));
}
/**
* @see DATAMONGO-995, DATAMONGO-420
*/
@Test
public void bindsSimplePropertyWithRegexCorrectly() throws Exception {
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class);
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*");
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}");
assertThat(query.getQueryObject(), is(reference.getQueryObject()));
}
private StringBasedMongoQuery createQueryForMethod(String name, Class<?>... parameters) throws Exception {
Method method = SampleRepository.class.getMethod(name, parameters);
@@ -170,6 +267,9 @@ public class StringBasedMongoQueryUnitTests {
@Query("{ 'lastname' : ?0 }")
Person findByLastname(String lastname);
@Query("{ 'lastname' : '?0' }")
Person findByLastnameQuoted(String lastname);
@Query("{ 'address' : ?0 }")
Person findByAddress(Address address);
@@ -184,5 +284,14 @@ public class StringBasedMongoQueryUnitTests {
@Query(value = "{ 'lastname' : ?0 }", delete = true, count = true)
void invalidMethod(String lastname);
@Query(value = "?0", fields = "?1")
DBObject findByParameterizedCriteriaAndFields(DBObject criteria, Map<String, Integer> fields);
@Query("{'title': { $regex : '^?0', $options : 'i'}}")
List<DBObject> findByTitleBeginsWithExplicitQuoting(String title);
@Query(value = "{$where: 'return this.date.getUTCMonth() == ?2 && this.date.getUTCDay() == ?3;'}")
List<DBObject> findByQueryWithParametersInExpression(int param1, int param2, int param3, int param4);
}
}

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,8 +17,10 @@ package org.springframework.data.mongodb.repository.support;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
import org.bson.types.ObjectId;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,6 +34,7 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.QAddress;
import org.springframework.data.mongodb.repository.QPerson;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mysema.query.types.expr.BooleanOperation;
@@ -43,6 +46,7 @@ import com.mysema.query.types.path.StringPath;
* Unit tests for {@link SpringDataMongodbSerializer}.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class SpringDataMongodbSerializerUnitTests {
@@ -132,6 +136,41 @@ public class SpringDataMongodbSerializerUnitTests {
assertThat(path, is("0"));
}
/**
* @see DATAMONGO-969
*/
@Test
public void shouldConvertObjectIdEvenWhenNestedInOperatorDbObject() {
ObjectId value = new ObjectId("53bb9fd14438765b29c2d56e");
DBObject serialized = serializer.asDBObject("_id", new BasicDBObject("$ne", value.toString()));
DBObject _id = getAsDBObject(serialized, "_id");
ObjectId $ne = getTypedValue(_id, "$ne", ObjectId.class);
assertThat($ne, is(value));
}
/**
* @see DATAMONGO-969
*/
@Test
public void shouldConvertCollectionOfObjectIdEvenWhenNestedInOperatorDbObject() {
ObjectId firstId = new ObjectId("53bb9fd14438765b29c2d56e");
ObjectId secondId = new ObjectId("53bb9fda4438765b29c2d56f");
BasicDBList objectIds = new BasicDBList();
objectIds.add(firstId.toString());
objectIds.add(secondId.toString());
DBObject serialized = serializer.asDBObject("_id", new BasicDBObject("$in", objectIds));
DBObject _id = getAsDBObject(serialized, "_id");
Object[] $in = getTypedValue(_id, "$in", Object[].class);
assertThat($in, Matchers.<Object> arrayContaining(firstId, secondId));
}
class Address {
String id;
String street;

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:mapping-converter id="mappingConverterWithCustomFieldNamingStrategy" field-naming-strategy-ref="customFieldNamingStrategy" />
<!-- Should work as well as long ass abbreviation is explicitly disabled -->
<mongo:mapping-converter id="mappingConverterWithCustomFieldNamingStrategyAndAbbreviationDisabled" field-naming-strategy-ref="customFieldNamingStrategy" abbreviate-field-names="false" />
<bean id="customFieldNamingStrategy" class="org.mockito.Mockito" factory-method="mock">
<property name="type" value="org.springframework.data.mongodb.core.mapping.FieldNamingStrategy" />
</bean>
</beans>

View File

@@ -0,0 +1,10 @@
<?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:mapping-converter abbreviate-field-names="true" field-naming-strategy-ref="foo" />
</beans>

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

@@ -2,13 +2,13 @@ Bundle-SymbolicName: org.springframework.data.mongodb
Bundle-Name: Spring Data MongoDB Support
Bundle-Vendor: Pivotal Software, Inc.
Bundle-ManifestVersion: 2
Import-Package:
Import-Package:
sun.reflect;version="0";resolution:=optional
Export-Template:
org.springframework.data.mongodb.*;version="${project.version}"
Import-Template:
Import-Template:
com.google.common.base.*;version="[11.0.0,14.0.0)";resolution:=optional,
com.mongodb.*;version="${mongo-osgi:[=.=.=,+1.0.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

@@ -68,7 +68,7 @@
<xi:include href="introduction/introduction.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.8.0.M1/src/docbkx/repositories.xml">
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.2.RELEASE/src/docbkx/repositories.xml">
<xi:fallback href="../../../spring-data-commons/src/docbkx/repositories.xml" />
</xi:include>
</part>
@@ -88,10 +88,10 @@
<part id="appendix">
<title>Appendix</title>
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.0.M1/src/docbkx/repository-namespace-reference.xml">
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.2.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.8.0.M1/src/docbkx/repository-query-keywords-reference.xml">
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.2.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

@@ -18,20 +18,19 @@
<section id="get-started:help:community">
<title>Community Forum</title>
<para>The Spring Data <ulink
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>
<para>Spring Data on Stackoverflow <ulink
url="http://stackoverflow.com/questions/tagged/spring-data">Stackoverflow
</ulink> is a tag 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>
</section>
<section id="get-started:help:professional">
<title>Professional Support</title>
<para>Professional, from-the-source support, with guaranteed response
time, is available from <ulink
url="http://gopivotal.com/">Pivotal Sofware, Inc.</ulink>, the company
behind Spring Data and Spring.</para>
time, is available from <ulink url="http://gopivotal.com/">Pivotal
Sofware, Inc.</ulink>, the company behind Spring Data and Spring.</para>
</section>
</section>
@@ -44,22 +43,21 @@
homepage</ulink>.</para>
<para>You can help make Spring Data best serve the needs of the Spring
community by interacting with developers through the Spring Community
<ulink url="http://forum.spring.io/">forums</ulink>. To follow
developer activity look for the mailing list information on the Spring
Data Mongo homepage.</para>
community by interacting with developers through the Community on <ulink
url="http://stackoverflow.com/questions/tagged/spring-data">Stackoverflow</ulink>.
To follow developer activity look for the mailing list information on the
Spring Data Mongo homepage.</para>
<para>If you encounter a bug or want to suggest an improvement, please
create a ticket on the Spring Data issue <ulink
url="https://jira.springframework.org/browse/DATAMONGO">tracker</ulink>.</para>
url="https://jira.spring.io/browse/DATAMONGO">tracker</ulink>.</para>
<para>To stay up to date with the latest news and announcements in the
Spring eco system, subscribe to the Spring Community <ulink
url="https://spring.io">Portal</ulink>.</para>
<para>Lastly, you can follow the SpringSource Data <ulink
url="https://spring.io/blog">blog </ulink>or
the project team on Twitter (<ulink
url="http://twitter.com/SpringData">SpringData</ulink>)</para>
url="https://spring.io/blog">blog </ulink>or the project team on Twitter
(<ulink url="http://twitter.com/SpringData">SpringData</ulink>).</para>
</section>
</chapter>

View File

@@ -312,6 +312,11 @@ public class Person {
document, making searches faster.</para>
</important>
<important>
<para>Automatic index creation is only done for types annotated with
<classname>@Document</classname>.</para>
</important>
<section id="mapping-usage-annotations">
<title>Mapping annotation overview</title>

View File

@@ -1,6 +1,152 @@
Spring Data MongoDB Changelog
=============================
Changes in version 1.5.2.RELEASE (2014-07-28)
---------------------------------------------
* DATAMONGO-1007 - Release 1.5.2.
* DATAMONGO-1002 - Update.toString(…) might throw exception.
* DATAMONGO-1001 - Can't save/update lazy load object.
* DATAMONGO-999 - Multiple Mongo Instances always have the same MongoOption Reference - MongoOptionsFactoryBean has a static instance of MongoOptions.
* DATAMONGO-995 - Parameter binding in String-based query does not bind all parameters.
* DATAMONGO-992 - Entity can't be deserialized if @TypeAlias is used.
* DATAMONGO-989 - MatchOperation should accept CriteriaDefinition.
* DATAMONGO-987 - Problem with lazy loading in @DBRef when getting data using MongoTemplate.
* DATAMONGO-983 - Remove links to forum.spring.io.
* DATAMONGO-982 - Assure compatibility with upcoming MongoDB driver versions.
* DATAMONGO-978 - deleteBy/removeBy repository methods don't set type information in Before/AfterDeleteEvent.
* DATAMONGO-972 - References are not handled properly in Querydsl integration.
* DATAMONGO-969 - String @id field is not mapped to ObjectId when using QueryDSL .id.in(Collection<String>).
* DATAMONGO-420 - Extra quotes being added to @Query values and fields.
Changes in version 1.6.0.M1 (2014-07-10)
----------------------------------------
* DATAMONGO-983 - Remove links to forum.spring.io.
* DATAMONGO-982 - Assure compatibility with upcoming MongoDB driver versions.
* DATAMONGO-981 - Release 1.6 M1.
* DATAMONGO-980 - Use meta annotations from spring data commons for @Score.
* DATAMONGO-978 - deleteBy/removeBy repository methods don't set type information in Before/AfterDeleteEvent.
* DATAMONGO-977 - Adapt to Spring 4 upgrade.
* DATAMONGO-976 - Add support for reading $meta projection on textScore into document.
* DATAMONGO-975 - Add support for date/time operators in aggregation framework.
* DATAMONGO-973 - Add support for deriving full text queries.
* DATAMONGO-972 - References are not handled properly in Querydsl integration.
* DATAMONGO-970 - Id query cannot be created if object to remove is DBObject.
* DATAMONGO-969 - String @id field is not mapped to ObjectId when using QueryDSL .id.in(Collection<String>).
* DATAMONGO-968 - Add support for $meta projections and sorting for textScore metadata.
* DATAMONGO-963 - Compound index with expireAfterSeconds causes repeating error on mongodb server.
* DATAMONGO-962 - “Cycle found” with Spring Data Mongo 1.5.
* DATAMONGO-960 - Allow to pass options to the Aggregation Pipeline.
* DATAMONGO-958 - Move to FieldNamingStrategy SPI in Spring Data Commons.
* DATAMONGO-954 - Add support for System Variables in Aggregations.
* DATAMONGO-953 - Update object should have a proper equals/hashcode/toString.
* DATAMONGO-952 - @Query annotation does not work with only field restrictions.
* DATAMONGO-950 - Add support for limiting the query result in the query derivation mechanism.
* DATAMONGO-949 - CyclicPropertyReferenceException in versions 1.5.0 + for MongoDB.
* DATAMONGO-948 - Assertion error in MongoTemplate.getMappedSortObject.
* DATAMONGO-944 - Add support $currentDate to Update.
* DATAMONGO-938 - Exception when creating geo within Criteria using MapReduce.
* DATAMONGO-937 - Add support for creating text index.
* DATAMONGO-850 - Add support for text search using $text.
* DATAMONGO-745 - @Query($in) and Pageable in result Page total = 0.
Changes in version 1.5.1.RELEASE (2014-06-30)
---------------------------------------------
* DATAMONGO-971 - Release 1.5.1.
* DATAMONGO-970 - Id query cannot be created if object to remove is DBObject.
* DATAMONGO-963 - Compound index with expireAfterSeconds causes repeating error on mongodb server.
* DATAMONGO-962 - “Cycle found” with Spring Data Mongo 1.5.
* DATAMONGO-953 - Update object should have a proper equals/hashcode/toString.
* DATAMONGO-952 - @Query annotation does not work with only field restrictions.
* DATAMONGO-949 - CyclicPropertyReferenceException in versions 1.5.0 + for MongoDB.
* DATAMONGO-948 - Assertion error in MongoTemplate.getMappedSortObject.
* DATAMONGO-938 - Exception when creating geo within Criteria using MapReduce.
* DATAMONGO-745 - @Query($in) and Pageable in result Page total = 0.
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
@@ -30,7 +176,7 @@ Changes in version 1.5.0.M1 (2014-03-31)
** 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.
@@ -58,7 +204,7 @@ 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.
@@ -84,7 +230,7 @@ Changes in version 1.4.0.RELEASE (2014-02-24)
* [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
@@ -134,7 +280,7 @@ Changes in version 1.4.0.RC1 (2014-01-29)
* [DATAMONGO-824] - Add contribution guidelines
* [DATAMONGO-826] - Release Spring Data MongoDB 1.4.0.RC1
* [DATAMONGO-835] - Code cleanups
Changes in version 1.3.3.RELEASE (2013-12-11)
---------------------------------------------
** Bug
@@ -352,7 +498,7 @@ Changes in version 1.3.0.M1 (2013-06-04)
* [DATAMONGO-672] - Upgrade to latest Spring Data Build and Commons
* [DATAMONGO-678] - Performance improvements in CustomConversions
* [DATAMONGO-690] - Release 1.3 M1
Changes in version 1.2.1.GA (2013-04-17)
----------------------------------------
** Bug
@@ -378,7 +524,7 @@ Changes in version 1.2.1.GA (2013-04-17)
* [DATAMONGO-637] - Typo in Query.query(…)
* [DATAMONGO-651] - WriteResult not available from thrown Exception
* [DATAMONGO-656] - Potential NullPointerException when debugging in MongoTemplate
** Task
* [DATAMONGO-597] - Website is severely out-of-date
* [DATAMONGO-654] - Release 1.2.1
@@ -663,7 +809,7 @@ Changes in version 1.1.0.M1 (2012-05-07)
* [DATAMONGO-396] - Release 1.1.0.M1.
* [DATAMONGO-432] - Upgrade to Spring Data Commons 1.3.0.RC1
* [DATAMONGO-439] - Add performance tests
Changes in version 1.0.1.RELEASE MongoDB (2012-02-11)
-----------------------------------------------------

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 1.4.0.RELEASE
Spring Data MongoDB 1.5.2
Copyright (c) [2010-2014] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (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,17 +0,0 @@
SPRING DATA MongoDB 1.4.0.RELEASE
---------------------------------
Spring Data MongoDB is released under the terms of the Apache Software License Version 2.0 (see license.txt).
DISTRIBUTION CONTENTS:
The JARs are available in the 'dist' directory, and the source JARs are in the 'src' directory.
The reference manual and javadoc are located in the 'docs' directory.
ADDITIONAL RESOURCES:
Spring Data Homepage: http://projects.spring.io/spring-data
Spring Data Forum: http://forum.spring.io/forum/spring-projects/data/nosql