Compare commits

..

307 Commits

Author SHA1 Message Date
Spring Buildmaster
41dc57c84f DATAMONGO-1106 - Release version 1.7.0.M1 (Fowler M1). 2014-12-01 13:36:32 +01:00
Oliver Gierke
85d1fe1ce6 DATAMONGO-1106 - Prepare 1.7.0.M1 (Fowler M1). 2014-12-01 12:26:52 +01:00
Oliver Gierke
ac6067ad53 DATAMONGO-1106 - Updated changelog. 2014-12-01 12:25:53 +01:00
Thomas Darimont
173a62b5ce DATAMONGO-1085 - Fixed sorting with Querydsl in QueryDslMongoRepository.
We now translate QSort's OrderSpecifiers into appropriate sort criteria.
Previously the OrderSpecifiers were not correctly translated to appropriate property path expressions.

We're now overriding support for findAll(Pageable) and findAll(Sort) to QueryDslMongoRepository to apply special QSort handling.

Original pull request: #236.
2014-12-01 12:09:14 +01:00
Oliver Gierke
cbbafce73d DATAMONGO-1043 - Make sure we dynamically lookup SpEL based collection names for query execution.
Changed SimpleMongoEntityMetadata to keep a reference to the collection entity instead of the eagerly resolved collection name. This is to make sure the name gets re-evaluated for every query execution to support dynamically changing collections defined via SpEL expressions.

Related pull request: #238.
2014-11-28 20:26:23 +01:00
Oliver Gierke
2e74c19995 DATAMONGO-1054 - Polishing.
Tweaked JavaDoc of the APIs to be less specific about implementation internals and rather point to the save(…) methods. Changed SimpleMongoRepository.save(…) methods to inspect the given entity/entities and use the optimized insert(All)-calls if all entities are considered new.

Original pull request: #253.
2014-11-28 18:33:19 +01:00
Thomas Darimont
a212b7566c DATAMONGO-1054 - Add support for fast insertion via MongoRepository.insert(..).
Introduced new insert(..) method variants on MongoRepositories that delegates to MongoTemplate.insert(..). This bypasses ID-population, save event generation and version checking and allows for fast insertion of bulk data.

Original pull request: #253.
2014-11-28 18:33:18 +01:00
Oliver Gierke
08faa52ef4 DATAMONGO-1108 - Performance improvements in BasicMongoPersistentEntity.
BasicMongoPersistentEntity.getCollection() now avoids repeated SpEL-parsing and evaluating in case no SpEL expression is used. Parsing is happening at most once now. Evaluation is skipped entirely if the configured collection String is not or does not contain an expression.
2014-11-28 16:24:25 +01:00
Oliver Gierke
33bc4fffd9 DATAMONGO-1079 - Updated changelog. 2014-11-28 12:06:05 +01:00
Christoph Strobl
eca2108e15 DATAMONGO-1087 - Fix index resolver detecting cycles for partial match.
We now check for presence of a dot path to verify that we’ve detected a cycle.

Original pull request: #240.
2014-11-28 12:03:38 +01:00
Christoph Strobl
dab6034eb9 DATAMONGO-943 - Add support for $position to Update $push $each.
We now support $position on update.push.

Original pull request: #248.
2014-11-28 11:41:51 +01:00
Christoph Strobl
461e7d05d7 DATAMONGO-1092 - Ensure compatibility with MongoDB 2.8.0.rc0 and java driver 2.13.0-rc0.
We updated GroupByResults to allow working with changed data types returned for count and keys and fixed assertion on error message for duplicate keys.
Using java-driver 2.12.x when connecting to an 2.8.0.rc-0 instance is likely to cause trouble with authentication. This is the intended behavior.

2.8.0-rc0 throws error when removing elements from a collection that does not yet exist, which is different to what 2.6.x does.

The java-driver 2.13.0-rc0 works perfectly fine with a 2.6.x Server instance.
We deprecated Index.Duplicates#DROP since it has been removed in MongoDB 2.8

Original pull request: #246.
2014-11-28 11:33:12 +01:00
Oliver Gierke
10c37b101d DATAMONGO-1105 - Added implementation of QueryDslPredicateExecutor.findAll(OrderSpecifier<?>... orders).
Renamed QuerydslRepositorySupportUnitTests to QuerydslRepositorySupportTests as it's an integration test.
2014-11-28 10:37:21 +01:00
Christoph Strobl
81f2c910f7 DATAMONGO-1075 - Containing keyword is now correctly translated for collection properties.
We now inspect the properties type when creating criteria for CONTAINS keyword so that, if the target property is of type String, we use an expression, and if the property is collection like we try to finds an exact match within the collection using $in.

Added support for NotContaining along the way.

Original pull request: #241.
2014-11-27 17:06:39 +01:00
Thomas Darimont
1fd97713c1 DATAMONGO-1093 - Added hashCode() and equals(…) in BasicQuery.
We now have equals(…) and hashCode(…) methods on BasicQuery. Previously we solely relied on Query.hashCode()/equals(…) which didn't consider the fields of BasicQuery.

Introduced equals verifier library to automatically test equals contracts.
Added some additional test cases to BasicQueryUnitTests.

Original pull request: #252.
2014-11-27 16:45:35 +01:00
Oliver Gierke
2d3eeed9ec DATAMONGO-1102 - Added support for Java 8 date/time types.
We're now able to persist and read non-time-zoned JDK 8 date/time types (LocalDate, LocalTime, LocalDateTime) to and from Date instances.
2014-11-27 16:28:36 +01:00
Christoph Strobl
b22eb6f12f DATAMONGO-1101 - Add support for $bit to Update.
We now support bitwise and/or/xor operations for Update.
2014-11-26 11:26:56 +01:00
Mikhail Mikhaylenko
dfb0a2a368 DATAMONGO-1096 - Use null-safe toString representation of query for debug logging.
We now use the null-safe serailizeToJsonSafely to avoid potential RuntimeExceptions during debug query printing in MongoTemplate.

Based on original PR: #247.

Original pull request: #251.
2014-11-26 09:40:30 +01:00
Thomas Darimont
03bcc56429 DATAMONGO-1094 - Fixed ambiguous field mapping error message in BasicMongoPersistentEntity.
Original pull request: #245.
2014-11-25 17:32:16 +01:00
Christoph Strobl
457fda3fc3 DATAMONGO-1097 - Add support for $mul to Update.
We now support multiply on Update allowing to multiply the value of the given key by a multiplier.
2014-11-24 20:38:44 +01:00
Oliver Gierke
54cee64610 DATAMONGO-1100 - Upgrade to new PersistentPropertyAccessor API. 2014-11-20 15:12:25 +01:00
Christoph Strobl
477499248a DATAMONGO-1086 - Mapping fails for collection with two embbeded types that extend a generic abstract.
We now use the type information of the raw property type to check if we need to include _class.
2014-11-20 15:12:25 +01:00
Oliver Gierke
3b70b6aeee DATAMONGO-1078 - Polishing.
Polished test cases. Simplified equals(…)/hashCode() for sample entity and its identifier type.

Original pull request: #239.
2014-11-10 16:38:03 +01:00
Christoph Strobl
163762e99e DATAMONGO-1078 - @Query annotated repository method fails for complex Id when used with Collection type.
Remove object type hint defaulting.
2014-11-10 16:37:56 +01:00
Oliver Gierke
b99833df75 DATAMONGO-1080 - AbstractMongoQuery now refrains from eagerly post-processing the query execution results.
To properly support general post processing of query execution results (in QueryExecutorMethodInterceptor) we need to remove the eager post-processing of query execution results in AbstractMongoQuery.

Removed the usage of the local ConversionService all together.
2014-10-30 11:35:51 +01:00
Thomas Darimont
4be6231426 DATAMONGO-1076 - Avoid resolving lazy-loading proxy for DBRefs during finalize.
We now handle intercepted finalize method invocations by not resolving the proxy. Previously the LazyLoadingProxy tried to resolve the proxy during finalization which could lead to unnecessary database accesses.

Original pull request: #234.
2014-10-29 10:16:12 +01:00
Christoph Strobl
4673e3d511 DATAMONGO-1077 - Fix Update removing $ operator for DBRef.
We now retain the positional parameter "$" when mapping field names for associations.

Orignal pull request: #235.
2014-10-28 14:28:22 +01:00
Christoph Strobl
00e48cc424 DATAMONGO-1050 - Explicitly annotated Field should not be considered Id.
We changed the id resolution to skip properties having an explicit name set via @Field unless they are marked with @Id. This means that

@Field(“id”) String id;

will be stored as “id” within mongodb. Prior to this change the fieldname would have been changed to “_id”.
Added tests to ensure proper field mapping for various "id" field variants.

Original pull request: #225.
2014-10-23 11:39:17 +02:00
Christoph Strobl
f8453825fb DATAMONGO-1072 - Fix annotated query placeholders not replaced correctly.
We now also check field names for potential placeholder matches to ensure those are registered for binding parameters.

Original pull request: #233.
2014-10-22 13:55:50 +02:00
Christoph Strobl
6cda9ab939 DATAMONGO-1068 - Fix getCritieriaObject returns empty DBO when no key defined.
We now check for the presence of a Critieria key.

Original pull request: #232.
2014-10-21 11:36:15 +02:00
Oliver Gierke
831d667896 DATAMONGO-1070 - Fixed a few glitches in DBRef binding for repository query methods.
The QueryMapping for derived repository queries pointing to the identifier of the referenced document. We now reduce the query field's key from reference.id to reference so that the generated DBRef is applied correctly and also take care that the id's are potentially converted to ObjectIds. This is mainly achieved by using the AssociationConverter pulled up from UpdateMapper in ObjectMapper.getMappedKey().

MongoQueryCreator now refrains from translating the field keys as that will fail the QueryMapper to correctly detect id properties.

Fixed DBRef handling for StringBasedMongoQuery which previously didn't parse the DBRef instance created after JSON parsing for placeholders.
2014-10-15 10:13:53 +02:00
Christoph Strobl
17c342895a DATAMONGO-1063 - Fix application of Querydsl'S any().in() throwing Exception.
We now only convert paths that point to either a property or variable.

Original pull request: #230.
2014-10-10 11:35:45 +02:00
Christoph Strobl
6ef518e6a0 DATAMONGO-1053 - Type check is now only performed on explicit language properties.
We now only perform a type check on via @Language explicitly defined language properties. Prior to this change non-String properties named language caused errors on entity validation.

Original pull request: #228.
2014-10-10 11:31:30 +02:00
Oliver Gierke
ddee2fbb12 DATAMONGO-1057 - Polishing.
Slightly tweaked the changes in SlicedExecution to simplify the implementation. We now apply the given pageable but tweak the limit the query uses to peek into the next page.

Original pull request: #226.
2014-10-08 07:06:18 +02:00
Christoph Strobl
6512c2cdfb DATAMONGO-1057 - Fix SliceExecution skipping elements.
We now directly set the offset to use instead of reading it from the used pageable. This asserts that every single element is read from the store.
Prior to this change the altered pageSize lead to an unintended increase of the number of elements to skip.

Original pull request: #226.
2014-10-08 07:06:14 +02:00
Oliver Gierke
0eee05adaa DATAMONGO-1062 - Polishing.
Removed exploded static imports. Updated copyright header.

Original pull request: #229.
2014-10-07 15:32:18 +02:00
Christoph Strobl
17e0154ff3 DATAMONGO-1058 - DBRef should respect explicit field name.
We now use property.getFieldName() for mapping DbRefs. This assures we also capture explicitly defined names set via @Field.

Original pull request: #227.
2014-10-01 10:06:22 +02:00
Thomas Darimont
2780f60c65 DATAMONGO-1062 - Fix failing test in ServerAddressPropertyEditorUnitTests.
The test rejectsAddressConfigWithoutASingleParsableServerAddress fails because the supposedly non-existing hostname "bar" "now" resolves to a real host-address.

The addresses "gugu.nonexistant.example.org, gaga.nonexistant.example.org" shouldn't be resolvable TM.

Original pull request: #229.
2014-09-30 12:55:24 +02:00
Christoph Strobl
7dd3450362 DATAMONGO-1049 - Check for explicitly declared language field.
We now check for an explicitly declared language field for setting language_override within a text index. Therefore the attribute (even if named with the reserved keyword language) has to be explicitly marked with @Language. Prior to this change having:

@Language String lang;
String language;

would have caused trouble when trying to resolve index structures as one cannot set language override to more than one property.

Original pull request: #224.
2014-09-25 12:40:26 +02:00
Oliver Gierke
ca4b2a61b8 DATAMONGO-1046 - After release cleanups. 2014-09-15 14:30:23 +02:00
Oliver Gierke
d2ecd65ca5 DATAMONGO-1046 - After release cleanups. 2014-09-05 14:27:21 +02:00
Spring Buildmaster
03bd49f6c8 DATAMONGO-1046 - Prepare next development iteration. 2014-09-05 03:12:04 -07:00
Spring Buildmaster
51607c5ed8 DATAMONGO-1046 - Release version 1.6.0.RELEASE (Evans GA). 2014-09-05 03:12:02 -07:00
Oliver Gierke
e2cbd3ee28 DATAMONGO-1046 - Prepare 1.6.0.RELEASE (Evans GA). 2014-09-05 11:43:58 +02:00
Oliver Gierke
5944e6b57e DATAMONGO-1046 - Updated changelog. 2014-09-05 09:23:31 +02:00
Oliver Gierke
efd46498ef DATAMONGO-1033 - Updated changelog. 2014-09-05 07:31:54 +02:00
Christoph Strobl
3d705a737f DATAMONGO-1040 - Derived delete should respect collection name.
Adding collection metadata allows to fine grained remove entities from specific collections using derived delete queries.

Original pull request: #223.
2014-09-04 15:47:13 +02:00
Christoph Strobl
996c57bccf DATAMONGO-1039 - Polish db clean hook implementation.
- Refactored internal structure.
- Updated documentation.
- Added some tests

Original pull request: #222.
2014-09-04 11:21:55 +02:00
Oliver Gierke
a31e72ff06 DATAMONGO-1045 - Tweak AspectJ setup in cross-store module to be able to build against Spring 4.1.
Added an aop.xml to only compile explicitly listed aspects in the cross-store module. This is needed as Spring 4.1 includes a new aspect for JavaEE 7 JCache support that has optional dependencies which we don't have in the classpath. Trying to compile all aspects contained in spring-aspects will result in ClassNotFoundExceptions for the aspects with missing dependencies.
2014-09-04 08:51:31 +02:00
Mark Paluch
f07d8fca8c DATAMONGO-1036 - Improved detection of custom implementations for CDI repositories.
Adapted to API changes in CDI extension.

Related ticket: DATACMNS-565.
2014-09-01 13:51:20 +02:00
Christoph Strobl
69dbdee01f DATAMONGO-1038 - Assert Mongo instances cleaned up properly after test runs.
Add JUnit rule and RunListener taking care of clean up task.

Original pull request: #221.
2014-08-27 11:12:39 +02:00
Oliver Gierke
dedb9f3dc0 DATAMONGO-1034 - Explicitly reject incompatible types in MappingMongoConverter.
Improved the exception message that is occurs if the source document contains a BasicDBList but has to be converted into a complex object. We now explicitly hint to use a custom Converter to manually.

Improved toString() method on ObjectPath to create more helpful output.
2014-08-26 20:07:46 +02:00
Oliver Gierke
7d69b840fe DATAMONGO-1030 - Projections now work on single-entity query method executions.
We now correctly forward the domain type collection to the query executing a query for a projection type.
2014-08-26 15:16:18 +02:00
Christoph Strobl
4eaef300cb DATAMONGO-1025 - Fix creation of nested named index.
Deprecated collection attribute for @Indexed, @CompoundIndex, @GeoSpatialIndexed. Removed deprecated attribute `expireAfterSeconds` from @CompoundIndex.

Original pull request: #219.
2014-08-26 14:33:47 +02:00
Christoph Strobl
ec1a6b5edd DATAMONGO-1025 - Fix creation of nested named index.
We new prefix explicitly named indexes on nested types (eg. for embedded properties) with the path pointing to the property. This avoids errors having equally named index definitions on different paths pointing to the same type within one collection.

Along the way we harmonized index naming for geospatial index definitions where only the properties field name was taken into account where it should have been the full property path.

Original pull request: #219.
2014-08-26 14:33:47 +02:00
Oliver Gierke
adc5485c09 DATAMONGO-1032 - Polished Asciidoctor documentation. 2014-08-26 14:24:51 +02:00
Oliver Gierke
f622b2916d DATAMONGO-1021 - After release cleanups. 2014-08-13 16:32:42 +02:00
Spring Buildmaster
26be0cf948 DATAMONGO-1021 - Prepare next development iteration. 2014-08-13 07:02:43 -07:00
Spring Buildmaster
e27c01fe5b DATAMONGO-1021 - Release version 1.6.0.RC1 (Evans RC1). 2014-08-13 07:02:41 -07:00
Oliver Gierke
d639e58fb9 DATAMONGO-1021 - Prepare 1.6.0.RC1 (Evans RC1). 2014-08-13 15:37:48 +02:00
Oliver Gierke
0195c2cb48 DATAMONGO-1021 - Updated changelog. 2014-08-13 15:37:44 +02:00
Oliver Gierke
068e2ec49b DATAMONGO-1024 - Upgraded to MongoDB Java driver 2.12.3. 2014-08-13 15:36:08 +02:00
Christoph Strobl
a9306b99ec DATAMONGO-957 - Add support for query modifiers.
Using Meta allows to the define $comment, $maxScan, $maxTimeMS and $snapshot on query. When executed we add the meta information to the cursor in use.

We’ve introduced the @Meta annotation that allows to the define $comment, $maxScan, $maxTimeMS and $snapshot on a repository finder method.
Added tests to verify proper invocation of template methods
Use DBCursor.copy() for CursorPreparer.

Original pull request: #216.
2014-08-13 14:56:10 +02:00
Thomas Darimont
3597194742 DATAMONGO-1012 - Improved identifier initialization on DBRef proxies.
Identifier initalization is now only triggered if field access is used. Before that the id initialization would've resolved the proxy eagerly as the getter access performed by the BeanWrapper would've been intercepted by the proxy and is indistinguishable from a normal method call. This would've rendered the entire use case to create proxies ad absurdum.

Added test case to check for non-initialization in the property access scenario.
2014-08-13 14:34:38 +02:00
Oliver Gierke
6f06ccec8e DATAMONGO-1012 - Identifier initialization for lazy DBRef proxies with field access.
We now initialize the ID property for proxies created for lazily initialized DBRefs. This will allow the lookup of ID properties for types that use field access without initializing the entire proxy.
2014-08-13 14:34:15 +02:00
Oliver Gierke
6fe7f220f9 DATAMONGO-1007 - Updated changelog. 2014-08-13 10:56:02 +02:00
Christoph Strobl
45e70d493d DATAMONGO-1016 - Remove deprecations in geospatial area.
Removed:
 - Box
 - Circle
 - CustomMetric
 - Distance
 - GeoPage
 - GeoResult
 - GeoResults
 - Metric
 - Metrics
 - Point
 - Polygon
 - Shape

Updated api doc.
Removed deprecation warnings.
2014-08-13 09:52:02 +02:00
Thomas Darimont
ce71ab83f2 DATAMONGO-1020 - LimitOperation is now a public class.
Original pull request: #218.
2014-08-12 12:30:41 +02:00
Oliver Gierke
bf85d8facd DATAMONGO-1005 - Polishing introduction of ObjectPath.
Simplified implementation of ObjectPath to use a static root instance and hand the path further down until final resolution in MappingMongoConverter.readValue(…). This removes a bit of boxing and unboxing code both in ObjectPath and the converter.

Introduced ObjectPath.getPathItem(…) to internalize the iteration to find a potentially already resolved object.

Renamed parameters and fields of type ObjectPath to path consistently. Removed obsolete method in MappingMongoConverter.

Original pull request: #209.
2014-08-12 08:12:31 +02:00
Thomas Darimont
c5ff7cdb2b DATAMONGO-1005 - Improve cycle-detection for DbRef's.
Introduced ObjectPath that collects the target objects while converting a DBObject to a domain object. If we detect that a potentially nested DBRef points to an object that is already under construction we simply return a reference to that object in order to avoid StackOverFlowErrors.

Original pull request: #209.
2014-08-12 08:10:47 +02:00
Mark Paluch
f9ccf4f532 DATAMONGO-1017 - Add support for custom implementations in CDI repositories.
Original pull request: #215.
2014-08-11 07:47:57 +02:00
Greg Turnquist
ab731f40a7 DATAMONGO-1019 - Corrected examples in reference documentation.
Examples were not properly converted. One table got dropped, so I added it back. Fix IMPORTANT notes.

Original pull requests: #214.
2014-08-10 16:04:45 +02:00
Oliver Gierke
d8434fffa8 DATAMONGO-1015 - Fixed link to Spring Data Commons reference docs. 2014-08-10 15:55:13 +02:00
Christoph Strobl
151b1d4510 DATAMONGO-973 - Add support for deriving full-text queries.
Added support to execute full-text queries on repositories. Query methods now can have a parameter of type TextCriteria which will be triggering a text search clause for the property annotated with @TestScore.

Retrieving document score and sorting by score is only possible if the entity holds a property annotated with @TextScore. If present, any find execution will be enriched so that it asserts loading of the according { $meta : textScore } field. The sort object will only be mapped in case the existing sort property already exists - in that case we replace the existing expression for the property with its $meta representation.

This allows for example the following:

TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("term");

repository.findAllBy(criteria, new Sort("score"));
repository.findAllBy(criteria, new PageRequest(0, 10, Direction.DESC, "score"));
repository.findByFooOrderByScoreDesc("foo", criteria);

For more details and examples see the "Full text search queries" section in the reference manual.
2014-08-06 22:25:38 +02:00
Greg Turnquist
168cf3e1f6 DATAMONGO-1015 - Migrate reference documentation from Docbook to Asciidoctor. 2014-08-06 21:38:46 +02:00
Oliver Gierke
52dab0fa20 DATAMONGO-1008 - Polishing.
Slightly changed the implementation of the 2dsphere check, Minor refactorings in the test case.

Original pull request: #210.
2014-07-31 17:23:19 +02:00
Christoph Strobl
9257bab06e DATAMONGO-1008 - DefaultIndexOperations no considers 2dsphere, too.
We now also check for 2dsphere when inspecting index keys and create an geo IndexField in that case.

Original pull request: #210.
2014-07-31 17:23:19 +02:00
Oliver Gierke
27f0a6f27a DATAMONGO-1008 - Added repository type based checks to strict matching algorithm.
Repositories extending MongoRepository are now considered strict matches as well.

Related ticket: DATACMNS-526.
2014-07-31 16:20:26 +02:00
Oliver Gierke
5bedbef2f2 DATAMONGO-1009 - Adapt to new multi-store configuration detection.
We now consider repositories managing domain types annotated with @Document MongoDB specific ones.

Related ticket: DATACMNS-526.
2014-07-28 20:15:40 +02:00
Christoph Strobl
51e7be8aa0 DATAMONGO-1001 - Renamed LazyLoadingProxy.initialize() to getTarget().
Original pull request: #208.
2014-07-24 13:29:27 +02:00
Christoph Strobl
6c85bb39a3 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:28:36 +02:00
Oliver Gierke
07f7247707 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:36:15 +02:00
Thomas Darimont
f669711670 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:15:18 +02:00
Oliver Gierke
5f3671f349 DATAMONGO-996 - Fixed boundary detection in pagination.
The fix for DATAMONGO-950 introduced a tiny glitch so that retrieving pages after the first one was broken in the repository query execution. We now correctly use the previously detected number of elements to detect whether the Pageable given is out of scope.

Related ticket: DATAMONGO-950.
2014-07-18 19:01:44 +02:00
Thomas Darimont
1335cb699b 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:46 +02:00
Oliver Gierke
84414b87c0 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:21 +02:00
Thomas Darimont
a1ecd4a501 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:21 +02:00
Thomas Darimont
d7e6f2ee41 DATAMONGO-989 - MatchOperation should accept CriteriaDefinition.
We replaced the constructor that accepted a Criteria with one that accepts a CriteriaDefinition to not force clients to extends Criteria. 
Original pull request: #206.
2014-07-17 09:21:29 +02:00
Oliver Gierke
04870fb8b3 DATAMONGO-991 - Adapted to deprecation removals in Spring Data Commons.
Related ticket: DATACMNS-469.
2014-07-16 12:04:10 +02:00
Oliver Gierke
9d196b78f7 DATAMONGO-981 - After release cleanups. 2014-07-10 20:44:20 +02:00
Spring Buildmaster
4229525928 DATAMONGO-981 - Prepare next development iteration. 2014-07-10 10:38:58 -07:00
Spring Buildmaster
d861fecdb8 DATAMONGO-981 - Release version 1.6.0.M1. 2014-07-10 10:38:55 -07:00
Oliver Gierke
f280e23095 DATAMONGO-981 - Prepare 1.6.0.M1 (Evans M1). 2014-07-10 19:28:34 +02:00
Oliver Gierke
ed0e1d92c0 DATAMONGO-981 - Updated changelog. 2014-07-10 17:14:24 +02:00
Christoph Strobl
d82fc22659 DATAMONGO-944 - Add support for $currentDate to Update.
Added currentDate and currentTimestamp to Update.

Original pull request: #200.
2014-07-10 15:13:59 +02:00
Thomas Darimont
6616d6788c DATAMONGO-975 - Add support for extracting date/time components from a field projection.
We added some extract-methods to ProjectionOperationBuilder to be able to extract date / time components from projected fields.

Original pull request: #204.
2014-07-10 12:45:17 +02:00
Christoph Strobl
322a7cf033 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:48:02 +02:00
Christoph Strobl
0f487c10ba DATAMONGO-983 - Remove links to forum.spring.io.
Replace forum links with those to stackoverflow.

Original Pull Request: #205
2014-07-09 21:21:04 +02:00
Christoph Strobl
11417144bd DATAMONGO-980 - Use meta annotations from commons for @Score.
We now use Spring Data Commons' @ReadOnlyProperty to meta-annotate @Score to mark it as read-only property.

Original pull request: #201.
Related tickets: DATACMNS-534.
2014-07-09 14:51:21 +02:00
Christoph Strobl
dafc59b163 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:04 +02:00
Oliver Gierke
566f9a80c4 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:36:01 +02:00
Christoph Strobl
89a42c5648 DATAMONGO-976 - Add support for reading $meta projection on textScore into document.
We introduced @TextScore that can be used to mark a property to take { $meta : “textScore” }. In contrast to @Transient the value will be considered when reading documents.
The value can and will only get picked up if the score field is retrieved from the store.

Original pull request: #198.
2014-07-07 11:43:43 +02:00
Christoph Strobl
83ffbb00e8 DATAMONGO-850 - Add support for full text search via $text
Using TextQuery and TextCriteria allows creation of queries using $text $search.

{ $meta : “textScore” } can be included using TextQuery.includeScore. As the fieldname used for textScore must not be fixed to “score” one can use the overload taking the fieldname to override the default.

Original pull request: #198.
2014-07-07 11:39:47 +02:00
Christoph Strobl
84913cecab DATAMONGO-937 - Add support for creating text index.
We now support creating text index based on information gathered on domain types.

Using @TextIndexed marks properties to be considered for the full text index. Use the weight attribute to influence document scoring during search operations.

Please note that using @TextIndexed on entity properties forces all properties of any sub document to be considered as part of the text index. Any set weight will in that case be propagated to the siblings taking the most recent weight information into account, which means that a the weight attribute can be overridden for properties in sub documents.

The setting the index default language can be done via @Document(language) while @Language can be used to define the language_override field.

As text search is disabled by default for mongodb 2.4 we use a jUnit ClassRule to restrict integration tests potentially creating text index (as the entities for testing are found in the classpath) to only be executed in when a 2.6+ mongodb server is present.

For usage hints please see section 6.3.4 (Text Indexes) of reference manual.

Original pull request: #198.
2014-07-07 11:30:08 +02:00
Christoph Strobl
998bb09a92 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:16:57 +02:00
Oliver Gierke
cd68a8db54 DATAMONGO-977 - Removed reflective detection of Spring 4 in DBRef proxy creation.
After the Spring 4 upgrade we can now directly use the Objenesis infrastructure of it.
2014-07-02 09:23:19 +02:00
Oliver Gierke
df8477d180 DATAMONGO-955 - Updated changelog. 2014-06-30 10:57:12 +02:00
Christoph Strobl
244fbae0ce 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:25:26 +02:00
Oliver Gierke
19e08a52c0 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:12:50 +02:00
Thomas Darimont
6389b1bb73 DATAMONGO-954 - Add support for system variables in aggregation operations.
Add assumes for appropriate MongoDB version.
2014-06-26 14:19:24 +02:00
Thomas Darimont
cadcbf6106 DATAMONGO-954 - Add support for system variables in aggregation operations.
We now support referring to system variables like for instance $$ROOT or $$CURRENT from within aggregation framework pipeline projection and group expressions.

Original pull request: #190.
2014-06-25 15:53:44 +02:00
Christoph Strobl
118f007ca6 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:12:32 +02:00
Christoph Strobl
cbb32bd29d DATAMONGO-950 - Add support for limiting the query result in the query derivation mechanism.
When deriving the query from its method name we check for the limit set on the PartTree to pass this on to the created query. PagedExecution not takes the overall limit into account, skips a query execution entirely (if the Pageable is out of scope completely) or alters the query limits accordingly.

Note, that there has been significant rework of this compared to the pull request to avoid new API in Query and extensive changes in MongoTemplate's QueryCursorPreparer.

Original pull request: #191.
2014-06-25 14:58:59 +02:00
Thomas Darimont
9858dcd740 DATAMONGO-960 - Allow to pass options to the Aggregation Pipeline.
We introduced AggregationOptions abstraction to conveniently construct option objects that can be handed to an Aggregation via the new Aggregation.withOptions(...) factory method. For more details, see the Builder class' JavaDoc.

Note that we exposed the "rawResults" in AggregationResults and put a null guard in MongoTemplate aggregate in order to support the "explain" option.

Original pull request: #195.
2014-06-25 13:25:57 +02:00
Thomas Darimont
1fb76d135b 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:30 +02:00
Oliver Gierke
bb62c8b2f1 DATAMONGO-958 - Switch to FieldNamingStrategy SPI in Spring Data Commons. 2014-06-20 21:27:24 +02:00
Christoph Strobl
2cbe7bf885 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:14 +02:00
Christoph Strobl
6043f6b74d 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:30 +02:00
Christoph Strobl
ef1366592a 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:27:57 +02:00
Thomas Darimont
01cf9fb8f3 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:27:54 +02:00
Thomas Darimont
285c406d5d 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:06 +02:00
Oliver Gierke
ad29e52a57 DATAMONGO-936 - After release cleanups. 2014-05-20 19:54:03 +02:00
Spring Buildmaster
3cfe207c83 DATAMONGO-936 - Prepare next development iteration. 2014-05-20 09:35:38 -07: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
Spring Buildmaster
22933e4493 DATAMONGO-859 - Release version 1.5.0.M1. 2014-03-31 08:04:03 -07:00
Thomas Darimont
40aa6bbdd5 DATAMONGO-859 - Prepare release 1.5 M1.
Updated readme.md and mongodb.xml to reflect recent version. Updated Spring Data Commons and Spring Data Build versions in pom.xml. Update pom.xml to use release repository. Updated docbkx to use recent Spring Data Commons version. Updated changelog to reflect changes and releases.

Original pull request: #161.
2014-03-31 16:47:15 +02:00
Christoph Strobl
5e43f5846a DATAMONGO-471 - Add support for $each when using $addToSet.
Additionally to Update.addToSet(String, Object) the method 'addToSet(String)' has been introduced, returning a builder to allow the creation of $addToSet command for either single value, or multiple values using $each.

Using value:
new Update().addToSet("key").value("spring");

Using each:
new Update().addToSet("key").each("spring", "data", "mongodb");

Original Pull Request: #157.
2014-03-31 15:25:14 +02:00
Thomas Darimont
2cfd4781bc DATAMONGO-884 - Improved handling for Object methods in LazyLoadingInterceptor.
We now handle invocations of equals(…)/hashCode()/toString()  methods that are not overridden with custom proxy aware logic. This avoids potentially NullPointerExceptions and makes it easier to debug code that deals with proxies (due to a proper toString representation of a proxy).

Original pull request: #158.
2014-03-31 15:19:30 +02:00
Thomas Darimont
031ab0c07b DATACMNS-482 - Fix compiler error due to changes in SD Commons.
Fixed a compiler that got introduced by making the geospatial types in Spring Data Commons serializable.
2014-03-31 15:14:08 +02:00
Thomas Darimont
10f69f6623 DATAMONGO-884 - Fix potential NullPointerException for lazy DBRefs.
We now initialize the proxy in case an Object-method is called that is overridden in the traget class. Removed the additional check for initialization and to-DBRef-methods as they're repeated in the target method.

Original pull requests: #152, #153.
2014-03-27 17:57:39 +01:00
Thomas Darimont
d7b03915a7 DATAMONGO-858 - Revised rendering of geo spatial structures.
Switched back to the old style of rendering (as in 1.4.x) of DBObjects when they are used as values in persistent domain objects, adjusted the GeoConverters accordingly. In order to render geo structures correctly when they are used within a query we now wrap them in a GeoCommand that triggers a different Shape rendering.

We now render the metric that was used in the Distance definition of the radius of a Circle or Sphere.
2014-03-27 13:18:55 +01:00
Oliver Gierke
ed55d48a53 DATAMONGO-858 - Polishing.
Moved to use the newly introduced geo types from Spring Data Commons. Adde deprecation warning suppression everywhere else.

Adapted Sonargraph architecture description file and split up namespace registration into repository specific stuff and everything else.
2014-03-27 13:18:55 +01:00
Thomas Darimont
d5ed4e0ac2 DATAMONGO-858 - Add support for common geospatial structures.
Backed the geo spatial structures of SD MongoDB by the new geo spatial structures in SD commons. Deprecated the MongoDB geo spatial types to make users aware that we're going to remove them in one of the following development iterations. Added custom conversions for basic geo spatial types.

We deliberately choose not to let Circle extends CMNS geo.Circle since it would break clients that use the legacy Circle API (getRadius() returns a Distance in CMNS where as it returns a plain double in Mongo).
2014-03-27 13:18:55 +01:00
Oliver Gierke
75194730e9 DATAMONGO-887 - Added unit tests to verify TreeMaps can be converted. 2014-03-27 08:58:53 +01:00
Oliver Gierke
a09183d2eb DATAMONGO-880 - Minor polishing in lazy-loading area.
Took the change to add @since tags to the types introduced for lazy loading. Polished JavaDoc where necessary. Removed methods solely existing for testing purposes and use reflection in tests to minimize the API being published.
2014-03-20 09:27:37 +01:00
Thomas Darimont
45dd3cd988 DATAMONGO-880 - Improved handling of persistence of lazy-loaded DBRefs.
Added LazyLoadingProxy interface that will be implemented by every LazyLoading-proxy that is created by the DefaultDbRefResolver. Clients can now cast those proxies to this interface and call it's methods initialize a proxy explicitly or to get the referenced DBRef if possible.

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

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

Original pull request: #151.
2014-03-20 09:26:08 +01:00
Oliver Gierke
b24e34c360 DATAMONGO-883 - Adapted to changes in auditing config in Spring Data MongoDB. 2014-03-18 20:10:43 +01:00
Oliver Gierke
fa9b5efdab DATAMONGO-882 - Adapted to removal of obsolete generics in BeanWrapper. 2014-03-18 20:08:22 +01:00
Oliver Gierke
8f2ced8ada DATAMONGO-881 - Allow custom conversions to override default conversions.
User provided converters are now registered *after* the default converters to make sure they enjoy precedence over the default ones. 

This is achieved by inverting the order of converters after the conversions have been registered. This is necessary as the registration order for convertible pairs is different from the one of the converters. For the pairs, earlier registered instances take precedence, while for the actual converter instances, instances registered later trump ones registered before.
2014-03-18 09:32:28 +01:00
Oliver Gierke
ff92cf1429 DATAMONGO-566 - Polishing.
Inlined a few methods to reduce the number of indirections. Added a bit of missing JavaDoc here and there. StringBasedMongoQuery now prevents a manually defined query from being marked as both count and delete query.

Polished test cases a little.

Original pull request: #147.
2014-03-17 17:51:54 +01:00
Christoph Strobl
ba48290a3e DATAMONGO-566 - Add support for derived delete-by queries.
Using keywords remove or delete in derived query, or setting @Query(delete=true) removes documents matching the query. If the return type is assignable to Number, the total number of affected documents is returned. In case the return type is collection like the query is executed against the store in first place. All documents included in the resulting collection are deleted in a subsequent call.

Additionally findAllAndRemove(…) methods have been added to MongoTemplate.

Original pull request: #147.
2014-03-17 17:51:08 +01:00
Oliver Gierke
70e5efd0d9 DATAMONGO-877 - Added guard against null-package in AbstractMappingConfiguration.
AbstractMappingConfiguration.getMappingBasePackage() now quards against a null package returned for the configuration class. This can happen if the class resides in the default package.
2014-03-10 12:47:26 +01:00
Oliver Gierke
4eae229bff DATAMONGO-876 - Adapt to API changes introduced for better property access config.
Adapted usage of BeanWrapper as the property access is now solely defined via the PersistentProperty. Adapted MongoPersistentEntityIndexCreator to lookup annotations via PersistentProperty instead of the backing field. Removed code from BasicMongoPersistentProperty which is now already implemented in the Spring Data Commons types.
2014-03-07 14:38:23 +01:00
Oliver Gierke
47f0607c49 DATAMONGO-809 - Polishing.
Added ticket references to test cases in GridFsTemplateIntegratinoTests.
2014-03-07 08:30:43 +01:00
Martin Baumgartner
753e794194 DATAMONGO-809 - Filename is now optional when storing files to GridFS.
Added method overloads to GridFsOperations and GridFsTemplate to store files without a filename given.

Original pull request: #119.
2014-03-07 08:30:43 +01:00
Thomas Darimont
d27bec8ed5 DATAMONGO-773 - Verify that @DBRef fields can be included in query.
Added test cases to verify that projection search with included @DBRef fields works as expected.

Original pull request: #142.
2014-03-06 13:34:17 +01:00
Christoph Strobl
c63f7f75dc DATAMONGO-868 - MongoTemplate.findAndModify(…) increases version if not handled manually.
MongoTemplate.findAndModify(…) increments the version property in case it's not manually set in the Update object given.

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

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

Original Pull Request: #139.
2014-03-06 11:22:39 +01:00
Oliver Gierke
a0c6b9aa64 DATAMONGO-843 - Register default MongoMappingContext for auditing.
The MongoAuditingRegistrar now also register a fallback MongoMappingContext in case none is present in the BeanDefinitionRegistry.
2014-03-06 09:10:31 +01:00
Thomas Darimont
9370c1ee01 DATAMONGO-843 - Improvements in auditing configuration.
Repositories now declare a fallback MappingContext in case none is configured explicitly to make sure @EnableMongoAuditing also works without an explicit MappingContext bean defined.

AuditingEntityListener is now referring to the IsNewAwareAuditingHandler via an intermediate ObjectFactory to prevent the downstream dependencies from being instantiated eagerly at listener init time.

This is to prevent circular initialization dependencies as Spring accesses ApplicationEventListener beans very early in the container lifecycle to check whether they might be interested in a certain even and just dropped immediately afterwards.

Changed BeanNames.MAPPING_CONTEXT constant to mongoMappingContext to let the XML configuration be consistent with AbstractMongoConfiguration.mongoMappingContext().
2014-03-05 19:50:27 +01:00
Oliver Gierke
2839e9491f DATAMONGO-871 - Add support for arrays as query method return types.
Changed AbstractMongoQuery to potentially convert all query execution results using the DefaultConversionService in case the query result doesn't match the expected return value.

This allows arrays to be returned for collection queries as the conversion service cam transparently convert between collections and arrays.
2014-03-05 10:03:09 +01:00
Oliver Gierke
6963f9e07a DATAMONGO-870 - Added support for sliced query execution.
Added support for Slice as return type for query methods. The execution will expand the requested page size by one to read one more element than actually requested. If that additional element is returned, it will considered to be an indicator for whether a next slice is available.

Related issues: DATACMNS-397.
2014-03-04 17:37:26 +01:00
Thomas Darimont
8dd08a36a0 DATAMONGO-865 - Adjust test dependencies to avoid ClassNotFoundException during test runs.
Added jul-to-slf4j dependency to avoid exceptions being logged during test runs.

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

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

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

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

Original pull request: #132.
2014-02-27 16:55:54 +01:00
Oliver Gierke
621b299f6f DATAMONGO-833 - Add support for reading EnumSets and EnumMaps.
Switched to use Spring Data Commons' CollectionFactory that is capable of creating EnumSets and EnumMaps. Added unit test inspired by pull request #113 for EnumSets and an additional one for EnumMaps.

Slightly refactored the algorithm for reading maps to prevent repeated type lookups.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fixed newly introduced test cases in MongoTemplateTests.

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

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

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

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

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

Original pull request: #116.
2014-02-10 09:59:15 +01:00
Oliver Gierke
3d4569be14 DATAMONGO-826 - Remove obsolete milestone repository. 2014-02-09 14:33:27 +01:00
Spring Buildmaster
57455c4a26 DATAMONGO-826 - Prepare next development iteration. 2014-01-29 06:04:53 -08:00
Spring Buildmaster
f9e20d12b2 DATAMONGO-826 - Release version 1.4.0.RC1. 2014-01-29 06:04:50 -08:00
Thomas Darimont
4d6152c65e DATAMONGO-826 - Prepare release 1.4.0.RC1.
Updated project metadata (changelog, notice, readme) and bumped versions.
Fixed broken links in the documentation.
2014-01-29 14:42:52 +01:00
Thomas Darimont
d81cc53c12 DATAMONGO-837 - Upgrade MongoDB java driver to 2.11.4.
Upgraded the MongoDB java driver from 2.11.3 (current) to the recommended bug fix version 2.11.4.
2014-01-29 13:27:46 +01:00
Oliver Gierke
af4b84ea43 DATAMONGO-835 - Code cleanups. 2014-01-28 12:46:36 +01:00
Thomas Darimont
f9110828bc DATAMONGO-407 - Fixed query mapping for updates using collection references.
When an update clause contained a collection element reference (….$.…) we failed to write the type information of the target value object as the key was not translated into a correct property path correctly. We now strip the reference literals and re-apply them when the mapped key is generated.
2014-01-27 19:39:00 +01:00
Christoph Strobl
f301837be5 DATAMONGO-807 - findAndModify(…) now retains type information.
Using findAndUpdate(…) did not retain type information when used to update a whole nested type instead of single fields within the type. We now use the UpdateMapper instead of QueryMapper in doFindAndModify(…).

Original pull request: #110.
2014-01-27 18:12:44 +01:00
Martin Baumgartner
4d29d937eb DATAMONGO-823 - Add bucket attribute to <mongo:gridFsTemplate />.
Original pull request: #114.
2014-01-25 22:05:20 +01:00
Thomas Darimont
86c11bc614 DATAMONGO-790 - Ensure compatibility with Spring Framework 4.0.
The GenericApplicationContext doesn't refresh automatically in Spring 4.x we thus we have to call refresh manually. Also the test for the custom application listener registration fails on 4.0, so I adapted it to run on both versions.
2014-01-24 18:53:42 +01:00
Oliver Gierke
be34b4e503 DATAMONGO-822 - Enable CDI repositories to be instantiated eagerly.
From the CDI extension we now use the callback newly introduced in Spring Data Commons to enable it to trigger eager initialization.

See also: DATACMNS-416.
2014-01-24 18:53:35 +01:00
Thomas Darimont
ebfa2c5689 DATAMONGO-686 - Fixed potential race-condition in QueryMapper.
We now create a new a new DBObject in QueryMapper#getMappedValue() instead of replacing them in the original objects in order to prevent the original query object being manipulated.

Added test case to verify that the original DBObject is not manipulated.

Original pull request: #111.
2014-01-23 13:05:31 +01:00
Oliver Gierke
b245ef2d9e DATAMONGO-826 - Polished poms.
Removed Spring dependency versions and fixed repository declarations.
2014-01-23 12:57:41 +01:00
Christoph Strobl
5ef40d54bc DATAMONGO-811 - UpdateFirst and updateMulti should increase version.
Version of documents are increased when updated via MongoTemplate.updateFirst and MongoTemplate.updateMulti just as it is done when calling MongoTemplate.save(...).

Original pull request #109
2014-01-22 11:03:23 +01:00
Laurent Canet
c679dba438 DATAMONGO-778 - Improved support for geospatial indexing with @GeoSpatialIndexed.
We now support to create geospatial indices of type 2D sphere and geoHaystack using the @GeospatialIndexed annotation on fields.

Original pull request #82, #104.
2014-01-14 15:18:36 +01:00
Thomas Darimont
fd6e4000b5 DATAMONGO-816 - Improve query handling in MongoTemplate.executeQuery().
We now process the given query with the queryMapper before passing it on to the executeQueryInternal(…) in order to deal with potentially required query modifications, e.g. enum value conversions.

Original pull request: #108.
2014-01-14 14:56:00 +01:00
Thomas Darimont
c12a27a8f8 DATAMONGO-805 - Excluding DBRef field in a query causes a MappingException.
Previously we tried to convert all DBRef associations into appropriate DBRef structures even if they were to be ignored. We now ignore excluded properties in DBRef associations correctly.

Original pull request: #102.
2014-01-14 14:32:10 +01:00
Oliver Gierke
df2184f204 DATAMONGO-824 - Added contribution guidelines.
Linked to the actual contribution guidelines maintained in the Spring Data Build project.
2014-01-14 13:43:37 +01:00
Oliver Gierke
e9c8644d23 DATACMNS-414 - Adapted to removed generics in AuditingHandler API. 2014-01-12 20:05:45 +01:00
Thomas Darimont
c730b8f479 DATAMONGO-813 - Improve handling for non-existing resources in GridFSTemplate.
We now return null for a non-existing resource instead of throwing a NPE.

Original pull request: #106.
2013-12-13 15:19:01 +01:00
Thomas Darimont
f3b31fc467 DATAMONGO-808 - Improve ServerAddressPropertyEditor to support IPv6 addresses.
Improved parsing of ServerAddress to be able to handle IPv6 addresses correctly. We now use the actor ServerAddress(InetAddress) to be able to pass an IPv6 address. The constructor which takes a String as the hostname can't deal with IPv6 addresses directly because it tries to extract a port at the wrong location of such an address.

This change should not change the behavior too much, since the constructor ServerAddress(String, int) already calls InetAddress.getByName(...) internally.

Original pull request: #103.
2013-12-13 14:59:12 +01:00
Oliver Gierke
f778b2554c DATAMONGO-799 - Refer to next version of build parent and Spring Data Commons. 2013-12-09 17:24:42 +01:00
Oliver Gierke
9d292f64b9 DATAMONGO-806 - Fixed invalid rendering of id field references.
Tweaked the rendering of projection operations to always use the field based reference lookup to make sure the reference gets rendered aliased. Moved value calculation logic into FieldReference.

Original pull request: #101.
2013-12-09 16:42:29 +01:00
Thomas Darimont
8ab038f83c DATAMONGO-806 - No property _id found for type com.entity.User.
Added test case to reproduce the issue.
2013-12-09 16:42:29 +01:00
Martin Baumgartner
689552c28e DATAMONGO-726 - Fixed classname references in namespace XSDs.
Original pull request: #100.
2013-12-09 09:59:51 +01:00
Thomas Darimont
9ea9912b23 DATAMONGO-799 - Fix failing test in MongoTemplateTests on MongoDB 2.5.x.
Generalized exception message matching to reflect the changed exception message in MongoDB 2.5.x that also works with previous versions of MongoDB.

Original pull request: #97.
2013-12-04 12:19:12 +01:00
Thomas Darimont
a952ce5d2b DATAMONGO-804 - Fix default annotation attribute value for repositoryImplementationPostfix().
Changed repositoryImplementationPostfix() in EnableMongoRepositories to "Impl" to be consistent with other EnableXXXRepositories annotations. Note that this change is of rather documenting nature, as the defaulting of the configuration is applied in DefaultRepositoryConfiguration.getImplementationPostfix() in Spring Data Commons.

Original pull request: #99.
2013-12-04 12:13:14 +01:00
Oliver Gierke
b88d960893 DATAMONGO-802 - Changed return type of AbstractMongoConfiguration.mongoDbFactroy().
Changed the return type of said method to MongoDbFactory to allow subclasses to return a completely different implementation class.
2013-12-04 12:13:01 +01:00
Spring Buildmaster
e44d1f5f9a DATAMONGO-799 - Prepare next development iteration. 2013-11-19 04:19:12 -08:00
Spring Buildmaster
2b5e2361a8 DATAMONGO-799 - Release version 1.4.0.M1. 2013-11-19 04:19:10 -08:00
Thomas Darimont
5737f2d19d DATAMONGO-801 - Prepare release 1.4.0 M1.
Updated project metadata and bumped versions.
2013-11-19 13:12:06 +01:00
Oliver Gierke
60494a6904 DATAMONGO-800 - Improved AuditingIntegrationTests.
Added a tiny Thread.sleep(…) to make sure the assertion works on fast machines. If the operations after the first step all happen within a millisecond, it will fail.
2013-11-18 13:50:42 +01:00
Oliver Gierke
ceb561e3e4 DATAMONGO-792 - Polishing of JavaConfig support for auditing.
Original pull request: #94.
2013-11-12 11:57:25 +01:00
Thomas Darimont
e2d0220cea DATAMONGO-792 - Add support to configure auditing via JavaConfig.
Introduces the necessary infrastructure in form of MongoAuditingRegistrar to configure auditing with MongoDB via Annotation config. The MongoDB auditing feature can be enabled by annotating a configuration class with the EnableMongoAuditing annotation. Added section to reference documentation.

Original pull request: #94.
2013-11-12 11:50:46 +01:00
Oliver Gierke
ea33e8b8c6 DATAMONGO-348 - Improved interceptor for lazy-loaded DBRefs.
We're now routing calls to methods declared on Object into the proxy to not accidentally resolve the lazy loading proxy on access of methods like toString() etc.
2013-11-08 19:19:16 +01:00
Oliver Gierke
506b6a2e85 DATAMONGO-795 - More predictable behavior in CustomConversions.
The target type lookup previously was unpredictable in cases two converters were registered for the same source type. We now use LinkedHashMaps to register the converters and also make sure that we prefer manually registered converters over the default ones.

Related pull request: #96.
2013-11-07 15:11:50 +01:00
Thomas Darimont
7c0eee9e09 DATAMONGO-789 - Support login via different authentication database.
MongoDbUtils now supports to perform the authentication against a dedicated authenticationDatabase - if no authenticationDatabase is given explicitly then the given regular database will be used. The authentication database can be configured via the authentication-dbname attribute of the db-factory element in xml config or by overriding the getAuthenticationDatabaseName() method of AbstractMongoConfiguration.

Original pull request: #92.
2013-11-06 11:59:48 +01:00
Thomas Darimont
332e5eb715 DATAMONGO-791 - Added newAggregation(…) overloads to accept a List.
Aggregations can now be constructed from a list of AggregateOperations. This simplifies the usage in cases where one has to conditionally in- or exclude AggregateOperations from an AggregationPipeline.

Original pull request: #93.
2013-11-06 11:00:31 +01:00
Oliver Gierke
39ee9b56e2 DATAMONGO-793 - Adapt test cases to new initialization model of repositories.
Moved tests for nested repositories to a separate package to prevent initialization of repositories that are not meant to be instantiated actually.
2013-11-05 17:29:05 +01:00
Thomas Darimont
8fb390ee88 DATAMONGO-348 - Added serialization support for lazy-loading for DBRefs. 2013-11-05 15:22:19 +01:00
Oliver Gierke
df1c4496dc DATAMONGO-348 - Polishing of lazy loading implementation.
Extracted DelegatingDbRefResolver and associates from MappingMongoConverter. Let MongoDbFactory expose PersistenceExceptionTranslator only to prevent invalid dependency to core package. Renamed DbRefResolveCallback to ResolverCallback. Removed AbstractDbRefResolver and moved the functionality implemented there (triggering of exception translation) into the DbRefResolver.

MappingMongoConverter now uses a slightly extended version of DbRefResolver so that we can essentially replace the MongoDbFactory dependency with the DbRefResolver one.

Added support for Objenesis based lazy-loading proxies to support domain classes without a default constructor. Explicitly check for Spring 4 being present as with it the default ProxyFactory already supports that out of the box.

Added missing JavaDoc and assertions. A lot of cleanups and removal of deprecation warnings in test cases.
2013-11-04 20:21:10 +01:00
Thomas Darimont
b808fd3003 DATAMONGO-348 - Add support for lazy loading of DbRefs.
Introduced DbRefResolver interface in order to be able to abstract how a DbRef is resolved that is used in MappingMongoConverter#doWithAssociations. The present behaviour was to resolve a DbRef eagerly. This functionality is now implemented by EagerDbRefResolver. In order to support lazy loading we have to provide some means to define the desired loading behaviour. This can now be done via the "lazy"-Attribute on @DbRef which defaults to false.
If the attribute is set to true the LazyDbRefResolver is used to create a Proxy that eagerly loads the required data on demand when one of the (non-Object) proxy methods is called. MongoDbFactory now exposes a MongoExceptionTranslator that is now used by the MappingMongoConverter and MongoTemplate.

Introduced a DelegatingDbRefResolver that can delegate to a DbRefResolveCallback in order  to perform the actual DbRef resolution.
We now use cglib-proxies if necessary if the referenced association is a concrete class.

Added unit tests for lazy loading of interface types, concrete collection types and concrete domain types. Exposed state from LazyLoadingInterceptor for better testability.

Added unit tests for lazy loading of classes with custom PersistenceConstructor.
Moved integration tests for PersonRepository into its own test class.
2013-11-04 20:15:24 +01:00
Oliver Gierke
ed12298271 DATAMONGO-788 - Polishing.
Slightly changed the way the the simple reference rendering for projections is implemented. Introduced an isAliased() method on Field to be able to determine whether the field reference has been renamed explicitly.

Original pull request: #90.
2013-10-31 14:40:09 +00:00
Thomas Darimont
682798325b DATAMONGO-788 - Projection operations do not render synthetic fields properly.
Introduced boolean isSynthetic() attribute to FieldReference to determine if the given reference points to a synthetic field, as this controls whether we render a simple include (1) or a concrete reference ($fieldName) E.g. isSynthetic() would be true for a field reference to _id.

Original pull request: #90.
2013-10-31 14:39:48 +00:00
Oliver Gierke
0e69021486 DATAMONGO-787 - Upgraded to Spring Framework 3.2.4.
Upgraded to Spring Data Commons 1.3.0.BUILD-SNAPSHOT to benefit from upgrade to Spring 3.2.4 Added a workaround in the code introduced for DATAMONGO-774 which now runs into SPR-11031.
2013-10-25 20:57:21 +02:00
Komi Serge Innocent
ae7e24f1b6 DATAMONGO-746 - Creating an IndexInfo now also works with Doubles.
DefaultIndexOperations is now able to detect that in contrast to what's currently documented in the MongoDB reference documentation, it apparently returns double values for the index direction.

Original pull request: #67.
2013-10-25 10:48:57 +02:00
Thomas Darimont
94d4fa613c DATAMONGO-780 - Add support for nested repositories.
Support for considering nested repository interfaces can now be configured on the EnableMongoRepositories annotation via the considerNestedRepositories property.

Original pull request: #87.
2013-10-24 14:56:02 +02:00
Max Leuthäuser
39c9593b39 DATAMONGO-782 - Fixed typo in documentation.
Original pull request: #86.
2013-10-21 17:33:49 +02:00
Oliver Gierke
6e5f3661a8 DATAMONGO-774 - Some polishing in the reference documentation. 2013-10-17 15:54:05 +02:00
Thomas Darimont
2bd78e0bf0 DATAMONGO-774 - Added usage examples for SpEL expressions in projections to the reference documentation.
Original pull request: #81.
2013-10-17 15:54:05 +02:00
Oliver Gierke
dd59cdc59a DATAMONGO-774 - A round of Jürgenization for SpEL support in aggregation framework support.
Introduced dedicated spel package and extracted value objects to encapsulate and express information about the node transformation in a more semantical way.

Moved a lot of the logic contained in the SpelExpressionTransformer into the value objects for cohesiveness and testability. Updated Sonargraph architecture model to reflect the new packages we've introduced.

Original pull request: #81.
2013-10-17 15:54:01 +02:00
Thomas Darimont
7e471e2301 DATAMONGO-774 - Support SpEL expressions to define projection operations in the aggregation framework.
ProjectionOperations can now be built using SpEL expressions as this significantly shortens the code needed to express the project, especially for slightly more complex mathematical expressions.

Projection now has an ….andExpression(…) method that takes a SpEL expression and optional arguments that can be referred to via their index, i.e. a SpEl expression "5 + [0]" can be expanded using ….andExpression("5 + [0]", 7).… so that the projection can be prepared and dynamically get values bound.

Original pull request: #81.
2013-10-17 15:43:29 +02:00
Oliver Gierke
0871a43831 DATAMONGO-764 - Polished JavaDoc in MongoOptionsFactoryBean. 2013-10-15 14:04:09 +02:00
Mike Saavedra
710e77dabe DATAMONGO-764 - Added SSL support to Mongo configuration options.
Added support for allowing Mongo clients to use secure SSL connections by introducing the "ssl" property in MongoOptionsFactoryBean that will enable the use of the configured SSLSocketFactory to create SSLSockets. If no custom SSLSocketFactory is configured SSLSocketFactory#getDefault() will be used. We introduce this configuration in a new version of spring-mongo-1.4.xsd.

Applied Mike Saavedra's pull request (#75) with the above mentioned extensions.

Original pull request: #83.
2013-10-15 14:03:36 +02:00
Oliver Gierke
9c996617e8 DATAMONGO-630 - Polishing in UpdateTests. 2013-10-13 15:23:11 +02:00
Becca Gaspard
eebd49ab8d DATAMONGO-630 - Support $setOnInsert update operator.
Original pull request: #70.
2013-10-13 15:13:12 +02:00
Oliver Gierke
fb979b1734 DATAMONGO-752 - Improved keyword detection in QueryMapper.
The check for keywords in QueryMapper now selectively decides between checks for a nested keyword (DBObject) object and the check for a simple key. This allows the usage of criteria values starting with $ (e.g. { 'myvalue' : '$334' }) without the value being considered a keyword and thus erroneously triggering a potential conversion of the value.

Moved more logic for a keyword into the Keyword value object.
2013-10-13 13:48:00 +02:00
Oliver Gierke
b5c88938e0 DATAMONGO-534 - GridFsTemplate supports queries with sorting.
Upgraded to Mongo Java Driver 2.11.3 to be able to forward the sorting options contained in a Query to eventually be able to return sorted results when querying for files.
2013-10-13 12:41:06 +02:00
Oliver Gierke
4027770701 DATAMONGO-777 - Upgraded to Mongo Java Driver 2.11.3.
Upgraded to latest mongo Java Driver release to benefit from new API introduced (required to fix DATAMONGO-534). Adapted test cases to API changes in DuplicateKey.
2013-10-13 12:41:06 +02:00
Oliver Gierke
a120ce2bb1 DATAMONGO-776 - Improvement in TypeBasedAggregationOperationContext.
TypeBasedAggregationOperationContext's getReference(…) doesn't use a PropertyPath anymore to avoid running the camel case property resolution.
2013-10-10 14:03:25 +02:00
Thomas Darimont
a5d40a049d DATAMONGO-769 - Improve support for arithmetic operators in AggregationFramework.
We now support the usage of field references in arithmetic projection operations.

Original pull request: #80.
2013-10-10 11:09:04 +02:00
Thomas Darimont
f0f12d5296 DATAMONGO-771 - Fix raw JSON string handling in MongoTemplate.insert(…).
We now support insertion of JSON objects as plain strings via MongoTemplate.insert(…).

Original pull request: #79.
2013-10-08 12:46:03 +02:00
Oliver Gierke
24e06cf219 DATAMONGO-761 - Fix path key lookup for non-properties in SpringDataMongoDBSerializer.
In our Querydsl MongodbSerializer implementation we now only inspect the MongoPersistentProperty for a field name if the given path is really a property path. Previously we tried to always resolve a persistent property even if the given path was an array index path, a map key or the like.
2013-10-08 12:37:18 +02:00
Oliver Gierke
1b83ff0382 DATAMONGO-766 - Support for nested field references through @Field.
The MappingMongoConverter now supports reading and writing nested fields from and to documents by using @Field(…) with a path expression, e.g. @Field("a.b"). We now correctly create the nested objects and also reuse the previously created intermediates when populating further properties.

Not that this might cause the need to define field ordering explicitly as later properties might override the values set using a path reference in @Field.
2013-10-08 12:23:24 +02:00
Thomas Darimont
fe41202f96 DATAMONGO-770 - Add support for IgnoreCase in query derivation.
We now support IgnoreCase and AllIgnoreCase in predicate expression for derived queries.

Original pull request: #78.
2013-10-07 17:13:33 +02:00
Thomas Darimont
78235b4799 DATAMONGO-768 - Improve documentation of how to use @PersistenceConstructor.
Added an additional section to chapter 7.3 that describes the parameter value binding when the @PersistenceConstructor annotation including a small usage example. Added a concrete example for the @Value annotation that uses SpEL.

Original pull request: #77.
2013-10-01 14:04:34 +02:00
Thomas Darimont
51ece4353b DATAMONGO-759 - Improved rendering of GroupOperation.
GroupOperation gets the _id field now rendered as null if no group fields were added to the operation. Previously it was rendered as empty document (i.e. { }). While this was technically correct as well, we're now closer to what the MongoDB reference documentation describes.

Original pull request: #73.
2013-09-30 19:20:05 +02:00
Thomas Darimont
51bab838b0 DATAMONGO-757 - Align output of projection operation with MongoDB defaults.
Adjusted FieldProjection to generate an appropriate representation of included / excluded fields (namely :1 for included and :0 for excluded).
Polished guards to handle only _id is allowed to be excluded (DATAMONGO-758).

Original pull request: #76.
2013-09-27 12:56:01 +02:00
Thomas Darimont
361f9daa45 DATAMONGO-753 - Add support for nested field references in aggregation operations.
Aggregation pipelines now correctly handle nested field references in aggregation operations. We introduced FieldsExposingAggregationOperation to mark AggregationOperations that change the set of exposed fields available for processing by later AggregationOperations. Extracted context state out of AggregationOperation to ExposedFieldsAggregationContext for better separation of concerns. Modified toDbObject(…) in Aggregation to only replace the aggregation context when the current AggregationOperation is a FieldExposingAggregationOperation.

Original pull request: #74.
2013-09-26 14:29:04 +02:00
Thomas Darimont
56b23a6dbe DATAMONGO-758 - Current mongodb Versions only support to explicitly exclude the _id property in a projection.
Added guard to FieldProjection.from within ProjectionOption to prevent users from excluding fields other than "_id".
2013-09-24 16:38:20 +02:00
Thomas Darimont
9e15c17e26 DATAMONGO-760 - Add ability to override CRUD methods using @Query.
Annotated @Query with @QueryAnnotation to mark it as a custom query annotation so that one can redeclare a repository CRUD method and let it execute the annotated query instead of triggering the generic implementation.

Original pull request: #71.
2013-09-24 16:09:28 +02:00
Thomas Darimont
a3c77a43b6 DATAMONGO-740 - Prepare for next iteration.
Updated pom.xml to refer to the latest SD Commons Version 1.7.0.BUILD-SNAPSHOT.
2013-09-18 13:50:02 +02:00
Oliver Gierke
55169e2e11 DATAMONGO-740 - Updated readme after 1.4.0.RELEASE release. 2013-09-09 16:10:55 -07:00
Spring Buildmaster
24672e6bdd DATAMONGO-740 - Prepare next development iteration. 2013-09-09 15:56:23 -07:00
Spring Buildmaster
1a46abfbb9 DATAMONGO-740 - Release version 1.3.0.RELEASE. 2013-09-09 15:56:20 -07:00
Thomas Darimont
61284228dd DATAMONGO-740 - Prepare 1.3.0 RELEASE. 2013-09-09 15:50:04 -07:00
Thomas Darimont
8cb92de1ee DATAMONGO-445 - Allow to skip unnecessary elements in NearQuery.
Added support for skipping elements for NearQuery in MongoTemplate. As mongodb currently (2.4.4) doesn't support he skipping of elements in geoNear-Queries we skip the unnecessary elements ourselves. We use the limit & skip information from the given query or an explicitly passed Pageable.

Original pull request: #64.
2013-08-22 09:20:19 +02:00
Oliver Gierke
5d3cc3fa04 DATAMONGO-743 - Added DBObjectToStringConverter.
Added a reading converter to dump DBObject instances into a String directly to enable executing queries against MongoDB into a String version of the query result without marshaling the DBObject into a Map first.
2013-08-22 08:55:07 +02:00
Thomas Darimont
c0b99740dc DATAMONGO-742 - Document CDI integration in reference documentation.
Added chapter for CDI Integration under the new chapter Miscellaneous.

Original pull request: #63.
2013-08-13 12:20:58 +02:00
Randy Watler
595bbd3aa7 DATAMONGO-737 - Register TransactionSynchronization holder once per Mongo instance.
Original pull request: #62.
2013-08-12 09:51:02 +02:00
Chuong Ngo
5d2fc31164 DATAMONGO-738 - Allow to pass collectionName along with entityClass as parameter to update methods in MongoTemplate.
Added appropriate overloaded methods to MongoOperations and MongoTemplate. Applied pull request from Chuong Ngo <chuong.h.ngo.ctr@mail.mil>.
Original pull request: #57.
2013-08-09 15:30:00 +02:00
Thomas Darimont
a9dc0fae69 DATAMONGO-725 - Improve configurability and documentation of TypeMapper on MappingMongoConverter.
Added new attribute type-mapper-ref to the mapping-converter element in spring-mongo-1.3.xsd in order to support the configuration of custom-type-mappers. Removed the unsupported attributes "mongo-ref" and "mongo-template-ref" from the mapping-converter element in spring-mongo-1.3.xsd because they are not considered anymore.

Updated MappingMongoConverterParser to be aware of the new attribute. Added examples for configuring a custom MongoTypeMapper the usage of @TypeAlias to the reference documentation.

Original pull request: #61.
2013-08-09 13:44:53 +02:00
Thomas Darimont
0605c7b753 DATAMONGO-507 - Reject incorrect usage of Criteria#not().
Added a guard to Criteria#(and|or|nor)Operator to prevent wrapping $and, $or or $nor expressions in a $not expression as mongodb currently doesn't support this. Added test case to CriteriaTests to verify that not() works as specified.

Original pull request: #60.
2013-08-09 12:23:25 +02:00
Thomas Darimont
21352a8829 DATAMONGO-602 - Added test case to MongoTemplate tests to verify that querying by BigInteger ids work.
Original pull request: #59.
2013-08-07 18:58:32 +02:00
Oliver Gierke
58e1d2dbd9 DATAMONGO-741 - Fixed check for nested property references in aggregation framework.
Fixed using the actual field reference instead of the field name on resolving. Added equals(…) and hashCode() methods to value objects. Added unit tests for TypeBasedAggregationOperationContext.
2013-08-07 10:21:48 +02:00
Spring Buildmaster
4f7821e3c2 DATAMONGO-732 - Prepare next development iteration. 2013-08-05 08:48:22 -07:00
361 changed files with 33080 additions and 9212 deletions

1
CONTRIBUTING.MD Normal file
View File

@@ -0,0 +1 @@
You find the contribution guidelines for Spring Data projects [here](https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.md).

View File

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

110
pom.xml
View File

@@ -2,23 +2,23 @@
<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.3.0.RC1</version>
<version>1.7.0.M1</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
<description>MongoDB support for Spring Data</description>
<url>http://www.springsource.org/spring-data/mongodb</url>
<url>http://projects.spring.io/spring-data-mongodb</url>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>1.1.1.RELEASE</version>
<version>1.6.0.M1</version>
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
</parent>
<modules>
<module>spring-data-mongodb</module>
<module>spring-data-mongodb-cross-store</module>
@@ -29,19 +29,20 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>1.6.0.RC1</springdata.commons>
<mongo>2.10.1</mongo>
<springdata.commons>1.10.0.M1</springdata.commons>
<mongo>2.12.3</mongo>
<mongo.osgi>2.12.3</mongo.osgi>
</properties>
<developers>
<developer>
<id>ogierke</id>
<name>Oliver Gierke</name>
<email>ogierke at vmware.com</email>
<organization>SpringSource</organization>
<organizationUrl>http://www.springsource.com</organizationUrl>
<email>ogierke at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>http://www.gopivotal.com</organizationUrl>
<roles>
<role>Project Lean</role>
<role>Project Lead</role>
</roles>
<timezone>+1</timezone>
</developer>
@@ -49,8 +50,8 @@
<id>trisberg</id>
<name>Thomas Risberg</name>
<email>trisberg at vmware.com</email>
<organization>SpringSource</organization>
<organizationUrl>http://www.springsource.com</organizationUrl>
<organization>Pivotal</organization>
<organizationUrl>http://www.gopivotal.com</organizationUrl>
<roles>
<role>Developer</role>
</roles>
@@ -59,9 +60,9 @@
<developer>
<id>mpollack</id>
<name>Mark Pollack</name>
<email>mpollack at vmware.com</email>
<organization>SpringSource</organization>
<organizationUrl>http://www.springsource.com</organizationUrl>
<email>mpollack at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>http://www.gopivotal.com</organizationUrl>
<roles>
<role>Developer</role>
</roles>
@@ -70,16 +71,72 @@
<developer>
<id>jbrisbin</id>
<name>Jon Brisbin</name>
<email>jbrisbin at vmware.com</email>
<organization>SpringSource</organization>
<organizationUrl>http://www.springsource.com</organizationUrl>
<email>jbrisbin at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>http://www.gopivotal.com</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>-6</timezone>
</developer>
<developer>
<id>tdarimont</id>
<name>Thomas Darimont</name>
<email>tdarimont at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>http://www.gopivotal.com</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>+1</timezone>
</developer>
<developer>
<id>cstrobl</id>
<name>Christoph Strobl</name>
<email>cstrobl at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>http://www.gopivotal.com</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>+1</timezone>
</developer>
</developers>
<profiles>
<profile>
<id>mongo-next</id>
<properties>
<mongo>2.12.5-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>
<dependencies>
<!-- MongoDB -->
<dependency>
@@ -88,12 +145,19 @@
<version>${mongo}</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-lib-milestone</id>
<url>http://repo.springsource.org/libs-milestone-local</url>
<id>spring-libs-milestone</id>
<url>http://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-plugins-release</id>
<url>http://repo.spring.io/plugins-release</url>
</pluginRepository>
</pluginRepositories>
</project>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
<aspects>
<aspect name="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect" />
<aspect name="org.springframework.data.mongodb.crossstore.MongoDocumentBacking" />
</aspects>
</aspectj>

View File

@@ -2,29 +2,28 @@
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.3.0.RC1</version>
<version>1.7.0.M1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-data-mongodb-cross-store</artifactId>
<name>Spring Data MongoDB - Cross-Store Persistence Support</name>
<name>Spring Data MongoDB - Cross-Store Support</name>
<properties>
<jpa>1.0.0.Final</jpa>
<hibernate>3.6.10.Final</hibernate>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
@@ -35,24 +34,21 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring}</version>
</dependency>
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.3.0.RC1</version>
<version>1.7.0.M1</version>
</dependency>
<dependency>
@@ -60,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 -->
@@ -106,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>
@@ -135,8 +127,10 @@
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
<complianceLevel>${source.level}</complianceLevel>
<source>${source.level}</source>
<target>${source.level}</target>
<xmlConfigured>aop.xml</xmlConfigured>
</configuration>
</plugin>
</plugins>

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.
@@ -41,17 +41,13 @@ import com.mongodb.MongoException;
public class MongoChangeSetPersister implements ChangeSetPersister<Object> {
private static final String ENTITY_CLASS = "_entity_class";
private static final String ENTITY_ID = "_entity_id";
private static final String ENTITY_FIELD_NAME = "_entity_field_name";
private static final String ENTITY_FIELD_CLASS = "_entity_field_class";
protected final Logger log = LoggerFactory.getLogger(getClass());
private MongoTemplate mongoTemplate;
private EntityManagerFactory entityManagerFactory;
public void setMongoTemplate(MongoTemplate mongoTemplate) {
@@ -113,12 +109,14 @@ public class MongoChangeSetPersister implements ChangeSetPersister<Object> {
* @see org.springframework.data.crossstore.ChangeSetPersister#getPersistentId(org.springframework.data.crossstore.ChangeSetBacked, org.springframework.data.crossstore.ChangeSet)
*/
public Object getPersistentId(ChangeSetBacked entity, ChangeSet cs) throws DataAccessException {
log.debug("getPersistentId called on " + entity);
if (entityManagerFactory == null) {
throw new DataAccessResourceFailureException("EntityManagerFactory cannot be null");
}
Object o = entityManagerFactory.getPersistenceUnitUtil().getIdentifier(entity);
return o;
return entityManagerFactory.getPersistenceUnitUtil().getIdentifier(entity);
}
/*

View File

@@ -1,18 +1,18 @@
Bundle-SymbolicName: org.springframework.data.mongodb.crossstore
Bundle-Name: Spring Data MongoDB Cross Store Support
Bundle-Vendor: SpringSource
Bundle-Vendor: Pivotal Software, Inc.
Bundle-ManifestVersion: 2
Import-Package:
Import-Package:
sun.reflect;version="0";resolution:=optional
Export-Template:
org.springframework.data.mongodb.crossstore.*;version="${project.version}"
Import-Template:
com.mongodb.*;version="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",
org.slf4j.*;version="${slf4j:[=.=.=,+1.0.0)}",
org.springframework.*;version="${spring30:[=.=.=.=,+1.0.0)}",
org.springframework.*;version="${spring:[=.=.=.=,+1.0.0)}",
org.springframework.data.*;version="${springdata.commons:[=.=.=.=,+1.0.0)}",
org.springframework.data.mongodb.*;version="${project.version:[=.=.=.=,+1.0.0)}",
org.w3c.dom.*;version="0"

View File

@@ -2,7 +2,7 @@
<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>
<artifactId>spring-data-mongodb-distribution</artifactId>
<packaging>pom</packaging>
@@ -13,10 +13,10 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.3.0.RC1</version>
<version>1.7.0.M1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<project.root>${basedir}/..</project.root>
<dist.key>SDMONGO</dist.key>
@@ -32,6 +32,10 @@
<groupId>org.codehaus.mojo</groupId>
<artifactId>wagon-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.3.0.RC1</version>
<version>1.7.0.M1</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

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

View File

@@ -1,153 +1,183 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<context version="7.1.7.187">
<scope name="spring-data-mongodb" type="Project">
<element name="Filter" type="TypeFilterReferenceOverridden">
<element name="org.springframework.data.mongodb.**" type="IncludeTypePattern"/>
<context version="7.1.10.209">
<scope type="Project" name="spring-data-mongodb">
<element type="TypeFilterReferenceOverridden" name="Filter">
<element type="IncludeTypePattern" name="org.springframework.data.mongodb.**"/>
</element>
<architecture>
<element name="Config" type="Layer">
<element name="Assignment" type="TypeFilter">
<element name="**.config.**" type="WeakTypePattern"/>
<element type="Layer" name="Repositories">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.repository.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core"/>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|GridFS"/>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Monitoring"/>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Repositories"/>
<element type="Subsystem" name="API">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.repository.*"/>
</element>
</element>
<element type="Subsystem" name="Query">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.query.**"/>
</element>
<dependency toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|API" type="AllowedDependency"/>
</element>
<element type="Subsystem" name="Implementation">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.support.**"/>
</element>
<dependency toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|API" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|Query" type="AllowedDependency"/>
</element>
<element type="Subsystem" name="Config">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.config.**"/>
</element>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Mapping" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|API" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|Implementation" type="AllowedDependency"/>
</element>
<dependency toName="Project|spring-data-mongodb::Layer|Config" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core" type="AllowedDependency"/>
</element>
<element name="Repositories" type="Layer">
<element name="Assignment" type="TypeFilter">
<element name="**.repository.**" type="IncludeTypePattern"/>
<element type="Layer" name="Config">
<element type="TypeFilter" name="Assignment">
<element type="WeakTypePattern" name="**.config.**"/>
</element>
<element name="API" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.repository.*" type="IncludeTypePattern"/>
</element>
</element>
<element name="Query" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.query.**" type="IncludeTypePattern"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|API"/>
</element>
<element name="Implementation" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.support.**" type="IncludeTypePattern"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|API"/>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|Query"/>
</element>
<element name="Config" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.config.**" type="IncludeTypePattern"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Repositories::Subsystem|Implementation"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|GridFS" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Monitoring" type="AllowedDependency"/>
</element>
<element name="Monitoring" type="Layer">
<element name="Assignment" type="TypeFilter">
<element name="**.monitor.**" type="IncludeTypePattern"/>
<element type="Layer" name="Monitoring">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.monitor.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core" type="AllowedDependency"/>
</element>
<element name="GridFS" type="Layer">
<element name="Assignment" type="TypeFilter">
<element name="**.gridfs.**" type="IncludeTypePattern"/>
<element type="Layer" name="GridFS">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.gridfs.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core" type="AllowedDependency"/>
</element>
<element name="Core" type="Layer">
<element name="Assignment" type="TypeFilter">
<element name="**.core.**" type="IncludeTypePattern"/>
</element>
<element name="Mapping" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.mapping.**" type="IncludeTypePattern"/>
<element type="Layer" name="Core">
<element type="TypeFilter" name="Assignment"/>
<element type="Subsystem" name="Mapping">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.core.mapping.**"/>
</element>
</element>
<element name="Geospatial" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.geo.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Geospatial">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.core.geo.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Mapping"/>
</element>
<element name="Query" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.query.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Query">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.core.query.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Geospatial"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Geospatial" type="AllowedDependency"/>
</element>
<element name="Index" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.index.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Conversion">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.core.convert.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Mapping"/>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Query"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Geospatial" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Mapping" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Query" type="AllowedDependency"/>
</element>
<element name="Core" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.core.**" type="WeakTypePattern"/>
<element type="Subsystem" name="SpEL">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.core.spel.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Geospatial"/>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Index"/>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Mapping"/>
<dependency type="AllowedDependency" toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Query"/>
</element>
<element type="Subsystem" name="Aggregation">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.core.aggregation.**"/>
</element>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Conversion" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Mapping" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Query" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|SpEL" type="AllowedDependency"/>
</element>
<element type="Subsystem" name="Index">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.core.index.**"/>
</element>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Mapping" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Query" type="AllowedDependency"/>
</element>
<element type="Subsystem" name="Core">
<element type="TypeFilter" name="Assignment">
<element type="WeakTypePattern" name="**.core.**"/>
</element>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Aggregation" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Conversion" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Geospatial" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Index" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Mapping" type="AllowedDependency"/>
<dependency toName="Project|spring-data-mongodb::Layer|Core::Subsystem|Query" type="AllowedDependency"/>
</element>
<element type="Subsystem" name="Util">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.util.**"/>
</element>
<stereotype name="Unrestricted"/>
<stereotype name="Public"/>
</element>
</element>
<element name="API" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="org.springframework.data.mongodb.*" type="IncludeTypePattern"/>
<element type="Subsystem" name="API">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="org.springframework.data.mongodb.*"/>
</element>
<stereotype name="Public"/>
</element>
</architecture>
<workspace>
<element name="src/main/java" type="JavaRootDirectory">
<element type="JavaRootDirectory" name="src/main/java">
<reference name="Project|spring-data-mongodb::BuildUnit|spring-data-mongodb"/>
</element>
<element name="target/classes" type="JavaRootDirectory">
<element type="JavaRootDirectory" name="target/classes">
<reference name="Project|spring-data-mongodb::BuildUnit|spring-data-mongodb"/>
</element>
</workspace>
<physical>
<element name="spring-data-mongodb" type="BuildUnit"/>
<element type="BuildUnit" name="spring-data-mongodb"/>
</physical>
</scope>
<scope name="External" type="External">
<element name="Filter" type="TypeFilter">
<element name="**" type="IncludeTypePattern"/>
<element name="java.**" type="ExcludeTypePattern"/>
<element name="javax.**" type="ExcludeTypePattern"/>
<scope type="External" name="External">
<element type="TypeFilter" name="Filter">
<element type="IncludeTypePattern" name="**"/>
<element type="ExcludeTypePattern" name="java.**"/>
<element type="ExcludeTypePattern" name="javax.**"/>
</element>
<architecture>
<element name="Spring" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="org.springframework.**" type="IncludeTypePattern"/>
<element name="org.springframework.data.**" type="ExcludeTypePattern"/>
<element type="Subsystem" name="Spring">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="org.springframework.**"/>
<element type="ExcludeTypePattern" name="org.springframework.data.**"/>
</element>
</element>
<element name="Spring Data Core" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="org.springframework.data.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Spring Data Core">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="org.springframework.data.**"/>
</element>
</element>
<element name="Mongo Java Driver" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="com.mongodb.**" type="IncludeTypePattern"/>
<element name="org.bson.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Mongo Java Driver">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="com.mongodb.**"/>
<element type="IncludeTypePattern" name="org.bson.**"/>
</element>
</element>
<element name="Querydsl" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="com.mysema.query.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Querydsl">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="com.mysema.query.**"/>
</element>
</element>
</architecture>
</scope>
<scope name="Global" type="Global">
<element name="Configuration" type="Configuration"/>
<element name="Filter" type="TypeFilter">
<element name="**" type="IncludeTypePattern"/>
<scope type="Global" name="Global">
<element type="Configuration" name="Configuration"/>
<element type="TypeFilter" name="Filter">
<element type="IncludeTypePattern" name="**"/>
</element>
</scope>
</context>

View File

@@ -2,7 +2,7 @@
<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>
<artifactId>spring-data-mongodb</artifactId>
<name>Spring Data MongoDB - Core</name>
@@ -11,36 +11,34 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.3.0.RC1</version>
<version>1.7.0.M1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<validation>1.0.0.GA</validation>
<objenesis>1.3</objenesis>
<equalsverifier>1.5</equalsverifier>
</properties>
<dependencies>
<!-- Spring -->
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
@@ -51,11 +49,10 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring}</version>
</dependency>
<!-- Spring Data -->
<dependency>
<!-- Spring Data -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-commons</artifactId>
<version>${springdata.commons}</version>
@@ -81,7 +78,7 @@
<version>1.0</version>
<optional>true</optional>
</dependency>
<!-- CDI -->
<dependency>
<groupId>javax.enterprise</groupId>
@@ -90,21 +87,21 @@
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>${cdi}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans.test</groupId>
<artifactId>cditest-owb</artifactId>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
@@ -120,29 +117,49 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>${objenesis}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jodatime}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${slf4j}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>${equalsverifier}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.0.8</version>
<version>${apt}</version>
<dependencies>
<dependency>
<groupId>com.mysema.querydsl</groupId>
@@ -179,9 +196,14 @@
<systemPropertyVariables>
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>
</systemPropertyVariables>
<properties>
<property>
<name>listener</name>
<value>org.springframework.data.mongodb.test.util.CleanMongoDBJunitRunListener</value>
</property>
</properties>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -13,23 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.mapping;
package org.springframework.data.mongodb;
import org.springframework.dao.UncategorizedDataAccessException;
/**
* {@link FieldNamingStrategy} simply using the {@link MongoPersistentProperty}'s name.
*
* @since 1.3
* @author Oliver Gierke
*/
public enum PropertyNameFieldNamingStrategy implements FieldNamingStrategy {
public class LazyLoadingException extends UncategorizedDataAccessException {
INSTANCE;
private static final long serialVersionUID = -7089224903873220037L;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.FieldNamingStrategy#getFieldName(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty)
/**
* @param msg
* @param cause
*/
public String getFieldName(MongoPersistentProperty property) {
return property.getName();
public LazyLoadingException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -1,6 +1,23 @@
/*
* Copyright 2011-2013 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;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.core.MongoExceptionTranslator;
import com.mongodb.DB;
@@ -8,6 +25,7 @@ import com.mongodb.DB;
* Interface for factories creating {@link DB} instances.
*
* @author Mark Pollack
* @author Thomas Darimont
*/
public interface MongoDbFactory {
@@ -27,4 +45,11 @@ public interface MongoDbFactory {
* @throws DataAccessException
*/
DB getDb(String dbName) throws DataAccessException;
/**
* Exposes a shared {@link MongoExceptionTranslator}.
*
* @return will never be {@literal null}.
*/
PersistenceExceptionTranslator getExceptionTranslator();
}

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.
@@ -28,11 +28,16 @@ import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory;
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.support.CachingIsNewStrategyFactory;
@@ -47,6 +52,8 @@ import com.mongodb.Mongo;
*
* @author Mark Pollack
* @author Oliver Gierke
* @author Thomas Darimont
* @author Ryan Tenney
*/
@Configuration
public abstract class AbstractMongoConfiguration {
@@ -58,6 +65,16 @@ public abstract class AbstractMongoConfiguration {
*/
protected abstract String getDatabaseName();
/**
* Return the name of the authentication database to use. Defaults to {@literal null} and will turn into the value
* returned by {@link #getDatabaseName()} later on effectively.
*
* @return
*/
protected String getAuthenticationDatabaseName() {
return null;
}
/**
* Return the {@link Mongo} instance to connect to. Annotate with {@link Bean} in case you want to expose a
* {@link Mongo} instance to the {@link org.springframework.context.ApplicationContext}.
@@ -88,15 +105,8 @@ public abstract class AbstractMongoConfiguration {
* @throws Exception
*/
@Bean
public SimpleMongoDbFactory mongoDbFactory() throws Exception {
UserCredentials credentials = getUserCredentials();
if (credentials == null) {
return new SimpleMongoDbFactory(mongo(), getDatabaseName());
} else {
return new SimpleMongoDbFactory(mongo(), getDatabaseName(), credentials);
}
public MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(mongo(), getDatabaseName(), getUserCredentials(), getAuthenticationDatabaseName());
}
/**
@@ -109,7 +119,9 @@ public abstract class AbstractMongoConfiguration {
* entities.
*/
protected String getMappingBasePackage() {
return getClass().getPackage().getName();
Package mappingBasePackage = getClass().getPackage();
return mappingBasePackage == null ? null : mappingBasePackage.getName();
}
/**
@@ -135,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;
}
@@ -178,8 +187,11 @@ public abstract class AbstractMongoConfiguration {
*/
@Bean
public MappingMongoConverter mappingMongoConverter() throws Exception {
MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory(), mongoMappingContext());
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext());
converter.setCustomConversions(customConversions());
return converter;
}
@@ -220,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

@@ -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.
@@ -24,13 +24,14 @@ package org.springframework.data.mongodb.config;
*/
public abstract class BeanNames {
static final String MAPPING_CONTEXT = "mappingContext";
static final String INDEX_HELPER = "indexCreationHelper";
static final String MONGO = "mongo";
static final String DB_FACTORY = "mongoDbFactory";
static final String VALIDATING_EVENT_LISTENER = "validatingMongoEventListener";
static final String IS_NEW_STRATEGY_FACTORY = "isNewStrategyFactory";
public static final String MAPPING_CONTEXT_BEAN_NAME = "mongoMappingContext";
static final String INDEX_HELPER_BEAN_NAME = "indexCreationHelper";
static final String MONGO_BEAN_NAME = "mongo";
static final String DB_FACTORY_BEAN_NAME = "mongoDbFactory";
static final String VALIDATING_EVENT_LISTENER_BEAN_NAME = "validatingMongoEventListener";
static final String IS_NEW_STRATEGY_FACTORY_BEAN_NAME = "isNewStrategyFactory";
static final String DEFAULT_CONVERTER_BEAN_NAME = "mappingConverter";
static final String MONGO_TEMPLATE = "mongoTemplate";
static final String GRID_FS_TEMPLATE = "gridFsTemplate";
static final String MONGO_TEMPLATE_BEAN_NAME = "mongoTemplate";
static final String GRID_FS_TEMPLATE_BEAN_NAME = "gridFsTemplate";
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.AuditorAware;
/**
* Annotation to enable auditing in MongoDB via annotation configuration.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MongoAuditingRegistrar.class)
public @interface EnableMongoAuditing {
/**
* Configures the {@link AuditorAware} bean to be used to lookup the current principal.
*
* @return
*/
String auditorAwareRef() default "";
/**
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
*
* @return
*/
boolean setDates() default true;
/**
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}.
*
* @return
*/
boolean modifyOnCreate() default true;
/**
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be
* used for setting creation and modification dates.
*
* @return
*/
String dateTimeProviderRef() default "";
}

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.
@@ -43,7 +43,7 @@ class GridFsTemplateParser extends AbstractBeanDefinitionParser {
throws BeanDefinitionStoreException {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.GRID_FS_TEMPLATE;
return StringUtils.hasText(id) ? id : BeanNames.GRID_FS_TEMPLATE_BEAN_NAME;
}
/*
@@ -57,13 +57,14 @@ class GridFsTemplateParser extends AbstractBeanDefinitionParser {
String converterRef = element.getAttribute("converter-ref");
String dbFactoryRef = element.getAttribute("db-factory-ref");
String bucket = element.getAttribute("bucket");
BeanDefinitionBuilder gridFsTemplateBuilder = BeanDefinitionBuilder.genericBeanDefinition(GridFsTemplate.class);
if (StringUtils.hasText(dbFactoryRef)) {
gridFsTemplateBuilder.addConstructorArgReference(dbFactoryRef);
} else {
gridFsTemplateBuilder.addConstructorArgReference(BeanNames.DB_FACTORY);
gridFsTemplateBuilder.addConstructorArgReference(BeanNames.DB_FACTORY_BEAN_NAME);
}
if (StringUtils.hasText(converterRef)) {
@@ -72,7 +73,11 @@ class GridFsTemplateParser extends AbstractBeanDefinitionParser {
gridFsTemplateBuilder.addConstructorArgReference(BeanNames.DEFAULT_CONVERTER_BEAN_NAME);
}
return (AbstractBeanDefinition) helper.getComponentIdButFallback(gridFsTemplateBuilder, BeanNames.GRID_FS_TEMPLATE)
if (StringUtils.hasText(bucket)) {
gridFsTemplateBuilder.addConstructorArgValue(bucket);
}
return (AbstractBeanDefinition) helper.getComponentIdButFallback(gridFsTemplateBuilder, BeanNames.GRID_FS_TEMPLATE_BEAN_NAME)
.getBeanDefinition();
}
}

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.
@@ -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;
@@ -51,10 +52,10 @@ import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.config.BeanComponentDefinitionBuilder;
import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory;
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
import org.springframework.data.mongodb.core.mapping.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
@@ -70,11 +71,13 @@ import org.w3c.dom.Element;
* @author Jon Brisbin
* @author Oliver Gierke
* @author Maciej Walkowiak
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class MappingMongoConverterParser implements BeanDefinitionParser {
private static final String BASE_PACKAGE = "base-package";
private static final boolean jsr303Present = ClassUtils.isPresent("javax.validation.Validator",
private static final boolean JSR_303_PRESENT = ClassUtils.isPresent("javax.validation.Validator",
MappingMongoConverterParser.class.getClassLoader());
/* (non-Javadoc)
@@ -82,10 +85,13 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
*/
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
if (parserContext.isNested()) {
parserContext.getReaderContext().error("Mongo Converter must not be defined as nested bean.", element);
}
BeanDefinitionRegistry registry = parserContext.getRegistry();
String id = element.getAttribute(AbstractBeanDefinitionParser.ID_ATTRIBUTE);
id = StringUtils.hasText(id) ? id : "mappingConverter";
id = StringUtils.hasText(id) ? id : DEFAULT_CONVERTER_BEAN_NAME;
parserContext.pushContainingComponent(new CompositeComponentDefinition("Mapping Mongo Converter", element));
@@ -97,7 +103,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
// Need a reference to a Mongo instance
String dbFactoryRef = element.getAttribute("db-factory-ref");
if (!StringUtils.hasText(dbFactoryRef)) {
dbFactoryRef = DB_FACTORY;
dbFactoryRef = DB_FACTORY_BEAN_NAME;
}
// Converter
@@ -105,15 +111,20 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
converterBuilder.addConstructorArgReference(dbFactoryRef);
converterBuilder.addConstructorArgReference(ctxRef);
String typeMapperRef = element.getAttribute("type-mapper-ref");
if (StringUtils.hasText(typeMapperRef)) {
converterBuilder.addPropertyReference("typeMapper", typeMapperRef);
}
if (conversionsDefinition != null) {
converterBuilder.addPropertyValue("customConversions", conversionsDefinition);
}
try {
registry.getBeanDefinition(INDEX_HELPER);
registry.getBeanDefinition(INDEX_HELPER_BEAN_NAME);
} catch (NoSuchBeanDefinitionException ignored) {
if (!StringUtils.hasText(dbFactoryRef)) {
dbFactoryRef = DB_FACTORY;
dbFactoryRef = DB_FACTORY_BEAN_NAME;
}
BeanDefinitionBuilder indexHelperBuilder = BeanDefinitionBuilder
.genericBeanDefinition(MongoPersistentEntityIndexCreator.class);
@@ -122,14 +133,14 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
indexHelperBuilder.addDependsOn(ctxRef);
parserContext.registerBeanComponent(new BeanComponentDefinition(indexHelperBuilder.getBeanDefinition(),
INDEX_HELPER));
INDEX_HELPER_BEAN_NAME));
}
BeanDefinition validatingMongoEventListener = potentiallyCreateValidatingMongoEventListener(element, parserContext);
if (validatingMongoEventListener != null) {
parserContext.registerBeanComponent(new BeanComponentDefinition(validatingMongoEventListener,
VALIDATING_EVENT_LISTENER));
VALIDATING_EVENT_LISTENER_BEAN_NAME));
}
parserContext.registerBeanComponent(new BeanComponentDefinition(converterBuilder.getBeanDefinition(), id));
@@ -160,7 +171,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
private RuntimeBeanReference getValidator(Object source, ParserContext parserContext) {
if (!jsr303Present) {
if (!JSR_303_PRESENT) {
return null;
}
@@ -174,7 +185,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
return new RuntimeBeanReference(validatorName);
}
static String potentiallyCreateMappingContext(Element element, ParserContext parserContext,
public static String potentiallyCreateMappingContext(Element element, ParserContext parserContext,
BeanDefinition conversionsDefinition, String converterId) {
String ctxRef = element.getAttribute("mapping-context-ref");
@@ -189,7 +200,8 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
BeanDefinitionBuilder mappingContextBuilder = BeanDefinitionBuilder
.genericBeanDefinition(MongoMappingContext.class);
Set<String> classesToAdd = getInititalEntityClasses(element, mappingContextBuilder);
Set<String> classesToAdd = getInititalEntityClasses(element);
if (classesToAdd != null) {
mappingContextBuilder.addPropertyValue("initialEntitySet", classesToAdd);
}
@@ -202,18 +214,43 @@ 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 + "." + MAPPING_CONTEXT;
ctxRef = converterId == null || DEFAULT_CONVERTER_BEAN_NAME.equals(converterId) ? MAPPING_CONTEXT_BEAN_NAME
: converterId + "." + MAPPING_CONTEXT_BEAN_NAME;
parserContext.registerBeanComponent(componentDefinitionBuilder.getComponent(mappingContextBuilder, ctxRef));
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");
@@ -256,7 +293,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
return null;
}
private static Set<String> getInititalEntityClasses(Element element, BeanDefinitionBuilder builder) {
private static Set<String> getInititalEntityClasses(Element element) {
String basePackage = element.getAttribute(BASE_PACKAGE);
@@ -303,9 +340,10 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
mappingContextStrategyFactoryBuilder.addConstructorArgReference(mappingContextRef);
BeanComponentDefinitionBuilder builder = new BeanComponentDefinitionBuilder(element, context);
context.registerBeanComponent(builder.getComponent(mappingContextStrategyFactoryBuilder, IS_NEW_STRATEGY_FACTORY));
context.registerBeanComponent(builder.getComponent(mappingContextStrategyFactoryBuilder,
IS_NEW_STRATEGY_FACTORY_BEAN_NAME));
return IS_NEW_STRATEGY_FACTORY;
return IS_NEW_STRATEGY_FACTORY_BEAN_NAME;
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 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.
@@ -15,14 +15,19 @@
*/
package org.springframework.data.mongodb.config;
import org.springframework.beans.factory.config.BeanDefinition;
import static org.springframework.data.config.ParsingUtils.*;
import static org.springframework.data.mongodb.config.BeanNames.*;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.config.IsNewAwareAuditingHandlerBeanDefinitionParser;
import org.springframework.data.auditing.config.IsNewAwareAuditingHandlerBeanDefinitionParser;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.event.AuditingEventListener;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
@@ -58,23 +63,24 @@ public class MongoAuditingBeanDefinitionParser extends AbstractSingleBeanDefinit
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
String mappingContextRef = element.getAttribute("mapping-context-ref");
if (!registry.containsBeanDefinition(BeanNames.IS_NEW_STRATEGY_FACTORY)) {
if (!StringUtils.hasText(mappingContextRef)) {
String mappingContextName = BeanNames.MAPPING_CONTEXT;
BeanDefinitionRegistry registry = parserContext.getRegistry();
if (!registry.containsBeanDefinition(BeanNames.MAPPING_CONTEXT)) {
mappingContextName = MappingMongoConverterParser.potentiallyCreateMappingContext(element, parserContext, null,
BeanNames.DEFAULT_CONVERTER_BEAN_NAME);
if (!registry.containsBeanDefinition(MAPPING_CONTEXT_BEAN_NAME)) {
registry.registerBeanDefinition(MAPPING_CONTEXT_BEAN_NAME, new RootBeanDefinition(MongoMappingContext.class));
}
MappingMongoConverterParser.createIsNewStrategyFactoryBeanDefinition(mappingContextName, parserContext, element);
mappingContextRef = MAPPING_CONTEXT_BEAN_NAME;
}
BeanDefinitionParser parser = new IsNewAwareAuditingHandlerBeanDefinitionParser(BeanNames.IS_NEW_STRATEGY_FACTORY);
BeanDefinition handlerBeanDefinition = parser.parse(element, parserContext);
IsNewAwareAuditingHandlerBeanDefinitionParser parser = new IsNewAwareAuditingHandlerBeanDefinitionParser(
mappingContextRef);
parser.parse(element, parserContext);
builder.addConstructorArgValue(handlerBeanDefinition);
builder.addConstructorArgValue(getObjectFactoryBeanDefinition(parser.getResolvedBeanName(),
parserContext.extractSource(element)));
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.config;
import static org.springframework.beans.factory.config.BeanDefinition.*;
import static org.springframework.data.mongodb.config.BeanNames.*;
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
import org.springframework.data.auditing.config.AuditingConfiguration;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.event.AuditingEventListener;
import org.springframework.data.support.IsNewStrategyFactory;
import org.springframework.util.Assert;
/**
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableMongoAuditing} annotation.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
class MongoAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
*/
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableMongoAuditing.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
*/
@Override
protected String getAuditingHandlerBeanName() {
return "mongoAuditingHandler";
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
defaultDependenciesIfNecessary(registry, annotationMetadata);
super.registerBeanDefinitions(annotationMetadata, registry);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
*/
@Override
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
builder.addConstructorArgReference(MAPPING_CONTEXT_BEAN_NAME);
return configureDefaultAuditHandlerAttributes(configuration, builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
BeanDefinitionRegistry registry) {
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(AuditingEventListener.class);
listenerBeanDefinitionBuilder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(
getAuditingHandlerBeanName(), registry));
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
AuditingEventListener.class.getName(), registry);
}
/**
* Register default bean definitions for a {@link MongoMappingContext} and an {@link IsNewStrategyFactory} in case we
* don't find beans with the assumed names in the registry.
*
* @param registry the {@link BeanDefinitionRegistry} to use to register the components into.
* @param source the source which the registered components shall be registered with
*/
private void defaultDependenciesIfNecessary(BeanDefinitionRegistry registry, Object source) {
if (!registry.containsBeanDefinition(MAPPING_CONTEXT_BEAN_NAME)) {
RootBeanDefinition definition = new RootBeanDefinition(MongoMappingContext.class);
definition.setRole(ROLE_INFRASTRUCTURE);
definition.setSource(source);
registry.registerBeanDefinition(MAPPING_CONTEXT_BEAN_NAME, definition);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 by the original author(s).
* Copyright 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.
@@ -41,6 +41,7 @@ import com.mongodb.MongoURI;
*
* @author Jon Brisbin
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
@@ -53,7 +54,7 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
throws BeanDefinitionStoreException {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.DB_FACTORY;
return StringUtils.hasText(id) ? id : BeanNames.DB_FACTORY_BEAN_NAME;
}
/*
@@ -70,6 +71,7 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
String uri = element.getAttribute("uri");
String mongoRef = element.getAttribute("mongo-ref");
String dbname = element.getAttribute("dbname");
BeanDefinition userCredentials = getUserCredentialsBeanDefinition(element, parserContext);
// Common setup
@@ -92,19 +94,16 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
dbFactoryBuilder.addConstructorArgValue(registerMongoBeanDefinition(element, parserContext));
}
dbname = StringUtils.hasText(dbname) ? dbname : "db";
dbFactoryBuilder.addConstructorArgValue(dbname);
if (userCredentials != null) {
dbFactoryBuilder.addConstructorArgValue(userCredentials);
}
dbFactoryBuilder.addConstructorArgValue(StringUtils.hasText(dbname) ? dbname : "db");
dbFactoryBuilder.addConstructorArgValue(userCredentials);
dbFactoryBuilder.addConstructorArgValue(element.getAttribute("authentication-dbname"));
BeanDefinitionBuilder writeConcernPropertyEditorBuilder = getWriteConcernPropertyEditorBuilder();
BeanComponentDefinition component = helper.getComponent(writeConcernPropertyEditorBuilder);
parserContext.registerBeanComponent(component);
return (AbstractBeanDefinition) helper.getComponentIdButFallback(dbFactoryBuilder, BeanNames.DB_FACTORY)
return (AbstractBeanDefinition) helper.getComponentIdButFallback(dbFactoryBuilder, BeanNames.DB_FACTORY_BEAN_NAME)
.getBeanDefinition();
}

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.
@@ -16,9 +16,6 @@
package org.springframework.data.mongodb.config;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.data.mongodb.repository.config.MongoRepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryBeanDefinitionParser;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
/**
* {@link org.springframework.beans.factory.xml.NamespaceHandler} for Mongo DB configuration.
@@ -34,10 +31,6 @@ public class MongoNamespaceHandler extends NamespaceHandlerSupport {
*/
public void init() {
RepositoryConfigurationExtension extension = new MongoRepositoryConfigurationExtension();
RepositoryBeanDefinitionParser repositoryBeanDefinitionParser = new RepositoryBeanDefinitionParser(extension);
registerBeanDefinitionParser("repositories", repositoryBeanDefinitionParser);
registerBeanDefinitionParser("mapping-converter", new MappingMongoConverterParser());
registerBeanDefinitionParser("mongo", new MongoParser());
registerBeanDefinitionParser("db-factory", new MongoDbFactoryParser());

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.
@@ -58,7 +58,7 @@ public class MongoParser implements BeanDefinitionParser {
MongoParsingUtils.parseMongoOptions(element, builder);
MongoParsingUtils.parseReplicaSet(element, builder);
String defaultedId = StringUtils.hasText(id) ? id : BeanNames.MONGO;
String defaultedId = StringUtils.hasText(id) ? id : BeanNames.MONGO_BEAN_NAME;
parserContext.pushContainingComponent(new CompositeComponentDefinition("Mongo", source));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 the original author or authors.
* Copyright 2011-2013 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.
@@ -33,6 +33,7 @@ import org.w3c.dom.Element;
*
* @author Mark Pollack
* @author Oliver Gierke
* @author Thomas Darimont
*/
abstract class MongoParsingUtils {
@@ -79,6 +80,8 @@ abstract class MongoParsingUtils {
setPropertyValue(optionsDefBuilder, optionsElement, "write-timeout", "writeTimeout");
setPropertyValue(optionsDefBuilder, optionsElement, "write-fsync", "writeFsync");
setPropertyValue(optionsDefBuilder, optionsElement, "slave-ok", "slaveOk");
setPropertyValue(optionsDefBuilder, optionsElement, "ssl", "ssl");
setPropertyReference(optionsDefBuilder, optionsElement, "ssl-socket-factory-ref", "sslSocketFactory");
mongoBuilder.addPropertyValue("mongoOptions", optionsDefBuilder.getBeanDefinition());
return true;

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.
@@ -35,6 +35,7 @@ import org.w3c.dom.Element;
* {@link BeanDefinitionParser} to parse {@code template} elements into {@link BeanDefinition}s.
*
* @author Martin Baumgartner
* @author Oliver Gierke
*/
class MongoTemplateParser extends AbstractBeanDefinitionParser {
@@ -47,7 +48,7 @@ class MongoTemplateParser extends AbstractBeanDefinitionParser {
throws BeanDefinitionStoreException {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.MONGO_TEMPLATE;
return StringUtils.hasText(id) ? id : BeanNames.MONGO_TEMPLATE_BEAN_NAME;
}
/*
@@ -68,7 +69,7 @@ class MongoTemplateParser extends AbstractBeanDefinitionParser {
if (StringUtils.hasText(dbFactoryRef)) {
mongoTemplateBuilder.addConstructorArgReference(dbFactoryRef);
} else {
mongoTemplateBuilder.addConstructorArgReference(BeanNames.DB_FACTORY);
mongoTemplateBuilder.addConstructorArgReference(BeanNames.DB_FACTORY_BEAN_NAME);
}
if (StringUtils.hasText(converterRef)) {
@@ -80,7 +81,7 @@ class MongoTemplateParser extends AbstractBeanDefinitionParser {
BeanComponentDefinition component = helper.getComponent(writeConcernPropertyEditorBuilder);
parserContext.registerBeanComponent(component);
return (AbstractBeanDefinition) helper.getComponentIdButFallback(mongoTemplateBuilder, BeanNames.MONGO_TEMPLATE)
.getBeanDefinition();
return (AbstractBeanDefinition) helper.getComponentIdButFallback(mongoTemplateBuilder,
BeanNames.MONGO_TEMPLATE_BEAN_NAME).getBeanDefinition();
}
}

View File

@@ -16,12 +16,14 @@
package org.springframework.data.mongodb.config;
import java.beans.PropertyEditorSupport;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.ServerAddress;
@@ -35,6 +37,11 @@ import com.mongodb.ServerAddress;
*/
public class ServerAddressPropertyEditor extends PropertyEditorSupport {
/**
* A port is a number without a leading 0 at the end of the address that is proceeded by just a single :.
*/
private static final String HOST_PORT_SPLIT_PATTERN = "(?<!:):(?=[123456789]\\d*$)";
private static final String COULD_NOT_PARSE_ADDRESS_MESSAGE = "Could not parse address {} '{}'. Check your replica set configuration!";
private static final Logger LOG = LoggerFactory.getLogger(ServerAddressPropertyEditor.class);
/*
@@ -77,22 +84,53 @@ public class ServerAddressPropertyEditor extends PropertyEditorSupport {
*/
private ServerAddress parseServerAddress(String source) {
String[] hostAndPort = StringUtils.delimitedListToStringArray(source.trim(), ":");
if (!StringUtils.hasText(source)) {
LOG.warn(COULD_NOT_PARSE_ADDRESS_MESSAGE, "source", source);
return null;
}
if (!StringUtils.hasText(source) || hostAndPort.length > 2) {
LOG.warn("Could not parse address source '{}'. Check your replica set configuration!", source);
String[] hostAndPort = extractHostAddressAndPort(source.trim());
if (hostAndPort.length > 2) {
LOG.warn(COULD_NOT_PARSE_ADDRESS_MESSAGE, "source", source);
return null;
}
try {
return hostAndPort.length == 1 ? new ServerAddress(hostAndPort[0]) : new ServerAddress(hostAndPort[0],
Integer.parseInt(hostAndPort[1]));
InetAddress hostAddress = InetAddress.getByName(hostAndPort[0]);
Integer port = hostAndPort.length == 1 ? null : Integer.parseInt(hostAndPort[1]);
return port == null ? new ServerAddress(hostAddress) : new ServerAddress(hostAddress, port);
} catch (UnknownHostException e) {
LOG.warn("Could not parse host '{}'. Check your replica set configuration!", hostAndPort[0]);
LOG.warn(COULD_NOT_PARSE_ADDRESS_MESSAGE, "host", hostAndPort[0]);
} catch (NumberFormatException e) {
LOG.warn("Could not parse port '{}'. Check your replica set configuration!", hostAndPort[1]);
LOG.warn(COULD_NOT_PARSE_ADDRESS_MESSAGE, "port", hostAndPort[1]);
}
return null;
}
/**
* Extract the host and port from the given {@link String}.
*
* @param addressAndPortSource must not be {@literal null}.
* @return
*/
private String[] extractHostAddressAndPort(String addressAndPortSource) {
Assert.notNull(addressAndPortSource, "Address and port source must not be null!");
String[] hostAndPort = addressAndPortSource.split(HOST_PORT_SPLIT_PATTERN);
String hostAddress = hostAndPort[0];
if (isHostAddressInIPv6BracketNotation(hostAddress)) {
hostAndPort[0] = hostAddress.substring(1, hostAddress.length() - 1);
}
return hostAndPort;
}
private boolean isHostAddressInIPv6BracketNotation(String hostAddress) {
return hostAddress.startsWith("[") && hostAddress.endsWith("]");
}
}

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,14 +15,17 @@
*/
package org.springframework.data.mongodb.core;
import static org.springframework.data.domain.Sort.Direction.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexField;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.query.Order;
import org.springframework.util.Assert;
import com.mongodb.DBCollection;
@@ -34,9 +37,15 @@ import com.mongodb.MongoException;
*
* @author Mark Pollack
* @author Oliver Gierke
* @author Komi Innocent
* @author Christoph Strobl
*/
public class DefaultIndexOperations implements IndexOperations {
private static final Double ONE = Double.valueOf(1);
private static final Double MINUS_ONE = Double.valueOf(-1);
private static final Collection<String> TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere");
private final MongoOperations mongoOperations;
private final String collectionName;
@@ -135,12 +144,24 @@ public class DefaultIndexOperations implements IndexOperations {
Object value = keyDbObject.get(key);
if (Integer.valueOf(1).equals(value)) {
indexFields.add(IndexField.create(key, Order.ASCENDING));
} else if (Integer.valueOf(-1).equals(value)) {
indexFields.add(IndexField.create(key, Order.DESCENDING));
} else if ("2d".equals(value)) {
if (TWO_D_IDENTIFIERS.contains(value)) {
indexFields.add(IndexField.geo(key));
} else if ("text".equals(value)) {
DBObject weights = (DBObject) ix.get("weights");
for (String fieldName : weights.keySet()) {
indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString())));
}
} else {
Double keyValue = new Double(value.toString());
if (ONE.equals(keyValue)) {
indexFields.add(IndexField.create(key, ASC));
} else if (MINUS_ONE.equals(keyValue)) {
indexFields.add(IndexField.create(key, DESC));
}
}
}
@@ -149,8 +170,8 @@ public class DefaultIndexOperations implements IndexOperations {
boolean unique = ix.containsField("unique") ? (Boolean) ix.get("unique") : false;
boolean dropDuplicates = ix.containsField("dropDups") ? (Boolean) ix.get("dropDups") : false;
boolean sparse = ix.containsField("sparse") ? (Boolean) ix.get("sparse") : false;
indexInfoList.add(new IndexInfo(indexFields, name, unique, dropDuplicates, sparse));
String language = ix.containsField("default_language") ? (String) ix.get("default_language") : "";
indexInfoList.add(new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language));
}
return indexInfoList;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 the original author or authors.
* Copyright 2011-2013 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.Mongo;
* Mongo server administration exposed via JMX annotations
*
* @author Mark Pollack
* @author Thomas Darimont
*/
@ManagedResource(description = "Mongo Admin Operations")
public class MongoAdmin implements MongoAdminOperations {
@@ -34,6 +35,7 @@ public class MongoAdmin implements MongoAdminOperations {
private final Mongo mongo;
private String username;
private String password;
private String authenticationDatabaseName;
public MongoAdmin(Mongo mongo) {
Assert.notNull(mongo);
@@ -82,7 +84,16 @@ public class MongoAdmin implements MongoAdminOperations {
this.password = password;
}
/**
* Sets the authenticationDatabaseName to use to authenticate with the Mongo database.
*
* @param authenticationDatabaseName The authenticationDatabaseName to use.
*/
public void setAuthenticationDatabaseName(String authenticationDatabaseName) {
this.authenticationDatabaseName = authenticationDatabaseName;
}
DB getDB(String databaseName) {
return MongoDbUtils.getDB(mongo, databaseName, new UserCredentials(username, password));
return MongoDbUtils.getDB(mongo, databaseName, new UserCredentials(username, password), authenticationDatabaseName);
}
}

View File

@@ -1,16 +1,34 @@
/*
* 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;
import org.springframework.jmx.export.annotation.ManagedOperation;
/**
* @author Mark Pollack
* @author Oliver Gierke
*/
public interface MongoAdminOperations {
@ManagedOperation
public abstract void dropDatabase(String databaseName);
void dropDatabase(String databaseName);
@ManagedOperation
public abstract void createDatabase(String databaseName);
void createDatabase(String databaseName);
@ManagedOperation
public abstract String getDatabaseStats(String databaseName);
}
String getDatabaseStats(String databaseName);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2011 the original author or authors.
* Copyright 2010-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,14 +26,14 @@ import com.mongodb.DB;
import com.mongodb.Mongo;
/**
* Helper class featuring helper methods for internal MongoDb classes.
* <p/>
* <p>
* Mainly intended for internal use within the framework.
* Helper class featuring helper methods for internal MongoDb classes. Mainly intended for internal use within the
* framework.
*
* @author Thomas Risberg
* @author Graeme Rocher
* @author Oliver Gierke
* @author Randy Watler
* @author Thomas Darimont
* @since 1.0
*/
public abstract class MongoDbUtils {
@@ -55,7 +55,7 @@ public abstract class MongoDbUtils {
* @return the {@link DB} connection
*/
public static DB getDB(Mongo mongo, String databaseName) {
return doGetDB(mongo, databaseName, UserCredentials.NO_CREDENTIALS, true);
return doGetDB(mongo, databaseName, UserCredentials.NO_CREDENTIALS, true, databaseName);
}
/**
@@ -67,15 +67,22 @@ public abstract class MongoDbUtils {
* @return the {@link DB} connection
*/
public static DB getDB(Mongo mongo, String databaseName, UserCredentials credentials) {
return getDB(mongo, databaseName, credentials, databaseName);
}
public static DB getDB(Mongo mongo, String databaseName, UserCredentials credentials,
String authenticationDatabaseName) {
Assert.notNull(mongo, "No Mongo instance specified!");
Assert.hasText(databaseName, "Database name must be given!");
Assert.notNull(credentials, "Credentials must not be null, use UserCredentials.NO_CREDENTIALS!");
Assert.hasText(authenticationDatabaseName, "Authentication database name must not be null or empty!");
return doGetDB(mongo, databaseName, credentials, true);
return doGetDB(mongo, databaseName, credentials, true, authenticationDatabaseName);
}
private static DB doGetDB(Mongo mongo, String databaseName, UserCredentials credentials, boolean allowCreate) {
private static DB doGetDB(Mongo mongo, String databaseName, UserCredentials credentials, boolean allowCreate,
String authenticationDatabaseName) {
DbHolder dbHolder = (DbHolder) TransactionSynchronizationManager.getResource(mongo);
@@ -104,14 +111,16 @@ public abstract class MongoDbUtils {
DB db = mongo.getDB(databaseName);
boolean credentialsGiven = credentials.hasUsername() && credentials.hasPassword();
synchronized (db) {
DB authDb = databaseName.equals(authenticationDatabaseName) ? db : mongo.getDB(authenticationDatabaseName);
if (credentialsGiven && !db.isAuthenticated()) {
synchronized (authDb) {
if (credentialsGiven && !authDb.isAuthenticated()) {
String username = credentials.getUsername();
String password = credentials.hasPassword() ? credentials.getPassword() : null;
if (!db.authenticate(username, password == null ? null : password.toCharArray())) {
if (!authDb.authenticate(username, password == null ? null : password.toCharArray())) {
throw new CannotGetMongoDbConnectionException("Failed to authenticate to database [" + databaseName + "], "
+ credentials.toString(), databaseName, credentials);
}
@@ -131,8 +140,11 @@ public abstract class MongoDbUtils {
holderToUse.addDB(databaseName, db);
}
TransactionSynchronizationManager.registerSynchronization(new MongoSynchronization(holderToUse, mongo));
holderToUse.setSynchronizedWithTransaction(true);
// synchronize holder only if not yet synchronized
if (!holderToUse.isSynchronizedWithTransaction()) {
TransactionSynchronizationManager.registerSynchronization(new MongoSynchronization(holderToUse, mongo));
holderToUse.setSynchronizedWithTransaction(true);
}
if (holderToUse != dbHolder) {
TransactionSynchronizationManager.bindResource(mongo, holderToUse);

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,16 +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);
}
if (ex instanceof MongoServerSelectionException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
if (ex instanceof MongoTimeoutException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
@@ -64,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

@@ -1,6 +1,6 @@
/*
* Copyright 2010-2013 the original author or authors.
*
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -19,12 +19,11 @@ import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.geo.GeoResult;
import org.springframework.data.mongodb.core.geo.GeoResults;
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
@@ -49,6 +48,9 @@ import com.mongodb.WriteResult;
* @author Mark Pollack
* @author Oliver Gierke
* @author Tobias Trelle
* @author Chuong Ngo
* @author Christoph Strobl
* @author Thomas Darimont
*/
public interface MongoOperations {
@@ -412,7 +414,7 @@ public interface MongoOperations {
MapReduceOptions mapReduceOptions, Class<T> entityClass);
/**
* Returns {@link GeoResult} for all entities matching the given {@link NearQuery}. Will consider entity mapping
* Returns {@link GeoResults} for all entities matching the given {@link NearQuery}. Will consider entity mapping
* information to determine the collection the query is ran against.
*
* @param near must not be {@literal null}.
@@ -422,7 +424,7 @@ public interface MongoOperations {
<T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass);
/**
* Returns {@link GeoResult} for all entities matching the given {@link NearQuery}.
* Returns {@link GeoResults} for all entities matching the given {@link NearQuery}.
*
* @param near must not be {@literal null}.
* @param entityClass must not be {@literal null}.
@@ -467,10 +469,32 @@ public interface MongoOperations {
*/
<T> T findOne(Query query, Class<T> entityClass, String collectionName);
/**
* Determine result of given {@link Query} contains at least one element.
*
* @param query the {@link Query} class that specifies the criteria used to find a record.
* @param collectionName name of the collection to check for objects.
* @return
*/
boolean exists(Query query, String collectionName);
/**
* Determine result of given {@link Query} contains at least one element.
*
* @param query the {@link Query} class that specifies the criteria used to find a record.
* @param entityClass the parameterized type.
* @return
*/
boolean exists(Query query, Class<?> entityClass);
/**
* Determine result of given {@link Query} contains at least one element.
*
* @param query the {@link Query} class that specifies the criteria used to find a record.
* @param entityClass the parameterized type.
* @param collectionName name of the collection to check for objects.
* @return
*/
boolean exists(Query query, Class<?> entityClass, String collectionName);
/**
@@ -528,12 +552,58 @@ public interface MongoOperations {
*/
<T> T findById(Object id, Class<T> entityClass, String collectionName);
/**
* Triggers <a href="http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param entityClass the parameterized type.
* @return
*/
<T> T findAndModify(Query query, Update update, Class<T> entityClass);
/**
* Triggers <a href="http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param entityClass the parameterized type.
* @param collectionName the collection to query.
* @return
*/
<T> T findAndModify(Query query, Update update, Class<T> entityClass, String collectionName);
/**
* Triggers <a href="http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
* {@link FindAndModifyOptions} into account.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param options the {@link FindAndModifyOptions} holding additional information.
* @param entityClass the parameterized type.
* @return
*/
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass);
/**
* Triggers <a href="http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
* {@link FindAndModifyOptions} into account.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param options the {@link FindAndModifyOptions} holding additional information.
* @param entityClass the parameterized type.
* @param collectionName the collection to query.
* @return
*/
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
String collectionName);
@@ -597,9 +667,9 @@ public interface MongoOperations {
* <p/>
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Spring 3.0's new Type Conversion API.
* See <a href="http://static.springsource.org/spring/docs/3.0.x/reference/validation.html#core-convert">Spring 3 Type
* Conversion"</a> for more details.
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See <a
* href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert"
* >Spring's Type Conversion"</a> for more details.
* <p/>
* <p/>
* Insert is used to initially store the object into the database. To update an existing object use the save method.
@@ -654,9 +724,9 @@ public interface MongoOperations {
* <p/>
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Spring 3.0's new Type Conversion API.
* See <a href="http://static.springsource.org/spring/docs/3.0.x/reference/validation.html#core-convert">Spring 3 Type
* Conversion"</a> for more details.
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See <a
* href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert"
* >Spring's Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection
*/
@@ -671,9 +741,9 @@ public interface MongoOperations {
* <p/>
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Spring 3.0's new Type Cobnversion API.
* See <a href="http://static.springsource.org/spring/docs/3.0.x/reference/validation.html#core-convert">Spring 3 Type
* Conversion"</a> for more details.
* property type will be handled by Spring's BeanWrapper class that leverages Type Cobnversion API. See <a
* http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert">Spring's
* Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection
* @param collectionName name of the collection to store the object in
@@ -703,6 +773,18 @@ public interface MongoOperations {
*/
WriteResult upsert(Query query, Update update, String collectionName);
/**
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
* combining the query document and the update document.
*
* @param query the query document that specifies the criteria used to select a record to be upserted
* @param update the update document that contains the updated object or $ operators to manipulate the existing object
* @param entityClass class of the pojo to be operated on
* @param collectionName name of the collection to update the object in
* @return the WriteResult which lets you access the results of the previous write.
*/
WriteResult upsert(Query query, Update update, Class<?> entityClass, String collectionName);
/**
* Updates the first object that is found in the collection of the entity class that matches the query document with
* the provided update document.
@@ -727,6 +809,19 @@ public interface MongoOperations {
*/
WriteResult updateFirst(Query query, Update update, String collectionName);
/**
* Updates the first object that is found in the specified collection that matches the query document criteria with
* the provided updated document.
*
* @param query the query document that specifies the criteria used to select a record to be updated
* @param update the update document that contains the updated object or $ operators to manipulate the existing
* object.
* @param entityClass class of the pojo to be operated on
* @param collectionName name of the collection to update the object in
* @return the WriteResult which lets you access the results of the previous write.
*/
WriteResult updateFirst(Query query, Update update, Class<?> entityClass, String collectionName);
/**
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
* with the provided updated document.
@@ -751,12 +846,25 @@ public interface MongoOperations {
*/
WriteResult updateMulti(Query query, Update update, String collectionName);
/**
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
* with the provided updated document.
*
* @param query the query document that specifies the criteria used to select a record to be updated
* @param update the update document that contains the updated object or $ operators to manipulate the existing
* object.
* @param entityClass class of the pojo to be operated on
* @param collectionName name of the collection to update the object in
* @return the WriteResult which lets you access the results of the previous write.
*/
WriteResult updateMulti(final Query query, final Update update, Class<?> entityClass, String collectionName);
/**
* Remove the given object from the collection by id.
*
* @param object
*/
void remove(Object object);
WriteResult remove(Object object);
/**
* Removes the given object from the given collection.
@@ -764,7 +872,7 @@ public interface MongoOperations {
* @param object
* @param collection must not be {@literal null} or empty.
*/
void remove(Object object, String collection);
WriteResult remove(Object object, String collection);
/**
* Remove all documents that match the provided query document criteria from the the collection used to store the
@@ -773,9 +881,17 @@ public interface MongoOperations {
* @param query
* @param entityClass
*/
void remove(Query query, Class<?> entityClass);
WriteResult remove(Query query, Class<?> entityClass);
void remove(Query query, Class<?> entityClass, String collectionName);
/**
* Remove all documents that match the provided query document criteria from the the collection used to store the
* entityClass. The Class parameter is also used to help convert the Id of the object if it is present in the query.
*
* @param query
* @param entityClass
* @param collectionName
*/
WriteResult remove(Query query, Class<?> entityClass, String collectionName);
/**
* Remove all documents from the specified collection that match the provided query document criteria. There is no
@@ -784,7 +900,40 @@ public interface MongoOperations {
* @param query the query document that specifies the criteria used to remove a record
* @param collectionName name of the collection where the objects will removed
*/
void remove(Query query, String collectionName);
WriteResult remove(Query query, String collectionName);
/**
* Returns and removes all documents form the specified collection that match the provided query.
*
* @param query
* @param collectionName
* @return
* @since 1.5
*/
<T> List<T> findAllAndRemove(Query query, String collectionName);
/**
* Returns and removes all documents matching the given query form the collection used to store the entityClass.
*
* @param query
* @param entityClass
* @return
* @since 1.5
*/
<T> List<T> findAllAndRemove(Query query, Class<T> entityClass);
/**
* Returns and removes all documents that match the provided query document criteria from the the collection used to
* store the entityClass. The Class parameter is also used to help convert the Id of the object if it is present in
* the query.
*
* @param query
* @param entityClass
* @param collectionName
* @return
* @since 1.5
*/
<T> List<T> findAllAndRemove(Query query, Class<T> entityClass, String collectionName);
/**
* Returns the underlying {@link MongoConverter}.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2011 the original author or authors.
* Copyright 2010-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,129 +15,92 @@
*/
package org.springframework.data.mongodb.core;
import com.mongodb.MongoOptions;
import javax.net.ssl.SSLSocketFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import com.mongodb.MongoOptions;
/**
* A factory bean for construction of a MongoOptions instance
* A factory bean for construction of a {@link MongoOptions} instance.
*
* @author Graeme Rocher
* @Author Mark Pollack
* @author Mark Pollack
* @author Mike Saavedra
* @author Thomas Darimont
*/
@SuppressWarnings("deprecation")
public class MongoOptionsFactoryBean implements FactoryBean<MongoOptions>, InitializingBean {
private static final MongoOptions MONGO_OPTIONS = new MongoOptions();
/**
* number of connections allowed per host will block if run out
*/
private int connectionsPerHost = MONGO_OPTIONS.connectionsPerHost;
private static final MongoOptions DEFAULT_MONGO_OPTIONS = new MongoOptions();
private int connectionsPerHost = DEFAULT_MONGO_OPTIONS.connectionsPerHost;
private int threadsAllowedToBlockForConnectionMultiplier = DEFAULT_MONGO_OPTIONS.threadsAllowedToBlockForConnectionMultiplier;
private int maxWaitTime = DEFAULT_MONGO_OPTIONS.maxWaitTime;
private int connectTimeout = DEFAULT_MONGO_OPTIONS.connectTimeout;
private int socketTimeout = DEFAULT_MONGO_OPTIONS.socketTimeout;
private boolean socketKeepAlive = DEFAULT_MONGO_OPTIONS.socketKeepAlive;
private boolean autoConnectRetry = DEFAULT_MONGO_OPTIONS.autoConnectRetry;
private long maxAutoConnectRetryTime = DEFAULT_MONGO_OPTIONS.maxAutoConnectRetryTime;
private int writeNumber = DEFAULT_MONGO_OPTIONS.w;
private int writeTimeout = DEFAULT_MONGO_OPTIONS.wtimeout;
private boolean writeFsync = DEFAULT_MONGO_OPTIONS.fsync;
private boolean slaveOk = DEFAULT_MONGO_OPTIONS.slaveOk;
private boolean ssl;
private SSLSocketFactory sslSocketFactory;
private MongoOptions options;
/**
* multiplier for connectionsPerHost for # of threads that can block if connectionsPerHost is 10, and
* threadsAllowedToBlockForConnectionMultiplier is 5, then 50 threads can block more than that and an exception will
* be throw
*/
private int threadsAllowedToBlockForConnectionMultiplier = MONGO_OPTIONS.threadsAllowedToBlockForConnectionMultiplier;
/**
* max wait time of a blocking thread for a connection
*/
private int maxWaitTime = MONGO_OPTIONS.maxWaitTime;
/**
* connect timeout in milliseconds. 0 is default and infinite
*/
private int connectTimeout = MONGO_OPTIONS.connectTimeout;
/**
* socket timeout. 0 is default and infinite
*/
private int socketTimeout = MONGO_OPTIONS.socketTimeout;
/**
* This controls whether or not to have socket keep alive turned on (SO_KEEPALIVE).
* Configures the maximum number of connections allowed per host until we will block.
*
* defaults to false
*/
public boolean socketKeepAlive = MONGO_OPTIONS.socketKeepAlive;
/**
* this controls whether or not on a connect, the system retries automatically
*/
private boolean autoConnectRetry = MONGO_OPTIONS.autoConnectRetry;
private long maxAutoConnectRetryTime = MONGO_OPTIONS.maxAutoConnectRetryTime;
/**
* This specifies the number of servers to wait for on the write operation, and exception raising behavior.
*
* Defaults to 0.
*/
private int writeNumber;
/**
* This controls timeout for write operations in milliseconds.
*
* Defaults to 0 (indefinite). Greater than zero is number of milliseconds to wait.
*/
private int writeTimeout;
/**
* This controls whether or not to fsync.
*
* Defaults to false.
*/
private boolean writeFsync;
/**
* Specifies if the driver is allowed to read from secondaries or slaves.
*
* Defaults to false
*/
@SuppressWarnings("deprecation")
private boolean slaveOk = MONGO_OPTIONS.slaveOk;
/**
* number of connections allowed per host will block if run out
* @param connectionsPerHost
*/
public void setConnectionsPerHost(int connectionsPerHost) {
this.connectionsPerHost = connectionsPerHost;
}
/**
* multiplier for connectionsPerHost for # of threads that can block if connectionsPerHost is 10, and
* threadsAllowedToBlockForConnectionMultiplier is 5, then 50 threads can block more than that and an exception will
* be throw
* A multiplier for connectionsPerHost for # of threads that can block a connection. If connectionsPerHost is 10, and
* threadsAllowedToBlockForConnectionMultiplier is 5, then 50 threads can block. If more threads try to block an
* exception will be thrown.
*
* @param threadsAllowedToBlockForConnectionMultiplier
*/
public void setThreadsAllowedToBlockForConnectionMultiplier(int threadsAllowedToBlockForConnectionMultiplier) {
this.threadsAllowedToBlockForConnectionMultiplier = threadsAllowedToBlockForConnectionMultiplier;
}
/**
* max wait time of a blocking thread for a connection
* Max wait time of a blocking thread for a connection.
*
* @param maxWaitTime
*/
public void setMaxWaitTime(int maxWaitTime) {
this.maxWaitTime = maxWaitTime;
}
/**
* connect timeout in milliseconds. 0 is default and infinite
* Configures the connect timeout in milliseconds. Defaults to 0 (infinite time).
*
* @param connectTimeout
*/
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
/**
* socket timeout. 0 is default and infinite
* Configures the socket timeout. Defaults to 0 (infinite time).
*
* @param socketTimeout
*/
public void setSocketTimeout(int socketTimeout) {
this.socketTimeout = socketTimeout;
}
/**
* This controls whether or not to have socket keep alive
* Configures whether or not to have socket keep alive turned on (SO_KEEPALIVE). Defaults to {@literal false}.
*
* @param socketKeepAlive
*/
@@ -152,7 +115,7 @@ public class MongoOptionsFactoryBean implements FactoryBean<MongoOptions>, Initi
* <li>-1 = don't even report network errors</li>
* <li>0 = default, don't call getLastError by default</li>
* <li>1 = basic, call getLastError, but don't wait for slaves</li>
* <li>2+= wait for slaves</li>
* <li>2 += wait for slaves</li>
* </ul>
*
* @param writeNumber the number of servers to wait for on the write operation, and exception raising behavior.
@@ -162,33 +125,33 @@ public class MongoOptionsFactoryBean implements FactoryBean<MongoOptions>, Initi
}
/**
* This controls timeout for write operations in milliseconds. The 'wtimeout' option to the getlasterror command.
* Configures the timeout for write operations in milliseconds. This defaults to {@literal 0} (indefinite).
*
* @param writeTimeout Defaults to 0 (indefinite). Greater than zero is number of milliseconds to wait.
* @param writeTimeout
*/
public void setWriteTimeout(int writeTimeout) {
this.writeTimeout = writeTimeout;
}
/**
* This controls whether or not to fsync. The 'fsync' option to the getlasterror command. Defaults to false.
* Configures whether or not to fsync. The 'fsync' option to the getlasterror command. Defaults to {@literal false}.
*
* @param writeFsync to fsync on write (true), otherwise false.
* @param writeFsync to fsync on <code>write (true)<code>, otherwise {@literal false}.
*/
public void setWriteFsync(boolean writeFsync) {
this.writeFsync = writeFsync;
}
/**
* this controls whether or not on a connect, the system retries automatically
* Configures whether or not the system retries automatically on a failed connect. This defaults to {@literal false}.
*/
public void setAutoConnectRetry(boolean autoConnectRetry) {
this.autoConnectRetry = autoConnectRetry;
}
/**
* 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.
* Configures the maximum amount of time in millisecons to spend retrying to open connection to the same server. This
* defaults to {@literal 0}, which means to use the default {@literal 15s} if {@link #autoConnectRetry} is on.
*
* @param maxAutoConnectRetryTime the maxAutoConnectRetryTime to set
*/
@@ -197,7 +160,7 @@ public class MongoOptionsFactoryBean implements FactoryBean<MongoOptions>, Initi
}
/**
* Specifies if the driver is allowed to read from secondaries or slaves. Defaults to false.
* Specifies if the driver is allowed to read from secondaries or slaves. Defaults to {@literal false}.
*
* @param slaveOk true if the driver should read from secondaries or slaves.
*/
@@ -205,32 +168,81 @@ public class MongoOptionsFactoryBean implements FactoryBean<MongoOptions>, Initi
this.slaveOk = slaveOk;
}
@SuppressWarnings("deprecation")
/**
* Specifies if the driver should use an SSL connection to Mongo. This defaults to {@literal false}. By default
* {@link SSLSocketFactory#getDefault()} will be used. See {@link #setSslSocketFactory(SSLSocketFactory)} if you want
* to configure a custom factory.
*
* @param ssl true if the driver should use an SSL connection.
* @see #setSslSocketFactory(SSLSocketFactory)
*/
public void setSsl(boolean ssl) {
this.ssl = ssl;
}
/**
* Specifies the {@link SSLSocketFactory} to use for creating SSL connections to Mongo. Defaults to
* {@link SSLSocketFactory#getDefault()}. Implicitly activates {@link #setSsl(boolean)} if a non-{@literal null} value
* is given.
*
* @param sslSocketFactory the sslSocketFactory to use.
* @see #setSsl(boolean)
*/
public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
setSsl(sslSocketFactory != null);
this.sslSocketFactory = sslSocketFactory;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() {
MONGO_OPTIONS.connectionsPerHost = connectionsPerHost;
MONGO_OPTIONS.threadsAllowedToBlockForConnectionMultiplier = threadsAllowedToBlockForConnectionMultiplier;
MONGO_OPTIONS.maxWaitTime = maxWaitTime;
MONGO_OPTIONS.connectTimeout = connectTimeout;
MONGO_OPTIONS.socketTimeout = socketTimeout;
MONGO_OPTIONS.socketKeepAlive = socketKeepAlive;
MONGO_OPTIONS.autoConnectRetry = autoConnectRetry;
MONGO_OPTIONS.maxAutoConnectRetryTime = maxAutoConnectRetryTime;
MONGO_OPTIONS.slaveOk = slaveOk;
MONGO_OPTIONS.w = writeNumber;
MONGO_OPTIONS.wtimeout = writeTimeout;
MONGO_OPTIONS.fsync = writeFsync;
MongoOptions options = new MongoOptions();
options.connectionsPerHost = connectionsPerHost;
options.threadsAllowedToBlockForConnectionMultiplier = threadsAllowedToBlockForConnectionMultiplier;
options.maxWaitTime = maxWaitTime;
options.connectTimeout = connectTimeout;
options.socketTimeout = socketTimeout;
options.socketKeepAlive = socketKeepAlive;
options.autoConnectRetry = autoConnectRetry;
options.maxAutoConnectRetryTime = maxAutoConnectRetryTime;
options.slaveOk = slaveOk;
options.w = writeNumber;
options.wtimeout = writeTimeout;
options.fsync = writeFsync;
if (ssl) {
options.setSocketFactory(sslSocketFactory != null ? sslSocketFactory : SSLSocketFactory.getDefault());
}
this.options = options;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
public MongoOptions getObject() {
return MONGO_OPTIONS;
return this.options;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<?> getObjectType() {
return MongoOptions.class;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
public boolean isSingleton() {
return true;
}
}

View File

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

View File

@@ -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 java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;
@@ -46,11 +47,17 @@ import org.springframework.core.io.ResourceLoader;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.annotation.Id;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
@@ -59,15 +66,13 @@ import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.Fields;
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
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.convert.MongoWriter;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.geo.Distance;
import org.springframework.data.mongodb.core.geo.GeoResult;
import org.springframework.data.mongodb.core.geo.GeoResults;
import org.springframework.data.mongodb.core.geo.Metric;
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@@ -92,6 +97,7 @@ import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.jca.cci.core.ConnectionCallback;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
@@ -123,7 +129,10 @@ import com.mongodb.util.JSONParseException;
* @author Tobias Trelle
* @author Sebastian Herold
* @author Thomas Darimont
* @author Chuong Ngo
* @author Christoph Strobl
*/
@SuppressWarnings("deprecation")
public class MongoTemplate implements MongoOperations, ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoTemplate.class);
@@ -144,7 +153,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
private final MongoConverter mongoConverter;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final MongoDbFactory mongoDbFactory;
private final MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
private final PersistenceExceptionTranslator exceptionTranslator;
private final QueryMapper queryMapper;
private final UpdateMapper updateMapper;
@@ -198,6 +207,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
Assert.notNull(mongoDbFactory);
this.mongoDbFactory = mongoDbFactory;
this.exceptionTranslator = mongoDbFactory.getExceptionTranslator();
this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter;
this.queryMapper = new QueryMapper(this.mongoConverter);
this.updateMapper = new UpdateMapper(this.mongoConverter);
@@ -346,7 +356,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));
}
/**
@@ -364,12 +374,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
Assert.notNull(query);
DBObject queryObject = query.getQueryObject();
DBObject queryObject = queryMapper.getMappedObject(query.getQueryObject(), null);
DBObject sortObject = query.getSortObject();
DBObject fieldsObject = query.getFieldsObject();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Executing query: %s sort: %s fields: %s in collection: $s",
LOGGER.debug(String.format("Executing query: %s sort: %s fields: %s in collection: %s",
serializeToJsonSafely(queryObject), sortObject, fieldsObject, collectionName));
}
@@ -524,7 +534,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) {
@@ -565,8 +575,26 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
mongoConverter, entityClass), near.getMetric());
List<GeoResult<T>> result = new ArrayList<GeoResult<T>>(results.size());
int index = 0;
int elementsToSkip = near.getSkip() != null ? near.getSkip() : 0;
for (Object element : results) {
result.add(callback.doWith((DBObject) element));
/*
* As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries
* we skip the elements ourselves to avoid at least the document 2 object mapping overhead.
*
* @see https://jira.mongodb.org/browse/SERVER-3925
*/
if (index >= elementsToSkip) {
result.add(callback.doWith((DBObject) element));
}
index++;
}
if (elementsToSkip > 0) {
// as we skipped some elements we have to calculate the averageDistance ourselves:
return new GeoResults<T>(result, near.getMetric());
}
DBObject stats = (DBObject) commandResult.get("stats");
@@ -588,8 +616,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
@@ -600,8 +628,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) {
@@ -681,10 +710,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
initializeVersionProperty(objectToSave);
BasicDBObject dbDoc = new BasicDBObject();
maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave));
writer.write(objectToSave, dbDoc);
DBObject dbDoc = toDbObject(objectToSave, writer);
maybeEmitEvent(new BeforeSaveEvent<T>(objectToSave, dbDoc));
Object id = insertDBObject(collectionName, dbDoc, objectToSave.getClass());
@@ -693,14 +721,34 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
maybeEmitEvent(new AfterSaveEvent<T>(objectToSave, dbDoc));
}
/**
* @param objectToSave
* @param writer
* @return
*/
private <T> DBObject toDbObject(T objectToSave, MongoWriter<T> writer) {
if (!(objectToSave instanceof String)) {
DBObject dbDoc = new BasicDBObject();
writer.write(objectToSave, dbDoc);
return dbDoc;
} else {
try {
return (DBObject) JSON.parse((String) objectToSave);
} catch (JSONParseException e) {
throw new MappingException("Could not parse given String to save into a JSON document!", e);
}
}
}
private void initializeVersionProperty(Object entity) {
MongoPersistentEntity<?> mongoPersistentEntity = getPersistentEntity(entity.getClass());
if (mongoPersistentEntity != null && mongoPersistentEntity.hasVersionProperty()) {
BeanWrapper<PersistentEntity<Object, ?>, Object> wrapper = BeanWrapper.create(entity,
this.mongoConverter.getConversionService());
wrapper.setProperty(mongoPersistentEntity.getVersionProperty(), 0);
ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(
mongoPersistentEntity.getPropertyAccessor(entity), mongoConverter.getConversionService());
accessor.setProperty(mongoPersistentEntity.getVersionProperty(), 0);
}
}
@@ -793,12 +841,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
private <T> void doSaveVersioned(T objectToSave, MongoPersistentEntity<?> entity, String collectionName) {
BeanWrapper<PersistentEntity<T, ?>, T> beanWrapper = BeanWrapper.create(objectToSave,
this.mongoConverter.getConversionService());
ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor(
entity.getPropertyAccessor(objectToSave), mongoConverter.getConversionService());
MongoPersistentProperty idProperty = entity.getIdProperty();
MongoPersistentProperty versionProperty = entity.getVersionProperty();
Number version = beanWrapper.getProperty(versionProperty, Number.class, !versionProperty.usePropertyAccess());
Object version = convertingAccessor.getProperty(versionProperty);
Number versionNumber = convertingAccessor.getProperty(versionProperty, Number.class);
// Fresh instance -> initialize version property
if (version == null) {
@@ -808,12 +858,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
assertUpdateableIdIfNotSet(objectToSave);
// Create query for entity with the id and old version
Object id = beanWrapper.getProperty(idProperty);
Object id = convertingAccessor.getProperty(idProperty);
Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version));
// Bump version number
Number number = beanWrapper.getProperty(versionProperty, Number.class, false);
beanWrapper.setProperty(versionProperty, number.longValue() + 1);
convertingAccessor.setProperty(versionProperty, versionNumber.longValue() + 1);
BasicDBObject dbObject = new BasicDBObject();
@@ -832,19 +881,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
assertUpdateableIdIfNotSet(objectToSave);
DBObject dbDoc = new BasicDBObject();
maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave));
if (!(objectToSave instanceof String)) {
writer.write(objectToSave, dbDoc);
} else {
try {
dbDoc = (DBObject) JSON.parse((String) objectToSave);
} catch (JSONParseException e) {
throw new MappingException("Could not parse given String to save into a JSON document!", e);
}
}
DBObject dbDoc = toDbObject(objectToSave, writer);
maybeEmitEvent(new BeforeSaveEvent<T>(objectToSave, dbDoc));
Object id = saveDBObject(collectionName, dbDoc, objectToSave.getClass());
@@ -928,6 +967,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
return doUpdate(collectionName, query, update, null, true, false);
}
public WriteResult upsert(Query query, Update update, Class<?> entityClass, String collectionName) {
return doUpdate(collectionName, query, update, entityClass, true, false);
}
public WriteResult updateFirst(Query query, Update update, Class<?> entityClass) {
return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, false);
}
@@ -936,6 +979,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
return doUpdate(collectionName, query, update, null, false, false);
}
public WriteResult updateFirst(Query query, Update update, Class<?> entityClass, String collectionName) {
return doUpdate(collectionName, query, update, entityClass, false, false);
}
public WriteResult updateMulti(Query query, Update update, Class<?> entityClass) {
return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, true);
}
@@ -944,6 +991,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
return doUpdate(collectionName, query, update, null, false, true);
}
public WriteResult updateMulti(final Query query, final Update update, Class<?> entityClass, String collectionName) {
return doUpdate(collectionName, query, update, entityClass, false, true);
}
protected WriteResult doUpdate(final String collectionName, final Query query, final Update update,
final Class<?> entityClass, final boolean upsert, final boolean multi) {
@@ -952,14 +1003,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);
increaseVersionForUpdateIfNecessary(entity, update);
DBObject queryObj = query == null ? new BasicDBObject() : queryMapper.getMappedObject(query.getQueryObject(),
entity);
DBObject updateObj = update == null ? new BasicDBObject() : updateMapper.getMappedObject(
update.getUpdateObject(), entity);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Calling update using query: " + queryObj + " and update: " + updateObj + " in collection: "
+ collectionName);
LOGGER.debug(String.format("Calling update using query: %s and update: %s in collection: %s",
serializeToJsonSafely(queryObj), serializeToJsonSafely(updateObj), collectionName));
}
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName,
@@ -969,7 +1022,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
: collection.update(queryObj, updateObj, upsert, multi, writeConcernToUse);
if (entity != null && entity.hasVersionProperty() && !multi) {
if (writeResult.getN() == 0) {
if (writeResult.getN() == 0 && dbObjectContainsVersionProperty(queryObj, entity)) {
throw new OptimisticLockingFailureException("Optimistic lock exception on saving entity: "
+ updateObj.toMap().toString() + " to collection " + collectionName);
}
@@ -981,24 +1034,71 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
});
}
public void remove(Object object) {
private void increaseVersionForUpdateIfNecessary(MongoPersistentEntity<?> persistentEntity, Update update) {
if (object == null) {
return;
if (persistentEntity != null && persistentEntity.hasVersionProperty()) {
String versionFieldName = persistentEntity.getVersionProperty().getFieldName();
if (!update.modifies(versionFieldName)) {
update.inc(versionFieldName, 1L);
}
}
remove(getIdQueryFor(object), object.getClass());
}
public void remove(Object object, String collection) {
private boolean dbObjectContainsVersionProperty(DBObject dbObject, MongoPersistentEntity<?> persistentEntity) {
if (persistentEntity == null || !persistentEntity.hasVersionProperty()) {
return false;
}
return dbObject.containsField(persistentEntity.getVersionProperty().getFieldName());
}
public WriteResult remove(Object object) {
if (object == null) {
return null;
}
return remove(getIdQueryFor(object), object.getClass());
}
public WriteResult remove(Object object, String collection) {
Assert.hasText(collection);
if (object == null) {
return;
return null;
}
doRemove(collection, getIdQueryFor(object), object.getClass());
return doRemove(collection, getIdQueryFor(object), object.getClass());
}
/**
* 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 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();
if (idProp == null || entity == null) {
throw new MappingException("No id property found for object of type " + objectType);
}
Object idValue = entity.getPropertyAccessor(object).getProperty(idProp);
return Collections.singletonMap(idProp.getFieldName(), idValue).entrySet().iterator().next();
}
/**
@@ -1009,21 +1109,31 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
*/
private Query getIdQueryFor(Object object) {
Assert.notNull(object);
Entry<String, Object> id = extractIdPropertyAndValue(object);
return new Query(where(id.getKey()).is(id.getValue()));
}
Class<?> objectType = object.getClass();
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(objectType);
MongoPersistentProperty idProp = entity == null ? null : entity.getIdProperty();
/**
* Returns a {@link Query} for the given entities by their ids.
*
* @param objects must not be {@literal null} or {@literal empty}.
* @return
*/
private Query getIdInQueryFor(Collection<?> objects) {
if (idProp == null) {
throw new MappingException("No id property found for object of type " + objectType);
Assert.notEmpty(objects, "Cannot create Query for empty collection.");
Iterator<?> it = objects.iterator();
Entry<String, Object> firstEntry = extractIdPropertyAndValue(it.next());
ArrayList<Object> ids = new ArrayList<Object>(objects.size());
ids.add(firstEntry.getValue());
while (it.hasNext()) {
ids.add(extractIdPropertyAndValue(it.next()).getValue());
}
ConversionService service = mongoConverter.getConversionService();
Object idProperty = null;
idProperty = BeanWrapper.create(object, service).getProperty(idProp, Object.class, true);
return new Query(where(idProp.getFieldName()).is(idProperty));
return new Query(where(firstEntry.getKey()).in(ids));
}
private void assertUpdateableIdIfNotSet(Object entity) {
@@ -1031,12 +1141,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entity.getClass());
MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty();
if (idProperty == null) {
if (idProperty == null || persistentEntity == null) {
return;
}
ConversionService service = mongoConverter.getConversionService();
Object idValue = BeanWrapper.create(entity, service).getProperty(idProperty, Object.class, true);
Object idValue = persistentEntity.getPropertyAccessor(entity).getProperty(idProperty);
if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) {
throw new InvalidDataAccessApiUsageException(String.format(
@@ -1045,19 +1154,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
}
public void remove(Query query, String collectionName) {
remove(query, null, collectionName);
public WriteResult remove(Query query, String collectionName) {
return remove(query, null, collectionName);
}
public void remove(Query query, Class<?> entityClass) {
remove(query, entityClass, determineCollectionName(entityClass));
public WriteResult remove(Query query, Class<?> entityClass) {
return remove(query, entityClass, determineCollectionName(entityClass));
}
public void remove(Query query, Class<?> entityClass, String collectionName) {
doRemove(collectionName, query, entityClass);
public WriteResult remove(Query query, Class<?> entityClass, String collectionName) {
return doRemove(collectionName, query, entityClass);
}
protected <T> void doRemove(final String collectionName, final Query query, final Class<T> entityClass) {
protected <T> WriteResult doRemove(final String collectionName, final Query query, final Class<T> entityClass) {
if (query == null) {
throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null!");
@@ -1068,8 +1177,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
final DBObject queryObject = query.getQueryObject();
final MongoPersistentEntity<?> entity = getPersistentEntity(entityClass);
execute(collectionName, new CollectionCallback<Void>() {
public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
return execute(collectionName, new CollectionCallback<WriteResult>() {
public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException {
maybeEmitEvent(new BeforeDeleteEvent<T>(queryObject, entityClass));
@@ -1080,16 +1189,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Remove using query: {} in collection: {}.", new Object[] { dboq, collection.getName() });
LOGGER.debug("Remove using query: {} in collection: {}.",
new Object[] { serializeToJsonSafely(dboq), collection.getName() });
}
WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) : collection.remove(dboq,
writeConcernToUse);
handleAnyWriteResultErrors(wr, dboq, MongoActionOperation.REMOVE);
maybeEmitEvent(new AfterDeleteEvent<T>(queryObject, entityClass));
return null;
return wr;
}
});
}
@@ -1123,6 +1234,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
public <T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName, String mapFunction,
String reduceFunction, MapReduceOptions mapReduceOptions, Class<T> entityClass) {
String mapFunc = replaceWithResourceIfNecessary(mapFunction);
String reduceFunc = replaceWithResourceIfNecessary(reduceFunction);
DBCollection inputCollection = getCollection(inputCollectionName);
@@ -1147,12 +1259,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
MapReduceOutput mapReduceOutput = new MapReduceOutput(inputCollection, commandObject, commandResult);
List<T> mappedResults = new ArrayList<T>();
DbObjectCallback<T> callback = new ReadDbObjectCallback<T>(mongoConverter, entityClass);
for (DBObject dbObject : mapReduceOutput.results()) {
mappedResults.add(callback.doWith(dbObject));
}
MapReduceResults<T> mapReduceResult = new MapReduceResults<T>(mappedResults, commandResult);
return mapReduceResult;
return new MapReduceResults<T>(mappedResults, commandResult);
}
public <T> GroupByResults<T> group(String inputCollectionName, GroupBy groupBy, Class<T> entityClass) {
@@ -1206,15 +1318,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
@SuppressWarnings("unchecked")
Iterable<DBObject> resultSet = (Iterable<DBObject>) commandResult.get("retval");
List<T> mappedResults = new ArrayList<T>();
DbObjectCallback<T> callback = new ReadDbObjectCallback<T>(mongoConverter, entityClass);
for (DBObject dbObject : resultSet) {
mappedResults.add(callback.doWith(dbObject));
}
GroupByResults<T> groupByResult = new GroupByResults<T>(mappedResults, commandResult);
return groupByResult;
return new GroupByResults<T>(mappedResults, commandResult);
}
@Override
@@ -1245,6 +1356,54 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
return aggregate(aggregation, collectionName, outputType, null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String)
*/
@Override
public <T> List<T> findAllAndRemove(Query query, String collectionName) {
return findAndRemove(query, null, collectionName);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class)
*/
@Override
public <T> List<T> findAllAndRemove(Query query, Class<T> entityClass) {
return findAllAndRemove(query, entityClass, determineCollectionName(entityClass));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
*/
@Override
public <T> List<T> findAllAndRemove(Query query, Class<T> entityClass, String collectionName) {
return doFindAndDelete(collectionName, query, entityClass);
}
/**
* Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)}
* and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is
* constructed out of the find result.
*
* @param collectionName
* @param query
* @param entityClass
* @return
*/
protected <T> List<T> doFindAndDelete(String collectionName, Query query, Class<T> entityClass) {
List<T> result = find(query, entityClass, collectionName);
if (!CollectionUtils.isEmpty(result)) {
remove(getIdInQueryFor(result), entityClass, collectionName);
}
return result;
}
protected <O> AggregationResults<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType,
AggregationOperationContext context) {
@@ -1262,17 +1421,32 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
CommandResult commandResult = executeCommand(command);
handleCommandError(commandResult, command);
// map results
return new AggregationResults<O>(returnPotentiallyMappedResults(outputType, commandResult), commandResult);
}
/**
* Returns the potentially mapped results of the given {@commandResult} contained some.
*
* @param outputType
* @param commandResult
* @return
*/
private <O> List<O> returnPotentiallyMappedResults(Class<O> outputType, CommandResult commandResult) {
@SuppressWarnings("unchecked")
Iterable<DBObject> resultSet = (Iterable<DBObject>) commandResult.get("result");
List<O> mappedResults = new ArrayList<O>();
if (resultSet == null) {
return Collections.emptyList();
}
DbObjectCallback<O> callback = new UnwrapAndReadDbObjectCallback<O>(mongoConverter, outputType);
List<O> mappedResults = new ArrayList<O>();
for (DBObject dbObject : resultSet) {
mappedResults.add(callback.doWith(dbObject));
}
return new AggregationResults<O>(mappedResults, commandResult);
return mappedResults;
}
protected String replaceWithResourceIfNecessary(String function) {
@@ -1304,13 +1478,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;
@@ -1445,12 +1619,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
CursorPreparer preparer, DbObjectCallback<T> objectCallback) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
DBObject mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity);
DBObject mappedFields = queryMapper.getMappedFields(fields, entity);
DBObject mappedQuery = queryMapper.getMappedObject(query, entity);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("find using query: %s fields: %s for class: %s in collection: %s",
serializeToJsonSafely(query), mappedFields, entityClass, collectionName));
serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName));
}
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer, objectCallback,
@@ -1488,8 +1663,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
Class<T> entityClass) {
EntityReader<? super T, DBObject> readerToUse = this.mongoConverter;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("findAndRemove using query: " + query + " fields: " + fields + " sort: " + sort + " for class: "
+ entityClass + " in collection: " + collectionName);
LOGGER.debug(String.format("findAndRemove using query: %s fields: %s sort: %s for class: %s in collection: %s",
serializeToJsonSafely(query), fields, sort, entityClass, collectionName));
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
return executeFindOneInternal(new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort),
@@ -1507,12 +1682,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
DBObject mappedUpdate = queryMapper.getMappedObject(update.getUpdateObject(), entity);
increaseVersionForUpdateIfNecessary(entity, update);
DBObject mappedQuery = queryMapper.getMappedObject(query, entity);
DBObject mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("findAndModify using query: " + mappedQuery + " fields: " + fields + " sort: " + sort
+ " for class: " + entityClass + " and update: " + mappedUpdate + " in collection: " + collectionName);
LOGGER.debug(String.format("findAndModify using query: %s fields: %s sort: %s for class: %s and update: %s " +
"in collection: %s", serializeToJsonSafely(mappedQuery), fields, sort, entityClass,
serializeToJsonSafely(mappedUpdate), collectionName));
}
return executeFindOneInternal(new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate, options),
@@ -1544,15 +1722,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
ConversionService conversionService = mongoConverter.getConversionService();
BeanWrapper<PersistentEntity<Object, ?>, Object> wrapper = BeanWrapper.create(savedObject, conversionService);
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(savedObject.getClass());
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(savedObject);
Object idValue = wrapper.getProperty(idProp, idProp.getType(), true);
if (idValue != null) {
if (accessor.getProperty(idProp) != null) {
return;
}
wrapper.setProperty(idProp, id);
new ConvertingPropertyAccessor(accessor, conversionService).setProperty(idProp, id);
}
private DBCollection getAndPrepareCollection(DB db, String collectionName) {
@@ -1783,11 +1960,22 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
private static final MongoConverter getDefaultMongoConverter(MongoDbFactory factory) {
MappingMongoConverter converter = new MappingMongoConverter(factory, new MongoMappingContext());
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
converter.afterPropertiesSet();
return converter;
}
private DBObject getMappedSortObject(Query query, Class<?> type) {
if (query == null || query.getSortObject() == null) {
return null;
}
return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type));
}
// Callback implementations
/**
@@ -1810,13 +1998,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException {
if (fields == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("findOne using query: " + query + " in db.collection: " + collection.getFullName());
LOGGER.debug(String.format("findOne using query: %s in db.collection: %s",
serializeToJsonSafely(query), collection.getFullName()));
}
return collection.findOne(query);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("findOne using query: " + query + " fields: " + fields + " in db.collection: "
+ collection.getFullName());
LOGGER.debug(String.format("findOne using query: %s fields: %s in db.collection: %s",
serializeToJsonSafely(query), fields, collection.getFullName()));
}
return collection.findOne(query, fields);
}
@@ -1845,7 +2034,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
public DBCursor doInCollection(DBCollection collection) throws MongoException, DataAccessException {
if (fields == null) {
if (fields == null || fields.toMap().isEmpty()) {
return collection.find(query);
} else {
return collection.find(query, fields);
@@ -1981,9 +2171,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;
}
/*
@@ -1997,11 +2190,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null
&& !StringUtils.hasText(query.getHint())) {
&& !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) {
return cursor;
}
DBCursor cursorToUse = cursor;
DBCursor cursorToUse = cursor.copy();
try {
if (query.getSkip() > 0) {
@@ -2011,11 +2204,18 @@ 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());
}
if (query.getMeta().hasValues()) {
for (Entry<String, Object> entry : query.getMeta().values()) {
cursorToUse = cursorToUse.addSpecial(entry.getKey(), entry.getValue());
}
}
} catch (RuntimeException e) {
throw potentiallyConvertRuntimeException(e);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 the original author or authors.
* Copyright 2011-2013 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.
@@ -19,9 +19,11 @@ import java.net.UnknownHostException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.DB;
import com.mongodb.Mongo;
@@ -34,6 +36,7 @@ import com.mongodb.WriteConcern;
*
* @author Mark Pollack
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
@@ -41,6 +44,9 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
private final String databaseName;
private final boolean mongoInstanceCreated;
private final UserCredentials credentials;
private final PersistenceExceptionTranslator exceptionTranslator;
private final String authenticationDatabaseName;
private WriteConcern writeConcern;
/**
@@ -50,7 +56,7 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
* @param databaseName database name, not be {@literal null} or empty.
*/
public SimpleMongoDbFactory(Mongo mongo, String databaseName) {
this(mongo, databaseName, UserCredentials.NO_CREDENTIALS, false);
this(mongo, databaseName, null);
}
/**
@@ -61,7 +67,20 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
* @param credentials username and password.
*/
public SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials) {
this(mongo, databaseName, credentials, false);
this(mongo, databaseName, credentials, false, null);
}
/**
* Create an instance of SimpleMongoDbFactory given the Mongo instance, database name, and username/password
*
* @param mongo Mongo instance, must not be {@literal null}.
* @param databaseName Database name, must not be {@literal null} or empty.
* @param credentials username and password.
* @param authenticationDatabaseName the database name to use for authentication
*/
public SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials,
String authenticationDatabaseName) {
this(mongo, databaseName, credentials, false, authenticationDatabaseName);
}
/**
@@ -72,12 +91,14 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
* @throws UnknownHostException
* @see MongoURI
*/
@SuppressWarnings("deprecation")
public SimpleMongoDbFactory(MongoURI uri) throws MongoException, UnknownHostException {
this(new Mongo(uri), uri.getDatabase(), new UserCredentials(uri.getUsername(), parseChars(uri.getPassword())), true);
this(new Mongo(uri), uri.getDatabase(), new UserCredentials(uri.getUsername(), parseChars(uri.getPassword())),
true, uri.getDatabase());
}
private SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials,
boolean mongoInstanceCreated) {
boolean mongoInstanceCreated, String authenticationDatabaseName) {
Assert.notNull(mongo, "Mongo must not be null");
Assert.hasText(databaseName, "Database name must not be empty");
@@ -88,6 +109,12 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
this.databaseName = databaseName;
this.mongoInstanceCreated = mongoInstanceCreated;
this.credentials = credentials == null ? UserCredentials.NO_CREDENTIALS : credentials;
this.exceptionTranslator = new MongoExceptionTranslator();
this.authenticationDatabaseName = StringUtils.hasText(authenticationDatabaseName) ? authenticationDatabaseName
: databaseName;
Assert.isTrue(this.authenticationDatabaseName.matches("[\\w-]+"),
"Authentication database name must only contain letters, numbers, underscores and dashes!");
}
/**
@@ -115,7 +142,7 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
Assert.hasText(dbName, "Database name must not be empty.");
DB db = MongoDbUtils.getDB(mongo, dbName, credentials);
DB db = MongoDbUtils.getDB(mongo, dbName, credentials, authenticationDatabaseName);
if (writeConcern != null) {
db.setWriteConcern(writeConcern);
@@ -138,4 +165,13 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
private static String parseChars(char[] chars) {
return chars == null ? null : String.valueOf(chars);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.MongoDbFactory#getExceptionTranslator()
*/
@Override
public PersistenceExceptionTranslator getExceptionTranslator() {
return this.exceptionTranslator;
}
}

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.
@@ -44,9 +44,32 @@ import com.mongodb.DBObject;
*/
public class Aggregation {
public static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext();
/**
* References the root document, i.e. the top-level document, currently being processed in the aggregation pipeline
* stage.
*/
public static final String ROOT = SystemVariable.ROOT.toString();
private final List<AggregationOperation> operations;
/**
* References the start of the field path being processed in the aggregation pipeline stage. Unless documented
* otherwise, all stages start with CURRENT the same as ROOT.
*/
public static final String CURRENT = SystemVariable.CURRENT.toString();
public static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext();
public static final AggregationOptions DEFAULT_OPTIONS = newAggregationOptions().build();
protected final List<AggregationOperation> operations;
private final AggregationOptions options;
/**
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
*
* @param operations must not be {@literal null} or empty.
*/
public static Aggregation newAggregation(List<? extends AggregationOperation> operations) {
return newAggregation(operations.toArray(new AggregationOperation[operations.size()]));
}
/**
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
@@ -57,6 +80,30 @@ public class Aggregation {
return new Aggregation(operations);
}
/**
* Returns a copy of this {@link Aggregation} with the given {@link AggregationOptions} set. Note that options are
* supported in MongoDB version 2.6+.
*
* @param options must not be {@literal null}.
* @return
* @since 1.6
*/
public Aggregation withOptions(AggregationOptions options) {
Assert.notNull(options, "AggregationOptions must not be null.");
return new Aggregation(this.operations, options);
}
/**
* Creates a new {@link TypedAggregation} for the given type and {@link AggregationOperation}s.
*
* @param type must not be {@literal null}.
* @param operations must not be {@literal null} or empty.
*/
public static <T> TypedAggregation<T> newAggregation(Class<T> type, List<? extends AggregationOperation> operations) {
return newAggregation(type, operations.toArray(new AggregationOperation[operations.size()]));
}
/**
* Creates a new {@link TypedAggregation} for the given type and {@link AggregationOperation}s.
*
@@ -73,11 +120,43 @@ public class Aggregation {
* @param aggregationOperations must not be {@literal null} or empty.
*/
protected Aggregation(AggregationOperation... aggregationOperations) {
this(asAggregationList(aggregationOperations));
}
/**
* @param aggregationOperations must not be {@literal null} or empty.
* @return
*/
protected static List<AggregationOperation> asAggregationList(AggregationOperation... aggregationOperations) {
Assert.notEmpty(aggregationOperations, "AggregationOperations must not be null or empty!");
return Arrays.asList(aggregationOperations);
}
/**
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
*
* @param aggregationOperations must not be {@literal null} or empty.
*/
protected Aggregation(List<AggregationOperation> aggregationOperations) {
this(aggregationOperations, DEFAULT_OPTIONS);
}
/**
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
*
* @param aggregationOperations must not be {@literal null} or empty.
* @param options must not be {@literal null} or empty.
*/
protected Aggregation(List<AggregationOperation> aggregationOperations, AggregationOptions options) {
Assert.notNull(aggregationOperations, "AggregationOperations must not be null!");
Assert.isTrue(aggregationOperations.length > 0, "At least one AggregationOperation has to be provided");
Assert.isTrue(aggregationOperations.size() > 0, "At least one AggregationOperation has to be provided");
Assert.notNull(options, "AggregationOptions must not be null!");
this.operations = Arrays.asList(aggregationOperations);
this.operations = aggregationOperations;
this.options = options;
}
/**
@@ -212,6 +291,16 @@ public class Aggregation {
return Fields.from(field(name, target));
}
/**
* Returns a new {@link AggregationOptions.Builder}.
*
* @return
* @since 1.6
*/
public static AggregationOptions.Builder newAggregationOptions() {
return new AggregationOptions.Builder();
}
/**
* Converts this {@link Aggregation} specification to a {@link DBObject}.
*
@@ -227,14 +316,17 @@ public class Aggregation {
operationDocuments.add(operation.toDBObject(context));
if (operation instanceof AggregationOperationContext) {
context = (AggregationOperationContext) operation;
if (operation instanceof FieldsExposingAggregationOperation) {
FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation;
context = new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), rootContext);
}
}
DBObject command = new BasicDBObject("aggregate", inputCollectionName);
command.put("pipeline", operationDocuments);
command = options.applyAndReturnPotentiallyChangedCommand(command);
return command;
}
@@ -282,4 +374,51 @@ public class Aggregation {
return new FieldReference(new ExposedField(new AggregationField(name), true));
}
}
/**
* Describes the system variables available in MongoDB aggregation framework pipeline expressions.
*
* @author Thomas Darimont
* @see http://docs.mongodb.org/manual/reference/aggregation-variables
*/
enum SystemVariable {
ROOT, CURRENT;
private static final String PREFIX = "$$";
/**
* Return {@literal true} if the given {@code fieldRef} denotes a well-known system variable, {@literal false}
* otherwise.
*
* @param fieldRef may be {@literal null}.
* @return
*/
public static boolean isReferingToSystemVariable(String fieldRef) {
if (fieldRef == null || !fieldRef.startsWith(PREFIX) || fieldRef.length() <= 2) {
return false;
}
int indexOfFirstDot = fieldRef.indexOf('.');
String candidate = fieldRef.substring(2, indexOfFirstDot == -1 ? fieldRef.length() : indexOfFirstDot);
for (SystemVariable value : values()) {
if (value.name().equals(candidate)) {
return true;
}
}
return false;
}
/*
* (non-Javadoc)
* @see java.lang.Enum#toString()
*/
@Override
public String toString() {
return PREFIX.concat(name());
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressionTransformer.AggregationExpressionTransformationContext;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.data.mongodb.core.spel.ExpressionNode;
import org.springframework.data.mongodb.core.spel.ExpressionTransformationContextSupport;
import org.springframework.data.mongodb.core.spel.ExpressionTransformer;
import org.springframework.util.Assert;
import com.mongodb.DBObject;
/**
* Interface to type an {@link ExpressionTransformer} to the contained
* {@link AggregationExpressionTransformationContext}.
*
* @author Oliver Gierke
*/
interface AggregationExpressionTransformer extends
ExpressionTransformer<AggregationExpressionTransformationContext<ExpressionNode>> {
/**
* A special {@link ExpressionTransformationContextSupport} to be aware of the {@link AggregationOperationContext}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public static class AggregationExpressionTransformationContext<T extends ExpressionNode> extends
ExpressionTransformationContextSupport<T> {
private final AggregationOperationContext aggregationContext;
/**
* Creates an {@link AggregationExpressionTransformationContext}.
*
* @param currentNode must not be {@literal null}.
* @param parentNode
* @param previousOperationObject
* @param aggregationContext must not be {@literal null}.
*/
public AggregationExpressionTransformationContext(T currentNode, ExpressionNode parentNode,
DBObject previousOperationObject, AggregationOperationContext context) {
super(currentNode, parentNode, previousOperationObject);
Assert.notNull(context, "AggregationOperationContext must not be null!");
this.aggregationContext = context;
}
/**
* Returns the underlying {@link AggregationOperationContext}.
*
* @return
*/
public AggregationOperationContext getAggregationContext() {
return aggregationContext;
}
/**
* Returns the {@link FieldReference} for the current {@link ExpressionNode}.
*
* @return
*/
public FieldReference getFieldReference() {
return aggregationContext.getReference(getCurrentNode().getName());
}
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* Holds a set of configurable aggregation options that can be used within an aggregation pipeline. A list of support
* aggregation options can be found in the MongoDB reference documentation
* http://docs.mongodb.org/manual/reference/command/aggregate/#aggregate
*
* @author Thomas Darimont
* @author Oliver Gierke
* @see Aggregation#withOptions(AggregationOptions)
* @see TypedAggregation#withOptions(AggregationOptions)
* @since 1.6
*/
public class AggregationOptions {
private static final String CURSOR = "cursor";
private static final String EXPLAIN = "explain";
private static final String ALLOW_DISK_USE = "allowDiskUse";
private final boolean allowDiskUse;
private final boolean explain;
private final DBObject cursor;
/**
* Creates a new {@link AggregationOptions}.
*
* @param allowDiskUse whether to off-load intensive sort-operations to disk.
* @param explain whether to get the execution plan for the aggregation instead of the actual results.
* @param cursor can be {@literal null}, used to pass additional options to the aggregation.
*/
public AggregationOptions(boolean allowDiskUse, boolean explain, DBObject cursor) {
this.allowDiskUse = allowDiskUse;
this.explain = explain;
this.cursor = cursor;
}
/**
* Enables writing to temporary files. When set to true, aggregation stages can write data to the _tmp subdirectory in
* the dbPath directory.
*
* @return
*/
public boolean isAllowDiskUse() {
return allowDiskUse;
}
/**
* Specifies to return the information on the processing of the pipeline.
*
* @return
*/
public boolean isExplain() {
return explain;
}
/**
* Specify a document that contains options that control the creation of the cursor object.
*
* @return
*/
public DBObject getCursor() {
return cursor;
}
/**
* Returns a new potentially adjusted copy for the given {@code aggregationCommandObject} with the configuration
* applied.
*
* @param command the aggregation command.
* @return
*/
DBObject applyAndReturnPotentiallyChangedCommand(DBObject command) {
DBObject result = new BasicDBObject(command.toMap());
if (allowDiskUse && !result.containsField(ALLOW_DISK_USE)) {
result.put(ALLOW_DISK_USE, allowDiskUse);
}
if (explain && !result.containsField(EXPLAIN)) {
result.put(EXPLAIN, explain);
}
if (cursor != null && !result.containsField(CURSOR)) {
result.put("cursor", cursor);
}
return result;
}
/**
* Returns a {@link DBObject} representation of this {@link AggregationOptions}.
*
* @return
*/
public DBObject toDbObject() {
DBObject dbo = new BasicDBObject();
dbo.put(ALLOW_DISK_USE, allowDiskUse);
dbo.put(EXPLAIN, explain);
dbo.put(CURSOR, cursor);
return dbo;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return toDbObject().toString();
}
/**
* A Builder for {@link AggregationOptions}.
*
* @author Thomas Darimont
*/
public static class Builder {
private boolean allowDiskUse;
private boolean explain;
private DBObject cursor;
/**
* Defines whether to off-load intensive sort-operations to disk.
*
* @param allowDiskUse
* @return
*/
public Builder allowDiskUse(boolean allowDiskUse) {
this.allowDiskUse = allowDiskUse;
return this;
}
/**
* Defines whether to get the execution plan for the aggregation instead of the actual results.
*
* @param explain
* @return
*/
public Builder explain(boolean explain) {
this.explain = explain;
return this;
}
/**
* Additional options to the aggregation.
*
* @param cursor
* @return
*/
public Builder cursor(DBObject cursor) {
this.cursor = cursor;
return this;
}
/**
* Returns a new {@link AggregationOptions} instance with the given configuration.
*
* @return
*/
public AggregationOptions build() {
return new AggregationOptions(allowDiskUse, explain, cursor);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import com.mongodb.DBObject;
*
* @author Tobias Trelle
* @author Oliver Gierke
* @author Thomas Darimont
* @param <T> The class in which the results are mapped onto.
* @since 1.3
*/
@@ -90,6 +91,16 @@ public class AggregationResults<T> implements Iterable<T> {
return serverUsed;
}
/**
* Returns the raw result that was returned by the server.
*
* @return
* @since 1.6
*/
public DBObject getRawResults() {
return rawResults;
}
private String parseServerUsed() {
Object object = rawResults.get("serverUsed");

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.
@@ -29,9 +29,10 @@ import org.springframework.util.CompositeIterator;
* Value object to capture the fields exposed by an {@link AggregationOperation}.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @since 1.3
*/
public class ExposedFields implements Iterable<ExposedField> {
public final class ExposedFields implements Iterable<ExposedField> {
private static final List<ExposedField> NO_FIELDS = Collections.emptyList();
private static final ExposedFields EMPTY = new ExposedFields(NO_FIELDS, NO_FIELDS);
@@ -151,13 +152,47 @@ public class ExposedFields implements Iterable<ExposedField> {
return null;
}
/**
* Returns whether the {@link ExposedFields} exposes no non-synthetic fields at all.
*
* @return
*/
boolean exposesNoNonSyntheticFields() {
return originalFields.isEmpty();
}
/**
* Returns whether the {@link ExposedFields} exposes a single non-synthetic field only.
*
* @return
*/
boolean exposesSingleNonSyntheticFieldOnly() {
return originalFields.size() == 1;
}
/**
* Returns whether the {@link ExposedFields} exposes no fields at all.
*
* @return
*/
boolean exposesNoFields() {
return exposedFieldsCount() == 0;
}
/**
* Returns whether the {@link ExposedFields} exposes a single field only.
*
* @return
*/
public boolean exposesSingleFieldOnly() {
return originalFields.size() + syntheticFields.size() == 1;
boolean exposesSingleFieldOnly() {
return exposedFieldsCount() == 1;
}
/**
* @return
*/
private int exposedFieldsCount() {
return originalFields.size() + syntheticFields.size();
}
/*
@@ -224,14 +259,30 @@ public class ExposedFields implements Iterable<ExposedField> {
return field.getTarget();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.Field#isAliased()
*/
@Override
public boolean isAliased() {
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);
}
/*
@@ -242,6 +293,41 @@ public class ExposedFields implements Iterable<ExposedField> {
public String toString() {
return String.format("AggregationField: %s, synthetic: %s", field, synthetic);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ExposedField)) {
return false;
}
ExposedField that = (ExposedField) obj;
return this.field.equals(that.field) && this.synthetic == that.synthetic;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += 31 * field.hashCode();
result += 31 * (synthetic ? 0 : 1);
return result;
}
}
/**
@@ -261,6 +347,7 @@ public class ExposedFields implements Iterable<ExposedField> {
public FieldReference(ExposedField field) {
Assert.notNull(field, "ExposedField must not be null!");
this.field = field;
}
@@ -270,10 +357,21 @@ public class ExposedFields implements Iterable<ExposedField> {
* @return
*/
public String getRaw() {
String target = field.getTarget();
return field.synthetic ? target : String.format("%s.%s", Fields.UNDERSCORE_ID, target);
}
/**
* Returns the referenve value for the given field reference. Will return 1 for a synthetic, unaliased field or the
* raw rendering of the reference otherwise.
*
* @return
*/
public Object getReferenceValue() {
return field.synthetic && !field.isAliased() ? 1 : toString();
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
@@ -282,5 +380,34 @@ public class ExposedFields implements Iterable<ExposedField> {
public String toString() {
return String.format("$%s", getRaw());
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FieldReference)) {
return false;
}
FieldReference that = (FieldReference) obj;
return this.field.equals(that.field);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return field.hashCode();
}
}
}

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,17 +17,38 @@ package org.springframework.data.mongodb.core.aggregation;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.util.Assert;
import com.mongodb.DBObject;
/**
* Support class to implement {@link AggregationOperation}s that will become an {@link AggregationOperationContext} as
* well defining {@link ExposedFields}.
* {@link AggregationOperationContext} that combines the available field references from a given
* {@code AggregationOperationContext} and an {@link FieldsExposingAggregationOperation}.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.3
* @since 1.4
*/
public abstract class ExposedFieldsAggregationOperationContext implements AggregationOperationContext {
class ExposedFieldsAggregationOperationContext implements AggregationOperationContext {
private final ExposedFields exposedFields;
private final AggregationOperationContext rootContext;
/**
* 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, AggregationOperationContext rootContext) {
Assert.notNull(exposedFields, "ExposedFields must not be null!");
Assert.notNull(rootContext, "RootContext must not be null!");
this.exposedFields = exposedFields;
this.rootContext = rootContext;
}
/*
* (non-Javadoc)
@@ -35,7 +56,7 @@ public abstract class ExposedFieldsAggregationOperationContext implements Aggreg
*/
@Override
public DBObject getMappedObject(DBObject dbObject) {
return dbObject;
return rootContext.getMappedObject(dbObject);
}
/*
@@ -44,7 +65,7 @@ public abstract class ExposedFieldsAggregationOperationContext implements Aggreg
*/
@Override
public FieldReference getReference(Field field) {
return getReference(field.getName());
return getReference(field, field.getTarget());
}
/*
@@ -53,15 +74,44 @@ public abstract class ExposedFieldsAggregationOperationContext implements Aggreg
*/
@Override
public FieldReference getReference(String name) {
return getReference(null, name);
}
ExposedField field = getFields().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));
}
protected abstract ExposedFields getFields();
}

View File

@@ -36,4 +36,11 @@ public interface Field {
* @return must not be {@literal null}.
*/
String getTarget();
/**
* Returns whether the Field is aliased, which means it has a name set different from the target.
*
* @return
*/
boolean isAliased();
}

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.
@@ -23,21 +23,23 @@ import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Value object to capture a list of {@link Field} instances.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @since 1.3
*/
public class Fields implements Iterable<Field> {
public final class Fields implements Iterable<Field> {
private static final String AMBIGUOUS_EXCEPTION = "Found two fields both using '%s' as name: %s and %s! Please "
+ "customize your field definitions to get to unique field names!";
public static String UNDERSCORE_ID = "_id";
public static String UNDERSCORE_ID_REF = "$_id";
public static final String UNDERSCORE_ID = "_id";
public static final String UNDERSCORE_ID_REF = "$_id";
private final List<Field> fields;
@@ -185,7 +187,7 @@ public class Fields implements Iterable<Field> {
private final String target;
/**
* Creates an aggregation fieldwith the given name. As no target is set explicitly, the name will be used as target
* Creates an aggregation field with the given name. As no target is set explicitly, the name will be used as target
* as well.
*
* @param key
@@ -196,17 +198,34 @@ public class Fields implements Iterable<Field> {
public AggregationField(String name, String target) {
Assert.hasText(name, "AggregationField name must not be null or empty!");
String nameToSet = cleanUp(name);
String targetToSet = cleanUp(target);
Assert.hasText(nameToSet, "AggregationField name must not be null or empty!");
if (target == null && name.contains(".")) {
this.name = name.substring(name.indexOf(".") + 1);
this.target = name;
this.name = nameToSet.substring(nameToSet.indexOf('.') + 1);
this.target = nameToSet;
} else {
this.name = name;
this.target = target;
this.name = nameToSet;
this.target = targetToSet;
}
}
private static final String cleanUp(String source) {
if (source == null) {
return source;
}
if (Aggregation.SystemVariable.isReferingToSystemVariable(source)) {
return source;
}
int dollarIndex = source.lastIndexOf('$');
return dollarIndex == -1 ? source : source.substring(dollarIndex + 1);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.Field#getKey()
@@ -223,6 +242,15 @@ public class Fields implements Iterable<Field> {
return StringUtils.hasText(this.target) ? this.target : this.name;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.Field#isAliased()
*/
@Override
public boolean isAliased() {
return !getName().equals(getTarget());
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
@@ -231,5 +259,40 @@ public class Fields implements Iterable<Field> {
public String toString() {
return String.format("AggregationField - name: %s, target: %s", name, target);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AggregationField)) {
return false;
}
AggregationField that = (AggregationField) obj;
return this.name.equals(that.name) && ObjectUtils.nullSafeEquals(this.target, that.target);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += 31 * name.hashCode();
result += 31 * ObjectUtils.nullSafeHashCode(target);
return result;
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
/**
* {@link AggregationOperation} that exposes new {@link ExposedFields} that can be used for later aggregation pipeline
* {@code AggregationOperation}s.
*
* @author Thomas Darimont
*/
public interface FieldsExposingAggregationOperation extends AggregationOperation {
/**
* Returns the fields exposed by the {@link AggregationOperation}.
*
* @return will never be {@literal null}.
*/
ExposedFields getFields();
}

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.
@@ -38,9 +38,13 @@ import com.mongodb.DBObject;
* @author Oliver Gierke
* @since 1.3
*/
public class GroupOperation extends ExposedFieldsAggregationOperationContext implements AggregationOperation {
public class GroupOperation implements FieldsExposingAggregationOperation {
/**
* Holds the non-synthetic fields which are the fields of the group-id structure.
*/
private final ExposedFields idFields;
private final ExposedFields nonSynthecticFields;
private final List<Operation> operations;
/**
@@ -50,7 +54,7 @@ public class GroupOperation extends ExposedFieldsAggregationOperationContext imp
*/
public GroupOperation(Fields fields) {
this.nonSynthecticFields = ExposedFields.nonSynthetic(fields);
this.idFields = ExposedFields.nonSynthetic(fields);
this.operations = new ArrayList<Operation>();
}
@@ -74,7 +78,7 @@ public class GroupOperation extends ExposedFieldsAggregationOperationContext imp
Assert.notNull(groupOperation, "GroupOperation must not be null!");
Assert.notNull(nextOperations, "NextOperations must not be null!");
this.nonSynthecticFields = groupOperation.nonSynthecticFields;
this.idFields = groupOperation.idFields;
this.operations = new ArrayList<Operation>(nextOperations.size() + 1);
this.operations.addAll(groupOperation.operations);
this.operations.addAll(nextOperations);
@@ -95,7 +99,7 @@ public class GroupOperation extends ExposedFieldsAggregationOperationContext imp
*
* @author Thomas Darimont
*/
public class GroupOperationBuilder {
public static final class GroupOperationBuilder {
private final GroupOperation groupOperation;
private final Operation operation;
@@ -261,7 +265,7 @@ public class GroupOperation extends ExposedFieldsAggregationOperationContext imp
@Override
public ExposedFields getFields() {
ExposedFields fields = this.nonSynthecticFields.and(new ExposedField(Fields.UNDERSCORE_ID, true));
ExposedFields fields = this.idFields.and(new ExposedField(Fields.UNDERSCORE_ID, true));
for (Operation operation : operations) {
fields = fields.and(operation.asField());
@@ -279,16 +283,20 @@ public class GroupOperation extends ExposedFieldsAggregationOperationContext imp
BasicDBObject operationObject = new BasicDBObject();
if (nonSynthecticFields.exposesSingleFieldOnly()) {
if (idFields.exposesNoNonSyntheticFields()) {
FieldReference reference = context.getReference(nonSynthecticFields.iterator().next());
operationObject.put(Fields.UNDERSCORE_ID, null);
} else if (idFields.exposesSingleNonSyntheticFieldOnly()) {
FieldReference reference = context.getReference(idFields.iterator().next());
operationObject.put(Fields.UNDERSCORE_ID, reference.toString());
} else {
BasicDBObject inner = new BasicDBObject();
for (ExposedField field : nonSynthecticFields) {
for (ExposedField field : idFields) {
FieldReference reference = context.getReference(field);
inner.put(field.getName(), reference.toString());
}
@@ -356,7 +364,16 @@ public class GroupOperation extends ExposedFieldsAggregationOperationContext imp
}
public Object getValue(AggregationOperationContext context) {
return reference == null ? value : context.getReference(reference).toString();
if (reference == null) {
return value;
}
if (Aggregation.SystemVariable.isReferingToSystemVariable(reference)) {
return reference;
}
return context.getReference(reference).toString();
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ import com.mongodb.DBObject;
* @author Oliver Gierke
* @since 1.3
*/
class LimitOperation implements AggregationOperation {
public class LimitOperation implements AggregationOperation {
private final long maxElements;

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,7 +15,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 +32,17 @@ 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}.
* Creates a new {@link MatchOperation} for the given {@link CriteriaDefinition}.
*
* @param criteria must not be {@literal null}.
* @param criteriaDefinition must not be {@literal null}.
*/
public MatchOperation(Criteria criteria) {
public MatchOperation(CriteriaDefinition criteriaDefinition) {
Assert.notNull(criteria, "Criteria must not be null!");
this.criteria = criteria;
Assert.notNull(criteriaDefinition, "Criteria must not be null!");
this.criteriaDefinition = criteriaDefinition;
}
/*
@@ -51,6 +51,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

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,11 +21,9 @@ import java.util.Collections;
import java.util.List;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection;
import org.springframework.util.Assert;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
@@ -41,9 +39,11 @@ import com.mongodb.DBObject;
* @author Oliver Gierke
* @since 1.3
*/
public class ProjectionOperation extends ExposedFieldsAggregationOperationContext implements AggregationOperation {
public class ProjectionOperation implements FieldsExposingAggregationOperation {
private static final List<Projection> NONE = Collections.emptyList();
private static final String EXCLUSION_ERROR = "Exclusion of field %s not allowed. Projections by the mongodb "
+ "aggregation framework only support the exclusion of the %s field!";
private final List<Projection> projections;
@@ -60,7 +60,7 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
* @param fields must not be {@literal null}.
*/
public ProjectionOperation(Fields fields) {
this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields, true));
this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields));
}
/**
@@ -114,26 +114,36 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
return new ProjectionOperationBuilder(name, this, null);
}
public ExpressionProjectionOperationBuilder andExpression(String expression, Object... params) {
return new ExpressionProjectionOperationBuilder(expression, this, params);
}
/**
* Excludes the given fields from the projection.
*
* @param fields must not be {@literal null}.
* @param fieldNames must not be {@literal null}.
* @return
*/
public ProjectionOperation andExclude(String... fields) {
List<FieldProjection> excludeProjections = FieldProjection.from(Fields.fields(fields), false);
public ProjectionOperation andExclude(String... fieldNames) {
for (String fieldName : fieldNames) {
Assert.isTrue(Fields.UNDERSCORE_ID.equals(fieldName),
String.format(EXCLUSION_ERROR, fieldName, Fields.UNDERSCORE_ID));
}
List<FieldProjection> excludeProjections = FieldProjection.from(Fields.fields(fieldNames), false);
return new ProjectionOperation(this.projections, excludeProjections);
}
/**
* Includes the given fields into the projection.
*
* @param fields must not be {@literal null}.
* @param fieldNames must not be {@literal null}.
* @return
*/
public ProjectionOperation andInclude(String... fields) {
public ProjectionOperation andInclude(String... fieldNames) {
List<FieldProjection> projections = FieldProjection.from(Fields.fields(fields), true);
List<FieldProjection> projections = FieldProjection.from(Fields.fields(fieldNames), true);
return new ProjectionOperation(this.projections, projections);
}
@@ -147,12 +157,12 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
return new ProjectionOperation(this.projections, FieldProjection.from(fields, true));
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ExposedFieldsAggregationOperationContext#getFields()
* @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields()
*/
@Override
protected ExposedFields getFields() {
public ExposedFields getFields() {
ExposedFields fields = null;
@@ -180,15 +190,166 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
return new BasicDBObject("$project", fieldObject);
}
/**
* Base class for {@link ProjectionOperationBuilder}s.
*
* @author Thomas Darimont
*/
private static abstract class AbstractProjectionOperationBuilder implements AggregationOperation {
protected final Object value;
protected final ProjectionOperation operation;
/**
* Creates a new {@link AbstractProjectionOperationBuilder} fot the given value and {@link ProjectionOperation}.
*
* @param value must not be {@literal null}.
* @param operation must not be {@literal null}.
*/
public AbstractProjectionOperationBuilder(Object value, ProjectionOperation operation) {
Assert.notNull(value, "value must not be null or empty!");
Assert.notNull(operation, "ProjectionOperation must not be null!");
this.value = value;
this.operation = operation;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public DBObject toDBObject(AggregationOperationContext context) {
return this.operation.toDBObject(context);
}
/**
* Returns the finally to be applied {@link ProjectionOperation} with the given alias.
*
* @param alias will never be {@literal null} or empty.
* @return
*/
public abstract ProjectionOperation as(String alias);
}
/**
* An {@link ProjectionOperationBuilder} that is used for SpEL expression based projections.
*
* @author Thomas Darimont
*/
public static class ExpressionProjectionOperationBuilder extends ProjectionOperationBuilder {
private final Object[] params;
private final String expression;
/**
* Creates a new {@link ExpressionProjectionOperationBuilder} for the given value, {@link ProjectionOperation} and
* parameters.
*
* @param expression must not be {@literal null}.
* @param operation must not be {@literal null}.
* @param parameters
*/
public ExpressionProjectionOperationBuilder(String expression, ProjectionOperation operation, Object[] parameters) {
super(expression, operation, null);
this.expression = expression;
this.params = parameters.clone();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder#project(java.lang.String, java.lang.Object[])
*/
@Override
public ProjectionOperationBuilder project(String operation, final Object... values) {
OperationProjection operationProjection = new OperationProjection(Fields.field(value.toString()), operation,
values) {
@Override
protected List<Object> getOperationArguments(AggregationOperationContext context) {
List<Object> result = new ArrayList<Object>(values.length + 1);
result.add(ExpressionProjection.toMongoExpression(context,
ExpressionProjectionOperationBuilder.this.expression, ExpressionProjectionOperationBuilder.this.params));
result.addAll(Arrays.asList(values));
return result;
}
};
return new ProjectionOperationBuilder(value, this.operation.and(operationProjection), operationProjection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#as(java.lang.String)
*/
@Override
public ProjectionOperation as(String alias) {
Field expressionField = Fields.field(alias, alias);
return this.operation.and(new ExpressionProjection(expressionField, this.value.toString(), params));
}
/**
* A {@link Projection} based on a SpEL expression.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
static class ExpressionProjection extends Projection {
private static final SpelExpressionTransformer TRANSFORMER = new SpelExpressionTransformer();
private final String expression;
private final Object[] params;
/**
* Creates a new {@link ExpressionProjection} for the given field, SpEL expression and parameters.
*
* @param field must not be {@literal null}.
* @param expression must not be {@literal null} or empty.
* @param parameters must not be {@literal null}.
*/
public ExpressionProjection(Field field, String expression, Object[] parameters) {
super(field);
Assert.hasText(expression, "Expression must not be null!");
Assert.notNull(parameters, "Parameters must not be null!");
this.expression = expression;
this.params = parameters.clone();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public DBObject toDBObject(AggregationOperationContext context) {
return new BasicDBObject(getExposedField().getName(), toMongoExpression(context, expression, params));
}
protected static Object toMongoExpression(AggregationOperationContext context, String expression, Object[] params) {
return TRANSFORMER.transform(expression, context, params);
}
}
}
/**
* Builder for {@link ProjectionOperation}s on a field.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public static class ProjectionOperationBuilder implements AggregationOperation {
public static class ProjectionOperationBuilder extends AbstractProjectionOperationBuilder {
private static final String NUMBER_NOT_NULL = "Number must not be null!";
private static final String FIELD_REFERENCE_NOT_NULL = "Field reference must not be null!";
private final String name;
private final ProjectionOperation operation;
private final OperationProjection previousProjection;
/**
@@ -200,12 +361,26 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
* @param previousProjection the previous operation projection, may be {@literal null}.
*/
public ProjectionOperationBuilder(String name, ProjectionOperation operation, OperationProjection previousProjection) {
Assert.hasText(name, "Field name must not be null or empty!");
Assert.notNull(operation, "ProjectionOperation must not be null!");
super(name, operation);
this.name = name;
this.operation = operation;
this.previousProjection = previousProjection;
}
/**
* Creates a new {@link ProjectionOperationBuilder} for the field with the given value on top of the given
* {@link ProjectionOperation}.
*
* @param value
* @param operation
* @param previousProjection
*/
protected ProjectionOperationBuilder(Object value, ProjectionOperation operation,
OperationProjection previousProjection) {
super(value, operation);
this.name = null;
this.previousProjection = previousProjection;
}
@@ -237,10 +412,11 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
* @param string
* @return
*/
@Override
public ProjectionOperation as(String alias) {
if (previousProjection != null) {
return this.operation.andReplaceLastOneWith(previousProjection.withAlias(alias));
if (this.previousProjection != null) {
return this.operation.andReplaceLastOneWith(this.previousProjection.withAlias(alias));
} else {
return this.operation.and(new FieldProjection(Fields.field(alias, name), null));
}
@@ -254,10 +430,22 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
*/
public ProjectionOperationBuilder plus(Number number) {
Assert.notNull(number, "Number must not be null!");
Assert.notNull(number, NUMBER_NOT_NULL);
return project("add", number);
}
/**
* Generates an {@code $add} expression that adds the value of the given field to the previously mentioned field.
*
* @param fieldReference
* @return
*/
public ProjectionOperationBuilder plus(String fieldReference) {
Assert.notNull(fieldReference, "Field reference must not be null!");
return project("add", Fields.field(fieldReference));
}
/**
* Generates an {@code $subtract} expression that subtracts the given number to the previously mentioned field.
*
@@ -270,6 +458,19 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
return project("subtract", number);
}
/**
* Generates an {@code $subtract} expression that subtracts the value of the given field to the previously mentioned
* field.
*
* @param fieldReference
* @return
*/
public ProjectionOperationBuilder minus(String fieldReference) {
Assert.notNull(fieldReference, FIELD_REFERENCE_NOT_NULL);
return project("subtract", Fields.field(fieldReference));
}
/**
* Generates an {@code $multiply} expression that multiplies the given number with the previously mentioned field.
*
@@ -278,10 +479,23 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
*/
public ProjectionOperationBuilder multiply(Number number) {
Assert.notNull(number, "Number must not be null!");
Assert.notNull(number, NUMBER_NOT_NULL);
return project("multiply", number);
}
/**
* Generates an {@code $multiply} expression that multiplies the value of the given field with the previously
* mentioned field.
*
* @param fieldReference
* @return
*/
public ProjectionOperationBuilder multiply(String fieldReference) {
Assert.notNull(fieldReference, FIELD_REFERENCE_NOT_NULL);
return project("multiply", Fields.field(fieldReference));
}
/**
* Generates an {@code $divide} expression that divides the previously mentioned field by the given number.
*
@@ -290,11 +504,24 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
*/
public ProjectionOperationBuilder divide(Number number) {
Assert.notNull(number, "Number must not be null!");
Assert.notNull(number, FIELD_REFERENCE_NOT_NULL);
Assert.isTrue(Math.abs(number.intValue()) != 0, "Number must not be zero!");
return project("divide", number);
}
/**
* Generates an {@code $divide} expression that divides the value of the given field by the previously mentioned
* field.
*
* @param fieldReference
* @return
*/
public ProjectionOperationBuilder divide(String fieldReference) {
Assert.notNull(fieldReference, FIELD_REFERENCE_NOT_NULL);
return project("divide", Fields.field(fieldReference));
}
/**
* Generates an {@code $mod} expression that divides the previously mentioned field by the given number and returns
* the remainder.
@@ -304,12 +531,26 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
*/
public ProjectionOperationBuilder mod(Number number) {
Assert.notNull(number, "Number must not be null!");
Assert.notNull(number, NUMBER_NOT_NULL);
Assert.isTrue(Math.abs(number.intValue()) != 0, "Number must not be zero!");
return project("mod", number);
}
/* (non-Javadoc)
/**
* Generates an {@code $mod} expression that divides the value of the given field by the previously mentioned field
* and returns the remainder.
*
* @param fieldReference
* @return
*/
public ProjectionOperationBuilder mod(String fieldReference) {
Assert.notNull(fieldReference, FIELD_REFERENCE_NOT_NULL);
return project("mod", Fields.field(fieldReference));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
@@ -325,8 +566,9 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
* @return
*/
public ProjectionOperationBuilder project(String operation, Object... values) {
OperationProjection projectionOperation = new OperationProjection(Fields.field(name), operation, values);
return new ProjectionOperationBuilder(name, this.operation.and(projectionOperation), projectionOperation);
OperationProjection operationProjection = new OperationProjection(Fields.field(value.toString()), operation,
values);
return new ProjectionOperationBuilder(value, this.operation.and(operationProjection), operationProjection);
}
/**
@@ -362,6 +604,7 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
* A {@link FieldProjection} to map a result of a previous {@link AggregationOperation} to a new field.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
static class FieldProjection extends Projection {
@@ -386,20 +629,31 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
this.value = value;
}
/**
* Factory method to easily create {@link FieldProjection}s for the given {@link Fields}. Fields are projected as
* references with their given name. A field {@code foo} will be projected as: {@code foo : 1 } .
*
* @param fields the {@link Fields} to in- or exclude, must not be {@literal null}.
* @return
*/
public static List<? extends Projection> from(Fields fields) {
return from(fields, null);
}
/**
* Factory method to easily create {@link FieldProjection}s for the given {@link Fields}.
*
* @param fields the {@link Fields} to in- or exclude, must not be {@literal null}.
* @param include whether to include or exclude the fields.
* @param value to use for the given field.
* @return
*/
public static List<FieldProjection> from(Fields fields, boolean include) {
public static List<FieldProjection> from(Fields fields, Object value) {
Assert.notNull(fields, "Fields must not be null!");
List<FieldProjection> projections = new ArrayList<FieldProjection>();
for (Field field : fields) {
projections.add(new FieldProjection(field, include ? null : 0));
projections.add(new FieldProjection(field, value));
}
return projections;
@@ -411,13 +665,28 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
*/
@Override
public DBObject toDBObject(AggregationOperationContext context) {
return new BasicDBObject(field.getName(), renderFieldValue(context));
}
if (value != null) {
return new BasicDBObject(field.getName(), value);
private Object renderFieldValue(AggregationOperationContext context) {
// implicit reference or explicit include?
if (value == null || Boolean.TRUE.equals(value)) {
if (Aggregation.SystemVariable.isReferingToSystemVariable(field.getTarget())) {
return field.getTarget();
}
// check whether referenced field exists in the context
return context.getReference(field).getReferenceValue();
} else if (Boolean.FALSE.equals(value)) {
// render field as excluded
return 0;
}
FieldReference reference = context.getReference(field.getTarget());
return new BasicDBObject(field.getName(), reference.toString());
return value;
}
}
@@ -430,7 +699,7 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
/**
* Creates a new {@link OperationProjection} for the given field.
*
* @param name the name of the field to add the operation projection for, must not be {@literal null} or empty.
* @param field the name of the field to add the operation projection for, must not be {@literal null} or empty.
* @param operation the actual operation key, must not be {@literal null} or empty.
* @param values the values to pass into the operation, must not be {@literal null}.
*/
@@ -453,18 +722,15 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
@Override
public DBObject toDBObject(AggregationOperationContext context) {
BasicDBList values = new BasicDBList();
values.addAll(buildReferences(context));
DBObject inner = new BasicDBObject("$" + operation, getOperationArguments(context));
DBObject inner = new BasicDBObject("$" + operation, values);
return new BasicDBObject(this.field.getName(), inner);
return new BasicDBObject(getField().getName(), inner);
}
private List<Object> buildReferences(AggregationOperationContext context) {
protected List<Object> getOperationArguments(AggregationOperationContext context) {
List<Object> result = new ArrayList<Object>(values.size());
result.add(context.getReference(field.getTarget()).toString());
result.add(context.getReference(getField().getName()).toString());
for (Object element : values) {
result.add(element instanceof Field ? context.getReference((Field) element).toString() : element);
@@ -473,6 +739,15 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
return result;
}
/**
* Returns the field that holds the {@link OperationProjection}.
*
* @return
*/
protected Field getField() {
return field;
}
/**
* Creates a new instance of this {@link OperationProjection} with the given alias.
*
@@ -480,7 +755,27 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
* @return
*/
public OperationProjection withAlias(String alias) {
return new OperationProjection(Fields.field(alias, this.field.getName()), operation, values.toArray());
final Field aliasedField = Fields.field(alias, this.field.getName());
return new OperationProjection(aliasedField, operation, values.toArray()) {
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.OperationProjection#getField()
*/
@Override
protected Field getField() {
return aliasedField;
}
@Override
protected List<Object> getOperationArguments(AggregationOperationContext context) {
// We have to make sure that we use the arguments from the "previous" OperationProjection that we replace
// with this new instance.
return OperationProjection.this.getOperationArguments(context);
}
};
}
}
@@ -512,6 +807,96 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
return new BasicDBObject(name, nestedObject);
}
}
/**
* Extracts the minute from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractMinute() {
return project("minute");
}
/**
* Extracts the hour from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractHour() {
return project("hour");
}
/**
* Extracts the second from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractSecond() {
return project("second");
}
/**
* Extracts the millisecond from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractMillisecond() {
return project("millisecond");
}
/**
* Extracts the year from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractYear() {
return project("year");
}
/**
* Extracts the month from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractMonth() {
return project("month");
}
/**
* Extracts the week from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractWeek() {
return project("week");
}
/**
* Extracts the dayOfYear from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractDayOfYear() {
return project("dayOfYear");
}
/**
* Extracts the dayOfMonth from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractDayOfMonth() {
return project("dayOfMonth");
}
/**
* Extracts the dayOfWeek from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractDayOfWeek() {
return project("dayOfWeek");
}
}
/**
@@ -552,5 +937,4 @@ public class ProjectionOperation extends ExposedFieldsAggregationOperationContex
*/
public abstract DBObject toDBObject(AggregationOperationContext context);
}
}

View File

@@ -0,0 +1,513 @@
/*
* 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.aggregation;
import static org.springframework.data.mongodb.util.DBObjectUtils.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.core.GenericTypeResolver;
import org.springframework.data.mongodb.core.spel.ExpressionNode;
import org.springframework.data.mongodb.core.spel.ExpressionTransformationContextSupport;
import org.springframework.data.mongodb.core.spel.LiteralNode;
import org.springframework.data.mongodb.core.spel.MethodReferenceNode;
import org.springframework.data.mongodb.core.spel.OperatorNode;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.ast.CompoundExpression;
import org.springframework.expression.spel.ast.Indexer;
import org.springframework.expression.spel.ast.InlineList;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression.
*
* @author Thomas Darimont
*/
class SpelExpressionTransformer implements AggregationExpressionTransformer {
// TODO: remove explicit usage of a configuration once SPR-11031 gets fixed
private static final SpelParserConfiguration CONFIG = new SpelParserConfiguration(false, false);
private static final SpelExpressionParser PARSER = new SpelExpressionParser(CONFIG);
private final List<ExpressionNodeConversion<? extends ExpressionNode>> conversions;
/**
* Creates a new {@link SpelExpressionTransformer}.
*/
public SpelExpressionTransformer() {
List<ExpressionNodeConversion<? extends ExpressionNode>> conversions = new ArrayList<ExpressionNodeConversion<? extends ExpressionNode>>();
conversions.add(new OperatorNodeConversion(this));
conversions.add(new LiteralNodeConversion(this));
conversions.add(new IndexerNodeConversion(this));
conversions.add(new InlineListNodeConversion(this));
conversions.add(new PropertyOrFieldReferenceNodeConversion(this));
conversions.add(new CompoundExpressionNodeConversion(this));
conversions.add(new MethodReferenceNodeConversion(this));
this.conversions = Collections.unmodifiableList(conversions);
}
/**
* Transforms the given SpEL expression to a corresponding MongoDB expression against the given
* {@link AggregationOperationContext} {@code context}.
* <p>
* Exposes the given @{code params} as <code>[0] ... [n]</code>.
*
* @param expression must not be {@literal null}
* @param context must not be {@literal null}
* @param params must not be {@literal null}
* @return
*/
public Object transform(String expression, AggregationOperationContext context, Object... params) {
Assert.notNull(expression, "Expression must not be null!");
Assert.notNull(context, "AggregationOperationContext must not be null!");
Assert.notNull(params, "Parameters must not be null!");
SpelExpression spelExpression = (SpelExpression) PARSER.parseExpression(expression);
ExpressionState state = new ExpressionState(new StandardEvaluationContext(params), CONFIG);
ExpressionNode node = ExpressionNode.from(spelExpression.getAST(), state);
return transform(new AggregationExpressionTransformationContext<ExpressionNode>(node, null, null, context));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.spel.ExpressionTransformer#transform(org.springframework.data.mongodb.core.spel.ExpressionTransformationContextSupport)
*/
public Object transform(AggregationExpressionTransformationContext<ExpressionNode> context) {
return lookupConversionFor(context.getCurrentNode()).convert(context);
}
/**
* Returns an appropriate {@link ExpressionNodeConversion} for the given {@code node}. Throws an
* {@link IllegalArgumentException} if no conversion could be found.
*
* @param node
* @return the appropriate {@link ExpressionNodeConversion} for the given {@link ExpressionNode}.
*/
@SuppressWarnings("unchecked")
private ExpressionNodeConversion<ExpressionNode> lookupConversionFor(ExpressionNode node) {
for (ExpressionNodeConversion<? extends ExpressionNode> candidate : conversions) {
if (candidate.supports(node)) {
return (ExpressionNodeConversion<ExpressionNode>) candidate;
}
}
throw new IllegalArgumentException("Unsupported Element: " + node + " Type: " + node.getClass()
+ " You probably have a syntax error in your SpEL expression!");
}
/**
* Abstract base class for {@link SpelNode} to (Db)-object conversions.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
private static abstract class ExpressionNodeConversion<T extends ExpressionNode> implements
AggregationExpressionTransformer {
private final AggregationExpressionTransformer transformer;
private final Class<? extends ExpressionNode> nodeType;
/**
* Creates a new {@link ExpressionNodeConversion}.
*
* @param transformer must not be {@literal null}.
*/
@SuppressWarnings("unchecked")
public ExpressionNodeConversion(AggregationExpressionTransformer transformer) {
Assert.notNull(transformer, "Transformer must not be null!");
this.nodeType = (Class<? extends ExpressionNode>) GenericTypeResolver.resolveTypeArgument(this.getClass(),
ExpressionNodeConversion.class);
this.transformer = transformer;
}
/**
* Returns whether the current conversion supports the given {@link ExpressionNode}. By default we will match the
* node type against the genric type the subclass types the type parameter to.
*
* @param node will never be {@literal null}.
* @return true if {@literal this} conversion can be applied to the given {@code node}.
*/
protected boolean supports(ExpressionNode node) {
return nodeType.equals(node.getClass());
}
/**
* Triggers the transformation for the given {@link ExpressionNode} and the given current context.
*
* @param node must not be {@literal null}.
* @param context must not be {@literal null}.
* @return
*/
protected Object transform(ExpressionNode node, AggregationExpressionTransformationContext<?> context) {
Assert.notNull(node, "ExpressionNode must not be null!");
Assert.notNull(context, "AggregationExpressionTransformationContext must not be null!");
return transform(node, context.getParentNode(), null, context);
}
/**
* Triggers the transformation with the given new {@link ExpressionNode}, new parent node, the current operation and
* the previous context.
*
* @param node must not be {@literal null}.
* @param parent
* @param operation
* @param context must not be {@literal null}.
* @return
*/
protected Object transform(ExpressionNode node, ExpressionNode parent, DBObject operation,
AggregationExpressionTransformationContext<?> context) {
Assert.notNull(node, "ExpressionNode must not be null!");
Assert.notNull(context, "AggregationExpressionTransformationContext must not be null!");
return transform(new AggregationExpressionTransformationContext<ExpressionNode>(node, parent, operation,
context.getAggregationContext()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#transform(org.springframework.data.mongodb.core.aggregation.AggregationExpressionTransformer.AggregationExpressionTransformationContext)
*/
@Override
public Object transform(AggregationExpressionTransformationContext<ExpressionNode> context) {
return transformer.transform(context);
}
/**
* Performs the actual conversion from {@link SpelNode} to the corresponding representation for MongoDB.
*
* @param context
* @return
*/
protected abstract Object convert(AggregationExpressionTransformationContext<T> context);
}
/**
* A {@link ExpressionNodeConversion} that converts arithmetic operations.
*
* @author Thomas Darimont
*/
private static class OperatorNodeConversion extends ExpressionNodeConversion<OperatorNode> {
public OperatorNodeConversion(AggregationExpressionTransformer transformer) {
super(transformer);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@Override
protected Object convert(AggregationExpressionTransformationContext<OperatorNode> context) {
OperatorNode currentNode = context.getCurrentNode();
DBObject operationObject = createOperationObjectAndAddToPreviousArgumentsIfNecessary(context, currentNode);
Object leftResult = transform(currentNode.getLeft(), currentNode, operationObject, context);
if (currentNode.isUnaryMinus()) {
return convertUnaryMinusOp(context, leftResult);
}
// we deliberately ignore the RHS result
transform(currentNode.getRight(), currentNode, operationObject, context);
return operationObject;
}
private DBObject createOperationObjectAndAddToPreviousArgumentsIfNecessary(
AggregationExpressionTransformationContext<OperatorNode> context, OperatorNode currentNode) {
DBObject nextDbObject = new BasicDBObject(currentNode.getMongoOperator(), new BasicDBList());
if (!context.hasPreviousOperation()) {
return nextDbObject;
}
if (context.parentIsSameOperation()) {
// same operator applied in a row e.g. 1 + 2 + 3 carry on with the operation and render as $add: [1, 2 ,3]
nextDbObject = context.getPreviousOperationObject();
} else if (!currentNode.isUnaryOperator()) {
// different operator -> add context object for next level to list if arguments of previous expression
context.addToPreviousOperation(nextDbObject);
}
return nextDbObject;
}
private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<OperatorNode> context, Object leftResult) {
Object result = leftResult instanceof Number ? leftResult
: new BasicDBObject("$multiply", dbList(-1, leftResult));
if (leftResult != null && context.hasPreviousOperation()) {
context.addToPreviousOperation(result);
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#supports(java.lang.Class)
*/
@Override
protected boolean supports(ExpressionNode node) {
return node.isMathematicalOperation();
}
}
/**
* A {@link ExpressionNodeConversion} that converts indexed expressions.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
private static class IndexerNodeConversion extends ExpressionNodeConversion<ExpressionNode> {
public IndexerNodeConversion(AggregationExpressionTransformer transformer) {
super(transformer);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@Override
protected Object convert(AggregationExpressionTransformationContext<ExpressionNode> context) {
return context.addToPreviousOrReturn(context.getCurrentNode().getValue());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
*/
@Override
protected boolean supports(ExpressionNode node) {
return node.isOfType(Indexer.class);
}
}
/**
* A {@link ExpressionNodeConversion} that converts in-line list expressions.
*
* @author Thomas Darimont
*/
private static class InlineListNodeConversion extends ExpressionNodeConversion<ExpressionNode> {
public InlineListNodeConversion(AggregationExpressionTransformer transformer) {
super(transformer);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@Override
protected Object convert(AggregationExpressionTransformationContext<ExpressionNode> context) {
ExpressionNode currentNode = context.getCurrentNode();
if (!currentNode.hasChildren()) {
return null;
}
// just take the first item
return transform(currentNode.getChild(0), currentNode, null, context);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
*/
@Override
protected boolean supports(ExpressionNode node) {
return node.isOfType(InlineList.class);
}
}
/**
* A {@link ExpressionNodeConversion} that converts property or field reference expressions.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
private static class PropertyOrFieldReferenceNodeConversion extends ExpressionNodeConversion<ExpressionNode> {
public PropertyOrFieldReferenceNodeConversion(AggregationExpressionTransformer transformer) {
super(transformer);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#convert(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionTransformationContext)
*/
@Override
protected Object convert(AggregationExpressionTransformationContext<ExpressionNode> context) {
String fieldReference = context.getFieldReference().toString();
return context.addToPreviousOrReturn(fieldReference);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
*/
@Override
protected boolean supports(ExpressionNode node) {
return node.isOfType(PropertyOrFieldReference.class);
}
}
/**
* A {@link ExpressionNodeConversion} that converts literal expressions.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
private static class LiteralNodeConversion extends ExpressionNodeConversion<LiteralNode> {
public LiteralNodeConversion(AggregationExpressionTransformer transformer) {
super(transformer);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@Override
@SuppressWarnings("unchecked")
protected Object convert(AggregationExpressionTransformationContext<LiteralNode> context) {
LiteralNode node = context.getCurrentNode();
Object value = node.getValue();
if (context.hasPreviousOperation()) {
if (node.isUnaryMinus(context.getParentNode())) {
// unary minus operator
return NumberUtils.convertNumberToTargetClass(((Number) value).doubleValue() * -1,
(Class<Number>) value.getClass()); // retain type, e.g. int to -int
}
return context.addToPreviousOperation(value);
}
return value;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#supports(org.springframework.expression.spel.SpelNode)
*/
@Override
protected boolean supports(ExpressionNode node) {
return node.isLiteral();
}
}
/**
* A {@link ExpressionNodeConversion} that converts method reference expressions.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
private static class MethodReferenceNodeConversion extends ExpressionNodeConversion<MethodReferenceNode> {
public MethodReferenceNodeConversion(AggregationExpressionTransformer transformer) {
super(transformer);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@Override
protected Object convert(AggregationExpressionTransformationContext<MethodReferenceNode> context) {
MethodReferenceNode node = context.getCurrentNode();
List<Object> args = new ArrayList<Object>();
for (ExpressionNode childNode : node) {
args.add(transform(childNode, context));
}
return context.addToPreviousOrReturn(new BasicDBObject(node.getMethodName(), dbList(args.toArray())));
}
}
/**
* A {@link ExpressionNodeConversion} that converts method compound expressions.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
private static class CompoundExpressionNodeConversion extends ExpressionNodeConversion<ExpressionNode> {
public CompoundExpressionNodeConversion(AggregationExpressionTransformer transformer) {
super(transformer);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@Override
protected Object convert(AggregationExpressionTransformationContext<ExpressionNode> context) {
ExpressionNode currentNode = context.getCurrentNode();
if (currentNode.hasfirstChildNotOfType(Indexer.class)) {
// we have a property path expression like: foo.bar -> render as reference
return context.addToPreviousOrReturn(context.getFieldReference().toString());
}
return context.addToPreviousOrReturn(currentNode.getValue());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
*/
@Override
protected boolean supports(ExpressionNode node) {
return node.isOfType(CompoundExpression.class);
}
}
}

View File

@@ -71,14 +71,14 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
return mapper.getMappedObject(dbObject, mappingContext.getPersistentEntity(type));
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.ExposedFields.AvailableField)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.Field)
*/
@Override
public FieldReference getReference(Field field) {
PropertyPath.from(field.getName(), type);
PropertyPath.from(field.getTarget(), type);
return getReferenceFor(field);
}
@@ -88,15 +88,16 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
*/
@Override
public FieldReference getReference(String name) {
PropertyPath path = PropertyPath.from(name, type);
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
return getReferenceFor(field(path.getLeafProperty().getSegment(),
propertyPath.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)));
return getReferenceFor(field(name));
}
private FieldReference getReferenceFor(Field field) {
return new FieldReference(new ExposedField(field, true));
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(
field.getTarget(), type);
Field mappedField = field(propertyPath.getLeafProperty().getName(),
propertyPath.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE));
return new FieldReference(new ExposedField(mappedField, true));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.List;
import org.springframework.util.Assert;
/**
@@ -30,11 +32,34 @@ public class TypedAggregation<I> extends Aggregation {
/**
* Creates a new {@link TypedAggregation} from the given {@link AggregationOperation}s.
*
* @param inputType must not be {@literal null}.
* @param operations must not be {@literal null} or empty.
*/
public TypedAggregation(Class<I> inputType, AggregationOperation... operations) {
this(inputType, asAggregationList(operations));
}
super(operations);
/**
* Creates a new {@link TypedAggregation} from the given {@link AggregationOperation}s.
*
* @param inputType must not be {@literal null}.
* @param operations must not be {@literal null} or empty.
*/
public TypedAggregation(Class<I> inputType, List<AggregationOperation> operations) {
this(inputType, operations, DEFAULT_OPTIONS);
}
/**
* Creates a new {@link TypedAggregation} from the given {@link AggregationOperation}s and the given
* {@link AggregationOptions}.
*
* @param inputType must not be {@literal null}.
* @param operations must not be {@literal null} or empty.
* @param options must not be {@literal null}.
*/
public TypedAggregation(Class<I> inputType, List<AggregationOperation> operations, AggregationOptions options) {
super(operations, options);
Assert.notNull(inputType, "Input type must not be null!");
this.inputType = inputType;
@@ -48,4 +73,14 @@ public class TypedAggregation<I> extends Aggregation {
public Class<I> getInputType() {
return inputType;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.Aggregation#withOptions(org.springframework.data.mongodb.core.aggregation.AggregationOptions)
*/
public TypedAggregation<I> withOptions(AggregationOptions options) {
Assert.notNull(options, "AggregationOptions must not be null.");
return new TypedAggregation<I>(inputType, operations, options);
}
}

View File

@@ -29,7 +29,7 @@ import com.mongodb.DBObject;
* @author Oliver Gierke
* @since 1.3
*/
public class UnwindOperation extends ExposedFieldsAggregationOperationContext implements AggregationOperation {
public class UnwindOperation implements AggregationOperation {
private final ExposedField field;
@@ -44,15 +44,6 @@ public class UnwindOperation extends ExposedFieldsAggregationOperationContext im
this.field = new ExposedField(field, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ExposedFieldsAggregationOperationContext#getFields()
*/
@Override
protected ExposedFields getFields() {
return ExposedFields.from(field);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)

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,12 +17,14 @@ package org.springframework.data.mongodb.core.convert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,14 +36,17 @@ import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.JodaTimeConverters;
import org.springframework.data.convert.Jsr310Converters;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.mongodb.core.convert.MongoConverters.BigDecimalToStringConverter;
import org.springframework.data.mongodb.core.convert.MongoConverters.BigIntegerToStringConverter;
import org.springframework.data.mongodb.core.convert.MongoConverters.DBObjectToStringConverter;
import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigDecimalConverter;
import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigIntegerConverter;
import org.springframework.data.mongodb.core.convert.MongoConverters.StringToURLConverter;
import org.springframework.data.mongodb.core.convert.MongoConverters.TermToStringConverter;
import org.springframework.data.mongodb.core.convert.MongoConverters.URLToStringConverter;
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
import org.springframework.util.Assert;
@@ -54,6 +59,8 @@ import org.springframework.util.Assert;
* .
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class CustomConversions {
@@ -65,7 +72,7 @@ public class CustomConversions {
private final Set<ConvertiblePair> writingPairs;
private final Set<Class<?>> customSimpleTypes;
private final SimpleTypeHolder simpleTypeHolder;
private final Map<Class<?>, HashMap<Class<?>, CacheValue>> cache;
private final ConcurrentMap<ConvertiblePair, CacheValue> customReadTargetTypes;
private final List<Object> converters;
@@ -85,26 +92,36 @@ public class CustomConversions {
Assert.notNull(converters);
this.readingPairs = new HashSet<ConvertiblePair>();
this.writingPairs = new HashSet<ConvertiblePair>();
this.readingPairs = new LinkedHashSet<ConvertiblePair>();
this.writingPairs = new LinkedHashSet<ConvertiblePair>();
this.customSimpleTypes = new HashSet<Class<?>>();
this.cache = new HashMap<Class<?>, HashMap<Class<?>, CacheValue>>();
this.customReadTargetTypes = new ConcurrentHashMap<GenericConverter.ConvertiblePair, CacheValue>();
this.converters = new ArrayList<Object>();
this.converters.add(CustomToStringConverter.INSTANCE);
this.converters.add(BigDecimalToStringConverter.INSTANCE);
this.converters.add(StringToBigDecimalConverter.INSTANCE);
this.converters.add(BigIntegerToStringConverter.INSTANCE);
this.converters.add(StringToBigIntegerConverter.INSTANCE);
this.converters.add(URLToStringConverter.INSTANCE);
this.converters.add(StringToURLConverter.INSTANCE);
this.converters.addAll(JodaTimeConverters.getConvertersToRegister());
this.converters.addAll(converters);
List<Object> toRegister = new ArrayList<Object>();
for (Object c : this.converters) {
// Add user provided converters to make sure they can override the defaults
toRegister.addAll(converters);
toRegister.add(CustomToStringConverter.INSTANCE);
toRegister.add(BigDecimalToStringConverter.INSTANCE);
toRegister.add(StringToBigDecimalConverter.INSTANCE);
toRegister.add(BigIntegerToStringConverter.INSTANCE);
toRegister.add(StringToBigIntegerConverter.INSTANCE);
toRegister.add(URLToStringConverter.INSTANCE);
toRegister.add(StringToURLConverter.INSTANCE);
toRegister.add(DBObjectToStringConverter.INSTANCE);
toRegister.add(TermToStringConverter.INSTANCE);
toRegister.addAll(JodaTimeConverters.getConvertersToRegister());
toRegister.addAll(GeoConverters.getConvertersToRegister());
toRegister.addAll(Jsr310Converters.getConvertersToRegister());
for (Object c : toRegister) {
registerConversion(c);
}
Collections.reverse(toRegister);
this.converters = Collections.unmodifiableList(toRegister);
this.simpleTypeHolder = new SimpleTypeHolder(customSimpleTypes, MongoSimpleTypes.HOLDER);
}
@@ -192,25 +209,25 @@ public class CustomConversions {
*
* @param pair
*/
private void register(ConverterRegistration context) {
private void register(ConverterRegistration converterRegistration) {
ConvertiblePair pair = context.getConvertiblePair();
ConvertiblePair pair = converterRegistration.getConvertiblePair();
if (context.isReading()) {
if (converterRegistration.isReading()) {
readingPairs.add(pair);
if (LOG.isWarnEnabled() && !context.isSimpleSourceType()) {
if (LOG.isWarnEnabled() && !converterRegistration.isSimpleSourceType()) {
LOG.warn(String.format(READ_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType()));
}
}
if (context.isWriting()) {
if (converterRegistration.isWriting()) {
writingPairs.add(pair);
customSimpleTypes.add(pair.getSourceType());
if (LOG.isWarnEnabled() && !context.isSimpleTargetType()) {
if (LOG.isWarnEnabled() && !converterRegistration.isSimpleTargetType()) {
LOG.warn(String.format(WRITE_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType()));
}
}
@@ -220,11 +237,11 @@ public class CustomConversions {
* Returns the target type to convert to in case we have a custom conversion registered to convert the given source
* type into a Mongo native one.
*
* @param source must not be {@literal null}
* @param sourceType must not be {@literal null}
* @return
*/
public Class<?> getCustomWriteTarget(Class<?> source) {
return getCustomWriteTarget(source, null);
public Class<?> getCustomWriteTarget(Class<?> sourceType) {
return getCustomWriteTarget(sourceType, null);
}
/**
@@ -232,71 +249,78 @@ public class CustomConversions {
* oth the given expected type though. If {@code expectedTargetType} is {@literal null} we will simply return the
* first target type matching or {@literal null} if no conversion can be found.
*
* @param source must not be {@literal null}
* @param expectedTargetType
* @param sourceType must not be {@literal null}
* @param requestedTargetType
* @return
*/
public Class<?> getCustomWriteTarget(Class<?> source, Class<?> expectedTargetType) {
Assert.notNull(source);
return getCustomTarget(source, expectedTargetType, writingPairs);
public Class<?> getCustomWriteTarget(Class<?> sourceType, Class<?> requestedTargetType) {
Assert.notNull(sourceType);
return getCustomTarget(sourceType, requestedTargetType, writingPairs);
}
/**
* Returns whether we have a custom conversion registered to write into a Mongo native type. The returned type might
* be a subclass oth the given expected type though.
* be a subclass of the given expected type though.
*
* @param source must not be {@literal null}
* @param sourceType must not be {@literal null}
* @return
*/
public boolean hasCustomWriteTarget(Class<?> source) {
return hasCustomWriteTarget(source, null);
public boolean hasCustomWriteTarget(Class<?> sourceType) {
Assert.notNull(sourceType);
return hasCustomWriteTarget(sourceType, null);
}
/**
* Returns whether we have a custom conversion registered to write an object of the given source type into an object
* of the given Mongo native target type.
*
* @param source must not be {@literal null}.
* @param expectedTargetType
* @param sourceType must not be {@literal null}.
* @param requestedTargetType
* @return
*/
public boolean hasCustomWriteTarget(Class<?> source, Class<?> expectedTargetType) {
return getCustomWriteTarget(source, expectedTargetType) != null;
public boolean hasCustomWriteTarget(Class<?> sourceType, Class<?> requestedTargetType) {
Assert.notNull(sourceType);
return getCustomWriteTarget(sourceType, requestedTargetType) != null;
}
/**
* Returns whether we have a custom conversion registered to read the given source into the given target type.
*
* @param source must not be {@literal null}
* @param expectedTargetType must not be {@literal null}
* @param sourceType must not be {@literal null}
* @param requestedTargetType must not be {@literal null}
* @return
*/
public boolean hasCustomReadTarget(Class<?> source, Class<?> expectedTargetType) {
public boolean hasCustomReadTarget(Class<?> sourceType, Class<?> requestedTargetType) {
Assert.notNull(source);
Assert.notNull(expectedTargetType);
Assert.notNull(sourceType);
Assert.notNull(requestedTargetType);
return getCustomReadTarget(source, expectedTargetType) != null;
return getCustomReadTarget(sourceType, requestedTargetType) != null;
}
/**
* Inspects the given {@link ConvertiblePair} for ones that have a source compatible type as source. Additionally
* checks assignabilty of the target type if one is given.
* checks assignability of the target type if one is given.
*
* @param source must not be {@literal null}
* @param expectedTargetType
* @param pairs must not be {@literal null}
* @param sourceType must not be {@literal null}.
* @param requestedTargetType can be {@literal null}.
* @param pairs must not be {@literal null}.
* @return
*/
private static Class<?> getCustomTarget(Class<?> source, Class<?> expectedTargetType, Iterable<ConvertiblePair> pairs) {
private static Class<?> getCustomTarget(Class<?> sourceType, Class<?> requestedTargetType,
Iterable<ConvertiblePair> pairs) {
Assert.notNull(source);
Assert.notNull(sourceType);
Assert.notNull(pairs);
for (ConvertiblePair typePair : pairs) {
if (typePair.getSourceType().isAssignableFrom(source)) {
if (typePair.getSourceType().isAssignableFrom(sourceType)) {
Class<?> targetType = typePair.getTargetType();
if (expectedTargetType == null || targetType.isAssignableFrom(expectedTargetType)) {
if (requestedTargetType == null || targetType.isAssignableFrom(requestedTargetType)) {
return targetType;
}
}
@@ -305,27 +329,33 @@ public class CustomConversions {
return null;
}
private Class<?> getCustomReadTarget(Class<?> source, Class<?> expectedTargetType) {
/**
* Returns the actual target type for the given {@code sourceType} and {@code requestedTargetType}. Note that the
* returned {@link Class} could be an assignable type to the given {@code requestedTargetType}.
*
* @param sourceType must not be {@literal null}.
* @param requestedTargetType can be {@literal null}.
* @return
*/
private Class<?> getCustomReadTarget(Class<?> sourceType, Class<?> requestedTargetType) {
Class<?> type = expectedTargetType == null ? PlaceholderType.class : expectedTargetType;
Assert.notNull(sourceType);
Map<Class<?>, CacheValue> map;
CacheValue toReturn;
if ((map = cache.get(source)) == null || (toReturn = map.get(type)) == null) {
Class<?> target = getCustomTarget(source, type, readingPairs);
if (cache.get(source) == null) {
cache.put(source, new HashMap<Class<?>, CacheValue>());
}
Map<Class<?>, CacheValue> value = cache.get(source);
toReturn = target == null ? CacheValue.NULL : new CacheValue(target);
value.put(type, toReturn);
if (requestedTargetType == null) {
return null;
}
return toReturn.clazz;
ConvertiblePair lookupKey = new ConvertiblePair(sourceType, requestedTargetType);
CacheValue readTargetTypeValue = customReadTargetTypes.get(lookupKey);
if (readTargetTypeValue != null) {
return readTargetTypeValue.getType();
}
readTargetTypeValue = CacheValue.of(getCustomTarget(sourceType, requestedTargetType, readingPairs));
CacheValue cacheValue = customReadTargetTypes.putIfAbsent(lookupKey, readTargetTypeValue);
return cacheValue != null ? cacheValue.getType() : readTargetTypeValue.getType();
}
@WritingConverter
@@ -334,8 +364,10 @@ public class CustomConversions {
INSTANCE;
public Set<ConvertiblePair> getConvertibleTypes() {
ConvertiblePair localeToString = new ConvertiblePair(Locale.class, String.class);
ConvertiblePair booleanToString = new ConvertiblePair(Character.class, String.class);
return new HashSet<ConvertiblePair>(Arrays.asList(localeToString, booleanToString));
}
@@ -344,29 +376,29 @@ public class CustomConversions {
}
}
/**
* Placeholder type to allow registering not-found values in the converter cache.
*
* @author Patryk Wasik
* @author Oliver Gierke
*/
private static class PlaceholderType {
}
/**
* Wrapper to safely store {@literal null} values in the type cache.
*
* @author Patryk Wasik
* @author Oliver Gierke
* @author Thomas Darimont
*/
private static class CacheValue {
public static final CacheValue NULL = new CacheValue(null);
private final Class<?> clazz;
private static final CacheValue ABSENT = new CacheValue(null);
public CacheValue(Class<?> clazz) {
this.clazz = clazz;
private final Class<?> type;
public CacheValue(Class<?> type) {
this.type = type;
}
public Class<?> getType() {
return type;
}
static CacheValue of(Class<?> type) {
return type == null ? ABSENT : new CacheValue(type);
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2013 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 java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* Wrapper value object for a {@link BasicDBObject} to be able to access raw values by {@link MongoPersistentProperty}
* references. The accessors will transparently resolve nested document values that a {@link MongoPersistentProperty}
* might refer to through a path expression in field names.
*
* @author Oliver Gierke
*/
class DBObjectAccessor {
private final DBObject dbObject;
/**
* Creates a new {@link DBObjectAccessor} for the given {@link DBObject}.
*
* @param dbObject must be a {@link BasicDBObject} effectively, must not be {@literal null}.
*/
public DBObjectAccessor(DBObject dbObject) {
Assert.notNull(dbObject, "DBObject must not be null!");
Assert.isInstanceOf(BasicDBObject.class, dbObject, "Given DBObject must be a BasicDBObject!");
this.dbObject = dbObject;
}
/**
* Puts the given value into the backing {@link DBObject} based on the coordinates defined through the given
* {@link MongoPersistentProperty}. By default this will be the plain field name. But field names might also consist
* of path traversals so we might need to create intermediate {@link BasicDBObject}s.
*
* @param prop must not be {@literal null}.
* @param value
*/
public void put(MongoPersistentProperty prop, Object value) {
Assert.notNull(prop, "MongoPersistentProperty must not be null!");
String fieldName = prop.getFieldName();
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
DBObject dbObject = this.dbObject;
while (parts.hasNext()) {
String part = parts.next();
if (parts.hasNext()) {
BasicDBObject nestedDbObject = new BasicDBObject();
dbObject.put(part, nestedDbObject);
dbObject = nestedDbObject;
} else {
dbObject.put(part, value);
}
}
}
/**
* Returns the value the given {@link MongoPersistentProperty} refers to. By default this will be a direct field but
* the method will also transparently resolve nested values the {@link MongoPersistentProperty} might refer to through
* a path expression in the field name metadata.
*
* @param property must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
public Object get(MongoPersistentProperty property) {
String fieldName = property.getFieldName();
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
Map<Object, Object> source = this.dbObject.toMap();
Object result = null;
while (source != null && parts.hasNext()) {
result = source.get(parts.next());
if (parts.hasNext()) {
source = getAsMap(result);
}
}
return result;
}
@SuppressWarnings("unchecked")
private Map<Object, Object> getAsMap(Object source) {
if (source instanceof BasicDBObject) {
return ((DBObject) source).toMap();
}
if (source instanceof Map) {
return (Map<Object, Object>) source;
}
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 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.
@@ -31,18 +31,30 @@ import com.mongodb.DBObject;
*/
class DBObjectPropertyAccessor extends MapAccessor {
static MapAccessor INSTANCE = new DBObjectPropertyAccessor();
static final MapAccessor INSTANCE = new DBObjectPropertyAccessor();
/*
* (non-Javadoc)
* @see org.springframework.context.expression.MapAccessor#getSpecificTargetClasses()
*/
@Override
public Class<?>[] getSpecificTargetClasses() {
return new Class[] { DBObject.class };
}
/*
* (non-Javadoc)
* @see org.springframework.context.expression.MapAccessor#canRead(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String)
*/
@Override
public boolean canRead(EvaluationContext context, Object target, String name) {
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.context.expression.MapAccessor#read(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String)
*/
@Override
@SuppressWarnings("unchecked")
public TypedValue read(EvaluationContext context, Object target, String name) {
@@ -52,4 +64,4 @@ class DBObjectPropertyAccessor extends MapAccessor {
Object value = source.get(name);
return value == null ? TypedValue.NULL : new TypedValue(value);
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import com.mongodb.DBRef;
/**
* @author Oliver Gierke
*/
public interface DbRefProxyHandler {
Object populateId(MongoPersistentProperty property, DBRef source, Object proxy);
}

View File

@@ -0,0 +1,56 @@
/*
* 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.convert;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import com.mongodb.DBRef;
/**
* Used to resolve associations annotated with {@link org.springframework.data.mongodb.core.mapping.DBRef}.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.4
*/
public interface DbRefResolver {
/**
* Resolves the given {@link DBRef} into an object of the given {@link MongoPersistentProperty}'s type. The method
* might return a proxy object for the {@link DBRef} or resolve it immediately. In both cases the
* {@link DbRefResolverCallback} will be used to obtain the actual backing object.
*
* @param property will never be {@literal null}.
* @param dbref the {@link DBRef} to resolve.
* @param callback will never be {@literal null}.
* @return
*/
Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
DbRefProxyHandler proxyHandler);
/**
* Creates a {@link DBRef} instance for the given {@link org.springframework.data.mongodb.core.mapping.DBRef}
* annotation, {@link MongoPersistentEntity} and id.
*
* @param annotation will never be {@literal null}.
* @param entity will never be {@literal null}.
* @param id will never be {@literal null}.
* @return
*/
DBRef createDbRef(org.springframework.data.mongodb.core.mapping.DBRef annotation, MongoPersistentEntity<?> entity,
Object id);
}

View File

@@ -13,24 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.mapping;
package org.springframework.data.mongodb.core.convert;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
/**
* SPI interface to determine how to name document fields in cases the field name is not manually defined.
* Callback interface to be used in conjunction with {@link DbRefResolver}.
*
* @see DocumentField
* @see PropertyNameFieldNamingStrategy
* @see CamelCaseAbbreviatingFieldNamingStrategy
* @since 1.3
* @author Thomas Darimont
* @author Oliver Gierke
*/
public interface FieldNamingStrategy {
public interface DbRefResolverCallback {
/**
* Returns the field name to be used for the given {@link MongoPersistentProperty}.
* Resolve the final object for the given {@link MongoPersistentProperty}.
*
* @param property must not be {@literal null} or empty;
* @param property will never be {@literal null}.
* @return
*/
String getFieldName(MongoPersistentProperty property);
Object resolve(MongoPersistentProperty property);
}

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.convert;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
/**
* @author Oliver Gierke
*/
class DefaultDbRefProxyHandler implements DbRefProxyHandler {
private final SpELContext spELContext;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final ValueResolver resolver;
/**
* @param spELContext must not be {@literal null}.
* @param conversionService must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
*/
public DefaultDbRefProxyHandler(SpELContext spELContext,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext, ValueResolver resolver) {
this.spELContext = spELContext;
this.mappingContext = mappingContext;
this.resolver = resolver;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.DbRefProxyHandler#populateId(com.mongodb.DBRef, java.lang.Object)
*/
@Override
public Object populateId(MongoPersistentProperty property, DBRef source, Object proxy) {
if (source == null) {
return proxy;
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(property);
MongoPersistentProperty idProperty = entity.getIdProperty();
if (idProperty.usePropertyAccess()) {
return proxy;
}
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(proxy, spELContext);
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(proxy);
DBObject object = new BasicDBObject(idProperty.getFieldName(), source.getId());
ObjectPath objectPath = ObjectPath.ROOT.push(proxy, entity, null);
accessor.setProperty(idProperty, resolver.getValueInternal(idProperty, object, evaluator, objectPath));
return proxy;
}
}

View File

@@ -0,0 +1,392 @@
/*
* 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.convert;
import static org.springframework.util.ReflectionUtils.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
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.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.LazyLoadingException;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.objenesis.ObjenesisStd;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import com.mongodb.DB;
import com.mongodb.DBRef;
/**
* A {@link DbRefResolver} that resolves {@link org.springframework.data.mongodb.core.mapping.DBRef}s by delegating to a
* {@link DbRefResolverCallback} than is able to generate lazy loading proxies.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.4
*/
public class DefaultDbRefResolver implements DbRefResolver {
private final MongoDbFactory mongoDbFactory;
private final PersistenceExceptionTranslator exceptionTranslator;
private final ObjenesisStd objenesis;
/**
* Creates a new {@link DefaultDbRefResolver} with the given {@link MongoDbFactory}.
*
* @param mongoDbFactory must not be {@literal null}.
*/
public DefaultDbRefResolver(MongoDbFactory mongoDbFactory) {
Assert.notNull(mongoDbFactory, "MongoDbFactory translator must not be null!");
this.mongoDbFactory = mongoDbFactory;
this.exceptionTranslator = mongoDbFactory.getExceptionTranslator();
this.objenesis = new ObjenesisStd(true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#resolveDbRef(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.convert.DbRefResolverCallback)
*/
@Override
public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
DbRefProxyHandler handler) {
Assert.notNull(property, "Property must not be null!");
Assert.notNull(callback, "Callback must not be null!");
if (isLazyDbRef(property)) {
return createLazyLoadingProxy(property, dbref, callback, handler);
}
return callback.resolve(property);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#created(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.Object)
*/
@Override
public DBRef createDbRef(org.springframework.data.mongodb.core.mapping.DBRef annotation,
MongoPersistentEntity<?> entity, Object id) {
DB db = mongoDbFactory.getDb();
db = annotation != null && StringUtils.hasText(annotation.db()) ? mongoDbFactory.getDb(annotation.db()) : db;
return new DBRef(db, entity.getCollection(), id);
}
/**
* Creates a proxy for the given {@link MongoPersistentProperty} using the given {@link DbRefResolverCallback} to
* eventually resolve the value of the property.
*
* @param property must not be {@literal null}.
* @param dbref can be {@literal null}.
* @param callback must not be {@literal null}.
* @return
*/
private Object createLazyLoadingProxy(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
DbRefProxyHandler handler) {
Class<?> propertyType = property.getType();
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, dbref, exceptionTranslator, callback);
if (!propertyType.isInterface()) {
Factory factory = (Factory) objenesis.newInstance(getEnhancedTypeFor(propertyType));
factory.setCallbacks(new Callback[] { interceptor });
return handler.populateId(property, dbref, factory);
}
ProxyFactory proxyFactory = new ProxyFactory();
for (Class<?> type : propertyType.getInterfaces()) {
proxyFactory.addInterface(type);
}
proxyFactory.addInterface(LazyLoadingProxy.class);
proxyFactory.addInterface(propertyType);
proxyFactory.addAdvice(interceptor);
return handler.populateId(property, dbref, proxyFactory.getProxy());
}
/**
* Returns the CGLib enhanced type for the given source type.
*
* @param type
* @return
*/
private Class<?> getEnhancedTypeFor(Class<?> type) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(type);
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
enhancer.setInterfaces(new Class[] { LazyLoadingProxy.class });
return enhancer.createClass();
}
/**
* Returns whether the property shall be resolved lazily.
*
* @param property must not be {@literal null}.
* @return
*/
private boolean isLazyDbRef(MongoPersistentProperty property) {
return property.getDBRef() != null && property.getDBRef().lazy();
}
/**
* A {@link MethodInterceptor} that is used within a lazy loading proxy. The property resolving is delegated to a
* {@link DbRefResolverCallback}. The resolving process is triggered by a method invocation on the proxy and is
* guaranteed to be performed only once.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
*/
static class LazyLoadingInterceptor implements MethodInterceptor, org.springframework.cglib.proxy.MethodInterceptor,
Serializable {
private static final Method INITIALIZE_METHOD, TO_DBREF_METHOD, FINALIZE_METHOD;
private final DbRefResolverCallback callback;
private final MongoPersistentProperty property;
private final PersistenceExceptionTranslator exceptionTranslator;
private volatile boolean resolved;
private Object result;
private DBRef dbref;
static {
try {
INITIALIZE_METHOD = LazyLoadingProxy.class.getMethod("getTarget");
TO_DBREF_METHOD = LazyLoadingProxy.class.getMethod("toDBRef");
FINALIZE_METHOD = Object.class.getDeclaredMethod("finalize");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Creates a new {@link LazyLoadingInterceptor} for the given {@link MongoPersistentProperty},
* {@link PersistenceExceptionTranslator} and {@link DbRefResolverCallback}.
*
* @param property must not be {@literal null}.
* @param dbref can be {@literal null}.
* @param callback must not be {@literal null}.
*/
public LazyLoadingInterceptor(MongoPersistentProperty property, DBRef dbref,
PersistenceExceptionTranslator exceptionTranslator, DbRefResolverCallback callback) {
Assert.notNull(property, "Property must not be null!");
Assert.notNull(exceptionTranslator, "Exception translator must not be null!");
Assert.notNull(callback, "Callback must not be null!");
this.dbref = dbref;
this.callback = callback;
this.exceptionTranslator = exceptionTranslator;
this.property = property;
}
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null);
}
/*
* (non-Javadoc)
* @see org.springframework.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], org.springframework.cglib.proxy.MethodProxy)
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (INITIALIZE_METHOD.equals(method)) {
return ensureResolved();
}
if (TO_DBREF_METHOD.equals(method)) {
return this.dbref;
}
if (isObjectMethod(method) && Object.class.equals(method.getDeclaringClass())) {
if (ReflectionUtils.isToStringMethod(method)) {
return proxyToString(proxy);
}
if (ReflectionUtils.isEqualsMethod(method)) {
return proxyEquals(proxy, args[0]);
}
if (ReflectionUtils.isHashCodeMethod(method)) {
return proxyHashCode(proxy);
}
// DATAMONGO-1076 - finalize methods should not trigger proxy initialization
if (FINALIZE_METHOD.equals(method)) {
return null;
}
}
Object target = ensureResolved();
if (target == null) {
return null;
}
return method.invoke(target, args);
}
/**
* Returns a to string representation for the given {@code proxy}.
*
* @param proxy
* @return
*/
private String proxyToString(Object proxy) {
StringBuilder description = new StringBuilder();
if (dbref != null) {
description.append(dbref.getRef());
description.append(":");
description.append(dbref.getId());
} else {
description.append(System.identityHashCode(proxy));
}
description.append("$").append(LazyLoadingProxy.class.getSimpleName());
return description.toString();
}
/**
* Returns the hashcode for the given {@code proxy}.
*
* @param proxy
* @return
*/
private int proxyHashCode(Object proxy) {
return proxyToString(proxy).hashCode();
}
/**
* Performs an equality check for the given {@code proxy}.
*
* @param proxy
* @param that
* @return
*/
private boolean proxyEquals(Object proxy, Object that) {
if (!(that instanceof LazyLoadingProxy)) {
return false;
}
if (that == proxy) {
return true;
}
return proxyToString(proxy).equals(that.toString());
}
/**
* Will trigger the resolution if the proxy is not resolved already or return a previously resolved result.
*
* @return
*/
private Object ensureResolved() {
if (!resolved) {
this.result = resolve();
this.resolved = true;
}
return this.result;
}
/**
* Callback method for serialization.
*
* @param out
* @throws IOException
*/
private void writeObject(ObjectOutputStream out) throws IOException {
ensureResolved();
out.writeObject(this.result);
}
/**
* Callback method for deserialization.
*
* @param in
* @throws IOException
*/
private void readObject(ObjectInputStream in) throws IOException {
try {
this.resolved = true;
this.result = in.readObject();
} catch (ClassNotFoundException e) {
throw new LazyLoadingException("Could not deserialize result", e);
}
}
/**
* Resolves the proxy into its backing object.
*
* @return
*/
private synchronized Object resolve() {
if (!resolved) {
try {
return callback.resolve(property);
} catch (RuntimeException ex) {
DataAccessException translatedException = this.exceptionTranslator.translateExceptionIfPossible(ex);
throw new LazyLoadingException("Unable to lazily resolve DBRef!", translatedException);
}
}
return result;
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import com.mongodb.DBObject;
/**
* Default implementation of {@link DbRefResolverCallback}.
*
* @author Oliver Gierke
*/
class DefaultDbRefResolverCallback implements DbRefResolverCallback {
private final DBObject surroundingObject;
private final ObjectPath path;
private final ValueResolver resolver;
private final SpELExpressionEvaluator evaluator;
/**
* Creates a new {@link DefaultDbRefResolverCallback} using the given {@link DBObject}, {@link ObjectPath},
* {@link ValueResolver} and {@link SpELExpressionEvaluator}.
*
* @param surroundingObject must not be {@literal null}.
* @param path must not be {@literal null}.
* @param evaluator must not be {@literal null}.
* @param resolver must not be {@literal null}.
*/
public DefaultDbRefResolverCallback(DBObject surroundingObject, ObjectPath path, SpELExpressionEvaluator evaluator,
ValueResolver resolver) {
this.surroundingObject = surroundingObject;
this.path = path;
this.resolver = resolver;
this.evaluator = evaluator;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.DbRefResolverCallback#resolve(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty)
*/
@Override
public Object resolve(MongoPersistentProperty property) {
return resolver.getValueInternal(property, surroundingObject, evaluator, path);
}
}

View File

@@ -0,0 +1,448 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.geo.Shape;
import org.springframework.data.mongodb.core.geo.Sphere;
import org.springframework.data.mongodb.core.query.GeoCommand;
import org.springframework.util.Assert;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* Wrapper class to contain useful geo structure converters for the usage with Mongo.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.5
*/
abstract class GeoConverters {
/**
* Private constructor to prevent instantiation.
*/
private GeoConverters() {}
/**
* Returns the geo converters to be registered.
*
* @return
*/
public static Collection<? extends Object> getConvertersToRegister() {
return Arrays.asList( //
BoxToDbObjectConverter.INSTANCE //
, PolygonToDbObjectConverter.INSTANCE //
, CircleToDbObjectConverter.INSTANCE //
, SphereToDbObjectConverter.INSTANCE //
, DbObjectToBoxConverter.INSTANCE //
, DbObjectToPolygonConverter.INSTANCE //
, DbObjectToCircleConverter.INSTANCE //
, DbObjectToSphereConverter.INSTANCE //
, DbObjectToPointConverter.INSTANCE //
, PointToDbObjectConverter.INSTANCE //
, GeoCommandToDbObjectConverter.INSTANCE);
}
/**
* Converts a {@link List} of {@link Double}s into a {@link Point}.
*
* @author Thomas Darimont
* @since 1.5
*/
@ReadingConverter
public static enum DbObjectToPointConverter implements Converter<DBObject, Point> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Point convert(DBObject source) {
Assert.isTrue(source.keySet().size() == 2, "Source must contain 2 elements");
return source == null ? null : new Point((Double) source.get("x"), (Double) source.get("y"));
}
}
/**
* Converts a {@link Point} into a {@link List} of {@link Double}s.
*
* @author Thomas Darimont
* @since 1.5
*/
public static enum PointToDbObjectConverter implements Converter<Point, DBObject> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public DBObject convert(Point source) {
return source == null ? null : new BasicDBObject("x", source.getX()).append("y", source.getY());
}
}
/**
* Converts a {@link Box} into a {@link BasicDBList}.
*
* @author Thomas Darimont
* @since 1.5
*/
@WritingConverter
public static enum BoxToDbObjectConverter implements Converter<Box, DBObject> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public DBObject convert(Box source) {
if (source == null) {
return null;
}
BasicDBObject result = new BasicDBObject();
result.put("first", PointToDbObjectConverter.INSTANCE.convert(source.getFirst()));
result.put("second", PointToDbObjectConverter.INSTANCE.convert(source.getSecond()));
return result;
}
}
/**
* Converts a {@link BasicDBList} into a {@link Box}.
*
* @author Thomas Darimont
* @since 1.5
*/
@ReadingConverter
public static enum DbObjectToBoxConverter implements Converter<DBObject, Box> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Box convert(DBObject source) {
if (source == null) {
return null;
}
Point first = DbObjectToPointConverter.INSTANCE.convert((DBObject) source.get("first"));
Point second = DbObjectToPointConverter.INSTANCE.convert((DBObject) source.get("second"));
return new Box(first, second);
}
}
/**
* Converts a {@link Circle} into a {@link BasicDBList}.
*
* @author Thomas Darimont
* @since 1.5
*/
public static enum CircleToDbObjectConverter implements Converter<Circle, DBObject> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public DBObject convert(Circle source) {
if (source == null) {
return null;
}
DBObject result = new BasicDBObject();
result.put("center", PointToDbObjectConverter.INSTANCE.convert(source.getCenter()));
result.put("radius", source.getRadius().getNormalizedValue());
result.put("metric", source.getRadius().getMetric().toString());
return result;
}
}
/**
* Converts a {@link DBObject} into a {@link Circle}.
*
* @author Thomas Darimont
* @since 1.5
*/
@ReadingConverter
public static enum DbObjectToCircleConverter implements Converter<DBObject, Circle> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Circle convert(DBObject source) {
if (source == null) {
return null;
}
DBObject center = (DBObject) source.get("center");
Double radius = (Double) source.get("radius");
Distance distance = new Distance(radius);
if (source.containsField("metric")) {
String metricString = (String) source.get("metric");
Assert.notNull(metricString, "Metric must not be null!");
distance = distance.in(Metrics.valueOf(metricString));
}
Assert.notNull(center, "Center must not be null!");
Assert.notNull(radius, "Radius must not be null!");
return new Circle(DbObjectToPointConverter.INSTANCE.convert(center), distance);
}
}
/**
* Converts a {@link Sphere} into a {@link BasicDBList}.
*
* @author Thomas Darimont
* @since 1.5
*/
public static enum SphereToDbObjectConverter implements Converter<Sphere, DBObject> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public DBObject convert(Sphere source) {
if (source == null) {
return null;
}
DBObject result = new BasicDBObject();
result.put("center", PointToDbObjectConverter.INSTANCE.convert(source.getCenter()));
result.put("radius", source.getRadius().getNormalizedValue());
result.put("metric", source.getRadius().getMetric().toString());
return result;
}
}
/**
* Converts a {@link BasicDBList} into a {@link Sphere}.
*
* @author Thomas Darimont
* @since 1.5
*/
@ReadingConverter
public static enum DbObjectToSphereConverter implements Converter<DBObject, Sphere> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Sphere convert(DBObject source) {
if (source == null) {
return null;
}
DBObject center = (DBObject) source.get("center");
Double radius = (Double) source.get("radius");
Distance distance = new Distance(radius);
if (source.containsField("metric")) {
String metricString = (String) source.get("metric");
Assert.notNull(metricString, "Metric must not be null!");
distance = distance.in(Metrics.valueOf(metricString));
}
Assert.notNull(center, "Center must not be null!");
Assert.notNull(radius, "Radius must not be null!");
return new Sphere(DbObjectToPointConverter.INSTANCE.convert(center), distance);
}
}
/**
* Converts a {@link Polygon} into a {@link BasicDBList}.
*
* @author Thomas Darimont
* @since 1.5
*/
public static enum PolygonToDbObjectConverter implements Converter<Polygon, DBObject> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public DBObject convert(Polygon source) {
if (source == null) {
return null;
}
List<Point> points = source.getPoints();
List<DBObject> pointTuples = new ArrayList<DBObject>(points.size());
for (Point point : points) {
pointTuples.add(PointToDbObjectConverter.INSTANCE.convert(point));
}
DBObject result = new BasicDBObject();
result.put("points", pointTuples);
return result;
}
}
/**
* Converts a {@link BasicDBList} into a {@link Polygon}.
*
* @author Thomas Darimont
* @since 1.5
*/
@ReadingConverter
public static enum DbObjectToPolygonConverter implements Converter<DBObject, Polygon> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
@SuppressWarnings({ "unchecked" })
public Polygon convert(DBObject source) {
if (source == null) {
return null;
}
List<DBObject> points = (List<DBObject>) source.get("points");
List<Point> newPoints = new ArrayList<Point>(points.size());
for (DBObject element : points) {
Assert.notNull(element, "Point elements of polygon must not be null!");
newPoints.add(DbObjectToPointConverter.INSTANCE.convert(element));
}
return new Polygon(newPoints);
}
}
/**
* Converts a {@link Sphere} into a {@link BasicDBList}.
*
* @author Thomas Darimont
* @since 1.5
*/
public static enum GeoCommandToDbObjectConverter implements Converter<GeoCommand, DBObject> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public DBObject convert(GeoCommand source) {
if (source == null) {
return null;
}
BasicDBList argument = new BasicDBList();
Shape shape = source.getShape();
if (shape instanceof Box) {
argument.add(toList(((Box) shape).getFirst()));
argument.add(toList(((Box) shape).getSecond()));
} else if (shape instanceof Circle) {
argument.add(toList(((Circle) shape).getCenter()));
argument.add(((Circle) shape).getRadius().getNormalizedValue());
} else if (shape instanceof Circle) {
argument.add(toList(((Circle) shape).getCenter()));
argument.add(((Circle) shape).getRadius());
} else if (shape instanceof Polygon) {
for (Point point : ((Polygon) shape).getPoints()) {
argument.add(toList(point));
}
} else if (shape instanceof Sphere) {
argument.add(toList(((Sphere) shape).getCenter()));
argument.add(((Sphere) shape).getRadius().getNormalizedValue());
}
return new BasicDBObject(source.getCommand(), argument);
}
}
static List<Double> toList(Point point) {
return Arrays.asList(point.getX(), point.getY());
}
}

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 by the original author(s).
* Copyright 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.
@@ -29,18 +29,19 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.convert.CollectionFactory;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.convert.TypeMapper;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.model.ParameterValueProvider;
@@ -56,12 +57,11 @@ 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 org.springframework.util.StringUtils;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
@@ -73,38 +73,40 @@ import com.mongodb.DBRef;
* @author Jon Brisbin
* @author Patrik Wasik
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware {
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware, ValueResolver {
protected static final Logger log = LoggerFactory.getLogger(MappingMongoConverter.class);
private static final String INCOMPATIBLE_TYPES = "Cannot convert %1$s of type %2$s into an instance of %3$s! Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions. Parent object was: %4$s";
protected static final Logger LOGGER = LoggerFactory.getLogger(MappingMongoConverter.class);
protected final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
protected final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
protected final MongoDbFactory mongoDbFactory;
protected final QueryMapper idMapper;
protected final DbRefResolver dbRefResolver;
protected ApplicationContext applicationContext;
protected boolean useFieldAccessOnly = true;
protected MongoTypeMapper typeMapper;
protected String mapKeyDotReplacement = null;
private SpELContext spELContext;
/**
* Creates a new {@link MappingMongoConverter} given the new {@link MongoDbFactory} and {@link MappingContext}.
* Creates a new {@link MappingMongoConverter} given the new {@link DbRefResolver} and {@link MappingContext}.
*
* @param mongoDbFactory must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
*/
@SuppressWarnings("deprecation")
public MappingMongoConverter(MongoDbFactory mongoDbFactory,
public MappingMongoConverter(DbRefResolver dbRefResolver,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
super(ConversionServiceFactory.createDefaultConversionService());
super(new DefaultConversionService());
Assert.notNull(mongoDbFactory);
Assert.notNull(mappingContext);
Assert.notNull(dbRefResolver, "DbRefResolver must not be null!");
Assert.notNull(mappingContext, "MappingContext must not be null!");
this.mongoDbFactory = mongoDbFactory;
this.dbRefResolver = dbRefResolver;
this.mappingContext = mappingContext;
this.typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext);
this.idMapper = new QueryMapper(this);
@@ -112,6 +114,19 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
this.spELContext = new SpELContext(DBObjectPropertyAccessor.INSTANCE);
}
/**
* Creates a new {@link MappingMongoConverter} given the new {@link MongoDbFactory} and {@link MappingContext}.
*
* @deprecated use the constructor taking a {@link DbRefResolver} instead.
* @param mongoDbFactory must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
*/
@Deprecated
public MappingMongoConverter(MongoDbFactory mongoDbFactory,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
this(new DefaultDbRefResolver(mongoDbFactory), mappingContext);
}
/**
* Configures the {@link MongoTypeMapper} to be used to add type information to {@link DBObject}s created by the
* converter and how to lookup type information from {@link DBObject}s when reading them. Uses a
@@ -154,17 +169,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return mappingContext;
}
/**
* Configures whether to use field access only for entity mapping. Setting this to true will force the
* {@link MongoConverter} to not go through getters or setters even if they are present for getting and setting
* property values.
*
* @param useFieldAccessOnly
*/
public void setUseFieldAccessOnly(boolean useFieldAccessOnly) {
this.useFieldAccessOnly = useFieldAccessOnly;
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
@@ -184,11 +188,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
protected <S extends Object> S read(TypeInformation<S> type, DBObject dbo) {
return read(type, dbo, null);
return read(type, dbo, ObjectPath.ROOT);
}
@SuppressWarnings("unchecked")
protected <S extends Object> S read(TypeInformation<S> type, DBObject dbo, Object parent) {
private <S extends Object> S read(TypeInformation<S> type, DBObject dbo, ObjectPath path) {
if (null == dbo) {
return null;
@@ -206,11 +210,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
if (typeToUse.isCollectionLike() && dbo instanceof BasicDBList) {
return (S) readCollectionOrArray(typeToUse, (BasicDBList) dbo, parent);
return (S) readCollectionOrArray(typeToUse, (BasicDBList) dbo, path);
}
if (typeToUse.isMap()) {
return (S) readMap(typeToUse, dbo, parent);
return (S) readMap(typeToUse, dbo, path);
}
if (dbo instanceof BasicDBList) {
throw new MappingException(String.format(INCOMPATIBLE_TYPES, dbo, BasicDBList.class, typeToUse.getType(), path));
}
// Retrieve persistent entity info
@@ -220,52 +228,79 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
throw new MappingException("No mapping metadata found for " + rawType.getName());
}
return read(persistentEntity, dbo, parent);
return read(persistentEntity, dbo, path);
}
private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(MongoPersistentEntity<?> entity,
DBObject source, DefaultSpELExpressionEvaluator evaluator, Object parent) {
DBObject source, DefaultSpELExpressionEvaluator evaluator, ObjectPath path) {
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, parent);
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, path);
PersistentEntityParameterValueProvider<MongoPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<MongoPersistentProperty>(
entity, provider, parent);
entity, provider, path.getCurrentObject());
return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider,
parent);
return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider, path);
}
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final DBObject dbo, Object parent) {
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final DBObject dbo, final ObjectPath path) {
final DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(dbo, spELContext);
ParameterValueProvider<MongoPersistentProperty> provider = getParameterProvider(entity, dbo, evaluator, parent);
ParameterValueProvider<MongoPersistentProperty> provider = getParameterProvider(entity, dbo, evaluator, path);
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
S instance = instantiator.createInstance(entity, provider);
final BeanWrapper<MongoPersistentEntity<S>, S> wrapper = BeanWrapper.create(instance, conversionService);
final S result = wrapper.getBean();
final PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance),
conversionService);
final MongoPersistentProperty idProperty = entity.getIdProperty();
final S result = instance;
// make sure id property is set before all other properties
Object idValue = null;
if (idProperty != null) {
idValue = getValueInternal(idProperty, dbo, evaluator, path);
accessor.setProperty(idProperty, idValue);
}
final ObjectPath currentPath = path.push(result, entity, idValue);
// Set properties not already set in the constructor
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
public void doWithPersistentProperty(MongoPersistentProperty prop) {
// we skip the id property since it was already set
if (idProperty != null && idProperty.equals(prop)) {
return;
}
if (!dbo.containsField(prop.getFieldName()) || entity.isConstructorArgument(prop)) {
return;
}
Object obj = getValueInternal(prop, dbo, evaluator, result);
wrapper.setProperty(prop, obj, useFieldAccessOnly);
accessor.setProperty(prop, getValueInternal(prop, dbo, evaluator, currentPath));
}
});
// Handle associations
entity.doWithAssociations(new AssociationHandler<MongoPersistentProperty>() {
public void doWithAssociation(Association<MongoPersistentProperty> association) {
MongoPersistentProperty inverseProp = association.getInverse();
Object obj = getValueInternal(inverseProp, dbo, evaluator, result);
wrapper.setProperty(inverseProp, obj);
final MongoPersistentProperty property = association.getInverse();
Object value = dbo.get(property.getFieldName());
if (value == null) {
return;
}
DBRef dbref = value instanceof DBRef ? (DBRef) value : null;
DbRefProxyHandler handler = new DefaultDbRefProxyHandler(spELContext, mappingContext,
MappingMongoConverter.this);
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(dbo, currentPath, evaluator,
MappingMongoConverter.this);
accessor.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, callback, handler));
}
});
@@ -285,7 +320,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Assert.isTrue(annotation != null, "The referenced property has to be mapped with @DBRef!");
}
return createDBRef(object, annotation);
// @see DATAMONGO-913
if (object instanceof LazyLoadingProxy) {
return ((LazyLoadingProxy) object).toDBRef();
}
return createDBRef(object, referingProperty);
}
/**
@@ -300,14 +340,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).getTarget() : obj;
writeInternal(target, dbo, type);
}
/**
@@ -323,7 +366,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);
@@ -331,17 +375,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);
}
@@ -356,15 +400,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
throw new MappingException("No mapping metadata found for entity of type " + obj.getClass().getName());
}
final BeanWrapper<MongoPersistentEntity<Object>, Object> wrapper = BeanWrapper.create(obj, conversionService);
final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(obj);
final MongoPersistentProperty idProperty = entity.getIdProperty();
if (!dbo.containsField("_id") && null != idProperty) {
boolean fieldAccessOnly = idProperty.usePropertyAccess() ? false : useFieldAccessOnly;
try {
Object id = wrapper.getProperty(idProperty, Object.class, fieldAccessOnly);
Object id = accessor.getProperty(idProperty);
dbo.put("_id", idMapper.convertId(id));
} catch (ConversionException ignored) {}
}
@@ -373,29 +415,30 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
public void doWithPersistentProperty(MongoPersistentProperty prop) {
if (prop.equals(idProperty)) {
if (prop.equals(idProperty) || !prop.isWritable()) {
return;
}
boolean fieldAccessOnly = prop.usePropertyAccess() ? false : useFieldAccessOnly;
Object propertyObj = wrapper.getProperty(prop, prop.getType(), fieldAccessOnly);
Object propertyObj = accessor.getProperty(prop);
if (null != propertyObj) {
if (!conversions.isSimpleType(propertyObj.getClass())) {
writePropertyInternal(propertyObj, dbo, prop);
} else {
writeSimpleInternal(propertyObj, dbo, prop.getFieldName());
writeSimpleInternal(propertyObj, dbo, prop);
}
}
}
});
entity.doWithAssociations(new AssociationHandler<MongoPersistentProperty>() {
public void doWithAssociation(Association<MongoPersistentProperty> association) {
MongoPersistentProperty inverseProp = association.getInverse();
Class<?> type = inverseProp.getType();
Object propertyObj = wrapper.getProperty(inverseProp, type, useFieldAccessOnly);
Object propertyObj = accessor.getProperty(inverseProp);
if (null != propertyObj) {
writePropertyInternal(propertyObj, dbo, inverseProp);
}
@@ -410,46 +453,68 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return;
}
String name = prop.getFieldName();
DBObjectAccessor accessor = new DBObjectAccessor(dbo);
TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
TypeInformation<?> type = prop.getTypeInformation();
if (valueType.isCollectionLike()) {
DBObject collectionInternal = createCollection(asCollection(obj), prop);
dbo.put(name, collectionInternal);
accessor.put(prop, collectionInternal);
return;
}
if (valueType.isMap()) {
DBObject mapDbObj = createMap((Map<Object, Object>) obj, prop);
dbo.put(name, mapDbObj);
accessor.put(prop, mapDbObj);
return;
}
if (prop.isDbReference()) {
DBRef dbRefObj = createDBRef(obj, prop.getDBRef());
DBRef dbRefObj = null;
/*
* If we already have a LazyLoadingProxy, we use it's cached DBRef value instead of
* unnecessarily initializing it only to convert it to a DBRef a few instructions later.
*/
if (obj instanceof LazyLoadingProxy) {
dbRefObj = ((LazyLoadingProxy) obj).toDBRef();
}
dbRefObj = dbRefObj != null ? dbRefObj : createDBRef(obj, prop);
if (null != dbRefObj) {
dbo.put(name, dbRefObj);
accessor.put(prop, dbRefObj);
return;
}
}
/*
* If we have a LazyLoadingProxy we make sure it is initialized first.
*/
if (obj instanceof LazyLoadingProxy) {
obj = ((LazyLoadingProxy) obj).getTarget();
}
// Lookup potential custom target type
Class<?> basicTargetType = conversions.getCustomWriteTarget(obj.getClass(), null);
if (basicTargetType != null) {
dbo.put(name, conversionService.convert(obj, basicTargetType));
accessor.put(prop, conversionService.convert(obj, basicTargetType));
return;
}
BasicDBObject propDbObj = new BasicDBObject();
addCustomTypeKeyIfNecessary(type, obj, propDbObj);
Object existingValue = accessor.get(prop);
BasicDBObject propDbObj = existingValue instanceof BasicDBObject ? (BasicDBObject) existingValue
: new BasicDBObject();
addCustomTypeKeyIfNecessary(ClassTypeInformation.from(prop.getRawType()), obj, propDbObj);
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass()) ? mappingContext
.getPersistentEntity(obj.getClass()) : mappingContext.getPersistentEntity(type);
writeInternal(obj, propDbObj, entity);
dbo.put(name, propDbObj);
accessor.put(prop, propDbObj);
}
private boolean isSubtype(Class<?> left, Class<?> right) {
@@ -494,7 +559,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
continue;
}
DBRef dbRef = createDBRef(element, property.getDBRef());
DBRef dbRef = createDBRef(element, property);
dbList.add(dbRef);
}
@@ -527,7 +592,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (conversions.isSimpleType(key.getClass())) {
String simpleKey = potentiallyEscapeMapKey(key.toString());
dbObject.put(simpleKey, value != null ? createDBRef(value, property.getDBRef()) : null);
dbObject.put(simpleKey, value != null ? createDBRef(value, property) : null);
} else {
throw new MappingException("Cannot use a complex object as a key value.");
@@ -640,19 +705,20 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
/**
* Adds custom type information to the given {@link DBObject} if necessary. That is if the value is not the same as
* the one given. This is usually the case if you store a subtype of the actual declared type of the property.
*
*
* @param type
* @param value must not be {@literal null}.
* @param dbObject must not be {@literal null}.
*/
protected void addCustomTypeKeyIfNecessary(TypeInformation<?> type, Object value, DBObject dbObject) {
TypeInformation<?> actualType = type != null ? type.getActualType() : type;
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);
}
}
@@ -667,6 +733,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
dbObject.put(key, getPotentiallyConvertedSimpleWrite(value));
}
private void writeSimpleInternal(Object value, DBObject dbObject, MongoPersistentProperty property) {
DBObjectAccessor accessor = new DBObjectAccessor(dbObject);
accessor.put(property, getPotentiallyConvertedSimpleWrite(value));
}
/**
* Checks whether we have a custom conversion registered for the given value into an arbitrary simple Mongo type.
* Returns the converted value if so. If not, we perform special enum handling or simply return the value as is.
@@ -715,7 +786,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return target.isAssignableFrom(value.getClass()) ? value : conversionService.convert(value, target);
}
protected DBRef createDBRef(Object target, org.springframework.data.mongodb.core.mapping.DBRef dbref) {
protected DBRef createDBRef(Object target, MongoPersistentProperty property) {
Assert.notNull(target);
@@ -724,6 +795,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
MongoPersistentEntity<?> targetEntity = mappingContext.getPersistentEntity(target.getClass());
targetEntity = targetEntity == null ? targetEntity = mappingContext.getPersistentEntity(property) : targetEntity;
if (null == targetEntity) {
throw new MappingException("No mapping metadata found for " + target.getClass());
@@ -735,24 +807,31 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
throw new MappingException("No id property found on class " + targetEntity.getType());
}
BeanWrapper<MongoPersistentEntity<Object>, Object> wrapper = BeanWrapper.create(target, conversionService);
Object id = wrapper.getProperty(idProperty, Object.class, useFieldAccessOnly);
Object id = null;
if (target.getClass().equals(idProperty.getType())) {
id = target;
} else {
PersistentPropertyAccessor accessor = targetEntity.getPropertyAccessor(target);
id = accessor.getProperty(idProperty);
}
if (null == id) {
throw new MappingException("Cannot create a reference to an object with a NULL id.");
}
DB db = mongoDbFactory.getDb();
db = dbref != null && StringUtils.hasText(dbref.db()) ? mongoDbFactory.getDb(dbref.db()) : db;
return new DBRef(db, targetEntity.getCollection(), idMapper.convertId(id));
return dbRefResolver.createDbRef(property == null ? null : property.getDBRef(), targetEntity,
idMapper.convertId(id));
}
protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator eval,
Object parent) {
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(dbo, spELContext, parent);
return provider.getPropertyValue(prop);
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.ValueResolver#getValueInternal(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, com.mongodb.DBObject, org.springframework.data.mapping.model.SpELExpressionEvaluator, java.lang.Object)
*/
@Override
public Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator,
ObjectPath path) {
return new MongoDbPropertyValueProvider(dbo, evaluator, path).getPropertyValue(prop);
}
/**
@@ -760,12 +839,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
*
* @param targetType must not be {@literal null}.
* @param sourceValue must not be {@literal null}.
* @param path must not be {@literal null}.
* @return the converted {@link Collection} or array, will never be {@literal null}.
*/
@SuppressWarnings("unchecked")
private Object readCollectionOrArray(TypeInformation<?> targetType, BasicDBList sourceValue, Object parent) {
private Object readCollectionOrArray(TypeInformation<?> targetType, BasicDBList sourceValue, ObjectPath path) {
Assert.notNull(targetType);
Assert.notNull(targetType, "Target type must not be null!");
Assert.notNull(path, "Object path must not be null!");
Class<?> collectionType = targetType.getType();
@@ -773,22 +853,22 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return getPotentiallyConvertedSimpleRead(new HashSet<Object>(), collectionType);
}
collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class;
Collection<Object> items = targetType.getType().isArray() ? new ArrayList<Object>() : CollectionFactory
.createCollection(collectionType, sourceValue.size());
TypeInformation<?> componentType = targetType.getComponentType();
Class<?> rawComponentType = componentType == null ? null : componentType.getType();
collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class;
Collection<Object> items = targetType.getType().isArray() ? new ArrayList<Object>() : CollectionFactory
.createCollection(collectionType, rawComponentType, sourceValue.size());
for (int i = 0; i < sourceValue.size(); i++) {
Object dbObjItem = sourceValue.get(i);
if (dbObjItem instanceof DBRef) {
items.add(DBRef.class.equals(rawComponentType) ? dbObjItem : read(componentType, ((DBRef) dbObjItem).fetch(),
parent));
items.add(DBRef.class.equals(rawComponentType) ? dbObjItem : read(componentType, readRef((DBRef) dbObjItem),
path));
} else if (dbObjItem instanceof DBObject) {
items.add(read(componentType, (DBObject) dbObjItem, parent));
items.add(read(componentType, (DBObject) dbObjItem, path));
} else {
items.add(getPotentiallyConvertedSimpleRead(dbObjItem, rawComponentType));
}
@@ -801,16 +881,25 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* Reads the given {@link DBObject} into a {@link Map}. will recursively resolve nested {@link Map}s as well.
*
* @param type the {@link Map} {@link TypeInformation} to be used to unmarshall this {@link DBObject}.
* @param dbObject
* @param dbObject must not be {@literal null}
* @param path must not be {@literal null}
* @return
*/
@SuppressWarnings("unchecked")
protected Map<Object, Object> readMap(TypeInformation<?> type, DBObject dbObject, Object parent) {
protected Map<Object, Object> readMap(TypeInformation<?> type, DBObject dbObject, ObjectPath path) {
Assert.notNull(dbObject);
Assert.notNull(dbObject, "DBObject must not be null!");
Assert.notNull(path, "Object path must not be null!");
Class<?> mapType = typeMapper.readType(dbObject, type).getType();
Map<Object, Object> map = CollectionFactory.createMap(mapType, dbObject.keySet().size());
TypeInformation<?> keyType = type.getComponentType();
Class<?> rawKeyType = keyType == null ? null : keyType.getType();
TypeInformation<?> valueType = type.getMapValueType();
Class<?> rawValueType = valueType == null ? null : valueType.getType();
Map<Object, Object> map = CollectionFactory.createMap(mapType, rawKeyType, dbObject.keySet().size());
Map<String, Object> sourceMap = dbObject.toMap();
for (Entry<String, Object> entry : sourceMap.entrySet()) {
@@ -820,20 +909,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Object key = potentiallyUnescapeMapKey(entry.getKey());
TypeInformation<?> keyTypeInformation = type.getComponentType();
if (keyTypeInformation != null) {
Class<?> keyType = keyTypeInformation.getType();
key = conversionService.convert(key, keyType);
if (rawKeyType != null) {
key = conversionService.convert(key, rawKeyType);
}
Object value = entry.getValue();
TypeInformation<?> valueType = type.getMapValueType();
Class<?> rawValueType = valueType == null ? null : valueType.getType();
if (value instanceof DBObject) {
map.put(key, read(valueType, (DBObject) value, parent));
map.put(key, read(valueType, (DBObject) value, path));
} else if (value instanceof DBRef) {
map.put(key, DBRef.class.equals(rawValueType) ? value : read(valueType, ((DBRef) value).fetch()));
map.put(key, DBRef.class.equals(rawValueType) ? value : read(valueType, readRef((DBRef) value)));
} else {
Class<?> valueClass = valueType == null ? null : valueType.getType();
map.put(key, getPotentiallyConvertedSimpleRead(value, valueClass));
@@ -843,21 +928,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return map;
}
protected <T> List<?> unwrapList(BasicDBList dbList, TypeInformation<T> targetType) {
List<Object> rootList = new ArrayList<Object>();
for (int i = 0; i < dbList.size(); i++) {
Object obj = dbList.get(i);
if (obj instanceof BasicDBList) {
rootList.add(unwrapList((BasicDBList) obj, targetType.getComponentType()));
} else if (obj instanceof DBObject) {
rootList.add(read(targetType, (DBObject) obj));
} else {
rootList.add(obj);
}
}
return rootList;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.MongoWriter#convertToMongoType(java.lang.Object, org.springframework.data.util.TypeInformation)
@@ -879,15 +949,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return getPotentiallyConvertedSimpleWrite(obj);
}
TypeInformation<?> typeHint = typeInformation;
if (obj instanceof BasicDBList) {
return maybeConvertList((BasicDBList) obj);
return maybeConvertList((BasicDBList) obj, typeHint);
}
if (obj instanceof DBObject) {
DBObject newValueDbo = new BasicDBObject();
for (String vk : ((DBObject) obj).keySet()) {
Object o = ((DBObject) obj).get(vk);
newValueDbo.put(vk, convertToMongoType(o));
newValueDbo.put(vk, convertToMongoType(o, typeHint));
}
return newValueDbo;
}
@@ -895,17 +967,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (obj instanceof Map) {
DBObject result = new BasicDBObject();
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) obj).entrySet()) {
result.put(entry.getKey().toString(), convertToMongoType(entry.getValue()));
result.put(entry.getKey().toString(), convertToMongoType(entry.getValue(), typeHint));
}
return result;
}
if (obj.getClass().isArray()) {
return maybeConvertList(Arrays.asList((Object[]) obj));
return maybeConvertList(Arrays.asList((Object[]) obj), typeHint);
}
if (obj instanceof Collection) {
return maybeConvertList((Collection<?>) obj);
return maybeConvertList((Collection<?>) obj, typeHint);
}
DBObject newDbo = new BasicDBObject();
@@ -918,11 +990,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return !obj.getClass().equals(typeInformation.getType()) ? newDbo : removeTypeInfoRecursively(newDbo);
}
public BasicDBList maybeConvertList(Iterable<?> source) {
public BasicDBList maybeConvertList(Iterable<?> source, TypeInformation<?> typeInformation) {
BasicDBList newDbl = new BasicDBList();
for (Object element : source) {
newDbl.add(convertToMongoType(element));
newDbl.add(convertToMongoType(element, typeInformation));
}
return newDbl;
}
@@ -963,24 +1037,34 @@ 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 DBObject source;
private final DBObjectAccessor source;
private final SpELExpressionEvaluator evaluator;
private final Object parent;
private final ObjectPath path;
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
* {@link ObjectPath}.
*
* @param source must not be {@literal null}.
* @param evaluator must not be {@literal null}.
* @param path can be {@literal null}.
*/
public MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path) {
Assert.notNull(source);
Assert.notNull(evaluator);
this.source = source;
this.source = new DBObjectAccessor(source);
this.evaluator = evaluator;
this.parent = parent;
this.path = path;
}
/*
@@ -990,13 +1074,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
public <T> T getPropertyValue(MongoPersistentProperty property) {
String expression = property.getSpelExpression();
Object value = expression != null ? evaluator.evaluate(expression) : source.get(property.getFieldName());
Object value = expression != null ? evaluator.evaluate(expression) : source.get(property);
if (value == null) {
return null;
}
return readValue(value, property.getTypeInformation(), parent);
return readValue(value, property.getTypeInformation(), path);
}
}
@@ -1009,7 +1093,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private class ConverterAwareSpELExpressionParameterValueProvider extends
SpELExpressionParameterValueProvider<MongoPersistentProperty> {
private final Object parent;
private final ObjectPath path;
/**
* Creates a new {@link ConverterAwareSpELExpressionParameterValueProvider}.
@@ -1019,10 +1103,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param delegate must not be {@literal null}.
*/
public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator,
ConversionService conversionService, ParameterValueProvider<MongoPersistentProperty> delegate, Object parent) {
ConversionService conversionService, ParameterValueProvider<MongoPersistentProperty> delegate, ObjectPath path) {
super(evaluator, conversionService, delegate);
this.parent = parent;
this.path = path;
}
/*
@@ -1031,25 +1115,51 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
*/
@Override
protected <T> T potentiallyConvertSpelValue(Object object, Parameter<T, MongoPersistentProperty> parameter) {
return readValue(object, parameter.getType(), parent);
return readValue(object, parameter.getType(), path);
}
}
@SuppressWarnings("unchecked")
private <T> T readValue(Object value, TypeInformation<?> type, Object parent) {
private <T> T readValue(Object value, TypeInformation<?> type, ObjectPath path) {
Class<?> rawType = type.getType();
if (conversions.hasCustomReadTarget(value.getClass(), rawType)) {
return (T) conversionService.convert(value, rawType);
} else if (value instanceof DBRef) {
return (T) (rawType.equals(DBRef.class) ? value : read(type, ((DBRef) value).fetch(), parent));
return potentiallyReadOrResolveDbRef((DBRef) value, type, path, rawType);
} else if (value instanceof BasicDBList) {
return (T) readCollectionOrArray(type, (BasicDBList) value, parent);
return (T) readCollectionOrArray(type, (BasicDBList) value, path);
} else if (value instanceof DBObject) {
return (T) read(type, (DBObject) value, parent);
return (T) read(type, (DBObject) value, path);
} else {
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
}
}
@SuppressWarnings("unchecked")
private <T> T potentiallyReadOrResolveDbRef(DBRef dbref, TypeInformation<?> type, ObjectPath path, Class<?> rawType) {
if (rawType.equals(DBRef.class)) {
return (T) dbref;
}
Object object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getRef());
if (object != null) {
return (T) object;
}
return (T) (object != null ? object : read(type, readRef(dbref), path));
}
/**
* Performs the fetch operation for the given {@link DBRef}.
*
* @param ref
* @return
*/
DBObject readRef(DBRef ref) {
return ref.fetch();
}
}

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.
@@ -24,21 +24,26 @@ import org.bson.types.ObjectId;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mongodb.core.query.Term;
import org.springframework.util.StringUtils;
import com.mongodb.DBObject;
/**
* Wrapper class to contain useful converters for the usage with Mongo.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
abstract class MongoConverters {
/**
* Private constructor to prevent instantiation.
*/
private MongoConverters() {
}
private MongoConverters() {}
/**
* Simple singleton to convert {@link ObjectId}s to their {@link String} representation.
@@ -147,4 +152,30 @@ abstract class MongoConverters {
}
}
}
@ReadingConverter
public static enum DBObjectToStringConverter implements Converter<DBObject, String> {
INSTANCE;
@Override
public String convert(DBObject source) {
return source == null ? null : source.toString();
}
}
/**
* @author Christoph Strobl
* @since 1.6
*/
@WritingConverter
public static enum TermToStringConverter implements Converter<Term, String> {
INSTANCE;
@Override
public String convert(Term source) {
return source == null ? null : source.getFormatted();
}
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.DBObject;
/**
* A path of objects nested into each other. The type allows access to all parent objects currently in creation even
* when resolving more nested objects. This allows to avoid re-resolving object instances that are logically equivalent
* to already resolved ones.
* <p>
* An immutable ordered set of target objects for {@link DBObject} to {@link Object} conversions. Object paths can be
* constructed by the {@link #toObjectPath(Object)} method and extended via {@link #push(Object)}.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.6
*/
class ObjectPath {
public static final ObjectPath ROOT = new ObjectPath();
private final List<ObjectPathItem> items;
private ObjectPath() {
this.items = Collections.emptyList();
}
/**
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} by adding the provided
* {@link ObjectPathItem} to it.
*
* @param parent can be {@literal null}.
* @param item
*/
private ObjectPath(ObjectPath parent, ObjectPath.ObjectPathItem item) {
List<ObjectPath.ObjectPathItem> items = new ArrayList<ObjectPath.ObjectPathItem>(parent.items);
items.add(item);
this.items = Collections.unmodifiableList(items);
}
/**
* Returns a copy of the {@link ObjectPath} with the given {@link Object} as current object.
*
* @param object must not be {@literal null}.
* @param entity must not be {@literal null}.
* @param id must not be {@literal null}.
* @return
*/
public ObjectPath push(Object object, MongoPersistentEntity<?> entity, Object id) {
Assert.notNull(object, "Object must not be null!");
Assert.notNull(entity, "MongoPersistentEntity must not be null!");
ObjectPathItem item = new ObjectPathItem(object, id, entity.getCollection());
return new ObjectPath(this, item);
}
/**
* Returns the object with the given id and stored in the given collection if it's contained in the {@link ObjectPath}
* .
*
* @param id must not be {@literal null}.
* @param collection must not be {@literal null} or empty.
* @return
*/
public Object getPathItem(Object id, String collection) {
Assert.notNull(id, "Id must not be null!");
Assert.hasText(collection, "Collection name must not be null!");
for (ObjectPathItem item : items) {
Object object = item.getObject();
if (object == null) {
continue;
}
if (item.getIdValue() == null) {
continue;
}
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())) {
return object;
}
}
return null;
}
/**
* Returns the current object of the {@link ObjectPath} or {@literal null} if the path is empty.
*
* @return
*/
public Object getCurrentObject() {
return items.isEmpty() ? null : items.get(items.size() - 1).getObject();
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
if (items.isEmpty()) {
return "[empty]";
}
List<String> strings = new ArrayList<String>(items.size());
for (ObjectPathItem item : items) {
strings.add(item.object.toString());
}
return StringUtils.collectionToDelimitedString(strings, " -> ");
}
/**
* An item in an {@link ObjectPath}.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
private static class ObjectPathItem {
private final Object object;
private final Object idValue;
private final String collection;
/**
* Creates a new {@link ObjectPathItem}.
*
* @param object
* @param idValue
* @param collection
*/
ObjectPathItem(Object object, Object idValue, String collection) {
this.object = object;
this.idValue = idValue;
this.collection = collection;
}
public Object getObject() {
return object;
}
public Object getIdValue() {
return idValue;
}
public String getCollection() {
return collection;
}
}
}

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,19 +17,26 @@ package org.springframework.data.mongodb.core.convert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.bson.types.ObjectId;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.Assert;
@@ -44,11 +51,17 @@ import com.mongodb.DBRef;
* @author Jon Brisbin
* @author Oliver Gierke
* @author Patryk Wasik
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class QueryMapper {
private static final List<String> DEFAULT_ID_NAMES = Arrays.asList("id", "_id");
private static final String N_OR_PATTERN = "\\$.*or";
private static final DBObject META_TEXT_SCORE = new BasicDBObject("$meta", "textScore");
private enum MetaMapping {
FORCE, WHEN_PRESENT, IGNORE;
}
private final ConversionService conversionService;
private final MongoConverter converter;
@@ -79,7 +92,7 @@ public class QueryMapper {
@SuppressWarnings("deprecation")
public DBObject getMappedObject(DBObject query, MongoPersistentEntity<?> entity) {
if (Keyword.isKeyword(query)) {
if (isNestedKeyword(query)) {
return getMappedKeyword(new Keyword(query), entity);
}
@@ -97,51 +110,132 @@ public class QueryMapper {
continue;
}
if (Keyword.isKeyword(key)) {
if (isKeyword(key)) {
result.putAll(getMappedKeyword(new Keyword(query, key), entity));
continue;
}
Field field = entity == null ? new Field(key) : new MetadataBackedField(key, entity, mappingContext);
Field field = createPropertyField(entity, key, mappingContext);
Entry<String, Object> entry = getMappedObjectForField(field, query.get(key));
Object rawValue = query.get(key);
String newKey = field.getMappedKey();
if (Keyword.isKeyword(rawValue) && !field.isIdField()) {
Keyword keyword = new Keyword((DBObject) rawValue);
result.put(newKey, getMappedKeyword(field, keyword));
} else {
result.put(newKey, getMappedValue(field, rawValue));
}
result.put(entry.getKey(), entry.getValue());
}
return result;
}
/**
* Maps fields used for sorting to the {@link MongoPersistentEntity}s properties. <br />
* Also converts properties to their {@code $meta} representation if present.
*
* @param sortObject
* @param entity
* @return
* @since 1.6
*/
public DBObject getMappedSort(DBObject sortObject, MongoPersistentEntity<?> entity) {
if (sortObject == null) {
return null;
}
DBObject mappedSort = getMappedObject(sortObject, entity);
mapMetaAttributes(mappedSort, entity, MetaMapping.WHEN_PRESENT);
return mappedSort;
}
/**
* Maps fields to retrieve to the {@link MongoPersistentEntity}s properties. <br />
* Also onverts and potentially adds missing property {@code $meta} representation.
*
* @param fieldsObject
* @param entity
* @return
* @since 1.6
*/
public DBObject getMappedFields(DBObject fieldsObject, MongoPersistentEntity<?> entity) {
DBObject mappedFields = fieldsObject != null ? getMappedObject(fieldsObject, entity) : new BasicDBObject();
mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE);
return mappedFields.keySet().isEmpty() ? null : mappedFields;
}
private void mapMetaAttributes(DBObject source, MongoPersistentEntity<?> entity, MetaMapping metaMapping) {
if (entity == null || source == null) {
return;
}
if (entity.hasTextScoreProperty() && !MetaMapping.IGNORE.equals(metaMapping)) {
MongoPersistentProperty textScoreProperty = entity.getTextScoreProperty();
if (MetaMapping.FORCE.equals(metaMapping)
|| (MetaMapping.WHEN_PRESENT.equals(metaMapping) && source.containsField(textScoreProperty.getFieldName()))) {
source.putAll(getMappedTextScoreField(textScoreProperty));
}
}
}
private DBObject getMappedTextScoreField(MongoPersistentProperty property) {
return new BasicDBObject(property.getFieldName(), META_TEXT_SCORE);
}
/**
* Extracts the mapped object value for given field out of rawValue taking nested {@link Keyword}s into account
*
* @param field
* @param rawValue
* @return
*/
protected Entry<String, Object> getMappedObjectForField(Field field, Object rawValue) {
String key = field.getMappedKey();
Object value;
if (isNestedKeyword(rawValue) && !field.isIdField()) {
Keyword keyword = new Keyword((DBObject) rawValue);
value = getMappedKeyword(field, keyword);
} else {
value = getMappedValue(field, rawValue);
}
return createMapEntry(key, value);
}
/**
* @param entity
* @param key
* @param mappingContext
* @return
*/
protected Field createPropertyField(MongoPersistentEntity<?> entity, String key,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
return entity == null ? new Field(key) : new MetadataBackedField(key, entity, mappingContext);
}
/**
* Returns the given {@link DBObject} representing a keyword by mapping the keyword's value.
*
* @param query the {@link DBObject} representing a keyword (e.g. {@code $ne : … } )
* @param keyword the {@link DBObject} representing a keyword (e.g. {@code $ne : … } )
* @param entity
* @return
*/
private DBObject getMappedKeyword(Keyword query, MongoPersistentEntity<?> entity) {
protected DBObject getMappedKeyword(Keyword keyword, MongoPersistentEntity<?> entity) {
// $or/$nor
if (query.key.matches(N_OR_PATTERN) || query.value instanceof Iterable) {
if (keyword.isOrOrNor() || keyword.hasIterableValue()) {
Iterable<?> conditions = (Iterable<?>) query.value;
Iterable<?> conditions = keyword.getValue();
BasicDBList newConditions = new BasicDBList();
for (Object condition : conditions) {
newConditions.add(condition instanceof DBObject ? getMappedObject((DBObject) condition, entity)
newConditions.add(isDBObject(condition) ? getMappedObject((DBObject) condition, entity)
: convertSimpleOrDBObject(condition, entity));
}
return new BasicDBObject(query.key, newConditions);
return new BasicDBObject(keyword.getKey(), newConditions);
}
return new BasicDBObject(query.key, convertSimpleOrDBObject(query.value, entity));
return new BasicDBObject(keyword.getKey(), convertSimpleOrDBObject(keyword.getValue(), entity));
}
/**
@@ -151,13 +245,15 @@ public class QueryMapper {
* @param keyword
* @return
*/
private DBObject getMappedKeyword(Field property, Keyword keyword) {
protected DBObject getMappedKeyword(Field property, Keyword keyword) {
boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists();
Object value = needsAssociationConversion ? convertAssociation(keyword.value, property.getProperty())
: getMappedValue(property.with(keyword.key), keyword.value);
Object value = keyword.getValue();
return new BasicDBObject(keyword.key, value);
Object convertedValue = needsAssociationConversion ? convertAssociation(value, property) : getMappedValue(
property.with(keyword.getKey()), value);
return new BasicDBObject(keyword.key, convertedValue);
}
/**
@@ -169,43 +265,79 @@ public class QueryMapper {
* @param newKey the key the value will be bound to eventually
* @return
*/
private Object getMappedValue(Field documentField, Object value) {
protected Object getMappedValue(Field documentField, Object value) {
if (documentField.isIdField()) {
if (value instanceof DBObject) {
if (isDBObject(value)) {
DBObject valueDbo = (DBObject) value;
DBObject resultDbo = new BasicDBObject(valueDbo.toMap());
if (valueDbo.containsField("$in") || valueDbo.containsField("$nin")) {
String inKey = valueDbo.containsField("$in") ? "$in" : "$nin";
List<Object> ids = new ArrayList<Object>();
for (Object id : (Iterable<?>) valueDbo.get(inKey)) {
ids.add(convertId(id));
}
valueDbo.put(inKey, ids.toArray(new Object[ids.size()]));
resultDbo.put(inKey, ids.toArray(new Object[ids.size()]));
} else if (valueDbo.containsField("$ne")) {
valueDbo.put("$ne", convertId(valueDbo.get("$ne")));
resultDbo.put("$ne", convertId(valueDbo.get("$ne")));
} else {
return getMappedObject((DBObject) value, null);
return getMappedObject(resultDbo, null);
}
return valueDbo;
return resultDbo;
} else {
return convertId(value);
}
}
if (Keyword.isKeyword(value)) {
if (isNestedKeyword(value)) {
return getMappedKeyword(new Keyword((DBObject) value), null);
}
if (documentField.isAssociation()) {
return convertAssociation(value, documentField.getProperty());
if (isAssociationConversionNecessary(documentField, value)) {
return convertAssociation(value, documentField);
}
return convertSimpleOrDBObject(value, documentField.getPropertyEntity());
}
/**
* Returns whether the given {@link Field} represents an association reference that together with the given value
* requires conversion to a {@link org.springframework.data.mongodb.core.mapping.DBRef} object. We check whether the
* 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 must not be {@literal null}.
* @param value
* @return
*/
protected boolean isAssociationConversionNecessary(Field documentField, Object value) {
Assert.notNull(documentField, "Document field must not be null!");
if (value == null) {
return false;
}
if (!documentField.isAssociation()) {
return false;
}
Class<? extends Object> type = value.getClass();
MongoPersistentProperty property = documentField.getProperty();
if (property.getActualType().isAssignableFrom(type)) {
return true;
}
MongoPersistentEntity<?> entity = documentField.getPropertyEntity();
return entity.hasIdProperty()
&& (type.equals(DBRef.class) || entity.getIdProperty().getActualType().isAssignableFrom(type));
}
/**
* Retriggers mapping if the given source is a {@link DBObject} or simply invokes the
*
@@ -213,13 +345,13 @@ public class QueryMapper {
* @param entity
* @return
*/
private Object convertSimpleOrDBObject(Object source, MongoPersistentEntity<?> entity) {
protected Object convertSimpleOrDBObject(Object source, MongoPersistentEntity<?> entity) {
if (source instanceof BasicDBList) {
return delegateConvertToMongoType(source, entity);
}
if (source instanceof DBObject) {
if (isDBObject(source)) {
return getMappedObject((DBObject) source, entity);
}
@@ -235,7 +367,11 @@ public class QueryMapper {
* @return the converted mongo type or null if source is null
*/
protected Object delegateConvertToMongoType(Object source, MongoPersistentEntity<?> entity) {
return converter.convertToMongoType(source);
return converter.convertToMongoType(source, entity == null ? null : entity.getTypeInformation());
}
protected Object convertAssociation(Object source, Field field) {
return convertAssociation(source, field.getProperty());
}
/**
@@ -245,16 +381,22 @@ public class QueryMapper {
* @param property
* @return
*/
private Object convertAssociation(Object source, MongoPersistentProperty property) {
protected Object convertAssociation(Object source, MongoPersistentProperty property) {
if (property == null || !property.isAssociation()) {
if (property == null || source == null || source instanceof DBObject) {
return source;
}
if (source instanceof DBRef) {
DBRef ref = (DBRef) source;
return new DBRef(ref.getDB(), ref.getRef(), convertId(ref.getId()));
}
if (source instanceof Iterable) {
BasicDBList result = new BasicDBList();
for (Object element : (Iterable<?>) source) {
result.add(element instanceof DBRef ? element : converter.toDBRef(element, property));
result.add(createDbRefFor(element, property));
}
return result;
}
@@ -263,13 +405,55 @@ public class QueryMapper {
BasicDBObject result = new BasicDBObject();
DBObject dbObject = (DBObject) source;
for (String key : dbObject.keySet()) {
Object o = dbObject.get(key);
result.put(key, o instanceof DBRef ? o : converter.toDBRef(o, property));
result.put(key, createDbRefFor(dbObject.get(key), property));
}
return result;
}
return source == null || source instanceof DBRef ? source : converter.toDBRef(source, property);
return createDbRefFor(source, property);
}
/**
* Checks whether the given value is a {@link DBObject}.
*
* @param value can be {@literal null}.
* @return
*/
protected final boolean isDBObject(Object value) {
return value instanceof DBObject;
}
/**
* Creates a new {@link Entry} for the given {@link Field} with the given value.
*
* @param field must not be {@literal null}.
* @param value can be {@literal null}.
* @return
*/
protected final Entry<String, Object> createMapEntry(Field field, Object value) {
return createMapEntry(field.getMappedKey(), value);
}
/**
* Creates a new {@link Entry} with the given key and value.
*
* @param key must not be {@literal null} or empty.
* @param value can be {@literal null}
* @return
*/
private Entry<String, Object> createMapEntry(String key, Object value) {
Assert.hasText(key, "Key must not be null or empty!");
return Collections.singletonMap(key, value).entrySet().iterator().next();
}
private DBRef createDbRefFor(Object source, MongoPersistentProperty property) {
if (source instanceof DBRef) {
return (DBRef) source;
}
return converter.toDBRef(source, property);
}
/**
@@ -289,15 +473,50 @@ public class QueryMapper {
return delegateConvertToMongoType(id, null);
}
/**
* Returns whether the given {@link Object} is a keyword, i.e. if it's a {@link DBObject} with a keyword key.
*
* @param candidate
* @return
*/
protected boolean isNestedKeyword(Object candidate) {
if (!(candidate instanceof BasicDBObject)) {
return false;
}
BasicDBObject dbObject = (BasicDBObject) candidate;
Set<String> keys = dbObject.keySet();
if (keys.size() != 1) {
return false;
}
return isKeyword(keys.iterator().next().toString());
}
/**
* Returns whether the given {@link String} is a MongoDB keyword. The default implementation will check against the
* set of registered keywords returned by {@link #getKeywords()}.
*
* @param candidate
* @return
*/
protected boolean isKeyword(String candidate) {
return candidate.startsWith("$");
}
/**
* Value object to capture a query keyword representation.
*
* @author Oliver Gierke
*/
private static class Keyword {
static class Keyword {
String key;
Object value;
private static final String N_OR_PATTERN = "\\$.*or";
private final String key;
private final Object value;
public Keyword(DBObject source, String key) {
this.key = key;
@@ -322,25 +541,21 @@ public class QueryMapper {
return "$exists".equalsIgnoreCase(key);
}
/**
* Returns whether the given value actually represents a keyword. If this returns {@literal true} it's safe to call
* the constructor.
*
* @param value
* @return
*/
public static boolean isKeyword(Object value) {
public boolean isOrOrNor() {
return key.matches(N_OR_PATTERN);
}
if (value instanceof String) {
return ((String) value).startsWith("$");
}
public boolean hasIterableValue() {
return value instanceof Iterable;
}
if (!(value instanceof DBObject)) {
return false;
}
public String getKey() {
return key;
}
DBObject dbObject = (DBObject) value;
return dbObject.keySet().size() == 1 && dbObject.keySet().iterator().next().startsWith("$");
@SuppressWarnings("unchecked")
public <T> T getValue() {
return (T) value;
}
}
@@ -349,7 +564,7 @@ public class QueryMapper {
*
* @author Oliver Gierke
*/
private static class Field {
protected static class Field {
private static final String ID_KEY = "_id";
@@ -386,7 +601,9 @@ public class QueryMapper {
}
/**
* Returns the underlying {@link MongoPersistentProperty} backing the field.
* Returns the underlying {@link MongoPersistentProperty} backing the field. For path traversals this will be the
* property that represents the value to handle. This means it'll be the leaf property for plain paths or the
* association property in case we refer to an association somewhere in the path.
*
* @return
*/
@@ -420,18 +637,36 @@ public class QueryMapper {
public String getMappedKey() {
return isIdField() ? ID_KEY : name;
}
/**
* Returns whether the field references an association in case it refers to a nested field.
*
* @return
*/
public boolean containsAssociation() {
return false;
}
public Association<MongoPersistentProperty> getAssociation() {
return null;
}
}
/**
* Extension of {@link DocumentField} to be backed with mapping metadata.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
private static class MetadataBackedField extends Field {
protected static class MetadataBackedField extends Field {
private static final String INVALID_ASSOCIATION_REFERENCE = "Invalid path reference %s! Associations can only be pointed to directly or via their id property!";
private final MongoPersistentEntity<?> entity;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final MongoPersistentProperty property;
private final PersistentPropertyPath<MongoPersistentProperty> path;
private final Association<MongoPersistentProperty> association;
/**
* Creates a new {@link MetadataBackedField} with the given name, {@link MongoPersistentEntity} and
@@ -443,6 +678,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);
@@ -451,8 +701,9 @@ public class QueryMapper {
this.entity = entity;
this.mappingContext = context;
PersistentPropertyPath<MongoPersistentProperty> path = getPath(name);
this.property = path == null ? null : path.getLeafProperty();
this.path = getPath(name);
this.property = path == null ? property : path.getLeafProperty();
this.association = findAssociation();
}
/*
@@ -461,7 +712,7 @@ public class QueryMapper {
*/
@Override
public MetadataBackedField with(String name) {
return new MetadataBackedField(name, entity, mappingContext);
return new MetadataBackedField(name, entity, mappingContext, property);
}
/*
@@ -486,7 +737,7 @@ public class QueryMapper {
*/
@Override
public MongoPersistentProperty getProperty() {
return property;
return association == null ? property : association.getInverse();
}
/*
@@ -505,9 +756,34 @@ public class QueryMapper {
*/
@Override
public boolean isAssociation() {
return association != null;
}
MongoPersistentProperty property = getProperty();
return property == null ? false : property.isAssociation();
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#getAssociation()
*/
@Override
public Association<MongoPersistentProperty> getAssociation() {
return association;
}
/**
* Finds the association property in the {@link PersistentPropertyPath}.
*
* @return
*/
private final Association<MongoPersistentProperty> findAssociation() {
if (this.path != null) {
for (MongoPersistentProperty p : this.path) {
if (p.isAssociation()) {
return p.getAssociation();
}
}
}
return null;
}
/*
@@ -516,19 +792,108 @@ public class QueryMapper {
*/
@Override
public String getMappedKey() {
PersistentPropertyPath<MongoPersistentProperty> path = getPath(name);
return path == null ? name : path.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE);
return path == null ? name : path.toDotPath(isAssociation() ? getAssociationConverter() : getPropertyConverter());
}
private PersistentPropertyPath<MongoPersistentProperty> getPath(String name) {
protected PersistentPropertyPath<MongoPersistentProperty> getPath() {
return path;
}
/**
* Returns the {@link PersistentPropertyPath} for the given <code>pathExpression</code>.
*
* @param pathExpression
* @return
*/
private PersistentPropertyPath<MongoPersistentProperty> getPath(String pathExpression) {
try {
PropertyPath path = PropertyPath.from(name, entity.getTypeInformation());
return mappingContext.getPersistentPropertyPath(path);
PropertyPath path = PropertyPath.from(pathExpression, entity.getTypeInformation());
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
boolean associationDetected = false;
while (iterator.hasNext()) {
MongoPersistentProperty property = iterator.next();
if (property.isAssociation()) {
associationDetected = true;
continue;
}
if (associationDetected && !property.isIdProperty()) {
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
}
}
return propertyPath;
} catch (PropertyReferenceException e) {
return null;
}
}
/**
* Return the {@link Converter} to be used to created the mapped key. Default implementation will use
* {@link PropertyToFieldNameConverter}.
*
* @return
*/
protected Converter<MongoPersistentProperty, String> getPropertyConverter() {
return PropertyToFieldNameConverter.INSTANCE;
}
/**
* Return the {@link Converter} to use for creating the mapped key of an association. Default implementation is
* {@link AssociationConverter}.
*
* @return
* @since 1.7
*/
protected Converter<MongoPersistentProperty, String> getAssociationConverter() {
return new AssociationConverter(getAssociation());
}
}
/**
* Converter to skip all properties after an association property was rendered.
*
* @author Oliver Gierke
*/
protected static class AssociationConverter implements Converter<MongoPersistentProperty, String> {
private final MongoPersistentProperty property;
private boolean associationFound;
/**
* Creates a new {@link AssociationConverter} for the given {@link Association}.
*
* @param association must not be {@literal null}.
*/
public AssociationConverter(Association<MongoPersistentProperty> association) {
Assert.notNull(association, "Association must not be null!");
this.property = association.getInverse();
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public String convert(MongoPersistentProperty source) {
if (associationFound) {
return null;
}
if (property.equals(source)) {
associationFound = true;
}
return source.getFieldName();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 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,16 +15,35 @@
*/
package org.springframework.data.mongodb.core.convert;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map.Entry;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update.Modifier;
import org.springframework.data.mongodb.core.query.Update.Modifiers;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* A subclass of {@link QueryMapper} that retains type information on the mongo types.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class UpdateMapper extends QueryMapper {
private final MongoWriter<?> converter;
private final MongoConverter converter;
/**
* Creates a new {@link UpdateMapper} using the given {@link MongoConverter}.
@@ -49,4 +68,237 @@ public class UpdateMapper extends QueryMapper {
return entity == null ? super.delegateConvertToMongoType(source, null) : converter.convertToMongoType(source,
entity.getTypeInformation());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper#getMappedObjectForField(org.springframework.data.mongodb.core.convert.QueryMapper.Field, java.lang.Object)
*/
@Override
protected Entry<String, Object> getMappedObjectForField(Field field, Object rawValue) {
if (isDBObject(rawValue)) {
return createMapEntry(field, convertSimpleOrDBObject(rawValue, field.getPropertyEntity()));
}
if (isQuery(rawValue)) {
return createMapEntry(field,
super.getMappedObject(((Query) rawValue).getQueryObject(), field.getPropertyEntity()));
}
if (isUpdateModifier(rawValue)) {
return getMappedUpdateModifier(field, rawValue);
}
return super.getMappedObjectForField(field, getMappedValue(field, rawValue));
}
private Entry<String, Object> getMappedUpdateModifier(Field field, Object rawValue) {
Object value = null;
if (rawValue instanceof Modifier) {
value = getMappedValue((Modifier) rawValue);
} else if (rawValue instanceof Modifiers) {
DBObject modificationOperations = new BasicDBObject();
for (Modifier modifier : ((Modifiers) rawValue).getModifiers()) {
modificationOperations.putAll(getMappedValue(modifier).toMap());
}
value = modificationOperations;
} else {
throw new IllegalArgumentException(String.format("Unable to map value of type '%s'!", rawValue.getClass()));
}
return createMapEntry(field, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper#isAssociationConversionNecessary(org.springframework.data.mongodb.core.convert.QueryMapper.Field, java.lang.Object)
*/
@Override
protected boolean isAssociationConversionNecessary(Field documentField, Object value) {
return super.isAssociationConversionNecessary(documentField, value) || documentField.containsAssociation();
}
private boolean isUpdateModifier(Object value) {
return value instanceof Modifier || value instanceof Modifiers;
}
private boolean isQuery(Object value) {
return value instanceof Query;
}
private DBObject getMappedValue(Modifier modifier) {
Object value = converter.convertToMongoType(modifier.getValue(), ClassTypeInformation.OBJECT);
return new BasicDBObject(modifier.getKey(), value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper#createPropertyField(org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.String, org.springframework.data.mapping.context.MappingContext)
*/
@Override
protected Field createPropertyField(MongoPersistentEntity<?> entity, String key,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
return entity == null ? super.createPropertyField(entity, key, mappingContext) : //
new MetadataBackedUpdateField(entity, key, mappingContext);
}
/**
* {@link MetadataBackedField} that handles {@literal $} paths inside a field key. We clean up an update key
* containing a {@literal $} before handing it to the super class to make sure property lookups and transformations
* continue to work as expected. We provide a custom property converter to re-applied the cleaned up {@literal $}s
* when constructing the mapped key.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
private static class MetadataBackedUpdateField extends MetadataBackedField {
private final String key;
/**
* Creates a new {@link MetadataBackedField} with the given {@link MongoPersistentEntity}, key and
* {@link MappingContext}. We clean up the key before handing it up to the super class to make sure it continues to
* work as expected.
*
* @param entity must not be {@literal null}.
* @param key must not be {@literal null} or empty.
* @param mappingContext must not be {@literal null}.
*/
public MetadataBackedUpdateField(MongoPersistentEntity<?> entity, String key,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
super(key.replaceAll("\\.\\$", ""), entity, mappingContext);
this.key = key;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper.MetadataBackedField#getMappedKey()
*/
@Override
public String getMappedKey() {
return this.getPath() == null ? key : super.getMappedKey();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper.MetadataBackedField#getPropertyConverter()
*/
@Override
protected Converter<MongoPersistentProperty, String> getPropertyConverter() {
return new UpdatePropertyConverter(key);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper.MetadataBackedField#getAssociationConverter()
*/
@Override
protected Converter<MongoPersistentProperty, String> getAssociationConverter() {
return new UpdateAssociationConverter(getAssociation(), key);
}
/**
* Special mapper handling positional parameter {@literal $} within property names.
*
* @author Christoph Strobl
* @since 1.7
*/
private static class UpdateKeyMapper {
private final Iterator<String> iterator;
protected UpdateKeyMapper(String rawKey) {
Assert.hasText(rawKey, "Key must not be null or empty!");
this.iterator = Arrays.asList(rawKey.split("\\.")).iterator();
this.iterator.next();
}
/**
* Maps the property name while retaining potential positional operator {@literal $}.
*
* @param property
* @return
*/
protected String mapPropertyName(MongoPersistentProperty property) {
String mappedName = PropertyToFieldNameConverter.INSTANCE.convert(property);
return iterator.hasNext() && iterator.next().equals("$") ? String.format("%s.$", mappedName) : mappedName;
}
}
/**
* Special {@link Converter} for {@link MongoPersistentProperty} instances that will concatenate the {@literal $}
* contained in the source update key.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
private static class UpdatePropertyConverter implements Converter<MongoPersistentProperty, String> {
private final UpdateKeyMapper mapper;
/**
* Creates a new {@link UpdatePropertyConverter} with the given update key.
*
* @param updateKey must not be {@literal null} or empty.
*/
public UpdatePropertyConverter(String updateKey) {
Assert.hasText(updateKey, "Update key must not be null or empty!");
this.mapper = new UpdateKeyMapper(updateKey);
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public String convert(MongoPersistentProperty property) {
return mapper.mapPropertyName(property);
}
}
/**
* {@link Converter} retaining positional parameter {@literal $} for {@link Association}s.
*
* @author Christoph Strobl
*/
protected static class UpdateAssociationConverter extends AssociationConverter {
private final UpdateKeyMapper mapper;
/**
* Creates a new {@link AssociationConverter} for the given {@link Association}.
*
* @param association must not be {@literal null}.
*/
public UpdateAssociationConverter(Association<MongoPersistentProperty> association, String key) {
super(association);
this.mapper = new UpdateKeyMapper(key);
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public String convert(MongoPersistentProperty source) {
return super.convert(source) == null ? null : mapper.mapPropertyName(source);
}
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import com.mongodb.DBObject;
/**
* Internal API to trigger the resolution of properties.
*
* @author Oliver Gierke
*/
interface ValueResolver {
/**
* Resolves the value for the given {@link MongoPersistentProperty} within the given {@link DBObject} using the given
* {@link SpELExpressionEvaluator} and {@link ObjectPath}.
*
* @param prop
* @param dbo
* @param evaluator
* @param parent
* @return
*/
Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator,
ObjectPath parent);
}

View File

@@ -1,106 +0,0 @@
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.geo;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.util.Assert;
/**
* Represents a geospatial box value
*
* @author Mark Pollack
* @author Oliver Gierke
*/
public class Box implements Shape {
@Field(order = 10)
private final Point first;
@Field(order = 20)
private final Point second;
public Box(Point lowerLeft, Point upperRight) {
Assert.notNull(lowerLeft);
Assert.notNull(upperRight);
this.first = lowerLeft;
this.second = upperRight;
}
public Box(double[] lowerLeft, double[] upperRight) {
Assert.isTrue(lowerLeft.length == 2, "Point array has to have 2 elements!");
Assert.isTrue(upperRight.length == 2, "Point array has to have 2 elements!");
this.first = new Point(lowerLeft[0], lowerLeft[1]);
this.second = new Point(upperRight[0], upperRight[1]);
}
public Point getLowerLeft() {
return first;
}
public Point getUpperRight() {
return second;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.Shape#asList()
*/
public List<? extends Object> asList() {
List<List<Double>> list = new ArrayList<List<Double>>();
list.add(getLowerLeft().asList());
list.add(getUpperRight().asList());
return list;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.Shape#getCommand()
*/
public String getCommand() {
return "$box";
}
@Override
public String toString() {
return String.format("Box [%s, %s]", first, second);
}
@Override
public int hashCode() {
int result = 31;
result += 17 * first.hashCode();
result += 17 * second.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Box that = (Box) obj;
return this.first.equals(that.first) && this.second.equals(that.second);
}
}

View File

@@ -1,138 +0,0 @@
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.geo;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.util.Assert;
/**
* Represents a geospatial circle value
*
* @author Mark Pollack
* @author Oliver Gierke
*/
public class Circle implements Shape {
private final Point center;
private final double radius;
/**
* Creates a new {@link Circle} from the given {@link Point} and radius.
*
* @param center must not be {@literal null}.
* @param radius must be greater or equal to zero.
*/
@PersistenceConstructor
public Circle(Point center, double radius) {
Assert.notNull(center);
Assert.isTrue(radius >= 0, "Radius must not be negative!");
this.center = center;
this.radius = radius;
}
/**
* Creates a new {@link Circle} from the given coordinates and radius.
*
* @param centerX
* @param centerY
* @param radius must be greater or equal to zero.
*/
public Circle(double centerX, double centerY, double radius) {
this(new Point(centerX, centerY), radius);
}
/**
* Returns the center of the {@link Circle}.
*
* @return will never be {@literal null}.
*/
public Point getCenter() {
return center;
}
/**
* Returns the radius of the {@link Circle}.
*
* @return
*/
public double getRadius() {
return radius;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.Shape#asList()
*/
public List<Object> asList() {
List<Object> result = new ArrayList<Object>();
result.add(getCenter().asList());
result.add(getRadius());
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.Shape#getCommand()
*/
public String getCommand() {
return "$center";
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("Circle [center=%s, radius=%f]", center, radius);
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !getClass().equals(obj.getClass())) {
return false;
}
Circle that = (Circle) obj;
return this.center.equals(that.center) && this.radius == that.radius;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += 31 * center.hashCode();
result += 31 * radius;
return result;
}
}

View File

@@ -1,145 +0,0 @@
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.geo;
import org.springframework.util.ObjectUtils;
/**
* Value object to represent distances in a given metric.
*
* @author Oliver Gierke
*/
public class Distance {
private final double value;
private final Metric metric;
/**
* Creates a new {@link Distance}.
*
* @param value
*/
public Distance(double value) {
this(value, Metrics.NEUTRAL);
}
/**
* Creates a new {@link Distance} with the given {@link Metric}.
*
* @param value
* @param metric
*/
public Distance(double value, Metric metric) {
this.value = value;
this.metric = metric == null ? Metrics.NEUTRAL : metric;
}
/**
* @return the value
*/
public double getValue() {
return value;
}
/**
* Returns the normalized value regarding the underlying {@link Metric}.
*
* @return
*/
public double getNormalizedValue() {
return value / metric.getMultiplier();
}
/**
* @return the metric
*/
public Metric getMetric() {
return metric;
}
/**
* Adds the given distance to the current one. The resulting {@link Distance} will be in the same metric as the
* current one.
*
* @param other
* @return
*/
public Distance add(Distance other) {
double newNormalizedValue = getNormalizedValue() + other.getNormalizedValue();
return new Distance(newNormalizedValue * metric.getMultiplier(), metric);
}
/**
* Adds the given {@link Distance} to the current one and forces the result to be in a given {@link Metric}.
*
* @param other
* @param metric
* @return
*/
public Distance add(Distance other, Metric metric) {
double newLeft = getNormalizedValue() * metric.getMultiplier();
double newRight = other.getNormalizedValue() * metric.getMultiplier();
return new Distance(newLeft + newRight, metric);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !getClass().equals(obj.getClass())) {
return false;
}
Distance that = (Distance) obj;
return this.value == that.value && ObjectUtils.nullSafeEquals(this.metric, that.metric);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += 31 * Double.doubleToLongBits(value);
result += 31 * ObjectUtils.nullSafeHashCode(metric);
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(value);
if (metric != Metrics.NEUTRAL) {
builder.append(" ").append(metric.toString());
}
return builder.toString();
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.geo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
/**
* Custom {@link Page} to carry the average distance retrieved from the {@link GeoResults} the {@link GeoPage} is set up
* from.
*
* @author Oliver Gierke
*/
public class GeoPage<T> extends PageImpl<GeoResult<T>> {
private static final long serialVersionUID = 23421312312412L;
private final Distance averageDistance;
/**
* Creates a new {@link GeoPage} from the given {@link GeoResults}.
*
* @param content must not be {@literal null}.
*/
public GeoPage(GeoResults<T> results) {
super(results.getContent());
this.averageDistance = results.getAverageDistance();
}
/**
* Creates a new {@link GeoPage} from the given {@link GeoResults}, {@link Pageable} and total.
*
* @param results must not be {@literal null}.
* @param pageable must not be {@literal null}.
* @param total
*/
public GeoPage(GeoResults<T> results, Pageable pageable, long total) {
super(results.getContent(), pageable, total);
this.averageDistance = results.getAverageDistance();
}
/**
* Returns the average distance of the underlying results.
*
* @return the averageDistance
*/
public Distance getAverageDistance() {
return averageDistance;
}
}

View File

@@ -1,102 +0,0 @@
/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.geo;
import org.springframework.util.Assert;
/**
* Calue object capturing some arbitrary object plus a distance.
*
* @author Oliver Gierke
*/
public class GeoResult<T> {
private final T content;
private final Distance distance;
/**
* Creates a new {@link GeoResult} for the given content and distance.
*
* @param content must not be {@literal null}.
* @param distance must not be {@literal null}.
*/
public GeoResult(T content, Distance distance) {
Assert.notNull(content);
Assert.notNull(distance);
this.content = content;
this.distance = distance;
}
/**
* Returns the actual content object.
*
* @return the content
*/
public T getContent() {
return content;
}
/**
* Returns the distance the actual content object has from the origin.
*
* @return the distance
*/
public Distance getDistance() {
return distance;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !getClass().equals(obj.getClass())) {
return false;
}
GeoResult<?> that = (GeoResult<?>) obj;
return this.content.equals(that.content) && this.distance.equals(that.distance);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += 31 * distance.hashCode();
result += 31 * content.hashCode();
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("GeoResult [content: %s, distance: %s, ]", content.toString(), distance.toString());
}
}

View File

@@ -1,145 +0,0 @@
/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.geo;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Value object to capture {@link GeoResult}s as well as the average distance they have.
*
* @author Oliver Gierke
*/
public class GeoResults<T> implements Iterable<GeoResult<T>> {
private final List<GeoResult<T>> results;
private final Distance averageDistance;
/**
* Creates a new {@link GeoResults} instance manually calculating the average distance from the distance values of the
* given {@link GeoResult}s.
*
* @param results must not be {@literal null}.
*/
public GeoResults(List<GeoResult<T>> results) {
this(results, (Metric) null);
}
public GeoResults(List<GeoResult<T>> results, Metric metric) {
this(results, calculateAverageDistance(results, metric));
}
/**
* Creates a new {@link GeoResults} instance from the given {@link GeoResult}s and average distance.
*
* @param results must not be {@literal null}.
* @param averageDistance
*/
@PersistenceConstructor
public GeoResults(List<GeoResult<T>> results, Distance averageDistance) {
Assert.notNull(results);
this.results = results;
this.averageDistance = averageDistance;
}
/**
* Returns the average distance of all {@link GeoResult}s in this list.
*
* @return the averageDistance
*/
public Distance getAverageDistance() {
return averageDistance;
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
public Iterator<GeoResult<T>> iterator() {
return results.iterator();
}
/**
* Returns the actual
*
* @return
*/
public List<GeoResult<T>> getContent() {
return Collections.unmodifiableList(results);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !getClass().equals(obj.getClass())) {
return false;
}
GeoResults<?> that = (GeoResults<?>) obj;
return this.results.equals(that.results) && this.averageDistance == that.averageDistance;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += 31 * results.hashCode();
result += 31 * averageDistance.hashCode();
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("GeoResults: [averageDistance: %s, results: %s]", averageDistance.toString(),
StringUtils.collectionToCommaDelimitedString(results));
}
private static Distance calculateAverageDistance(List<? extends GeoResult<?>> results, Metric metric) {
if (results.isEmpty()) {
return new Distance(0, null);
}
double averageDistance = 0;
for (GeoResult<?> result : results) {
averageDistance += result.getDistance().getValue();
}
return new Distance(averageDistance / results.size(), metric);
}
}

View File

@@ -1,16 +0,0 @@
package org.springframework.data.mongodb.core.geo;
/**
* Interface for {@link Metric}s that can be applied to a base scale.
*
* @author Oliver Gierke
*/
public interface Metric {
/**
* Returns the multiplier to calculate metrics values from a base scale.
*
* @return
*/
double getMultiplier();
}

View File

@@ -1,27 +0,0 @@
package org.springframework.data.mongodb.core.geo;
import org.springframework.data.mongodb.core.query.NearQuery;
/**
* Commonly used {@link Metrics} for {@link NearQuery}s.
*
* @author Oliver Gierke
*/
public enum Metrics implements Metric {
KILOMETERS(6378.137), MILES(3963.191), NEUTRAL(1);
private final double multiplier;
private Metrics(double multiplier) {
this.multiplier = multiplier;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.Metric#getMultiplier()
*/
public double getMultiplier() {
return multiplier;
}
}

View File

@@ -1,103 +0,0 @@
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.geo;
import java.util.Arrays;
import java.util.List;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.util.Assert;
/**
* Represents a geospatial point value.
*
* @author Mark Pollack
* @author Oliver Gierke
*/
public class Point {
@Field(order = 10)
private final double x;
@Field(order = 20)
private final double y;
@PersistenceConstructor
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public Point(Point point) {
Assert.notNull(point);
this.x = point.x;
this.y = point.y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double[] asArray() {
return new double[] { x, y };
}
public List<Double> asList() {
return Arrays.asList(x, y);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(x);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(y);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Point other = (Point) obj;
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) {
return false;
}
if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) {
return false;
}
return true;
}
@Override
public String toString() {
return String.format("Point [latitude=%f, longitude=%f]", x, y);
}
}

View File

@@ -1,113 +0,0 @@
/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.geo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.springframework.util.Assert;
/**
* Simple value object to represent a {@link Polygon}.
*
* @author Oliver Gierke
*/
public class Polygon implements Shape, Iterable<Point> {
private final List<Point> points;
/**
* Creates a new {@link Polygon} for the given Points.
*
* @param x
* @param y
* @param z
* @param others
*/
public Polygon(Point x, Point y, Point z, Point... others) {
Assert.notNull(x);
Assert.notNull(y);
Assert.notNull(z);
Assert.notNull(others);
this.points = new ArrayList<Point>(3 + others.length);
this.points.addAll(Arrays.asList(x, y, z));
this.points.addAll(Arrays.asList(others));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.Shape#asList()
*/
public List<List<Double>> asList() {
List<List<Double>> result = new ArrayList<List<Double>>();
for (Point point : points) {
result.add(point.asList());
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.Shape#getCommand()
*/
public String getCommand() {
return "$polygon";
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
public Iterator<Point> iterator() {
return this.points.iterator();
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !getClass().equals(obj.getClass())) {
return false;
}
Polygon that = (Polygon) obj;
return this.points.equals(that.points);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return points.hashCode();
}
}

View File

@@ -0,0 +1,152 @@
/*
* 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.geo;
import java.util.Arrays;
import java.util.List;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Shape;
import org.springframework.util.Assert;
/**
* Represents a geospatial sphere value.
*
* @author Thomas Darimont
* @since 1.5
*/
public class Sphere implements Shape {
public static final String COMMAND = "$centerSphere";
private final Point center;
private final Distance radius;
/**
* Creates a Sphere around the given center {@link Point} with the given radius.
*
* @param center must not be {@literal null}.
* @param radius must not be {@literal null}.
*/
@PersistenceConstructor
public Sphere(Point center, Distance radius) {
Assert.notNull(center);
Assert.notNull(radius);
Assert.isTrue(radius.getValue() >= 0, "Radius must not be negative!");
this.center = center;
this.radius = radius;
}
/**
* Creates a Sphere around the given center {@link Point} with the given radius.
*
* @param center
* @param radius
*/
public Sphere(Point center, double radius) {
this(center, new Distance(radius));
}
/**
* Creates a Sphere from the given {@link Circle}.
*
* @param circle
*/
public Sphere(Circle circle) {
this(circle.getCenter(), circle.getRadius());
}
/**
* Returns the center of the {@link Circle}.
*
* @return will never be {@literal null}.
*/
public Point getCenter() {
return new Point(this.center);
}
/**
* Returns the radius of the {@link Circle}.
*
* @return
*/
public Distance getRadius() {
return radius;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("Sphere [center=%s, radius=%s]", center, radius);
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof Sphere)) {
return false;
}
Sphere that = (Sphere) obj;
return this.center.equals(that.center) && this.radius.equals(that.radius);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += 31 * center.hashCode();
result += 31 * radius.hashCode();
return result;
}
/**
* Returns the {@link Shape} as a list of usually {@link Double} or {@link List}s of {@link Double}s. Wildcard bound
* to allow implementations to return a more concrete element type.
*
* @return
*/
public List<? extends Object> asList() {
return Arrays.asList(Arrays.asList(center.getX(), center.getY()), this.radius.getValue());
}
/**
* Returns the command to be used to create the {@literal $within} criterion.
*
* @return
*/
public String getCommand() {
return COMMAND;
}
}

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.
*/
/**
* Support for MongoDB geo-spatial queries.
*/

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()}
@@ -51,25 +52,87 @@ public @interface CompoundIndex {
@Deprecated
IndexDirection direction() default IndexDirection.ASCENDING;
/**
* @see http://docs.mongodb.org/manual/core/index-unique/
* @return
*/
boolean unique() default false;
/**
* If set to true index will skip over any document that is missing the indexed field.
*
* @see http://docs.mongodb.org/manual/core/index-sparse/
* @return
*/
boolean sparse() default false;
/**
* @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping
* @return
*/
boolean dropDups() default false;
/**
* The name of the index to be created.
* The name of the index to be created. <br />
* <br />
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
* provided name will be prefixed with the path leading to the entity. <br />
* <br />
* The structure below
*
* <pre>
* <code>
* &#64;Document
* class Root {
* Hybrid hybrid;
* Nested nested;
* }
*
* &#64;Document
* &#64;CompoundIndex(name = "compound_index", def = "{'h1': 1, 'h2': 1}")
* class Hybrid {
* String h1, h2;
* }
*
* &#64;CompoundIndex(name = "compound_index", def = "{'n1': 1, 'n2': 1}")
* class Nested {
* String n1, n2;
* }
* </code>
* </pre>
*
* resolves in the following index structures
*
* <pre>
* <code>
* db.root.ensureIndex( { hybrid.h1: 1, hybrid.h2: 1 } , { name: "hybrid.compound_index" } )
* db.root.ensureIndex( { nested.n1: 1, nested.n2: 1 } , { name: "nested.compound_index" } )
* db.hybrid.ensureIndex( { h1: 1, h2: 1 } , { name: "compound_index" } )
* </code>
* </pre>
*
* @return
*/
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.
*
* @return
* @deprecated The collection name is derived from the domain type. Fixing the collection via this attribute might
* result in broken definitions. Will be removed in 1.7.
*/
@Deprecated
String collection() default "";
/**
@@ -80,11 +143,4 @@ public @interface CompoundIndex {
*/
boolean background() default false;
/**
* Configures the number of seconds after which the collection should expire. Defaults to -1 for no expiry.
*
* @see http://docs.mongodb.org/manual/tutorial/expire-data/
* @return
*/
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

@@ -13,31 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.geo;
package org.springframework.data.mongodb.core.index;
/**
* Value object to create custom {@link Metric}s on the fly.
* Geoposatial index type.
*
* @author Laurent Canet
* @author Oliver Gierke
* @since 1.4
*/
public class CustomMetric implements Metric {
private final double multiplier;
public enum GeoSpatialIndexType {
/**
* Creates a custom {@link Metric} using the given multiplier.
*
* @param multiplier
* Simple 2-Dimensional index for legacy-format points.
*/
public CustomMetric(double multiplier) {
this.multiplier = multiplier;
}
GEO_2D,
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.Metric#getMultiplier()
/**
* 2D Index for GeoJSON-formatted data over a sphere. Only available in Mongo 2.4.
*/
public double getMultiplier() {
return multiplier;
}
GEO_2DSPHERE,
/**
* An haystack index for grouping results over small results.
*/
GEO_HAYSTACK
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright (c) 2011 by the original author(s).
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.index;
import java.lang.annotation.ElementType;
@@ -24,24 +23,72 @@ import java.lang.annotation.Target;
/**
* Mark a field to be indexed using MongoDB's geospatial indexing feature.
*
* @author Jon Brisbin <jbrisbin@vmware.com>
* @author Jon Brisbin
* @author Laurent Canet
* @author Thomas Darimont
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GeoSpatialIndexed {
/**
* Name of the property in the document that contains the [x, y] or radial coordinates to index.
* Index name. <br />
* <br />
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
* provided name will be prefixed with the path leading to the entity. <br />
* <br />
* The structure below
*
* <pre>
* <code>
* &#64;Document
* class Root {
* Hybrid hybrid;
* Nested nested;
* }
*
* &#64;Document
* class Hybrid {
* &#64;GeoSpatialIndexed(name="index") Point h1;
* }
*
* class Nested {
* &#64;GeoSpatialIndexed(name="index") Point n1;
* }
* </code>
* </pre>
*
* resolves in the following index structures
*
* <pre>
* <code>
* db.root.ensureIndex( { hybrid.h1: "2d" } , { name: "hybrid.index" } )
* db.root.ensureIndex( { nested.n1: "2d" } , { name: "nested.index" } )
* db.hybrid.ensureIndex( { h1: "2d" } , { name: "index" } )
* </code>
* </pre>
*
* @return
*/
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.
*
* @return
* @deprecated The collection name is derived from the domain type. Fixing the collection via this attribute might
* result in broken definitions. Will be removed in 1.7.
*/
@Deprecated
String collection() default "";
/**
@@ -65,4 +112,27 @@ public @interface GeoSpatialIndexed {
*/
int bits() default 26;
/**
* The type of the geospatial index. Default is {@link GeoSpatialIndexType#GEO_2D}
*
* @since 1.4
* @return
*/
GeoSpatialIndexType type() default GeoSpatialIndexType.GEO_2D;
/**
* The bucket size for {@link GeoSpatialIndexType#GEO_HAYSTACK} indexes, in coordinate units.
*
* @since 1.4
* @return
*/
double bucketSize() default 1.0;
/**
* The name of the additional field to use for {@link GeoSpatialIndexType#GEO_HAYSTACK} indexes
*
* @since 1.4
* @return
*/
String additionalField() default "";
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2011 the original author or authors.
* Copyright 2010-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core.index;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
@@ -25,14 +26,19 @@ import com.mongodb.DBObject;
*
* @author Jon Brisbin
* @author Oliver Gierke
* @author Laurent Canet
* @author Christoph Strobl
*/
public class GeospatialIndex implements IndexDefinition {
private final String field;
private String name;
private Integer min = null;
private Integer max = null;
private Integer bits = null;
private Integer min;
private Integer max;
private Integer bits;
private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D;
private Double bucketSize = 1.0;
private String additionalField;
/**
* Creates a new {@link GeospatialIndex} for the given field.
@@ -40,53 +46,146 @@ public class GeospatialIndex implements IndexDefinition {
* @param field must not be empty or {@literal null}.
*/
public GeospatialIndex(String field) {
Assert.hasText(field);
Assert.hasText(field, "Field must have text!");
this.field = field;
}
/**
* @param name must not be {@literal null} or empty.
* @return
*/
public GeospatialIndex named(String name) {
this.name = name;
return this;
}
/**
* @param min
* @return
*/
public GeospatialIndex withMin(int min) {
this.min = Integer.valueOf(min);
return this;
}
/**
* @param max
* @return
*/
public GeospatialIndex withMax(int max) {
this.max = Integer.valueOf(max);
return this;
}
/**
* @param bits
* @return
*/
public GeospatialIndex withBits(int bits) {
this.bits = Integer.valueOf(bits);
return this;
}
/**
* @param type must not be {@literal null}.
* @return
*/
public GeospatialIndex typed(GeoSpatialIndexType type) {
Assert.notNull(type, "Type must not be null!");
this.type = type;
return this;
}
/**
* @param bucketSize
* @return
*/
public GeospatialIndex withBucketSize(double bucketSize) {
this.bucketSize = bucketSize;
return this;
}
/**
* @param fieldName.
* @return
*/
public GeospatialIndex withAdditionalField(String fieldName) {
this.additionalField = fieldName;
return this;
}
public DBObject getIndexKeys() {
DBObject dbo = new BasicDBObject();
dbo.put(field, "2d");
switch (type) {
case GEO_2D:
dbo.put(field, "2d");
break;
case GEO_2DSPHERE:
dbo.put(field, "2dsphere");
break;
case GEO_HAYSTACK:
dbo.put(field, "geoHaystack");
if (!StringUtils.hasText(additionalField)) {
throw new IllegalArgumentException("When defining geoHaystack index, an additionnal field must be defined");
}
dbo.put(additionalField, 1);
break;
default:
throw new IllegalArgumentException("Unsupported geospatial index " + type);
}
return dbo;
}
public DBObject getIndexOptions() {
if (name == null && min == null && max == 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);
}
if (min != null) {
dbo.put("min", min);
}
if (max != null) {
dbo.put("max", max);
}
if (bits != null) {
dbo.put("bits", bits);
switch (type) {
case GEO_2D:
if (min != null) {
dbo.put("min", min);
}
if (max != null) {
dbo.put("max", max);
}
if (bits != null) {
dbo.put("bits", bits);
}
break;
case GEO_2DSPHERE:
break;
case GEO_HAYSTACK:
if (bucketSize != null) {
dbo.put("bucketSize", bucketSize);
}
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,18 +17,35 @@ 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 {
public enum Duplicates {
RETAIN, DROP
RETAIN, //
/**
* Dropping Duplicates was removed in MongoDB Server 2.8.0-rc0.
* <p>
* See https://jira.mongodb.org/browse/SERVER-14710
*
* @deprecated since 1.7.
*/
@Deprecated//
DROP
}
private final Map<String, Direction> fieldSpec = new LinkedHashMap<String, Direction>();
@@ -41,8 +58,11 @@ public class Index implements IndexDefinition {
private boolean sparse = false;
public Index() {
}
private boolean background = false;
private long expire = -1;
public Index() {}
public Index(String key, Direction direction) {
fieldSpec.put(key, direction);
@@ -83,16 +103,71 @@ public class Index implements IndexDefinition {
return this;
}
/**
* Reject all documents that contain a duplicate value for the indexed field.
*
* @see http://docs.mongodb.org/manual/core/index-unique/
* @return
*/
public Index unique() {
this.unique = true;
return this;
}
/**
* Skip over any document that is missing the indexed field.
*
* @see http://docs.mongodb.org/manual/core/index-sparse/
* @return
*/
public Index sparse() {
this.sparse = true;
return this;
}
/**
* 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
* @return
*/
public Index unique(Duplicates duplicates) {
if (duplicates == Duplicates.DROP) {
this.dropDuplicates = true;
@@ -109,11 +184,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) {
@@ -125,6 +198,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

@@ -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.
@@ -24,22 +24,33 @@ import org.springframework.util.ObjectUtils;
* Value object for an index field.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
@SuppressWarnings("deprecation")
public final class IndexField {
enum Type {
GEO, TEXT, DEFAULT;
}
private final String key;
private final Direction direction;
private final boolean isGeo;
private final Type type;
private final Float weight;
private IndexField(String key, Direction direction, boolean isGeo) {
private IndexField(String key, Direction direction, Type type) {
this(key, direction, type, Float.NaN);
}
private IndexField(String key, Direction direction, Type type, Float weight) {
Assert.hasText(key);
Assert.isTrue(direction != null ^ isGeo);
Assert.isTrue(direction != null ^ (Type.GEO.equals(type) || Type.TEXT.equals(type)));
this.key = key;
this.direction = direction;
this.isGeo = isGeo;
this.type = type == null ? Type.DEFAULT : type;
this.weight = weight == null ? Float.NaN : weight;
}
/**
@@ -53,12 +64,12 @@ public final class IndexField {
@Deprecated
public static IndexField create(String key, Order order) {
Assert.notNull(order);
return new IndexField(key, order.toDirection(), false);
return new IndexField(key, order.toDirection(), Type.DEFAULT);
}
public static IndexField create(String key, Direction order) {
Assert.notNull(order);
return new IndexField(key, order, false);
return new IndexField(key, order, Type.DEFAULT);
}
/**
@@ -68,7 +79,16 @@ public final class IndexField {
* @return
*/
public static IndexField geo(String key) {
return new IndexField(key, null, true);
return new IndexField(key, null, Type.GEO);
}
/**
* Creates a text {@link IndexField} for the given key.
*
* @since 1.6
*/
public static IndexField text(String key, Float weight) {
return new IndexField(key, null, Type.TEXT, weight);
}
/**
@@ -101,10 +121,20 @@ public final class IndexField {
/**
* Returns whether the {@link IndexField} is a geo index field.
*
* @return the isGeo
* @return true if type is {@link Type#GEO}.
*/
public boolean isGeo() {
return isGeo;
return Type.GEO.equals(type);
}
/**
* Returns wheter the {@link IndexField} is a text index field.
*
* @return true if type is {@link Type#TEXT}
* @since 1.6
*/
public boolean isText() {
return Type.TEXT.equals(type);
}
/*
@@ -125,7 +155,7 @@ public final class IndexField {
IndexField that = (IndexField) obj;
return this.key.equals(that.key) && ObjectUtils.nullSafeEquals(this.direction, that.direction)
&& this.isGeo == that.isGeo;
&& this.type == that.type;
}
/*
@@ -138,7 +168,8 @@ public final class IndexField {
int result = 17;
result += 31 * ObjectUtils.nullSafeHashCode(key);
result += 31 * ObjectUtils.nullSafeHashCode(direction);
result += 31 * ObjectUtils.nullSafeHashCode(isGeo);
result += 31 * ObjectUtils.nullSafeHashCode(type);
result += 31 * ObjectUtils.nullSafeHashCode(weight);
return result;
}
@@ -148,6 +179,7 @@ public final class IndexField {
*/
@Override
public String toString() {
return String.format("IndexField [ key: %s, direction: %s, isGeo: %s]", key, direction, isGeo);
return String.format("IndexField [ key: %s, direction: %s, type: %s, weight: %s]", key, direction, type, weight);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-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.
@@ -23,6 +23,11 @@ import java.util.List;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* @author Mark Pollack
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class IndexInfo {
private final List<IndexField> indexFields;
@@ -31,14 +36,30 @@ public class IndexInfo {
private final boolean unique;
private final boolean dropDuplicates;
private final boolean sparse;
private final String language;
/**
* @deprecated Will be removed in 1.7. Please use {@link #IndexInfo(List, String, boolean, boolean, boolean, String)}
* @param indexFields
* @param name
* @param unique
* @param dropDuplicates
* @param sparse
*/
@Deprecated
public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse) {
this(indexFields, name, unique, dropDuplicates, sparse, "");
}
public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse,
String language) {
this.indexFields = Collections.unmodifiableList(indexFields);
this.name = name;
this.unique = unique;
this.dropDuplicates = dropDuplicates;
this.sparse = sparse;
this.language = language;
}
/**
@@ -84,14 +105,23 @@ public class IndexInfo {
return sparse;
}
/**
* @return
* @since 1.6
*/
public String getLanguage() {
return language;
}
@Override
public String toString() {
return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", dropDuplicates="
+ dropDuplicates + ", sparse=" + sparse + "]";
+ dropDuplicates + ", sparse=" + sparse + ", language=" + language + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (dropDuplicates ? 1231 : 1237);
@@ -99,6 +129,7 @@ public class IndexInfo {
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + (sparse ? 1231 : 1237);
result = prime * result + (unique ? 1231 : 1237);
result = prime * result + ObjectUtils.nullSafeHashCode(language);
return result;
}
@@ -137,6 +168,9 @@ public class IndexInfo {
if (unique != other.unique) {
return false;
}
if (!ObjectUtils.nullSafeEquals(language, other.language)) {
return false;
}
return true;
}
}

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,21 +27,94 @@ import java.lang.annotation.Target;
* @author Oliver Gierke
* @author Philipp Schneider
* @author Johno Crawford
* @author Thomas Darimont
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Indexed {
/**
* If set to true reject all documents that contain a duplicate value for the indexed field.
*
* @see http://docs.mongodb.org/manual/core/index-unique/
* @return
*/
boolean unique() default false;
IndexDirection direction() default IndexDirection.ASCENDING;
/**
* If set to true index will skip over any document that is missing the indexed field.
*
* @see http://docs.mongodb.org/manual/core/index-sparse/
* @return
*/
boolean sparse() default false;
/**
* @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping
* @return
*/
boolean dropDups() default false;
/**
* Index name. <br />
* <br />
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
* provided name will be prefixed with the path leading to the entity. <br />
* <br />
* The structure below
*
* <pre>
* <code>
* &#64;Document
* class Root {
* Hybrid hybrid;
* Nested nested;
* }
*
* &#64;Document
* class Hybrid {
* &#64;Indexed(name="index") String h1;
* }
*
* class Nested {
* &#64;Indexed(name="index") String n1;
* }
* </code>
* </pre>
*
* resolves in the following index structures
*
* <pre>
* <code>
* db.root.ensureIndex( { hybrid.h1: 1 } , { name: "hybrid.index" } )
* db.root.ensureIndex( { nested.n1: 1 } , { name: "nested.index" } )
* db.hybrid.ensureIndex( { h1: 1} , { name: "index" } )
* </code>
* </pre>
*
* @return
*/
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;
/**
* Collection name for index to be created on.
*
* @return
* @deprecated The collection name is derived from the domain type. Fixing the collection via this attribute might
* result in broken definitions. Will be removed in 1.7.
*/
@Deprecated
String collection() default "";
/**

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.
@@ -15,7 +15,6 @@
*/
package org.springframework.data.mongodb.core.index;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -23,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,30 +40,48 @@ import com.mongodb.util.JSON;
* @author Oliver Gierke
* @author Philipp Schneider
* @author Johno Crawford
* @author Laurent Canet
* @author Christoph Strobl
*/
public class MongoPersistentEntityIndexCreator implements
ApplicationListener<MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty>> {
private static final Logger log = LoggerFactory.getLogger(MongoPersistentEntityIndexCreator.class);
private static final Logger LOGGER = LoggerFactory.getLogger(MongoPersistentEntityIndexCreator.class);
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)) {
if (log.isDebugEnabled()) {
log.debug("Analyzing class " + type + " for index information.");
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 (log.isDebugEnabled()) {
log.debug("Created compound index " + index);
}
}
}
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
Field field = persistentProperty.getField();
if (field.isAnnotationPresent(Indexed.class)) {
Indexed index = field.getAnnotation(Indexed.class);
String name = index.name();
if (!StringUtils.hasText(name)) {
name = persistentProperty.getFieldName();
} else {
if (!name.equals(field.getName()) && index.unique() && !index.sparse()) {
// Names don't match, and sparse is not true. This situation will generate an error on the server.
if (log.isWarnEnabled()) {
log.warn("The index name " + name + " doesn't match this property name: " + field.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(persistentProperty.getFieldName(), direction);
ensureIndex(collection, name, definition, index.unique(), index.dropDups(), index.sparse(),
index.background(), index.expireAfterSeconds());
if (log.isDebugEnabled()) {
log.debug("Created property index " + index);
}
} else if (field.isAnnotationPresent(GeoSpatialIndexed.class)) {
GeoSpatialIndexed index = field.getAnnotation(GeoSpatialIndexed.class);
GeospatialIndex indexObject = new GeospatialIndex(persistentProperty.getFieldName());
indexObject.withMin(index.min()).withMax(index.max());
indexObject.named(StringUtils.hasText(index.name()) ? index.name() : field.getName());
String collection = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection();
mongoDbFactory.getDb().getCollection(collection)
.ensureIndex(indexObject.getIndexKeys(), indexObject.getIndexOptions());
if (log.isDebugEnabled()) {
log.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,680 @@
/*
* 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.Collection;
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.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.index.MongoPersistentEntityIndexResolver.TextIndexIncludeOptions.IncludeStrategy;
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder;
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexedFieldSpec;
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));
indexInformation.addAll(potentiallyCreateTextIndexDefinition(root));
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) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, entity));
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,
MongoPersistentEntity<?> entity) {
if (entity.findAnnotation(CompoundIndexes.class) == null && entity.findAnnotation(CompoundIndex.class) == null) {
return Collections.emptyList();
}
return createCompoundIndexDefinitions(dotPath, collection, entity);
}
private Collection<? extends IndexDefinitionHolder> potentiallyCreateTextIndexDefinition(MongoPersistentEntity<?> root) {
TextIndexDefinitionBuilder indexDefinitionBuilder = new TextIndexDefinitionBuilder().named(root.getType()
.getSimpleName() + "_TextIndex");
if (StringUtils.hasText(root.getLanguage())) {
indexDefinitionBuilder.withDefaultLanguage(root.getLanguage());
}
try {
appendTextIndexInformation("", indexDefinitionBuilder, root,
new TextIndexIncludeOptions(IncludeStrategy.DEFAULT), new CycleGuard());
} catch (CyclicPropertyReferenceException e) {
LOGGER.warn(e.getMessage());
}
TextIndexDefinition indexDefinition = indexDefinitionBuilder.build();
if (!indexDefinition.hasFieldSpec()) {
return Collections.emptyList();
}
IndexDefinitionHolder holder = new IndexDefinitionHolder("", indexDefinition, root.getCollection());
return Collections.singletonList(holder);
}
private void appendTextIndexInformation(final String dotPath,
final TextIndexDefinitionBuilder indexDefinitionBuilder, final MongoPersistentEntity<?> entity,
final TextIndexIncludeOptions includeOptions, final CycleGuard guard) {
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
@Override
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
guard.protect(persistentProperty, dotPath);
if (persistentProperty.isExplicitLanguageProperty() && !StringUtils.hasText(dotPath)) {
indexDefinitionBuilder.withLanguageOverride(persistentProperty.getFieldName());
}
TextIndexed indexed = persistentProperty.findAnnotation(TextIndexed.class);
if (includeOptions.isForce() || indexed != null || persistentProperty.isEntity()) {
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "")
+ persistentProperty.getFieldName();
Float weight = indexed != null ? indexed.weight()
: (includeOptions.getParentFieldSpec() != null ? includeOptions.getParentFieldSpec().getWeight() : 1.0F);
if (persistentProperty.isEntity()) {
TextIndexIncludeOptions optionsForNestedType = includeOptions;
if (!IncludeStrategy.FORCE.equals(includeOptions.getStrategy()) && indexed != null) {
optionsForNestedType = new TextIndexIncludeOptions(IncludeStrategy.FORCE, new TextIndexedFieldSpec(
propertyDotPath, weight));
}
try {
appendTextIndexInformation(propertyDotPath, indexDefinitionBuilder,
mappingContext.getPersistentEntity(persistentProperty.getActualType()), optionsForNestedType, guard);
} catch (CyclicPropertyReferenceException e) {
LOGGER.warn(e.getMessage(), e);
} catch (InvalidDataAccessApiUsageException e) {
LOGGER.warn(
String.format("Potentially invald index structure discovered. Breaking operation for %s.",
entity.getName()), e);
}
} else if (includeOptions.isForce() || indexed != null) {
indexDefinitionBuilder.onField(propertyDotPath, weight);
}
}
}
});
}
/**
* 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,
MongoPersistentEntity<?> entity) {
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
CompoundIndexes indexes = entity.findAnnotation(CompoundIndexes.class);
if (indexes != null) {
for (CompoundIndex index : indexes.value()) {
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index, entity));
}
}
CompoundIndex index = entity.findAnnotation(CompoundIndex.class);
if (index != null) {
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index, entity));
}
return indexDefinitions;
}
@SuppressWarnings("deprecation")
protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, String fallbackCollection,
CompoundIndex index, MongoPersistentEntity<?> entity) {
CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition(resolveCompoundIndexKeyFromStringDefinition(
dotPath, index.def()));
if (!index.useGeneratedName()) {
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, null));
}
if (index.unique()) {
indexDefinition.unique(index.dropDups() ? Duplicates.DROP : Duplicates.RETAIN);
}
if (index.sparse()) {
indexDefinition.sparse();
}
if (index.background()) {
indexDefinition.background();
}
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(pathAwareIndexName(index.name(), dotPath, persitentProperty));
}
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(pathAwareIndexName(index.name(), dotPath, persistentProperty));
}
indexDefinition.typed(index.type()).withBucketSize(index.bucketSize()).withAdditionalField(index.additionalField());
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}
private String pathAwareIndexName(String indexName, String dotPath, MongoPersistentProperty property) {
String nameToUse = StringUtils.hasText(indexName) ? indexName : "";
if (!StringUtils.hasText(dotPath) || (property != null && dotPath.equals(property.getFieldName()))) {
return StringUtils.hasText(nameToUse) ? nameToUse : dotPath;
}
if (StringUtils.hasText(dotPath)) {
nameToUse = StringUtils.hasText(nameToUse) ? (property != null ? dotPath.replace("." + property.getFieldName(),
"") : dotPath) + "." + nameToUse : dotPath;
}
return nameToUse;
}
/**
* {@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.equals(this.path) || path.contains(this.path + ".") || 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();
}
}
/**
* @author Christoph Strobl
* @since 1.6
*/
static class TextIndexIncludeOptions {
enum IncludeStrategy {
FORCE, DEFAULT;
}
private final IncludeStrategy strategy;
private final TextIndexedFieldSpec parentFieldSpec;
public TextIndexIncludeOptions(IncludeStrategy strategy, TextIndexedFieldSpec parentFieldSpec) {
this.strategy = strategy;
this.parentFieldSpec = parentFieldSpec;
}
public TextIndexIncludeOptions(IncludeStrategy strategy) {
this(strategy, null);
}
public IncludeStrategy getStrategy() {
return strategy;
}
public TextIndexedFieldSpec getParentFieldSpec() {
return parentFieldSpec;
}
public boolean isForce() {
return IncludeStrategy.FORCE.equals(strategy);
}
}
}

View File

@@ -0,0 +1,336 @@
/*
* 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.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* {@link IndexDefinition} to span multiple keys for text search.
*
* @author Christoph Strobl
* @since 1.6
*/
public class TextIndexDefinition implements IndexDefinition {
private String name;
private Set<TextIndexedFieldSpec> fieldSpecs;
private String defaultLanguage;
private String languageOverride;
TextIndexDefinition() {
fieldSpecs = new LinkedHashSet<TextIndexedFieldSpec>();
}
/**
* Creates a {@link TextIndexDefinition} for all fields in the document.
*
* @return
*/
public static TextIndexDefinition forAllFields() {
return new TextIndexDefinitionBuilder().onAllFields().build();
}
/**
* Get {@link TextIndexDefinitionBuilder} to create {@link TextIndexDefinition}.
*
* @return
*/
public static TextIndexDefinitionBuilder builder() {
return new TextIndexDefinitionBuilder();
}
/**
* @param fieldSpec
*/
public void addFieldSpec(TextIndexedFieldSpec fieldSpec) {
this.fieldSpecs.add(fieldSpec);
}
/**
* @param fieldSpecs
*/
public void addFieldSpecs(Collection<TextIndexedFieldSpec> fieldSpecs) {
this.fieldSpecs.addAll(fieldSpecs);
}
/**
* Returns if the {@link TextIndexDefinition} has fields assigned.
*
* @return
*/
public boolean hasFieldSpec() {
return !fieldSpecs.isEmpty();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys()
*/
@Override
public DBObject getIndexKeys() {
DBObject keys = new BasicDBObject();
for (TextIndexedFieldSpec fieldSpec : fieldSpecs) {
keys.put(fieldSpec.fieldname, "text");
}
return keys;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexOptions()
*/
@Override
public DBObject getIndexOptions() {
DBObject options = new BasicDBObject();
if (StringUtils.hasText(name)) {
options.put("name", name);
}
if (StringUtils.hasText(defaultLanguage)) {
options.put("default_language", defaultLanguage);
}
BasicDBObject weightsDbo = new BasicDBObject();
for (TextIndexedFieldSpec fieldSpec : fieldSpecs) {
if (fieldSpec.isWeighted()) {
weightsDbo.put(fieldSpec.getFieldname(), fieldSpec.getWeight());
}
}
if (!weightsDbo.isEmpty()) {
options.put("weights", weightsDbo);
}
if (StringUtils.hasText(languageOverride)) {
options.put("language_override", languageOverride);
}
return options;
}
/**
* @author Christoph Strobl
* @since 1.6
*/
public static class TextIndexedFieldSpec {
private final String fieldname;
private final Float weight;
/**
* Create new {@link TextIndexedFieldSpec} for given fieldname without any weight.
*
* @param fieldname
*/
public TextIndexedFieldSpec(String fieldname) {
this(fieldname, 1.0F);
}
/**
* Create new {@link TextIndexedFieldSpec} for given fieldname and weight.
*
* @param fieldname
* @param weight
*/
public TextIndexedFieldSpec(String fieldname, Float weight) {
Assert.hasText(fieldname, "Text index field cannot be blank.");
this.fieldname = fieldname;
this.weight = weight != null ? weight : 1.0F;
}
/**
* Get the fieldname associated with the {@link TextIndexedFieldSpec}.
*
* @return
*/
public String getFieldname() {
return fieldname;
}
/**
* Get the weight associated with the {@link TextIndexedFieldSpec}.
*
* @return
*/
public Float getWeight() {
return weight;
}
/**
* @return true if {@link #weight} has a value that is a valid number.
*/
public boolean isWeighted() {
return this.weight != null && this.weight.compareTo(1.0F) != 0;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(fieldname);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof TextIndexedFieldSpec)) {
return false;
}
TextIndexedFieldSpec other = (TextIndexedFieldSpec) obj;
return ObjectUtils.nullSafeEquals(this.fieldname, other.fieldname);
}
}
/**
* {@link TextIndexDefinitionBuilder} helps defining options for creating {@link TextIndexDefinition}.
*
* @author Christoph Strobl
* @since 1.6
*/
public static class TextIndexDefinitionBuilder {
private TextIndexDefinition instance;
private static final TextIndexedFieldSpec ALL_FIELDS = new TextIndexedFieldSpec("$**");
public TextIndexDefinitionBuilder() {
this.instance = new TextIndexDefinition();
}
/**
* Define the name to be used when creating the index in the store.
*
* @param name
* @return
*/
public TextIndexDefinitionBuilder named(String name) {
this.instance.name = name;
return this;
}
/**
* Define the index to span all fields using wilcard. <br/>
* <strong>NOTE</strong> {@link TextIndexDefinition} cannot contain any other fields when defined with wildcard.
*
* @return
*/
public TextIndexDefinitionBuilder onAllFields() {
if (!instance.fieldSpecs.isEmpty()) {
throw new InvalidDataAccessApiUsageException("Cannot add wildcard fieldspect to non empty.");
}
this.instance.fieldSpecs.add(ALL_FIELDS);
return this;
}
/**
* Include given fields with default weight.
*
* @param fieldnames
* @return
*/
public TextIndexDefinitionBuilder onFields(String... fieldnames) {
for (String fieldname : fieldnames) {
onField(fieldname);
}
return this;
}
/**
* Include given field with default weight.
*
* @param fieldname
* @return
*/
public TextIndexDefinitionBuilder onField(String fieldname) {
return onField(fieldname, 1F);
}
/**
* Include given field with weight.
*
* @param fieldname
* @return
*/
public TextIndexDefinitionBuilder onField(String fieldname, Float weight) {
if (this.instance.fieldSpecs.contains(ALL_FIELDS)) {
throw new InvalidDataAccessApiUsageException(String.format("Cannot add %s to field spec for all fields.",
fieldname));
}
this.instance.fieldSpecs.add(new TextIndexedFieldSpec(fieldname, weight));
return this;
}
/**
* Define the default language to be used when indexing documents.
*
* @param language
* @see http://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/#specify-default-language-text-index
* @return
*/
public TextIndexDefinitionBuilder withDefaultLanguage(String language) {
this.instance.defaultLanguage = language;
return this;
}
/**
* Define field for language override.
*
* @param fieldname
* @return
*/
public TextIndexDefinitionBuilder withLanguageOverride(String fieldname) {
if (StringUtils.hasText(this.instance.languageOverride)) {
throw new InvalidDataAccessApiUsageException(String.format(
"Cannot set language override on %s as it is already defined on %s.", fieldname,
this.instance.languageOverride));
}
this.instance.languageOverride = fieldname;
return this;
}
public TextIndexDefinition build() {
return this.instance;
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@link TextIndexed} marks a field to be part of the text index. As there can be only one text index per collection
* all fields marked with {@link TextIndexed} are combined into one single index. <br />
*
* @author Christoph Strobl
* @since 1.6
*/
@Documented
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TextIndexed {
/**
* Defines the significance of the filed relative to other indexed fields. The value directly influences the documents
* score. <br/>
* Defaulted to {@literal 1.0}.
*
* @return
*/
float weight() default 1.0F;
}

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.
@@ -35,9 +35,11 @@ import org.springframework.data.mongodb.MongoCollectionUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
@@ -47,36 +49,45 @@ import org.springframework.util.StringUtils;
* @author Jon Brisbin
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, MongoPersistentProperty> implements
MongoPersistentEntity<T>, ApplicationContextAware {
private static final String AMBIGUOUS_FIELD_MAPPING = "Ambiguous field mapping detected! Both %s and %s map to the same field name %s! Disambiguate using @DocumentField annotation!";
private static final String AMBIGUOUS_FIELD_MAPPING = "Ambiguous field mapping detected! Both %s and %s map to the same field name %s! Disambiguate using @Field annotation!";
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private final String collection;
private final SpelExpressionParser parser;
private final String language;
private final StandardEvaluationContext context;
private final Expression expression;
/**
* Creates a new {@link BasicMongoPersistentEntity} with the given {@link TypeInformation}. Will default the
* collection name to the entities simple type name.
*
* @param typeInformation
* @param typeInformation must not be {@literal null}.
*/
public BasicMongoPersistentEntity(TypeInformation<T> typeInformation) {
super(typeInformation, MongoPersistentPropertyComparator.INSTANCE);
this.parser = new SpelExpressionParser();
this.context = new StandardEvaluationContext();
Class<?> rawType = typeInformation.getType();
String fallback = MongoCollectionUtils.getPreferredCollectionName(rawType);
if (rawType.isAnnotationPresent(Document.class)) {
Document d = rawType.getAnnotation(Document.class);
this.collection = StringUtils.hasText(d.collection()) ? d.collection() : fallback;
Document document = rawType.getAnnotation(Document.class);
this.expression = detectExpression(document);
this.context = new StandardEvaluationContext();
if (document != null) {
this.collection = StringUtils.hasText(document.collection()) ? document.collection() : fallback;
this.language = StringUtils.hasText(document.language()) ? document.language() : "";
} else {
this.collection = fallback;
this.language = "";
}
}
@@ -96,8 +107,34 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getCollection()
*/
public String getCollection() {
Expression expression = parser.parseExpression(collection, ParserContext.TEMPLATE_EXPRESSION);
return expression.getValue(context, String.class);
return expression == null ? collection : expression.getValue(context, String.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getLanguage()
*/
@Override
public String getLanguage() {
return this.language;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getTextScoreProperty()
*/
@Override
public MongoPersistentProperty getTextScoreProperty() {
return getPersistentProperty(TextScore.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#hasTextScoreProperty()
*/
@Override
public boolean hasTextScoreProperty() {
return getTextScoreProperty() != null;
}
/*
@@ -107,12 +144,22 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
@Override
public void verify() {
verifyFieldUniqueness();
verifyFieldTypes();
}
private void verifyFieldUniqueness() {
AssertFieldNameUniquenessHandler handler = new AssertFieldNameUniquenessHandler();
doWithProperties(handler);
doWithAssociations(handler);
}
private void verifyFieldTypes() {
doWithProperties(new PropertyTypeAssertionHandler());
}
/**
* {@link Comparator} implementation inspecting the {@link MongoPersistentProperty}'s order.
*
@@ -194,6 +241,31 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
return null;
}
/**
* Returns a SpEL {@link Expression} frór the collection String expressed in the given {@link Document} annotation if
* present or {@literal null} otherwise. Will also return {@literal null} it the collection {@link String} evaluates
* to a {@link LiteralExpression} (indicating that no subsequent evaluation is necessary).
*
* @param document can be {@literal null}
* @return
*/
private static Expression detectExpression(Document document) {
if (document == null) {
return null;
}
String collection = document.collection();
if (!StringUtils.hasText(collection)) {
return null;
}
Expression expression = PARSER.parseExpression(document.collection(), ParserContext.TEMPLATE_EXPRESSION);
return expression instanceof LiteralExpression ? null : expression;
}
/**
* Handler to collect {@link MongoPersistentProperty} instances and check that each of them is mapped to a distinct
* field name.
@@ -226,4 +298,46 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
properties.put(fieldName, property);
}
}
/**
* @author Christoph Strobl
* @since 1.6
*/
private static class PropertyTypeAssertionHandler implements PropertyHandler<MongoPersistentProperty> {
@Override
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
potentiallyAssertTextScoreType(persistentProperty);
potentiallyAssertLanguageType(persistentProperty);
}
private void potentiallyAssertLanguageType(MongoPersistentProperty persistentProperty) {
if (persistentProperty.isExplicitLanguageProperty()) {
assertPropertyType(persistentProperty, String.class);
}
}
private void potentiallyAssertTextScoreType(MongoPersistentProperty persistentProperty) {
if (persistentProperty.isTextScoreProperty()) {
assertPropertyType(persistentProperty, Float.class, Double.class);
}
}
private void assertPropertyType(MongoPersistentProperty persistentProperty, Class<?>... validMatches) {
for (Class<?> potentialMatch : validMatches) {
if (ClassUtils.isAssignable(potentialMatch, persistentProperty.getActualType())) {
return;
}
}
throw new MappingException(String.format("Missmatching types for %s. Found %s expected one of %s.",
persistentProperty.getField(), persistentProperty.getActualType(),
StringUtils.arrayToCommaDelimitedString(validMatches)));
}
}
}

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,9 +27,10 @@ import org.slf4j.LoggerFactory;
import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import com.mongodb.DBObject;
@@ -40,6 +41,7 @@ import com.mongodb.DBObject;
* @author Oliver Gierke
* @author Patryk Wasik
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class BasicMongoPersistentProperty extends AnnotationBasedPersistentProperty<MongoPersistentProperty> implements
MongoPersistentProperty {
@@ -47,20 +49,18 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
private static final Logger LOG = LoggerFactory.getLogger(BasicMongoPersistentProperty.class);
private static final String ID_FIELD_NAME = "_id";
private static final String LANGUAGE_FIELD_NAME = "language";
private static final Set<Class<?>> SUPPORTED_ID_TYPES = new HashSet<Class<?>>();
private static final Set<String> SUPPORTED_ID_PROPERTY_NAMES = new HashSet<String>();
private static final Field CAUSE_FIELD;
static {
SUPPORTED_ID_TYPES.add(ObjectId.class);
SUPPORTED_ID_TYPES.add(String.class);
SUPPORTED_ID_TYPES.add(BigInteger.class);
SUPPORTED_ID_PROPERTY_NAMES.add("id");
SUPPORTED_ID_PROPERTY_NAMES.add("_id");
CAUSE_FIELD = ReflectionUtils.findField(Throwable.class, "cause");
}
private final FieldNamingStrategy fieldNamingStrategy;
@@ -86,14 +86,6 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
}
}
/* (non-Javadoc)
* @see org.springframework.data.mapping.FooBasicPersistentProperty#isAssociation()
*/
@Override
public boolean isAssociation() {
return field.isAnnotationPresent(DBRef.class) || super.isAssociation();
}
/**
* Also considers fields as id that are of supported id type and name.
*
@@ -108,7 +100,8 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
}
// We need to support a wider range of ID types than just the ones that can be converted to an ObjectId
return SUPPORTED_ID_PROPERTY_NAMES.contains(field.getName());
// but still we need to check if there happens to be an explicit name set
return SUPPORTED_ID_PROPERTY_NAMES.contains(getName()) && !hasExplicitFieldName();
}
/*
@@ -142,10 +135,8 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
}
}
org.springframework.data.mongodb.core.mapping.Field annotation = findAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
if (annotation != null && StringUtils.hasText(annotation.value())) {
return annotation.value();
if (hasExplicitFieldName()) {
return getAnnotatedFieldName();
}
String fieldName = fieldNamingStrategy.getFieldName(this);
@@ -158,13 +149,32 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
return fieldName;
}
/**
* @return true if {@link org.springframework.data.mongodb.core.mapping.Field} having non blank
* {@link org.springframework.data.mongodb.core.mapping.Field#value()} present.
* @since 1.7
*/
protected boolean hasExplicitFieldName() {
return StringUtils.hasText(getAnnotatedFieldName());
}
private String getAnnotatedFieldName() {
org.springframework.data.mongodb.core.mapping.Field annotation = findAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
if (annotation != null && StringUtils.hasText(annotation.value())) {
return annotation.value();
}
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#getFieldOrder()
*/
public int getFieldOrder() {
org.springframework.data.mongodb.core.mapping.Field annotation = getField().getAnnotation(
org.springframework.data.mongodb.core.mapping.Field.class);
org.springframework.data.mongodb.core.mapping.Field annotation = findAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
return annotation != null ? annotation.order() : Integer.MAX_VALUE;
}
@@ -182,7 +192,7 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isDbReference()
*/
public boolean isDbReference() {
return getField().isAnnotationPresent(DBRef.class);
return isAnnotationPresent(DBRef.class);
}
/*
@@ -190,14 +200,33 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#getDBRef()
*/
public DBRef getDBRef() {
return getField().getAnnotation(DBRef.class);
return findAnnotation(DBRef.class);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#usePropertyAccess()
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isLanguageProperty()
*/
public boolean usePropertyAccess() {
return CAUSE_FIELD.equals(getField());
@Override
public boolean isLanguageProperty() {
return getFieldName().equals(LANGUAGE_FIELD_NAME) || isExplicitLanguageProperty();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isExplicitLanguageProperty()
*/
@Override
public boolean isExplicitLanguageProperty() {
return isAnnotationPresent(Language.class);
};
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isTextScoreProperty()
*/
@Override
public boolean isTextScoreProperty() {
return isAnnotationPresent(TextScore.class);
}
}

Some files were not shown because too many files have changed in this diff Show More