Compare commits

...

193 Commits

Author SHA1 Message Date
Mark Paluch
cdc78592ee DATAMONGO-1688 - Release version 2.0 M4 (Kay). 2017-06-14 17:03:48 +02:00
Mark Paluch
ab97e58793 DATAMONGO-1688 - Prepare 2.0 M4 (Kay). 2017-06-14 17:03:00 +02:00
Mark Paluch
a4eeb9f305 DATAMONGO-1688 - Updated changelog. 2017-06-14 17:02:58 +02:00
Oliver Gierke
de6c649c83 DATAMONGO-1689 - Polish compiler configuration in POM.
Original pull request: #463.
2017-06-14 16:43:20 +02:00
Sebastien Deleuze
e90c6b0790 DATAMONGO-1689 - Add Kotlin extensions for new fluent API.
Original pull request: #463.
2017-06-14 16:38:18 +02:00
Sebastien Deleuze
d2e68cd925 DATAMONGO-1689 - Polishing.
Improve Maven Kotlin configuration for now + documentation fixes.

Original pull request: #463.
2017-06-14 16:37:52 +02:00
Christoph Strobl
7ed48f5e76 DATAMONGO-1689 - Polishing.
Additionally format code, update license header, update JavaDoc and add issue reference to tests.

Original pull request: #463.
2017-06-14 16:37:47 +02:00
Sebastien Deleuze
2359357977 DATAMONGO-1689 - Add Kotlin extensions for [Reactive]MongoOperations.
We now offer dedicated Kotlin extensions for MongoOperations and ReactiveMongoOperations.

Original pull request: #463.
2017-06-14 16:37:40 +02:00
Mark Paluch
a90f238574 DATAMONGO-1716 - Upgrade to Reactive Streams driver 1.5.0. 2017-06-14 11:53:23 +02:00
Christoph Strobl
1c9188f7e1 DATAMONGO-1712 - Polishing.
Follow ReactiveCrudRepository contract, fix spelling, update issue references and add deleteById(Publisher).

Related ticket: DATACMNS-1063.

Original Pull Request: #467
2017-06-13 19:46:27 +02:00
Mark Paluch
a2f7c3f482 DATAMONGO-1712 - Adopt to ReactiveCrudRepository.findById(Publisher) and existsById(Publisher).
Related ticket: DATACMNS-1063.

Original Pull Request: #467
2017-06-13 19:45:30 +02:00
Christoph Strobl
3440bf6c4d DATAMONGO-1714 - Deprecate MongoLog4jAppender. 2017-06-13 19:24:52 +02:00
Mark Paluch
deed19187f DATAMONGO-1563 - Polishing.
Rename TerminatingAggregationOperation.get() to TerminatingAggregationOperation.all() to name methods consistently. Extract collection name retrieval to method. Javadoc, formatting, add generics where required/use diamond syntax where applicable.

Original pull request: #466.
2017-06-13 10:39:40 +02:00
Christoph Strobl
c5f2abe037 DATAMONGO-1563 - Add fluent alternative for MongoOperations.
We now provide an alternative API for MongoOperations that allows defining operations in a fluent way. FluentMongoOperations reduces the number of methods and strips down the interface to a minimum while offering a more readable API.

// find all with filter query and projecting return type
template.query(Person.class)
    .matching(query(where("firstname").is("luke")))
    .as(Jedi.class)
    .all();

// insert
template.insert(Person.class)
    .inCollection(STAR_WARS)
    .one(luke);

// update with filter & upsert
template.update(Person.class)
    .apply(new Update().set("firstname", "Han"))
    .matching(query(where("id").is("han-solo")))
    .upsert();

// remove all matching
template.remove(Jedi.class)
    .inCollection(STAR_WARS)
    .matching(query(where("name").is("luke")))
    .all();

// aggregate
template.aggregateAndReturn(Jedi.class)
    .inCollection("star-wars)
    .by(newAggregation(project("name")))
    .all();

Original pull request: #466.
2017-06-13 10:39:10 +02:00
Mark Paluch
6cce16414e DATAMONGO-1672 - Updated changelog. 2017-06-08 11:56:20 +02:00
Mark Paluch
a85855a307 DATAMONGO-1710 - Adopt to changed AnnotationUtils.getValue(…) and OperatorNode.getRightOperand() behavior.
Related ticket: SPR-15540.
2017-06-07 17:20:58 +02:00
Mark Paluch
31390d41e0 DATAMONGO-1671 - Updated changelog. 2017-06-07 12:23:35 +02:00
Mark Paluch
117ab7c033 DATAMONGO-1707 - Polishing.
Fix typo in test method names.
2017-06-01 10:08:52 +02:00
Mark Paluch
73fbaaf3bd DATAMONGO-1707 - Upgrade to Reactor 3.1 M2.
Adopt to API change from Publisher.subscribe() to Publisher.toProcessor(). Adopt to changed reactor-test groupId. Provide mocks for calls that allowed previously null Publishers.
2017-06-01 10:08:52 +02:00
Christoph Strobl
17937b0475 DATAMONGO-1699 - Upgrade travis-ci build to use MongoDB 3.4 server.
We now do it explicitly as there seems to be almost no movement getting the alias on the whitelist.
2017-05-24 12:56:57 +02:00
Mark Paluch
46943716ee DATAMONGO-1693 - Support collation in ReactiveMongoTemplate.createCollection.
We now consider Collation via CollectionOptions when creating collections using ReactiveMongoTemplate.createCollection.

Original Pull Request: #462.
2017-05-24 11:03:54 +02:00
Christoph Strobl
25af5b5f79 DATAMONGO-1687 - Polishing.
CollectionOptions is now immutable and returns Optional#empty for values not set. Some minor changes to JavaDoc and required updates for tests involved.

Original Pull Request: #462.
2017-05-24 11:01:00 +02:00
Mark Paluch
a5a4c6d8c4 DATAMONGO-1687 - Initialize collation in CollectionOptions eagerly.
CollectionOptions.collation is initialized with Optional.empty() to guard collection creation against null dereference.

Original Pull Request: #462
2017-05-24 10:58:41 +02:00
Christoph Strobl
5885d084be DATAMONGO-1619 - Polishing.
Align to changes in DATACMNS-995 and emit Exception if findOne yields more than one result.

Original Pull Request: #444
2017-05-22 15:18:06 +02:00
Mark Paluch
d8fdc18265 DATAMONGO-1619 - Use ReactiveQueryByExampleExecutor in ReactiveMongoRepository.
Add ReactiveQueryByExampleExecutor to ReactiveMongoRepository and check by providing tests for the execution invocation.
Move methods into order and add some missing @Override annotations along the way.

Related ticket: DATACMNS-995 via (spring-projects/spring-data-commons#197)

Original Pull Request: #444
2017-05-22 15:16:44 +02:00
Christoph Strobl
840bde65e8 DATAMONGO-1686 - Upgrade to mongodb-driver-reactivestreams 1.4.0. 2017-05-22 14:32:54 +02:00
Christoph Strobl
f2ee7d90c4 DATAMONGO-1690 - Polishing.
Rename QueryDslMongoRepository -> QuerydslMongoRepository. Migrate assertions to AssertJ.

Original pull request #461.
Related ticket: DATACMNS-1059.
2017-05-19 09:38:15 +02:00
Christoph Strobl
898489fecf DATAMONGO-1690 - Adapt to QuerydslPredicateExecutor API changes.
We now return Optional<T> for QuerydslPredicateExecutor.findOne(Predicate).

Original pull request #461.
Related ticket: DATACMNS-1059.
2017-05-19 09:36:44 +02:00
Oliver Gierke
3575d5461e DATAMONGO-1695 - Polishing.
Javadoc. Variable names. Move off deprecated methods. Removed obsolete private method.
2017-05-18 13:27:46 +02:00
Oliver Gierke
4fa09d80db DATAMONGO-1695 - Make sure we read GridFs content type from the same field we write it to.
We now consistently store the content type of a file in _contentType in the metadata document. On the lookup side we still fall back to the deprecated file.getContentType().
2017-05-18 13:27:10 +02:00
Christoph Strobl
bb84b92d1d DATAMONGO-1685 - Polishing.
Migrate assertions to AssertJ. Fix Javadoc.

Original pull request: #460.
2017-05-11 09:51:58 +02:00
Christoph Strobl
af85b46e7d DATAMONGO-1685 - Adapt to QueryByExampleExecutor API changes.
Use Optional as return type for findOne(Example example).

Related ticket: DATACMNS-1058.

Original pull request: #460.
2017-05-11 09:51:33 +02:00
Mark Paluch
96fbe49cdb DATAMONGO-1664 - After release cleanups. 2017-05-09 11:34:42 +02:00
Mark Paluch
6b36c792b9 DATAMONGO-1664 - Prepare next development iteration. 2017-05-09 11:34:41 +02:00
Mark Paluch
006c8cfae2 DATAMONGO-1664 - Release version 2.0 M3 (Kay). 2017-05-09 11:23:00 +02:00
Mark Paluch
14aed4c348 DATAMONGO-1664 - Prepare 2.0 M3 (Kay). 2017-05-09 11:22:12 +02:00
Mark Paluch
37165dd76d DATAMONGO-1664 - Updated changelog. 2017-05-09 11:22:07 +02:00
Mark Paluch
b22fd056aa DATAMONGO-1667 - Rename @InfiniteStream to @Tailable.
Rename InfiniteStream annotation to Tailable to reflect the related MongoDB approach used for repository query methods returning an infinite stream. InfiniteStream is the high-level concept that is achieved by using tailable cursors.

Original Pull Request: #458
2017-05-04 15:17:14 +02:00
Mark Paluch
42672a6df9 DATAMONGO-1518 - Polishing.
Rename ICULocale to CollationLocale. Introduce interface for ComparisonLevel construction and let ICUComparisonLevel types implement that interface. Make value types immutable where possible. Provide static instances for default comparison level instances.

Replace collation conversion IndexConverters with Collation.from(…).toMongoCollation() converter. Introduce missing generic types. Replace Optional.get() with Optional.map(…).orElse(…).

Update reference documentation.

Original pull request: #459.
2017-05-04 11:36:05 +02:00
Christoph Strobl
0b169d5341 DATAMONGO-1518 - Add support for collations.
We now support collations for collections, indexes, queries, findAndModify, delete, geo, bulk and aggregation operations in both the imperative and reactive implementations on template level.

Collation collation = Collation.of("fr")
  .strength(ComparisonLevel.secondary()
    .includeCase())
  .numericOrderingEnabled()
  .alternate(Alternate.shifted().punct())
  .forwardDiacriticSort()
  .normalizationEnabled();

template.createCollection(Person.class, CollectionOptions.just(collation));

Query query = new Query(Criteria.where("firstName").is("Amél")).collation(collation);

Original pull request: #459.
2017-05-04 11:33:44 +02:00
Oliver Gierke
b9d301e525 DATAMONGO-1679 - Further removal of Serializable as requirement for identifiers. 2017-05-03 18:23:31 +02:00
Oliver Gierke
e3238593ce DATAMONGO-1679 - Adapt to API changes in repository interfaces. 2017-05-03 17:51:45 +02:00
Mark Paluch
5d8370fa90 DATAMONGO-1684 - Adopt documentation to removed JodaTime DateMidnight support.
Related ticket: DATACMNS-1014.
2017-05-03 08:34:56 +02:00
Oliver Gierke
e01c745884 DATAMONGO-1674 - Adapted to Range API changes. 2017-04-26 18:02:29 +02:00
Mark Paluch
9ae3a29103 DATAMONGO-1660 - Adapt to moved CustomConversions to Spring Data Commons.
Introduce MongoCustomConversions extending o.s.d.convert.CustomConversions.

Remove o.s.d.mongo.core.convert.CustomConversions implementation code and utility classes and let it extend MongoCustomConversions. Replace references to o.s.d.m.c.c.CustomConversions with o.s.d.convert.CustomConversions. Adapt tests and MappingMongoConverter to MongoCustomConversions.

Related ticket: DATACMNS-1035.
2017-04-24 13:20:11 +02:00
Mark Paluch
6a446cbc7f DATAMONGO-1325 - Polishing.
Add since tag to new method. Set year of inception in copyright header. Add ticket reference to test. Minor code reformatting. Add integration test.

Change query keyword for query-by-example from $sample to $example to prevent accidental collisions.

Remove Mongo 3.4-next build profile due to removed Mongo 3.4 driver snapshots.

Original pull request: #452.
2017-04-20 10:55:49 +02:00
Gustavo de Geus
d40084f78e DATAMONGO-1325 - Add $sample aggregation stage.
We now support the $sample aggregation pipeline stage via Aggregation to select a random subset of result documents.

TypedAggregation<Employee> agg = Aggregation.newAggregation(Employee.class,
		sample(5));

Original pull request: #452.
2017-04-20 10:42:50 +02:00
Mark Paluch
7bfbff0602 DATAMONGO-1205 - Polishing.
Add author tag. Extend year range in copyright header.

Original pull request: #397.
2017-04-20 08:41:22 +02:00
Martin Macko
fc377cf0f9 DATAMONGO-1205 - Log only CyclicPropertyReferenceException message.
We log CyclicPropertyReferenceException with its message only and removed the stack trace from the log. The stacktrace points to a verifier location and is not particularly useful in finding the offending code. This change creates consistency over how CyclicPropertyReferenceException is logged.

Original pull request: #397.
2017-04-20 08:41:00 +02:00
Oliver Gierke
d594afb5bb DATAMONGO-1670 - Updated changelog. 2017-04-19 21:04:19 +02:00
Oliver Gierke
3012e228c5 DATAMONGO-1669 - Updated changelog. 2017-04-19 20:01:47 +02:00
Mark Paluch
0a947da7e4 DATAMONGO-1668 - Polishing.
Replace new Sort(…) with Sort.by(…) and new PageRequest(…) with PageRequest.of(…).
2017-04-19 15:11:19 +02:00
Mark Paluch
eb85fb3a14 DATAMONGO-1668 - Adopt changed Mono and Flux error handling API.
Replace Flux/Mono.onErrorResumeWith(…) with Flux/Mono.onErrorMap(…) and turn translateException into a method returning a mapping function instead of a reactive type emitting the mapped exception.
2017-04-19 14:54:14 +02:00
Oliver Gierke
ebc8c5df3a DATAMONGO-1634 - Updated changelog. 2017-04-19 13:04:08 +02:00
Oliver Gierke
5b52af4efe DATAMONGO-1633 - Updated changelog. 2017-04-19 11:50:47 +02:00
Christoph Strobl
74161162b4 DATAMONGO-1666 - Polishing.
Some minor language level cleanups and removal of deprecated API usage.

Original Pull Request: #457
2017-04-13 11:26:18 +02:00
Mark Paluch
bcba123e32 DATAMONGO-1666 - Consider collection type in bulk DBRef fetching.
We now consider the property's collection type after bulk-fetching DBRefs before returning the actual result value. The issue got only visible if bulk fetching is possible and constructor creation is used. Setting the property value on through an property accessor works fine because the property accessor checks all values for assignability and potentially converts values to their target type. That's different for constructor creation.

Original Pull Request: #457
2017-04-13 11:25:47 +02:00
Sebastien Deleuze
935db07511 DATAMONGO-1665 - Upgraded to Reactor 3.1.0.BUILD-SNAPSHOT.
Original pull request: #456.
Related ticket: spring-projects/spring-data-build#333
2017-04-12 07:20:55 +02:00
Oliver Gierke
314b95370f DATAMONGO-1597 - Updated changelog. 2017-04-10 13:41:25 +02:00
Michael J. Simons
20929b34f1 DATAMONGO-1662 - Fix classname in reference docs about projections in aggregations.
Original pull request: #455.
2017-04-10 09:13:12 +02:00
Oliver Gierke
782bcd4d5f DATAMONGO-1535 - After release cleanups. 2017-04-04 22:08:18 +02:00
Oliver Gierke
33fb40c872 DATAMONGO-1535 - Prepare next development iteration. 2017-04-04 22:08:15 +02:00
Oliver Gierke
bf61c5782e DATAMONGO-1535 - Release version 2.0 M2 (Kay). 2017-04-04 21:12:35 +02:00
Oliver Gierke
431e71f4a0 DATAMONGO-1535 - Prepare 2.0 M2 (Kay). 2017-04-04 21:12:02 +02:00
Oliver Gierke
3192d7dd78 DATAMONGO-1535 - Updated changelog. 2017-04-04 21:11:53 +02:00
Christoph Strobl
d7a6594933 DATAMONGO-1655 - Remove obsolete build profiles.
And add snapshot profile for 3.5.0.
2017-04-03 18:37:19 +02:00
Christoph Strobl
609d6d5a19 DATAMONGO-1656 - Upgrade to MongoDB Driver 3.4.2 and Reactive Streams Driver 1.3.0.
Prepare issue branch.
2017-04-03 18:37:06 +02:00
Mark Paluch
19c8788376 DATAMONGO-1643 - Polishing.
Fix documentation for namespace types. Remove unused ReflectiveDBCollectionInvoker. Simplify tests, align bean name to mongoClient. Remove injection of Mongo in favor of injecting MongoTemplate.

Remove throws declaration from AbstractMongoConfiguration.mongoClient(). MongoClient creation does not throw checked exceptions so throwing an Exception is no longer required. Replace Mongo by MongoClient in reference documentation.

Original pull request: #451.
2017-04-03 15:36:58 +02:00
Christoph Strobl
db9934c7d8 DATAMONGO-1643 - Replace references to Mongo by MongoClient.
Remove and replace usage of "mongo" by "mongoClient". This involves xsd schema, bean names, constructor and parameter types. This required some API changes as some server commands are no longer directly available through the api, but have to be invoked via runCommand.

Also remove references to outdated API using Credentials and an authentication DB instead of MongoCredentials for authentication.

Updated and removed (unused) tests; Altered documentation.

Original pull request: #451.
2017-04-03 15:35:43 +02:00
Christoph Strobl
b5679744e7 DATAMONGO-1643 - Add and use 2.0 namespace xsd.
Original pull request: #451.
2017-04-03 15:35:27 +02:00
Mark Paluch
b1acda4188 DATAMONGO-1610 - Support RxJava 2 repositories.
Add RxJava 2 dependency. Add test to verify RxJava 2 interoperability.

Original Pull Request: #440
2017-04-03 14:57:11 +02:00
Christoph Strobl
2ab466eb35 DATAMONGO-1447 - Add support for isolations on Update.
We now allow usage of the $isolated update operator via Update.isolated().
In case isolated is set the query involved in MongoOperations.updateMulti will be enhanced by '$isolated' : 1 in case the isolation level has not already been set explicitly via eg. new BasicQuery("{'$isolated' : 0}").

Original pull request: #371.
2017-04-03 13:53:00 +02:00
Mark Paluch
e9da449667 DATAMONGO-1559 - Drop collections used in ReactiveMongoTemplateExecuteTests before tests.
We now drop the collections used in ReactiveMongoTemplateExecuteTests on test start to create a clean state for the tests. Previously, collections were dropped after the tests only so existing data in the collections could interfere with the tests themselves.
2017-03-27 08:40:10 +02:00
John Blum
6bc72a654f DATAMONGO-1648 - Rename getRepositoryFactoryClassName to getRepositoryFactoryBeanClassName. 2017-03-24 13:36:11 -07:00
Mark Paluch
4fd9edf585 DATAMONGO-1559 - Polishing.
Migrate off deprecated Cancellation API to Disposable.
2017-03-24 17:46:41 +01:00
Mark Paluch
955597bb54 DATAMONGO-1559 - Migrate reactive tests from TestSubscriber to StepVerifier. 2017-03-24 17:42:31 +01:00
Oliver Gierke
f59bbd351d DATAMONGO-1647 - Polishing. 2017-03-24 12:25:21 +01:00
Oliver Gierke
43ab3cad13 DATAMONGO-1647 - Switched to use IdentifierAccessor.getRequiredIdentifier() in MongoTemplate.doSaveVersioned(…). 2017-03-24 12:24:57 +01:00
Oliver Gierke
5ba46dadb8 DATAMONGO-1609 - Switched to Mockito from parent POM.
Moved of deprecated runner declarations.
2017-03-24 08:54:29 +01:00
Oliver Gierke
a245c0f280 DATAMONGO-1609 - Adapt to API changes in Spring Data Commons. 2017-03-24 08:54:29 +01:00
Christoph Strobl
288f244c34 DATAMONGO-1609 - Fix failing tests.
Fix issues pointed out by failing tests. Main focus was to restore functionality and not a Java 8 code cleanup. So, this one still needs some love and polishing.
2017-03-24 08:54:29 +01:00
Christoph Strobl
90bb6262f9 DATAMONGO-1609 - Fix compile errors.
Still way to go:
  - Failures: 113, Errors: 836, Skipped: 16
2017-03-24 08:54:29 +01:00
Oliver Gierke
826d00afa7 DATAMONGO-1609 - Hacking. 2017-03-24 08:54:29 +01:00
Mark Paluch
ac3f7dbf99 DATAMONGO-1645 - Polishing.
Clean up appender and log level after test run. Suppress log output during tests.

Original pull request: #450.
2017-03-21 10:52:14 +01:00
Christoph Strobl
f70e1fa291 DATAMONGO-1645 - Safely serialize JSON output for log message in LoggingEventListener.
We now make sure to safely serialize JSON output for mapped documents. This prevents the logger from rendering false exception messages to log appender.

Original pull request: #450.
2017-03-21 10:45:13 +01:00
Mark Paluch
a84c4b064d DATAMONGO-1637 - Polishing.
Move aggregation options conversion to AggregationOptions.getMongoAggregationOptions(). Allow cursor options to control cursor batch size. Add command logging to stream execution. Rearrange method order. Close cursor in tests. Change author name from user name to full name.

Original pull request: #447.
2017-03-21 09:57:25 +01:00
Mainder Singh
1a65828365 DATAMONGO-1637 - Add support for aggregation result streaming.
We now support aggregation result streaming backed by a MongoDB cursor. Result streaming fetches aggregation results in batches from MongoDB and converts results as they are retrieved through the iterator.

Aggregation aggregation = …

CloseableIterator<TagCount> results = mongoOperations.aggregateStream(aggregation, "inputCollection", TagCount.class);

List<TagCount> tagCount = new ArrayList<TagCount>();
while (results.hasNext()) {
	tagCount.add(results.next());
}

results.close();

Original pull request: #447.
2017-03-21 09:56:53 +01:00
Mark Paluch
f4f5e02e66 DATAMONGO-1620 - Polishing.
Fix test method name. Add JavaDoc.

Original pull request: #449.
2017-03-13 16:28:54 +01:00
Christoph Strobl
4b8d35262f DATAMONGO-1620 - Add server-selection-timeout to XML MongoClientOptions config.
We now allow server-selection-timeout attribute on MongoClientOptions XML configuration for a MongoDB 3.x client.

Original pull request: #449.
2017-03-13 16:28:49 +01:00
Mark Paluch
b486fec048 DATAMONGO-1641 - Remove formatting config from the repository.
See https://github.com/spring-projects/spring-data-build/tree/master/etc/ide for most recent IDE settings.
2017-03-08 11:45:42 +01:00
Mark Paluch
50fcbd18f2 DATAMONGO-1421 - Polishing.
Remove trailing whitespaces. Construct exception message with String.format(…).

Original pull request: #448.
2017-03-08 08:56:03 +01:00
Christoph Strobl
c37dfd9688 DATAMONGO-1421 - Fix serialization in error message causing error itself.
We now make sure to safely serialize the criteria object used for creating the error message when raising an `InvalidMongoDbApiUsageException` in cases where `addCriteria` is used to add multiple entries for the same property.

Original pull request: #448.
2017-03-08 08:55:43 +01:00
Oliver Gierke
98d655f4e2 DATAMONGO-1639 - Make sure BeforeConvertEvent sees new version for updates.
The changes for DATAMONGO-1617 subtley changed the behavior for entity updates in terms of the version value they see for entities using optimistic locking. Previously the updates already saw the new version value, where after the fix for DATAMONGO-1617 it saw the old one. That caused BeforeConvertEvent listeners not being able to distinguish between an original insert and the first update anymore.

This change is now rolled back and we introduced a test case that encodes this expectation explicitly.
2017-03-06 16:32:19 +01:00
Oliver Gierke
98a9b66e6b DATAMONGO-1617 - Reinstantiate version property initialization before BeforeConvertEvent publication.
Related pull request: #443.
2017-03-03 08:46:28 +01:00
Oliver Gierke
827b6204a9 DATAMONGO-1617 - Polishing.
Some cleanups in MongoTemplateTests. Removed manual ID assignment in general id handling test to make sure we use the id generation. Removed unneccessary code from domain type in favor of Lombok.

Original pull request: #443.
2017-03-03 08:36:21 +01:00
Laszlo Csontos
781717faa8 DATAMONGO-1617 - BeforeConvertEvent is now emitted before updatable idendifier assertion.
We now make sure the BeforeConvertEvent is published before we check for identifier types that can potentially be auto-generated. That allows the event listeners to populate identifiers. Previously the identifier check kicked in before that and thus caused the listener not being able to populate the property.

Original pull request: #443.
2017-03-03 08:36:05 +01:00
Oliver Gierke
084a167e20 DATAMONGO-1598 - Updated changelog. 2017-03-02 11:11:03 +01:00
Mark Paluch
456ae2459f DATAMONGO-1605 - Polishing.
Remove additional quoting around JSON serialization because JSON serialization adds quotes to a string. Reformat code.
2017-03-01 16:14:03 +01:00
Christoph Strobl
d079edb92c DATAMONGO-1605 - Retain type of SpEL expression result when used in @Query.
Fix issue where any result of a SpEL expression had been treated as quoted String within the resulting MongoDB query.
2017-03-01 16:14:00 +01:00
Mark Paluch
eb97944fd4 DATAMONGO-1600 - Make GraphLookupOperationBuilder public.
Make GraphLookupOperationBuilder public so it can be used in types outside the aggregation package.

Original Pull Request: #437
2017-03-01 12:55:39 +01:00
Mark Paluch
e2d6f187c2 DATAMONGO-1603 - Polishing.
Remove code that became unused. Reformat code. Extend years in copyright header.

Original pull request: #441.
2017-03-01 08:54:32 +01:00
Christoph Strobl
8068e36679 DATAMONGO-1603 - Fix Placeholder not replaced correctly in @Query.
Fix issues when placeholders are appended with other chars eg. '?0xyz' or have been reused multiple times within the query. Additional tests and fixes for complex quoted replacements eg. in regex query. Rely on placeholder quotation indication instead of binding one. Might be misleading when placeholder is used more than once.

Original pull request: #441.
2017-03-01 08:52:18 +01:00
Christoph Strobl
c62d13154f DATAMONGO-1608 - Polishing.
Throw an IllegalArgumentException when trying to create a query using 'null' as an argument for queries resulting in a $regex query operator.

Original Pull Request: #439
2017-02-13 08:33:12 +01:00
Edward Prentice
7d53f21d58 DATAMONGO-1608 - Add guard against NPE in MongoQueryCreator when using IgnoreCase.
Original Pull Request: #439
2017-02-13 08:28:26 +01:00
Christoph Strobl
0000a8fd11 DATAMONGO-1607 - Polishing.
Move coordinate conversion to dedicated method. Additionally fix issue with assertions applied to late in the chain and added some tests.

Original Pull Request: #438
2017-02-10 14:24:37 +01:00
Thiago Diniz da Silveira
fad9756a93 DATAMONGO-1607 - Fix ClassCastException in Circle, Point and Sphere when coordinates are not Double.
Original Pull Request: #438
2017-02-10 14:19:06 +01:00
Mark Paluch
54acc4934c DATAMONGO-1602 - Remove references to Assert single-arg methods.
Replace references to Assert single-arg methods with references to methods accepting the test object and message.

Related ticket: SPR-15196.
2017-02-01 11:08:11 +01:00
Oliver Gierke
2e593bb9b2 DATAMONGO-1573 - Updated changelog. 2017-01-26 12:12:37 +01:00
Oliver Gierke
2bf32a25be DATAMONGO-1574 - Updated changelog. 2017-01-26 12:12:07 +01:00
Christoph Strobl
148c4f9e24 DATAMONGO-1517 - Polishing.
Remove ReflectiveSimpleTypes in favor of MongoSimpleTypes.
Add add integration test.
2017-01-25 17:12:31 +01:00
Mark Paluch
ae6171802e DATAMONGO-1517 - Add support for Decimal128 BSON type.
Support Decimal128 as Mongo simple type if present. Decimal128 is stored as NumberDecimal.

class Person {

  String id;
  Decimal128 decimal128;

  Person(String id, Decimal128 decimal128) {
    this.id = id;
    this.decimal128 = decimal128;
  }
}

mongoTemplate.save(new Person("foo", new Decimal128(new BigDecimal("123.456"))));

is represented as:

{ "_id" : "foo", "decimal128" : NumberDecimal("123.456") }
2017-01-25 17:12:31 +01:00
Mark Paluch
1b97d1d1d0 DATAMONGO-1596 - Fix typo in JavaDoc.
Use correct @RelatedDocument annotation in MongoDB cross store reference documentation.
2017-01-25 16:53:41 +01:00
Mark Paluch
91495825a5 DATAMONGO-1575 - Polishing.
Extend year range in license headers. Use MongoDB JSON serializer for String escaping. Move unquoting/quote checking to inner QuotedString utility class. Reformat code.
2017-01-25 11:44:12 +01:00
Christoph Strobl
18e6b9cfa7 DATAMONGO-1575 - Escape Strings correctly.
Use regex groups and parameter index values for replacement in string based queries.
2017-01-25 11:44:09 +01:00
Christoph Strobl
0e0b8d5f79 DATAMONGO-1594 - Update "what’s new" section in reference documentation. 2017-01-23 08:23:51 +01:00
Oliver Gierke
11882724fa DATAMONGO-1592 - Adapt AuditingEventListenerUnitTests to changes in core auditing.
The core auditing implementation now skips the invocation of auditing in case the candidate aggregate doesn't need any auditing in the first place. We needed to adapt the sample class we use to actually carry the necessary auditing annotations.

Related ticket: DATACMNS-957.
2017-01-20 16:36:45 +01:00
Oliver Gierke
eb2a58cdbe DATAMONGO-1590 - Polishing.
Removed some compiler warnings. Hide newly introduced class in package scope and made use of Lombok annotations to avoid boilerplate code.

Original pull request: #436.
2017-01-18 19:42:46 +01:00
Christoph Strobl
8c9bbf7f91 DATAMONGO-1590 - EntityInformation selected now correctly considers Persistable.
We now wrap the MappingMongoEntityInformation into one that delegates the methods implemented by Persistable to the actual entity in case it implements said interface.

Original pull request: #436.
2017-01-18 19:42:42 +01:00
Mark Paluch
63fc047160 DATAMONGO-1581 - Expose ReactiveMongoRepositoryConfigurationExtension as public type.
Expose ReactiveMongoRepositoryConfigurationExtension so configuration extensions such as Spring Boot's ReactiveMongoRepositoriesAutoConfigureRegistrar can pick it up and reuse the repository configuration extension.
2017-01-17 14:14:08 +01:00
Mark Paluch
44e872c7df DATAMONGO-1588 - Polishing.
Remove unused fields. Fix typo in method name. Reformat inner class to align formatting.

Original pull request: #435.
2017-01-16 09:29:10 +01:00
Christoph Strobl
1135e90be0 DATAMONGO-1588 - Fix derived finder not accepting subclass of parameter type.
We now allow using sub types as arguments for derived queries. This makes it possible to use eg. a GeoJsonPoint for querying while the declared property type in the domain object remains a regular (legacy) Point.

Original pull request: #435.
2017-01-16 09:29:06 +01:00
Mark Paluch
65da90afd8 DATAMONGO-1589 - Update project documentation with the CLA tool integration. 2017-01-13 11:46:58 +01:00
Mark Paluch
4856c9b4f5 DATAMONGO-1587 - Polishing.
Convert @see http://… links to valid format using @see <a href=…>…</a>
2017-01-12 17:24:03 +01:00
Mark Paluch
644c1a2c89 DATAMONGO-1587 - Migrate ticket references in test code to Spring Framework style. 2017-01-12 16:51:57 +01:00
Mark Paluch
8df9d30d2e DATAMONGO-1586 - Consider field name in TypeBasedAggregationOperationContext.getReferenceFor(…).
We now consider the provided field name (alias) in mapped fields with which it is exposed. The field name applies to the exposed field after property path resolution in TypeBasedAggregationOperationContext. Previously, the field reference used the property name which caused fields to be considered non-aliased, so aggregation projection operations dropped the alias and exposed the field with its leaf property name.

Original Pull Request: #434
2017-01-12 10:34:02 +01:00
Christoph Strobl
90ae6d1805 DATAMONGO-1585 - Polishing.
Update documentation for better readability in html and pdf format.

Original Pull Request: #433
2017-01-12 10:19:55 +01:00
Mark Paluch
1fe79f1194 DATAMONGO-1585 - Expose synthetic fields in $project aggregation stage.
Field projections now expose their fields as synthetic simple fields. Projection aggregation stage redefines the available field set available for later aggregation stages entirely so projected fields are considered synthetic. A simple synthetic field has no target field which causes later aggregation stages to not pick up the underlying target but the exposed field name when rendering aggregation operations to Mongo documents.

The change is motivated by a bug where previously an aggregation consisting of projection of an aliased field and sort caused the sort projection stage to render with the original field name instead of the aliased field. The sort did not apply any sorting since projection redefines the available field set entirely and the original field is no longer accessible.

Original Pull Request: #433
2017-01-12 10:19:10 +01:00
Christoph Strobl
2c6bd6ecea DATAMONGO-1576 - Update lifecycle event documentation.
Add note on lifecycle event handling for property types.
2017-01-11 13:10:25 +01:00
Mark Paluch
fc5bb3f1d3 DATAMONGO-1578 - Polishing.
Add ticket references to test methods. Extend license years in copyright header.

Original pull request: #398.
2017-01-02 11:46:57 +01:00
Martin Macko
d4d9c7673a DATAMONGO-1578 - Add missing @Test annotation to ProjectionOperationUnitTests.
Original pull request: #398.
2017-01-02 11:45:04 +01:00
Mark Paluch
20e2cfb273 DATAMONGO-1508 - Improve reference documentation.
Replace Spring Data Document with Spring Data MongoDB. Extend copyright year range. Replace static Spring version leftover with variable. Fix typos.
2017-01-02 11:19:59 +01:00
Lukasz Kryger
634f618eb1 DATAMONGO-1577 - Fix wording repetition in MongoRepository JavaDoc.
Original pull request: #407.
2017-01-02 11:19:59 +01:00
Ken Dombeck
c6e2662151 DATAMONGO-1577 - Fix Reference and JavaDoc spelling issues.
Replaced invalid class name MongoMappingConverter with actual class name of MappingMongoConverter. Fix typos.

Original pull request: #425.
2017-01-02 11:19:56 +01:00
Mark Paluch
51ae618585 DATAMONGO-1508 - Polishing.
Highlight attribute name. Replace tabs with spaces.

Original pull request: #399.
2017-01-02 11:14:25 +01:00
John Lilley
b584f04a41 DATAMONGO-1508 - Document authentication-dbname attribute in db-factory.
Original pull request: #399.
2017-01-02 11:14:25 +01:00
Oliver Gierke
6e1e9967af DATAMONGO-1522 - Updated changelog. 2016-12-21 19:35:37 +01:00
Oliver Gierke
af49230093 DATAMONGO-1469 - Updated changelog. 2016-12-21 18:43:02 +01:00
Oliver Gierke
3355a436c8 DATAMONGO-1467 - Polishing.
Original pull request: #431.
2016-12-20 14:26:40 +01:00
Christoph Strobl
9a0241992e DATAMONGO-1467 - Add support for MongoDB 3.2 partialFilterExpression for index creation.
We now support partial filter expression on indexes via Index.partial(…). This allows to create partial indexes that only index the documents in a collection that meet a specified filter expression.

new Index().named("idx").on("k3y", ASC).partial(filter(where("age").gte(10)))

The filter expression can be set via a plain DBObject or a CriteriaDefinition and is mapped against the associated domain type.

Original pull request: #431.
2016-12-20 14:19:28 +01:00
Oliver Gierke
779145645d DATAMONGO-1565 - Polishing.
Formatting in ExpressionEvaluatingParameterBinder and StringBasedMongoQueryUnitTests. Turned Placeholder into value object.
2016-12-20 11:37:28 +01:00
Mark Paluch
c0f3255e26 DATAMONGO-1565 - Polishing.
Consider quoted/unquoted parameter use with the same parameter reference. Extend date range in license headers.
2016-12-20 11:37:05 +01:00
Christoph Strobl
b1de52f05a DATAMONGO-1565 - Ignore placeholder pattern in replacement values for annotated queries.
We now make sure to quote single and double ticks in the replacement values before actually appending them to the query. We also replace single ticks around parameters in the actual raw annotated query by double quotes to make sure they are treated as a single string parameter.
2016-12-20 11:30:02 +01:00
Mark Paluch
646b525d86 DATAMONGO-1564 - Polishing.
Fix JavaDoc references and minor a import formatting issue.

Original pull request: #429.
2016-12-16 14:10:15 +01:00
Christoph Strobl
2c29f204c3 DATAMONGO-1564 - Split up AggregationExpressions.
Refactored to multiple smaller Aggregation Operator classes reflecting the grouping (array operators, string operators,…) predefined by MongoDB.

Original pull request: #429.
2016-12-16 14:00:22 +01:00
Mark Paluch
9cd44faeb7 DATAMONGO-1567 - Use newer Java 8 on Travis CI. 2016-12-16 10:31:22 +01:00
Mark Paluch
362de45664 DATAMONGO-1533 - Polishing.
Extend JavaDoc. Minor reformatting.

Original pull request: #428.
2016-12-16 09:24:42 +01:00
Christoph Strobl
b7317892a2 DATAMONGO-1533 - Add AggregationExpression derived from SpEL AST.
We added an AggregationExpression that renders a MongoDB Aggregation Framework expression from the AST of a SpEL expression. This allows usage with various stages (eg. $project, $group) throughout the aggregation support.

  // { $and: [ { $gt: [ "$qty", 100 ] }, { $lt: [ "$qty", 250 ] } ] }
  expressionOf("qty > 100 && qty < 250);

  // { $cond : { if : { $gte : [ "$a", 42 ]}, then : "answer", else : "no-answer" } }
  expressionOf("cond(a >= 42, 'answer', 'no-answer')");

Original pull request: #428.
2016-12-16 09:21:27 +01:00
Mark Paluch
35f43e9ab8 DATAMONGO-1566 - Adapt API in ReactiveMongoRepositoryFactoryBean.
Related tickets: DATACMNS-891.
2016-12-16 09:21:27 +01:00
Oliver Gierke
a77c5b6e1d DATAMONGO-1566 - Adapt API in MongoRepositoryFactoryBean.
Related tickets: DATACMNS-891.
2016-12-15 16:17:54 +01:00
Christoph Strobl
5cf8ec3e55 DATAMONGO-1552 - Polishing.
Updated doc, removed whitespaces, minor method wording changes.

Original Pull Request: #426
2016-12-14 13:09:06 +01:00
Mark Paluch
450549150d DATAMONGO-1552 - Add $facet aggregation stage.
Original Pull Request: #426
2016-12-14 12:00:30 +01:00
Mark Paluch
b7a0b1d523 DATAMONGO-1552 - Add $bucketAuto aggregation stage.
Original Pull Request: #426
2016-12-14 11:46:54 +01:00
Mark Paluch
5c5c616be9 DATAMONGO-1552 - Add $bucket aggregation stage.
Original Pull Request: #426
2016-12-14 11:32:50 +01:00
Mark Paluch
0bd98d67c8 DATAMONGO-442 - Polishing.
Reformat code according to Spring Data style. Add test for authenticated use. Add JavaDoc to newly introduced methods. Allow configuration of an authentication database. Update reference documentation.

Original pull request: #419.
2016-12-13 16:51:02 +01:00
Ricardo Espirito Santo
8302727b23 DATAMONGO-442 - Support username/password authentication with MongoLog4jAppender.
Added optional username and password for authentication support on Log4jAppender.

Original pull request: #419.
2016-12-13 16:48:02 +01:00
Christoph Strobl
86dbd95220 DATAMONGO-1551 - Polishing.
Add startWith overload allowing to mix expressions, removed white spaces, updated doc.

Original Pull Request: #424
2016-12-13 15:15:00 +01:00
Mark Paluch
3d0750afc5 DATAMONGO-1551 - Add $graphLookup aggregation stage.
We now support the $graphLookup aggregation pipeline stage via Aggregation to perform recursive lookup adding the lookup result as array to documents entering $graphLookup.

TypedAggregation<Employee> agg = Aggregation.newAggregation(Employee.class,
		graphLookup("employee")
			.startWith("reportsTo")
			.connectFrom("reportsTo")
			.connectTo("name")
			.depthField("depth")
			.maxDepth(5)
			.as("reportingHierarchy"));

Original Pull Request: #424
2016-12-13 14:44:53 +01:00
Christoph Strobl
1bf8eb09ca DATAMONGO-1550 - Polishing $replaceRoot (aggregation stage).
Original Pull Request: #422.
2016-12-13 08:18:40 +01:00
Mark Paluch
ae4cfaa58c DATAMONGO-1550 - Add $replaceRoot aggregation stage.
We now support the $replaceRoot stage in aggregation pipelines. $replaceRoot can reference either a field, an aggregation expression or it can be used to compose a replacement document.

newAggregation(
	replaceRoot().withDocument()
		.andValue("value").as("field")
		.and(MULTIPLY.of(field("total"), field("discounted")))
);

newAggregation(
	replaceRoot("item")));

Original Pull Request: #422
2016-12-13 08:18:40 +01:00
Christoph Strobl
1dea009e32 DATAMONGO-1549 - Polishing $count (aggregation stage).
Original Pull Request: #422
2016-12-13 08:18:27 +01:00
Mark Paluch
4c07235107 DATAMONGO-1549 - Add $count aggregation stage.
We now support the $count stage in aggregation pipelines.

newAggregation(
	match(where("hotelCode").is("0360")),
	count().as("documents"));

Original Pull Request: #422
2016-12-13 08:18:10 +01:00
Christoph Strobl
5ebbf93cf9 DATAMONGO-1558 - Upgrade MongoDB server version to, and add build profile for MongoDB 3.4.
Added MongoDB 3.4 profile to pom.xml and upgraded to MongoDB 3.4 on travis-ci.
Delete assertion checking property that has been removed in MongoDB 3.4 (see: https://jira.mongodb.org/browse/SERVER-24928)
2016-12-13 08:18:10 +01:00
Mark Paluch
794756d055 DATAMONGO-1548 - Polishing.
Enhance JavaDoc. Minor formatting. Fix typos.

Original pull request: #423.
2016-12-12 12:58:36 +01:00
Christoph Strobl
4fcc09c6c1 DATAMONGO-1548 - Add support for MongoDB 3.4 aggregation operators.
We now support the following MongoDB 3.4 aggregation operators:

$indexOfBytes, $indexOfCP, $split, $strLenBytes, $strLenCP, $substrCP, $indexOfArray, $range, $reverseArray, $reduce, $zip, $in, $isoDayOfWeek, $isoWeek, $isoWeekYear, $switch and $type.

Original pull request: #423.
2016-12-12 12:58:32 +01:00
Mark Paluch
d297f5a253 DATAMONGO-1538 - Polishing.
Use InheritingExposedFieldsAggregationOperationContext instead of anonymous context class for condition mapping. Drop aggregation input collections before tests. Minor reformatting.

Original pull request: #417.
2016-12-07 10:01:20 +01:00
Christoph Strobl
696e53ff60 DATAMONGO-1538 - Add support for $let to aggregation.
We now support $let in aggregation $project stage.

ExpressionVariable total = newExpressionVariable("total").forExpression(ADD.of(field("price"), field("tax")));
ExpressionVariable discounted = newExpressionVariable("discounted").forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D));

newAggregation(Sales.class,
	project()
		.and(define(total, discounted)
			.andApply(MULTIPLY.of(field("total"), field("discounted"))))
		.as("finalTotal"));

Original pull request: #417.
2016-12-07 10:01:17 +01:00
Christoph Strobl
f512d8cb16 DATAMONGO-1542 - Polishing.
Added some static entry points for better readability.

Original Pull Request: #421
2016-12-06 10:08:05 +01:00
Mark Paluch
b98bc0e2bf DATAMONGO-1542 - Refactor CondOperator and IfNullOperator to children of AggregationExpressions.
Renamed CondOperator to Cond and IfNullOperator to IfNull. Both conditional operations are now available from ConditionalOperators.when and ConditionalOperators.ifNull and accept AggregationExpressions for conditions and values.

Original Pull Request: #421
2016-12-06 10:06:58 +01:00
Christoph Strobl
f64d205522 DATAMONGO-1520 - Add overload for aggregation $match accepting CriteriaDefinition.
We now also accept CriteriaDefinition next to Criteria for Aggregation.match. The existing match(Criteria) method remains to preserve binary compatibility.
2016-12-06 09:05:39 +01:00
Mark Paluch
853b2b2d5c DATAMONGO-1540 - Polishing.
Reduce Map aggregation expression builder entrypoint. Fix JavaDoc.

Original pull request: #420.
2016-12-05 16:46:50 +01:00
Christoph Strobl
c3f9af01e6 DATAMONGO-1540 - Add support for $map (aggregation).
We now support $map operator in aggregation.

Original pull request: #420.
2016-12-05 16:46:41 +01:00
Oliver Gierke
655a1e0351 DATAMONGO-1547 - Register MongoRepositoryFactory in spring.factories.
This is required for the switch in support for multi-store detection.

Related ticket: DATACMNS-952.
2016-12-05 14:28:26 +01:00
Oliver Gierke
26395f1b78 DATAMONGO-1546 - Register GeoJsonConfiguration via spring.factories.
Related tickets: DATACMNS-952.
2016-12-05 14:24:18 +01:00
Oliver Gierke
ecd8dae876 DATAMONGO-1141 - Polishing.
Aligned assertion messages for consistency. Fixed imports in  UpdateMapperUnitTests.

Original pull request: #405.
2016-12-02 18:33:08 +01:00
Mark Paluch
4b59736f82 DATAMONGO-1141 - Polishing.
Add property to field name mapping for Sort orders by moving Sort mapping to UpdateMapper. Fix typo. Add JavaDoc. Reformat code. Remove trailing whitespaces.

Original pull request: #405.
2016-12-02 18:32:50 +01:00
Pavel Vodrážka
31d4434562 DATAMONGO-1141 - Add support for $push $sort in Update.
Sorting update modifier added. Supports sorting arrays by document fields and element values.

Original pull request: #405.
2016-12-02 18:26:09 +01:00
Oliver Gierke
f5a339bfe4 DATAMONGO-1454 - Polishing.
Formatting in test case.

Original pull request: #381.
2016-12-02 16:46:06 +01:00
Mark Paluch
5e60867750 DATAMONGO-1454 - Add support for exists projection in repository query methods.
We now support exists projections for query methods in query methods for derived and string queries.

public PersonRepository extends Repository<Person, String> {

  boolean existsByFirstname(String firstname);

  @ExistsQuery(value = "{ 'lastname' : ?0 }")
  boolean someExistQuery(String lastname);

  @Query(value = "{ 'lastname' : ?0 }", exists = true)
  boolean anotherExistQuery(String lastname);
}

Original pull request: #381.
2016-12-02 16:45:58 +01:00
Mark Paluch
8a5da0e737 DATAMONGO-1536 - Polishing.
Add JavaDoc. Change visibility of AbstractAggregationExpression.getMongoMethod() to protected.

Original pull request: #418.
2016-12-02 15:43:11 +01:00
Christoph Strobl
e01ebcf605 DATAMONGO-1536 - Add aggregation operators for array, arithmetic, date and set operations.
We now support the following aggregation framework operators:

- setEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTrue
- stdDevPop, stdDevSamp
- abs, ceil, exp, floor, ln, log, log10, pow, sqrt, trunc
- arrayElementAt, concatArrays, isArray
- literal
- dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString

Original pull request: #418.
2016-12-02 15:43:05 +01:00
Oliver Gierke
b01a34b2b1 DATAMONGO-1539 - Polishing.
Renamed @Count and @Delete to @CountQuery and @DeleteQuery. Minor polishing in test cases and test repository methods. JavaDoc, formatting.

Original pull request: #416.
2016-12-02 11:59:10 +01:00
Fırat KÜÇÜK
5650e35eb6 DATAMONGO-1539 - Introduce @CountQuery and @DeleteQuery.
Introducing dedicated annotations for manually defined count and delete queries to avoid misconfiguration and generally simplifying the declaration.

Original pull request: 416.
2016-12-02 11:59:10 +01:00
Christoph Strobl
e5a41ad7f9 DATAMONGO-1534 - Fix bulk operations missing to write type info.
We now correctly convert entities into their MongoDB representation including type information via _class property.

Original pull request: #415.
2016-11-28 09:13:57 +01:00
Christoph Strobl
dd7d25cdb3 DATAMONGO-1530 - Polishing.
Add missing transformations for ConstructorReference, OperatorNot, OpNE, OpEQ, OpGT, OpGE, OpLT, OpLE, OperatorPower, OpOr and OpAnd. This allows usage of logical operators &, || and ! as part of the expression, while ConstructorReference allows instantiating eg. arrays via an expression `new int[]{4,5,6}`. This can be useful eg. comparing arrays using $setEquals.

More complex aggregation operators like $filter can be created by defining the variable references as string inside the expression like filter(a, 'num', '$$num' > 10).
Commands like $let requires usage of InlineMap to pass in required arguments like eg. let({low:1, high:'$$low'}, gt('$$low', '$$high')).

Original Pull Request: #410
2016-11-25 17:14:17 +01:00
Sebastien Gerard
0ddd7e3afd DATAMONGO-1530 - Add support for missing MongoDB 3.2 aggregation pipeline operators.
Original Pull Request: #410
2016-11-25 15:46:49 +01:00
Mark Paluch
81c501dea3 DATAMONGO-784 - Polishing.
Add JavaDoc for compareValue.

Original pull request: #414.
2016-11-24 13:49:12 +01:00
Christoph Strobl
b1cd7cfa53 DATAMONGO-784 - Add support for comparison aggregation operators to group & project.
We now directly support comparison aggregation operators ($cmp, $eq, $gt, $gte, $lt, $lte and $ne) on both group and project stages.

Original pull request: #414.
2016-11-24 13:49:09 +01:00
Mark Paluch
2ffac0a74e DATAMONGO-1491 - Polishing.
Remove variable before returning value. Add generics for list creation.

Original pull request: #412.
2016-11-24 12:49:30 +01:00
Christoph Strobl
ded99a74a3 DATAMONGO-1491 - Add support for $filter (aggregation).
We new support $filter in aggregation pipeline.

Aggregation.newAggregation(Sales.class,
	Aggregation.project()
		.and(filter("items").as("item").by(GTE.of(field("item.price"), 100)))
		.as("items"))

Original pull request: #412.
2016-11-24 12:45:56 +01:00
Christoph Strobl
f275aaad7f DATAMONGO-1327 - Polishing.
Just added overloads for stdDevSamp and stdDevPop taking AggregationExpression and updated the doc.
Also replaced String operation based MongoDB operation building by using operators directly.

Original Pull Request: #360
2016-11-24 07:57:17 +01:00
gustavodegeus
5d2faef072 DATAMONGO-1327 - Added support for $stdDevSamp and $stdDevPop to aggregation $group stage.
Original Pull Request: #360
CLA: 171720160409030719 (Gustavo de Geus)
2016-11-24 07:56:33 +01:00
Oliver Gierke
2c3cbb3613 DATAMONGO-1527 - After release cleanups. 2016-11-23 10:59:11 +01:00
Oliver Gierke
2b635fa151 DATAMONGO-1527 - Prepare next development iteration. 2016-11-23 10:59:08 +01:00
488 changed files with 35155 additions and 16496 deletions

View File

@@ -1,9 +1,12 @@
<!--
Thank you for proposing a pull request. This template will guide you through the essential steps necessary for a pull request.
Make sure that:
-->
- [ ] You have read the [Spring Data contribution guidelines](https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc).
- [ ] There is a ticket in the bug tracker for the project in our [JIRA](https://jira.spring.io/browse/DATAMONGO).
- [ ] You use the code formatters provided [here](https://github.com/spring-projects/spring-data-build/tree/master/etc/ide) and have them applied to your changes. Dont submit any formatting related changes.
- [ ] You submit test cases (unit or integration tests) that back your changes.
- [ ] You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only).
- [ ] You provide your full name and an email address registered with your GitHub account. If youre a first-time submitter, make sure you have completed the [Contributors License Agreement form](https://support.springsource.com/spring_committer_signup).

View File

@@ -9,22 +9,20 @@ before_script:
env:
matrix:
- PROFILE=ci
- PROFILE=mongo3
- PROFILE=mongo3-next
- PROFILE=mongo31
- PROFILE=mongo32
- PROFILE=mongo33
- PROFILE=mongo34-next
- PROFILE=mongo35-next
# Current MongoDB version is 2.4.2 as of 2016-04, see https://github.com/travis-ci/travis-ci/issues/3694
# apt-get starts a MongoDB instance so it's not started using before_script
addons:
apt:
sources:
- mongodb-3.2-precise
- mongodb-upstart
- sourceline: 'deb [arch=amd64] http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.4 multiverse'
key_url: 'https://www.mongodb.org/static/pgp/server-3.4.asc'
packages:
- mongodb-org-server
- mongodb-org-shell
- oracle-java8-installer
sudo: false

View File

@@ -143,8 +143,8 @@ public class MyService {
Here are some ways for you to get involved in the community:
* 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.
* Create [JIRA](https://jira.spring.io/browse/DATAMONGO) tickets for bugs and new features and comment and vote on the ones that you are interested in.
* Github is for social coding: if you want to write code, we encourage contributions through pull requests from [forks of this repository](http://help.github.com/forking/). If you want to contribute code this way, please reference a JIRA ticket as well covering the specific issue you are addressing.
* Watch for upcoming articles on Spring by [subscribing](http://spring.io/blog) to spring.io.
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.
Before we accept a non-trivial patch or pull request we will need you to [sign the Contributor License Agreement](https://cla.pivotal.io/sign/spring). Signing the contributors 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. If you forget to do so, you'll be reminded when you submit a pull request. Active contributors might be asked to join the core team, and given the ability to merge pull requests.

View File

@@ -1,291 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="12">
<profile kind="CodeFormatterProfile" name="Spring Data" version="12">
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.compiler.source" value="1.7"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.7"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.7"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
</profile>
</profiles>

86
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.M1</version>
<version>2.0.0.M4</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.0.0.M1</version>
<version>2.0.0.M4</version>
</parent>
<modules>
@@ -28,9 +28,9 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>2.0.0.M1</springdata.commons>
<mongo>3.2.2</mongo>
<mongo.reactivestreams>1.2.0</mongo.reactivestreams>
<springdata.commons>2.0.0.M4</springdata.commons>
<mongo>3.4.2</mongo>
<mongo.reactivestreams>1.5.0</mongo.reactivestreams>
</properties>
<developers>
@@ -114,70 +114,28 @@
</developers>
<profiles>
<profile>
<id>mongo3</id>
<properties>
<mongo>3.0.4</mongo>
</properties>
</profile>
<profile>
<id>mongo3-next</id>
<properties>
<mongo>3.0.5-SNAPSHOT</mongo>
</properties>
<repositories>
<repository>
<id>mongo-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
</profile>
<profile>
<id>mongo31</id>
<properties>
<mongo>3.1.1</mongo>
</properties>
</profile>
<profile>
<id>mongo32</id>
<properties>
<mongo>3.2.2</mongo>
</properties>
</profile>
<profile>
<id>mongo33</id>
<properties>
<mongo>3.3.0</mongo>
</properties>
<repositories>
<repository>
<id>mongo-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
</profile>
<profile>
<id>mongo34-next</id>
<properties>
<mongo>3.4.0-SNAPSHOT</mongo>
<mongo>3.4.3-SNAPSHOT</mongo>
</properties>
<repositories>
<repository>
<id>mongo-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
</profile>
<profile>
<id>mongo35-next</id>
<properties>
<mongo>3.5.0-SNAPSHOT</mongo>
</properties>
<repositories>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.M1</version>
<version>2.0.0.M4</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -48,7 +48,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.0.0.M1</version>
<version>2.0.0.M4</version>
</dependency>
<!-- reactive -->

View File

@@ -20,13 +20,13 @@
<mongo:mapping-converter/>
<!-- Mongo config -->
<bean id="mongo" class="org.springframework.data.mongodb.core.MongoClientFactoryBean">
<bean id="mongoClient" class="org.springframework.data.mongodb.core.MongoClientFactoryBean">
<property name="host" value="localhost"/>
<property name="port" value="27017"/>
</bean>
<bean id="mongoDbFactory" class="org.springframework.data.mongodb.core.SimpleMongoDbFactory">
<constructor-arg name="mongo" ref="mongo"/>
<constructor-arg name="mongoClient" ref="mongoClient"/>
<constructor-arg name="databaseName" value="database"/>
</bean>

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.M1</version>
<version>2.0.0.M4</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -1,5 +1,7 @@
# MongoDB Log4J Appender
:warning: Deprecated. About to be removed for 2.0.0.RC1.
This module sets up a Log4J appender that puts logging events in MongoDB. It is fully configurable
and connects directly to the MongoDB server using the driver. It has no dependency on any Spring package.

View File

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

View File

@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.log4j;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.log4j.AppenderSkeleton;
@@ -31,6 +33,8 @@ import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;
/**
@@ -38,8 +42,11 @@ import com.mongodb.WriteConcern;
*
* @author Jon Brisbin
* @author Oliver Gierke
* @auhtor Christoph Strobl
* @author Christoph Strobl
* @author Ricardo Espirito Santo
* @deprecated since 2.0.0.M4. About to be removed for 2.0.0.RC1.
*/
@Deprecated
public class MongoLog4jAppender extends AppenderSkeleton {
public static final String LEVEL = "level";
@@ -56,6 +63,9 @@ public class MongoLog4jAppender extends AppenderSkeleton {
protected String host = "localhost";
protected int port = 27017;
protected String username;
protected String password;
protected String authenticationDatabase;
protected String database = "logs";
protected String collectionPattern = "%c";
protected PatternLayout collectionLayout = new PatternLayout(collectionPattern);
@@ -65,8 +75,7 @@ public class MongoLog4jAppender extends AppenderSkeleton {
protected Mongo mongo;
protected DB db;
public MongoLog4jAppender() {
}
public MongoLog4jAppender() {}
public MongoLog4jAppender(boolean isActive) {
super(isActive);
@@ -88,6 +97,53 @@ public class MongoLog4jAppender extends AppenderSkeleton {
this.port = port;
}
/**
* @return
* @since 1.10
*/
public String getUsername() {
return username;
}
/**
* @param username may be {@literal null} for unauthenticated access.
* @since 1.10
*/
public void setUsername(String username) {
this.username = username;
}
/**
* @return
* @since 1.10
*/
public String getPassword() {
return password;
}
/**
* @param password may be {@literal null} for unauthenticated access.
* @since 1.10
*/
public void setPassword(String password) {
this.password = password;
}
/**
* @return
*/
public String getAuthenticationDatabase() {
return authenticationDatabase;
}
/**
* @param authenticationDatabase may be {@literal null} to use {@link #getDatabase()} as authentication database.
* @since 1.10
*/
public void setAuthenticationDatabase(String authenticationDatabase) {
this.authenticationDatabase = authenticationDatabase;
}
public String getDatabase() {
return database;
}
@@ -113,14 +169,14 @@ public class MongoLog4jAppender extends AppenderSkeleton {
this.applicationId = applicationId;
}
public void setWarnOrHigherWriteConcern(String wc) {
this.warnOrHigherWriteConcern = WriteConcern.valueOf(wc);
}
public String getWarnOrHigherWriteConcern() {
return warnOrHigherWriteConcern.toString();
}
public void setWarnOrHigherWriteConcern(String wc) {
this.warnOrHigherWriteConcern = WriteConcern.valueOf(wc);
}
public String getInfoOrLowerWriteConcern() {
return infoOrLowerWriteConcern.toString();
}
@@ -130,10 +186,26 @@ public class MongoLog4jAppender extends AppenderSkeleton {
}
protected void connectToMongo() throws UnknownHostException {
this.mongo = new MongoClient(host, port);
this.mongo = createMongoClient();
this.db = mongo.getDB(database);
}
private MongoClient createMongoClient() throws UnknownHostException {
ServerAddress serverAddress = new ServerAddress(host, port);
if (null == password || null == username) {
return new MongoClient(serverAddress);
}
String authenticationDatabaseToUse = authenticationDatabase == null ? this.database : authenticationDatabase;
MongoCredential mongoCredential = MongoCredential.createCredential(username,
authenticationDatabaseToUse, password.toCharArray());
List<MongoCredential> credentials = Collections.singletonList(mongoCredential);
return new MongoClient(serverAddress, credentials);
}
/*
* (non-Javadoc)
* @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent)

View File

@@ -1,5 +1,6 @@
/**
* Infrastructure for to use MongoDB as a logging sink.
*
* @deprecated since 2.0.0.M4. About to be removed for 2.0.0.RC1.
*/
package org.springframework.data.mongodb.log4j;

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2016 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.log4j;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Calendar;
import java.util.Collections;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.MDC;
import org.apache.log4j.PropertyConfigurator;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DB;
import com.mongodb.DBCursor;
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
/**
* Integration tests for {@link MongoLog4jAppender} using authentication.
*
* @author Mark Paluch
*/
public class MongoLog4jAppenderAuthenticationIntegrationTests {
private final static String username = "admin";
private final static String password = "test";
private final static String authenticationDatabase = "logs";
MongoClient mongo;
DB db;
String collection;
ServerAddress serverLocation;
Logger log;
@Before
public void setUp() throws Exception {
serverLocation = new ServerAddress("localhost", 27017);
mongo = new MongoClient(serverLocation);
db = mongo.getDB("logs");
BasicDBList roles = new BasicDBList();
roles.add("dbOwner");
db.command(new BasicDBObjectBuilder().add("createUser", username).add("pwd", password).add("roles", roles).get());
mongo.close();
mongo = new MongoClient(serverLocation, Collections
.singletonList(MongoCredential.createCredential(username, authenticationDatabase, password.toCharArray())));
db = mongo.getDB("logs");
Calendar now = Calendar.getInstance();
collection = String.valueOf(now.get(Calendar.YEAR)) + String.format("%1$02d", now.get(Calendar.MONTH) + 1);
LogManager.resetConfiguration();
PropertyConfigurator.configure(getClass().getResource("/log4j-with-authentication.properties"));
log = Logger.getLogger(MongoLog4jAppenderIntegrationTests.class.getName());
}
@After
public void tearDown() {
if (db != null) {
db.getCollection(collection).remove(new BasicDBObject());
db.command(new BasicDBObject("dropUser", username));
}
LogManager.resetConfiguration();
PropertyConfigurator.configure(getClass().getResource("/log4j.properties"));
}
@Test
public void testLogging() {
log.debug("DEBUG message");
log.info("INFO message");
log.warn("WARN message");
log.error("ERROR message");
DBCursor msgs = db.getCollection(collection).find();
assertThat(msgs.count(), is(4));
}
@Test
public void testProperties() {
MDC.put("property", "one");
log.debug("DEBUG message");
}
}

View File

@@ -20,8 +20,10 @@ import static org.junit.Assert.*;
import java.util.Calendar;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.MDC;
import org.apache.log4j.PropertyConfigurator;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -30,6 +32,7 @@ import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCursor;
import com.mongodb.MongoClient;
import com.mongodb.ServerAddress;
/**
* Integration tests for {@link MongoLog4jAppender}.
@@ -40,21 +43,23 @@ import com.mongodb.MongoClient;
*/
public class MongoLog4jAppenderIntegrationTests {
static final String NAME = MongoLog4jAppenderIntegrationTests.class.getName();
private static final Logger log = Logger.getLogger(NAME);
MongoClient mongo;
DB db;
String collection;
ServerAddress serverLocation;
Logger log;
@Before
public void setUp() throws Exception {
serverLocation = new ServerAddress("localhost", 27017);
mongo = new MongoClient("localhost", 27017);
mongo = new MongoClient(serverLocation);
db = mongo.getDB("logs");
Calendar now = Calendar.getInstance();
collection = String.valueOf(now.get(Calendar.YEAR)) + String.format("%1$02d", now.get(Calendar.MONTH) + 1);
log = Logger.getLogger(MongoLog4jAppenderIntegrationTests.class.getName());
}
@After
@@ -76,7 +81,6 @@ public class MongoLog4jAppenderIntegrationTests {
@Test
public void testProperties() {
MDC.put("property", "one");
log.debug("DEBUG message");
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2017 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,10 +24,7 @@ import org.junit.Test;
*/
public class MongoLog4jAppenderUnitTests {
/**
* @see DATAMONGO-641
*/
@Test
@Test // DATAMONGO-641
public void closesWithoutMongoInstancePresent() {
new MongoLog4jAppender().close();
}

View File

@@ -0,0 +1,16 @@
log4j.rootCategory=INFO, mongo
log4j.appender.mongo=org.springframework.data.mongodb.log4j.MongoLog4jAppender
log4j.appender.mongo.layout=org.apache.log4j.PatternLayout
log4j.appender.mongo.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.mongo.host = localhost
log4j.appender.mongo.port = 27017
log4j.appender.mongo.database = logs
log4j.appender.mongo.username = admin
log4j.appender.mongo.password = test
log4j.appender.mongo.authenticationDatabase = logs
log4j.appender.mongo.collectionPattern = %X{year}%X{month}
log4j.appender.mongo.applicationId = my.application
log4j.appender.mongo.warnOrHigherWriteConcern = FSYNC_SAFE
log4j.category.org.springframework.data.mongodb=DEBUG

View File

@@ -1,13 +1,13 @@
log4j.rootCategory=INFO, stdout
log4j.rootCategory=INFO, mongo
log4j.appender.stdout=org.springframework.data.mongodb.log4j.MongoLog4jAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.stdout.host = localhost
log4j.appender.stdout.port = 27017
log4j.appender.stdout.database = logs
log4j.appender.stdout.collectionPattern = %X{year}%X{month}
log4j.appender.stdout.applicationId = my.application
log4j.appender.stdout.warnOrHigherWriteConcern = FSYNC_SAFE
log4j.appender.mongo=org.springframework.data.mongodb.log4j.MongoLog4jAppender
log4j.appender.mongo.layout=org.apache.log4j.PatternLayout
log4j.appender.mongo.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.mongo.host = localhost
log4j.appender.mongo.port = 27017
log4j.appender.mongo.database = logs
log4j.appender.mongo.collectionPattern = %X{year}%X{month}
log4j.appender.mongo.applicationId = my.application
log4j.appender.mongo.warnOrHigherWriteConcern = FSYNC_SAFE
log4j.category.org.springframework.data.mongodb=DEBUG

View File

@@ -11,14 +11,14 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.M1</version>
<version>2.0.0.M4</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<objenesis>1.3</objenesis>
<equalsverifier>1.5</equalsverifier>
<mongo>3.3.0</mongo>
<kotlin>1.1.2-5</kotlin>
</properties>
<dependencies>
@@ -111,6 +111,13 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>${reactor}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
@@ -125,6 +132,13 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>${rxjava2}</version>
<optional>true</optional>
</dependency>
<!-- CDI -->
<dependency>
<groupId>javax.enterprise</groupId>
@@ -218,11 +232,117 @@
<scope>test</scope>
</dependency>
<!-- Kotlin extension -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.nhaarman</groupId>
<artifactId>mockito-kotlin</artifactId>
<version>1.5.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</exclusion>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</exclusion>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin}</version>
<configuration>
<jvmTarget>${source.level}</jvmTarget>
</configuration>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 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,7 +17,6 @@ package org.springframework.data.mongodb.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
@@ -26,7 +25,6 @@ import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.Document;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
/**
@@ -41,35 +39,21 @@ import com.mongodb.MongoClient;
* @see MongoConfigurationSupport
*/
@Configuration
public abstract class AbstractMongoConfiguration extends MongoConfigurationSupport {
public abstract class
AbstractMongoConfiguration extends MongoConfigurationSupport {
/**
* 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 the {@link MongoClient} instance to connect to. Annotate with {@link Bean} in case you want to expose a
* {@link MongoClient} instance to the {@link org.springframework.context.ApplicationContext}.
*
* @return
* @deprecated since 1.7. {@link MongoClient} should hold authentication data within
* {@link MongoClient#getCredentialsList()}
*/
@Deprecated
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}.
*
* @return
* @throws Exception
*/
public abstract Mongo mongo() throws Exception;
public abstract MongoClient mongoClient();
/**
* Creates a {@link MongoTemplate}.
*
* @return
* @throws Exception
*/
@Bean
public MongoTemplate mongoTemplate() throws Exception {
@@ -77,17 +61,16 @@ public abstract class AbstractMongoConfiguration extends MongoConfigurationSuppo
}
/**
* Creates a {@link SimpleMongoDbFactory} to be used by the {@link MongoTemplate}. Will use the {@link Mongo} instance
* configured in {@link #mongo()}.
* Creates a {@link SimpleMongoDbFactory} to be used by the {@link MongoTemplate}. Will use the {@link MongoClient}
* instance configured in {@link #mongo()}.
*
* @see #mongo()
* @see #mongoClient()
* @see #mongoTemplate()
* @return
* @throws Exception
*/
@Bean
public MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(mongo(), getDatabaseName(), getUserCredentials(), getAuthenticationDatabaseName());
public MongoDbFactory mongoDbFactory() {
return new SimpleMongoDbFactory(mongoClient(), getDatabaseName());
}
/**
@@ -107,19 +90,6 @@ public abstract class AbstractMongoConfiguration extends MongoConfigurationSuppo
return mappingBasePackage == null ? null : mappingBasePackage.getName();
}
/**
* Return {@link UserCredentials} to be used when connecting to the MongoDB instance or {@literal null} if none shall
* be used.
*
* @return
* @deprecated since 1.7. {@link MongoClient} should hold authentication data within
* {@link MongoClient#getCredentialsList()}
*/
@Deprecated
protected UserCredentials getUserCredentials() {
return null;
}
/**
* Creates a {@link MappingMongoConverter} using the configured {@link #mongoDbFactory()} and
* {@link #mongoMappingContext()}. Will get {@link #customConversions()} applied.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2017 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,13 +21,14 @@ package org.springframework.data.mongodb.config;
* @author Jon Brisbin
* @author Oliver Gierke
* @author Martin Baumgartner
* @author Christoph Strobl
*/
public abstract class BeanNames {
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 MONGO_BEAN_NAME = "mongoClient";
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";

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 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.
@@ -53,8 +53,9 @@ 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.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@@ -121,11 +122,15 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
converterBuilder.addPropertyValue("customConversions", conversionsDefinition);
}
if(!registry.containsBeanDefinition("indexOperationsProvider")){
if (!registry.containsBeanDefinition("indexOperationsProvider")) {
BeanDefinitionBuilder indexOperationsProviderBuilder = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.data.mongodb.core.DefaultIndexOperationsProvider");
BeanDefinitionBuilder indexOperationsProviderBuilder = BeanDefinitionBuilder
.genericBeanDefinition("org.springframework.data.mongodb.core.DefaultIndexOperationsProvider");
indexOperationsProviderBuilder.addConstructorArgReference(dbFactoryRef);
parserContext.registerBeanComponent(new BeanComponentDefinition(indexOperationsProviderBuilder.getBeanDefinition(), "indexOperationsProvider"));
indexOperationsProviderBuilder.addConstructorArgValue(BeanDefinitionBuilder
.genericBeanDefinition(QueryMapper.class).addConstructorArgReference(id).getBeanDefinition());
parserContext.registerBeanComponent(
new BeanComponentDefinition(indexOperationsProviderBuilder.getBeanDefinition(), "indexOperationsProvider"));
}
try {
@@ -138,15 +143,15 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
indexHelperBuilder.addConstructorArgReference("indexOperationsProvider");
indexHelperBuilder.addDependsOn(ctxRef);
parserContext.registerBeanComponent(new BeanComponentDefinition(indexHelperBuilder.getBeanDefinition(),
INDEX_HELPER_BEAN_NAME));
parserContext.registerBeanComponent(
new BeanComponentDefinition(indexHelperBuilder.getBeanDefinition(), INDEX_HELPER_BEAN_NAME));
}
BeanDefinition validatingMongoEventListener = potentiallyCreateValidatingMongoEventListener(element, parserContext);
if (validatingMongoEventListener != null) {
parserContext.registerBeanComponent(new BeanComponentDefinition(validatingMongoEventListener,
VALIDATING_EVENT_LISTENER_BEAN_NAME));
parserContext.registerBeanComponent(
new BeanComponentDefinition(validatingMongoEventListener, VALIDATING_EVENT_LISTENER_BEAN_NAME));
}
parserContext.registerBeanComponent(new BeanComponentDefinition(converterBuilder.getBeanDefinition(), id));
@@ -285,7 +290,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
}
}
BeanDefinitionBuilder conversionsBuilder = BeanDefinitionBuilder.rootBeanDefinition(CustomConversions.class);
BeanDefinitionBuilder conversionsBuilder = BeanDefinitionBuilder.rootBeanDefinition(MongoCustomConversions.class);
conversionsBuilder.addConstructorArgValue(converterBeans);
AbstractBeanDefinition conversionsBean = conversionsBuilder.getBeanDefinition();
@@ -333,8 +338,8 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
return beanDef;
}
parserContext.getReaderContext().error(
"Element <converter> must specify 'ref' or contain a bean definition for the converter", element);
parserContext.getReaderContext()
.error("Element <converter> must specify 'ref' or contain a bean definition for the converter", element);
return null;
}
@@ -346,8 +351,8 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
mappingContextStrategyFactoryBuilder.addConstructorArgReference(mappingContextRef);
BeanComponentDefinitionBuilder builder = new BeanComponentDefinitionBuilder(element, context);
context.registerBeanComponent(builder.getComponent(mappingContextStrategyFactoryBuilder,
IS_NEW_STRATEGY_FACTORY_BEAN_NAME));
context.registerBeanComponent(
builder.getComponent(mappingContextStrategyFactoryBuilder, IS_NEW_STRATEGY_FACTORY_BEAN_NAME));
return IS_NEW_STRATEGY_FACTORY_BEAN_NAME;
}
@@ -367,7 +372,9 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
* @param filters
*/
public NegatingFilter(TypeFilter... filters) {
Assert.notNull(filters);
Assert.notNull(filters, "TypeFilters must not be null");
this.delegates = new HashSet<TypeFilter>(Arrays.asList(filters));
}
@@ -375,7 +382,8 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
* (non-Javadoc)
* @see org.springframework.core.type.filter.TypeFilter#match(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory)
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
for (TypeFilter delegate : delegates) {
if (delegate.match(metadataReader, metadataReaderFactory)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.config;
import java.util.Arrays;
@@ -28,20 +27,14 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory;
import org.springframework.data.mapping.context.PersistentEntities;
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.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.support.CachingIsNewStrategyFactory;
@@ -49,9 +42,6 @@ import org.springframework.data.support.IsNewStrategyFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
/**
* Base class for Spring Data MongoDB to be extended for JavaConfiguration usage.
*
@@ -86,7 +76,7 @@ public abstract class MongoConfigurationSupport {
/**
* Creates a {@link MongoMappingContext} equipped with entity classes scanned from the mapping base package.
*
* @see #getMappingBasePackage()
* @see #getMappingBasePackages()
* @return
* @throws ClassNotFoundException
*/
@@ -117,13 +107,13 @@ public abstract class MongoConfigurationSupport {
/**
* Register custom {@link Converter}s in a {@link CustomConversions} object if required. These
* {@link CustomConversions} will be registered with the {@link #mappingMongoConverter()} and
* {@link #mongoMappingContext()}. Returns an empty {@link CustomConversions} instance by default.
* {@link #mongoMappingContext()}. Returns an empty {@link MongoCustomConversions} instance by default.
*
* @return must not be {@literal null}.
*/
@Bean
public CustomConversions customConversions() {
return new CustomConversions(Collections.emptyList());
return new MongoCustomConversions(Collections.emptyList());
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2015 by the original author(s).
* Copyright 2011-2017 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.
@@ -30,7 +30,6 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.config.BeanComponentDefinitionBuilder;
import org.springframework.data.mongodb.core.MongoClientFactoryBean;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
@@ -99,8 +98,6 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
String mongoRef = element.getAttribute("mongo-ref");
String dbname = element.getAttribute("dbname");
BeanDefinition userCredentials = getUserCredentialsBeanDefinition(element, parserContext);
// Defaulting
if (StringUtils.hasText(mongoRef)) {
dbFactoryBuilder.addConstructorArgReference(mongoRef);
@@ -109,8 +106,6 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
}
dbFactoryBuilder.addConstructorArgValue(StringUtils.hasText(dbname) ? dbname : "db");
dbFactoryBuilder.addConstructorArgValue(userCredentials);
dbFactoryBuilder.addConstructorArgValue(element.getAttribute("authentication-dbname"));
BeanDefinitionBuilder writeConcernPropertyEditorBuilder = getWriteConcernPropertyEditorBuilder();
@@ -138,28 +133,6 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
return getSourceBeanDefinition(mongoBuilder, parserContext, element);
}
/**
* Returns a {@link BeanDefinition} for a {@link UserCredentials} object.
*
* @param element
* @return the {@link BeanDefinition} or {@literal null} if neither username nor password given.
*/
private BeanDefinition getUserCredentialsBeanDefinition(Element element, ParserContext context) {
String username = element.getAttribute("username");
String password = element.getAttribute("password");
if (!StringUtils.hasText(username) && !StringUtils.hasText(password)) {
return null;
}
BeanDefinitionBuilder userCredentialsBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserCredentials.class);
userCredentialsBuilder.addConstructorArgValue(StringUtils.hasText(username) ? username : null);
userCredentialsBuilder.addConstructorArgValue(StringUtils.hasText(password) ? password : null);
return getSourceBeanDefinition(userCredentialsBuilder, context, element);
}
/**
* Creates a {@link BeanDefinition} for a {@link MongoURI} or {@link MongoClientURI} depending on configured
* attributes. <br />
@@ -193,7 +166,7 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
parserContext.extractSource(element));
}
Class<?> type = hasClientUri ? MongoClientURI.class : MongoURI.class;
Class<?> type = MongoClientURI.class;
String uri = hasClientUri ? element.getAttribute("client-uri") : element.getAttribute("uri");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(type);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011 the original author or authors.
* Copyright 2011-2017 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,12 +26,19 @@ import org.springframework.data.mongodb.monitor.*;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* @author Mark Pollack
* @author Thomas Risberg
* @author John Brisbin
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class MongoJmxParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
String name = element.getAttribute("mongo-ref");
if (!StringUtils.hasText(name)) {
name = "mongo";
name = BeanNames.MONGO_BEAN_NAME;
}
registerJmxComponents(name, element, parserContext);
return null;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2015 the original author or authors.
* Copyright 2011-2017 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,7 +33,6 @@ public class MongoNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("mapping-converter", new MappingMongoConverterParser());
registerBeanDefinitionParser("mongo", new MongoParser());
registerBeanDefinitionParser("mongo-client", new MongoClientParser());
registerBeanDefinitionParser("db-factory", new MongoDbFactoryParser());
registerBeanDefinitionParser("jmx", new MongoJmxParser());

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2011-2015 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 org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.config.BeanComponentDefinitionBuilder;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.mongodb.core.MongoFactoryBean;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for &lt;mongo;gt; definitions.
*
* @author Mark Pollack
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class MongoParser implements BeanDefinitionParser {
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.xml.BeanDefinitionParser#parse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext)
*/
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
String id = element.getAttribute("id");
BeanComponentDefinitionBuilder helper = new BeanComponentDefinitionBuilder(element, parserContext);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MongoFactoryBean.class);
ParsingUtils.setPropertyValue(builder, element, "port", "port");
ParsingUtils.setPropertyValue(builder, element, "host", "host");
ParsingUtils.setPropertyValue(builder, element, "write-concern", "writeConcern");
MongoParsingUtils.parseMongoOptions(element, builder);
MongoParsingUtils.parseReplicaSet(element, builder);
String defaultedId = StringUtils.hasText(id) ? id : BeanNames.MONGO_BEAN_NAME;
parserContext.pushContainingComponent(new CompositeComponentDefinition("Mongo", source));
BeanComponentDefinition mongoComponent = helper.getComponent(builder, defaultedId);
parserContext.registerBeanComponent(mongoComponent);
BeanComponentDefinition serverAddressPropertyEditor = helper.getComponent(MongoParsingUtils
.getServerAddressPropertyEditorBuilder());
parserContext.registerBeanComponent(serverAddressPropertyEditor);
BeanComponentDefinition writeConcernPropertyEditor = helper.getComponent(MongoParsingUtils
.getWriteConcernPropertyEditorBuilder());
parserContext.registerBeanComponent(writeConcernPropertyEditor);
parserContext.popAndRegisterContainingComponent();
return mongoComponent.getBeanDefinition();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2015 the original author or authors.
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.data.mongodb.core.MongoClientOptionsFactoryBean;
import org.springframework.data.mongodb.core.MongoOptionsFactoryBean;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
@@ -54,42 +53,6 @@ abstract class MongoParsingUtils {
setPropertyValue(mongoBuilder, element, "replica-set", "replicaSetSeeds");
}
/**
* Parses the {@code mongo:options} sub-element. Populates the given attribute factory with the proper attributes.
*
* @return true if parsing actually occured, {@literal false} otherwise
*/
static boolean parseMongoOptions(Element element, BeanDefinitionBuilder mongoBuilder) {
Element optionsElement = DomUtils.getChildElementByTagName(element, "options");
if (optionsElement == null) {
return false;
}
BeanDefinitionBuilder optionsDefBuilder = BeanDefinitionBuilder
.genericBeanDefinition(MongoOptionsFactoryBean.class);
setPropertyValue(optionsDefBuilder, optionsElement, "connections-per-host", "connectionsPerHost");
setPropertyValue(optionsDefBuilder, optionsElement, "threads-allowed-to-block-for-connection-multiplier",
"threadsAllowedToBlockForConnectionMultiplier");
setPropertyValue(optionsDefBuilder, optionsElement, "max-wait-time", "maxWaitTime");
setPropertyValue(optionsDefBuilder, optionsElement, "connect-timeout", "connectTimeout");
setPropertyValue(optionsDefBuilder, optionsElement, "socket-timeout", "socketTimeout");
setPropertyValue(optionsDefBuilder, optionsElement, "socket-keep-alive", "socketKeepAlive");
setPropertyValue(optionsDefBuilder, optionsElement, "auto-connect-retry", "autoConnectRetry");
setPropertyValue(optionsDefBuilder, optionsElement, "max-auto-connect-retry-time", "maxAutoConnectRetryTime");
setPropertyValue(optionsDefBuilder, optionsElement, "write-number", "writeNumber");
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;
}
/**
* Parses the {@code mongo:client-options} sub-element. Populates the given attribute factory with the proper
* attributes.
@@ -129,6 +92,7 @@ abstract class MongoParsingUtils {
setPropertyValue(clientOptionsDefBuilder, optionsElement, "heartbeat-socket-timeout", "heartbeatSocketTimeout");
setPropertyValue(clientOptionsDefBuilder, optionsElement, "ssl", "ssl");
setPropertyReference(clientOptionsDefBuilder, optionsElement, "ssl-socket-factory-ref", "sslSocketFactory");
setPropertyValue(clientOptionsDefBuilder, optionsElement, "server-selection-timeout", "serverSelectionTimeout");
mongoClientBuilder.addPropertyValue("mongoClientOptions", clientOptionsDefBuilder.getBeanDefinition());

View File

@@ -0,0 +1,835 @@
/*
* Copyright 2017 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 lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Locale;
import java.util.Optional;
import org.bson.Document;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.client.model.Collation.Builder;
import com.mongodb.client.model.CollationAlternate;
import com.mongodb.client.model.CollationCaseFirst;
import com.mongodb.client.model.CollationMaxVariable;
import com.mongodb.client.model.CollationStrength;
/**
* Central abstraction for MongoDB collation support. <br />
* Allows fluent creation of a collation {@link Document} that can be used for creating collections & indexes as well as
* querying data.
* <p />
* <strong>NOTE:</strong> Please keep in mind that queries will only make use of an index with collation settings if the
* query itself specifies the same collation.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.0
* @see <a href="https://docs.mongodb.com/manual/reference/collation/">MongoDB Reference - Collation</a>
*/
public class Collation {
private static final Collation SIMPLE = of("simple");
private final CollationLocale locale;
private Optional<ComparisonLevel> strength = Optional.empty();
private Optional<Boolean> numericOrdering = Optional.empty();
private Optional<Alternate> alternate = Optional.empty();
private Optional<Boolean> backwards = Optional.empty();
private Optional<Boolean> normalization = Optional.empty();
private Optional<String> version = Optional.empty();
private Collation(CollationLocale locale) {
Assert.notNull(locale, "ICULocale must not be null!");
this.locale = locale;
}
/**
* Create a {@link Collation} using {@literal simple} binary comparison.
*
* @return a {@link Collation} for {@literal simple} binary comparison.
*/
public static Collation simple() {
return SIMPLE;
}
/**
* Create new {@link Collation} with locale set to {{@link java.util.Locale#getLanguage()}} and
* {@link java.util.Locale#getVariant()}.
*
* @param locale must not be {@literal null}.
* @return
*/
public static Collation of(Locale locale) {
Assert.notNull(locale, "Locale must not be null!");
String format;
if (StringUtils.hasText(locale.getCountry())) {
format = String.format("%s_%s", locale.getLanguage(), locale.getCountry());
} else {
format = locale.getLanguage();
}
return of(CollationLocale.of(format).variant(locale.getVariant()));
}
/**
* Create new {@link Collation} with locale set to the given ICU language.
*
* @param language must not be {@literal null}.
* @return
*/
public static Collation of(String language) {
return of(CollationLocale.of(language));
}
/**
* Create new {@link Collation} with locale set to the given {@link CollationLocale}.
*
* @param locale must not be {@literal null}.
* @return
*/
public static Collation of(CollationLocale locale) {
return new Collation(locale);
}
/**
* Create new {@link Collation} from values in {@link Document}.
*
* @param source must not be {@literal null}.
* @return
* @see <a href="https://docs.mongodb.com/manual/reference/collation/#collation-document">MongoDB Reference -
* Collation Document</a>
*/
public static Collation from(Document source) {
Assert.notNull(source, "Source must not be null!");
Collation collation = Collation.of(source.getString("locale"));
if (source.containsKey("strength")) {
collation = collation.strength(source.getInteger("strength"));
}
if (source.containsKey("caseLevel")) {
collation = collation.caseLevel(source.getBoolean("caseLevel"));
}
if (source.containsKey("caseFirst")) {
collation = collation.caseFirst(source.getString("caseFirst"));
}
if (source.containsKey("numericOrdering")) {
collation = collation.numericOrdering(source.getBoolean("numericOrdering"));
}
if (source.containsKey("alternate")) {
collation = collation.alternate(source.getString("alternate"));
}
if (source.containsKey("maxVariable")) {
collation = collation.maxVariable(source.getString("maxVariable"));
}
if (source.containsKey("backwards")) {
collation = collation.backwards(source.getBoolean("backwards"));
}
if (source.containsKey("normalization")) {
collation = collation.normalization(source.getBoolean("normalization"));
}
if (source.containsKey("version")) {
collation.version = Optional.of(source.get("version").toString());
}
return collation;
}
/**
* Set the level of comparison to perform.
*
* @param strength
* @return new {@link Collation}.
*/
public Collation strength(int strength) {
ComparisonLevel current = this.strength.orElseGet(() -> new ICUComparisonLevel(strength));
return strength(new ICUComparisonLevel(strength, current.getCaseFirst(), current.getCaseLevel()));
}
/**
* Set the level of comparison to perform.
*
* @param comparisonLevel must not be {@literal null}.
* @return new {@link Collation}
*/
public Collation strength(ComparisonLevel comparisonLevel) {
Collation newInstance = copy();
newInstance.strength = Optional.of(comparisonLevel);
return newInstance;
}
/**
* Set whether to include {@code caseLevel} comparison. <br />
*
* @param caseLevel
* @return new {@link Collation}.
*/
public Collation caseLevel(boolean caseLevel) {
ComparisonLevel strengthValue = strength.orElseGet(ComparisonLevel::primary);
return strength(
new ICUComparisonLevel(strengthValue.getLevel(), strengthValue.getCaseFirst(), Optional.of(caseLevel)));
}
/**
* Set the flag that determines sort order of case differences during tertiary level comparisons.
*
* @param caseFirst must not be {@literal null}.
* @return
*/
public Collation caseFirst(String caseFirst) {
return caseFirst(new CaseFirst(caseFirst));
}
/**
* Set the flag that determines sort order of case differences during tertiary level comparisons.
*
* @param caseFirst must not be {@literal null}.
* @return
*/
public Collation caseFirst(CaseFirst sort) {
ComparisonLevel strengthValue = strength.orElseGet(ComparisonLevel::tertiary);
return strength(new ICUComparisonLevel(strengthValue.getLevel(), Optional.of(sort), strengthValue.getCaseLevel()));
}
/**
* Treat numeric strings as numbers for comparison.
*
* @return new {@link Collation}.
*/
public Collation numericOrderingEnabled() {
return numericOrdering(true);
}
/**
* Treat numeric strings as string for comparison.
*
* @return new {@link Collation}.
*/
public Collation numericOrderingDisabled() {
return numericOrdering(false);
}
/**
* Set the flag that determines whether to compare numeric strings as numbers or as strings.
*
* @return new {@link Collation}.
*/
public Collation numericOrdering(boolean flag) {
Collation newInstance = copy();
newInstance.numericOrdering = Optional.of(flag);
return newInstance;
}
/**
* Set the Field that determines whether collation should consider whitespace and punctuation as base characters for
* purposes of comparison.
*
* @param alternate must not be {@literal null}.
* @return new {@link Collation}.
*/
public Collation alternate(String alternate) {
Alternate instance = this.alternate.orElseGet(() -> new Alternate(alternate, Optional.empty()));
return alternate(new Alternate(alternate, instance.maxVariable));
}
/**
* Set the Field that determines whether collation should consider whitespace and punctuation as base characters for
* purposes of comparison.
*
* @param alternate must not be {@literal null}.
* @return new {@link Collation}.
*/
public Collation alternate(Alternate alternate) {
Collation newInstance = copy();
newInstance.alternate = Optional.ofNullable(alternate);
return newInstance;
}
/**
* Sort string with diacritics sort from back of the string.
*
* @return new {@link Collation}.
*/
public Collation backwardDiacriticSort() {
return backwards(true);
}
/**
* Do not sort string with diacritics sort from back of the string.
*
* @return new {@link Collation}.
*/
public Collation forwardDiacriticSort() {
return backwards(false);
}
/**
* Set the flag that determines whether strings with diacritics sort from back of the string.
*
* @param backwards must not be {@literal null}.
* @return new {@link Collation}.
*/
public Collation backwards(Boolean backwards) {
Collation newInstance = copy();
newInstance.backwards = Optional.ofNullable(backwards);
return newInstance;
}
/**
* Enable text normalization.
*
* @return new {@link Collation}.
*/
public Collation normalizationEnabled() {
return normalization(true);
}
/**
* Disable text normalization.
*
* @return new {@link Collation}.
*/
public Collation normalizationDisabled() {
return normalization(false);
}
/**
* Set the flag that determines whether to check if text require normalization and to perform normalization.
*
* @param normalization must not be {@literal null}.
* @return new {@link Collation}.
*/
public Collation normalization(Boolean normalization) {
Collation newInstance = copy();
newInstance.normalization = Optional.ofNullable(normalization);
return newInstance;
}
/**
* Set the field that determines up to which characters are considered ignorable when alternate is {@code shifted}.
*
* @param maxVariable must not be {@literal null}.
* @return new {@link Collation}.
*/
public Collation maxVariable(String maxVariable) {
Alternate alternateValue = alternate.orElseGet(Alternate::shifted);
return alternate(new AlternateWithMaxVariable(alternateValue.alternate, maxVariable));
}
/**
* Get the {@link Document} representation of the {@link Collation}.
*
* @return
*/
public Document toDocument() {
return map(toMongoDocumentConverter());
}
/**
* Get the {@link com.mongodb.client.model.Collation} representation of the {@link Collation}.
*
* @return
*/
public com.mongodb.client.model.Collation toMongoCollation() {
return map(toMongoCollationConverter());
}
/**
* Transform {@code this} {@link Collation} by applying a {@link Converter}.
*
* @param mapper
* @param <R>
* @return
*/
public <R> R map(Converter<? super Collation, ? extends R> mapper) {
return mapper.convert(this);
}
@Override
public String toString() {
return toDocument().toJson();
}
private Collation copy() {
Collation collation = new Collation(locale);
collation.strength = this.strength;
collation.normalization = this.normalization;
collation.numericOrdering = this.numericOrdering;
collation.alternate = this.alternate;
collation.backwards = this.backwards;
return collation;
}
/**
* Abstraction for the ICU Comparison Levels.
*
* @since 2.0
*/
public interface ComparisonLevel {
/**
* Primary level of comparison. Collation performs comparisons of the base characters only, ignoring other
* differences such as diacritics and case. <br />
* The {@code caseLevel} can be set via {@link PrimaryICUComparisonLevel#includeCase()} and
* {@link PrimaryICUComparisonLevel#excludeCase()}.
*
* @return new {@link SecondaryICUComparisonLevel}.
*/
static PrimaryICUComparisonLevel primary() {
return PrimaryICUComparisonLevel.DEFAULT;
}
/**
* Secondary level of comparison. Collation performs comparisons up to secondary differences, such as
* diacritics.<br />
* The {@code caseLevel} can be set via {@link SecondaryICUComparisonLevel#includeCase()} and
* {@link SecondaryICUComparisonLevel#excludeCase()}.
*
* @return new {@link SecondaryICUComparisonLevel}.
*/
static SecondaryICUComparisonLevel secondary() {
return SecondaryICUComparisonLevel.DEFAULT;
}
/**
* Tertiary level of comparison. Collation performs comparisons up to tertiary differences, such as case and letter
* variants. <br />
* The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
*
* @return new {@link ICUComparisonLevel}.
*/
static TertiaryICUComparisonLevel tertiary() {
return TertiaryICUComparisonLevel.DEFAULT;
}
/**
* Quaternary Level. Limited for specific use case to consider punctuation. <br />
* The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
*
* @return new {@link ComparisonLevel}.
*/
static ComparisonLevel quaternary() {
return ComparisonLevels.QUATERNARY;
}
/**
* Identical Level. Limited for specific use case of tie breaker. <br />
* The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
*
* @return new {@link ComparisonLevel}.
*/
static ComparisonLevel identical() {
return ComparisonLevels.IDENTICAL;
}
/**
* @return collation strength, {@literal 1} for primary, {@literal 2} for secondary and so on.
*/
int getLevel();
default Optional<CaseFirst> getCaseFirst() {
return Optional.empty();
}
default Optional<Boolean> getCaseLevel() {
return Optional.empty();
}
}
/**
* Abstraction for the ICU Comparison Levels.
*
* @since 2.0
*/
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@Getter
static class ICUComparisonLevel implements ComparisonLevel {
private final int level;
private final Optional<CaseFirst> caseFirst;
private final Optional<Boolean> caseLevel;
ICUComparisonLevel(int level) {
this(level, Optional.empty(), Optional.empty());
}
}
/**
* Simple comparison levels.
*/
enum ComparisonLevels implements ComparisonLevel {
QUATERNARY(4), IDENTICAL(5);
private final int level;
ComparisonLevels(int level) {
this.level = level;
}
@Override
public int getLevel() {
return level;
}
}
/**
* Primary-strength {@link ICUComparisonLevel}.
*/
public static class PrimaryICUComparisonLevel extends ICUComparisonLevel {
static final PrimaryICUComparisonLevel DEFAULT = new PrimaryICUComparisonLevel();
static final PrimaryICUComparisonLevel WITH_CASE_LEVEL = new PrimaryICUComparisonLevel(true);
static final PrimaryICUComparisonLevel WITHOUT_CASE_LEVEL = new PrimaryICUComparisonLevel(false);
private PrimaryICUComparisonLevel() {
super(1);
}
private PrimaryICUComparisonLevel(boolean caseLevel) {
super(1, Optional.empty(), Optional.of(caseLevel));
}
/**
* Include case comparison.
*
* @return new {@link ICUComparisonLevel}
*/
public ComparisonLevel includeCase() {
return WITH_CASE_LEVEL;
}
/**
* Exclude case comparison.
*
* @return new {@link ICUComparisonLevel}
*/
public ComparisonLevel excludeCase() {
return WITHOUT_CASE_LEVEL;
}
}
/**
* Secondary-strength {@link ICUComparisonLevel}.
*/
public static class SecondaryICUComparisonLevel extends ICUComparisonLevel {
static final SecondaryICUComparisonLevel DEFAULT = new SecondaryICUComparisonLevel();
static final SecondaryICUComparisonLevel WITH_CASE_LEVEL = new SecondaryICUComparisonLevel(true);
static final SecondaryICUComparisonLevel WITHOUT_CASE_LEVEL = new SecondaryICUComparisonLevel(false);
private SecondaryICUComparisonLevel() {
super(2);
}
private SecondaryICUComparisonLevel(boolean caseLevel) {
super(2, Optional.empty(), Optional.of(caseLevel));
}
/**
* Include case comparison.
*
* @return new {@link SecondaryICUComparisonLevel}
*/
public ComparisonLevel includeCase() {
return WITH_CASE_LEVEL;
}
/**
* Exclude case comparison.
*
* @return new {@link SecondaryICUComparisonLevel}
*/
public ComparisonLevel excludeCase() {
return WITHOUT_CASE_LEVEL;
}
}
/**
* Tertiary-strength {@link ICUComparisonLevel}.
*/
public static class TertiaryICUComparisonLevel extends ICUComparisonLevel {
static final TertiaryICUComparisonLevel DEFAULT = new TertiaryICUComparisonLevel();
private TertiaryICUComparisonLevel() {
super(3);
}
private TertiaryICUComparisonLevel(CaseFirst caseFirst) {
super(3, Optional.of(caseFirst), Optional.empty());
}
/**
* Set the flag that determines sort order of case differences.
*
* @param caseFirst must not be {@literal null}.
* @return new {@link ICUComparisonLevel}
*/
public ComparisonLevel caseFirst(CaseFirst caseFirst) {
Assert.notNull(caseFirst, "CaseFirst must not be null!");
return new TertiaryICUComparisonLevel(caseFirst);
}
}
/**
* @since 2.0
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class CaseFirst {
private static final CaseFirst UPPER = new CaseFirst("upper");
private static final CaseFirst LOWER = new CaseFirst("lower");
private static final CaseFirst OFF = new CaseFirst("off");
private final String state;
/**
* Sort uppercase before lowercase.
*
* @return new {@link CaseFirst}.
*/
public static CaseFirst upper() {
return UPPER;
}
/**
* Sort lowercase before uppercase.
*
* @return new {@link CaseFirst}.
*/
public static CaseFirst lower() {
return LOWER;
}
/**
* Use the default.
*
* @return new {@link CaseFirst}.
*/
public static CaseFirst off() {
return OFF;
}
}
/**
* @since 2.0
*/
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public static class Alternate {
private static final Alternate NON_IGNORABLE = new Alternate("non-ignorable", Optional.empty());
final String alternate;
final Optional<String> maxVariable;
/**
* Consider Whitespace and punctuation as base characters.
*
* @return new {@link Alternate}.
*/
public static Alternate nonIgnorable() {
return NON_IGNORABLE;
}
/**
* Whitespace and punctuation are <strong>not</strong> considered base characters and are only distinguished at
* strength. <br />
* <strong>NOTE:</strong> Only works for {@link ICUComparisonLevel} above {@link ComparisonLevel#tertiary()}.
*
* @return new {@link AlternateWithMaxVariable}.
*/
public static AlternateWithMaxVariable shifted() {
return AlternateWithMaxVariable.DEFAULT;
}
}
/**
* @since 2.0
*/
public static class AlternateWithMaxVariable extends Alternate {
static final AlternateWithMaxVariable DEFAULT = new AlternateWithMaxVariable("shifted");
static final Alternate SHIFTED_PUNCT = new AlternateWithMaxVariable("shifted", "punct");
static final Alternate SHIFTED_SPACE = new AlternateWithMaxVariable("shifted", "space");
private AlternateWithMaxVariable(String alternate) {
super(alternate, Optional.empty());
}
private AlternateWithMaxVariable(String alternate, String maxVariable) {
super(alternate, Optional.of(maxVariable));
}
/**
* Consider both whitespaces and punctuation as ignorable.
*
* @return new {@link AlternateWithMaxVariable}.
*/
public Alternate punct() {
return SHIFTED_PUNCT;
}
/**
* Only consider whitespaces as ignorable.
*
* @return new {@link AlternateWithMaxVariable}.
*/
public Alternate space() {
return SHIFTED_SPACE;
}
}
/**
* ICU locale abstraction for usage with MongoDB {@link Collation}.
*
* @since 2.0
* @see <a href="http://site.icu-project.org">ICU - International Components for Unicode</a>
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class CollationLocale {
private final String language;
private final Optional<String> variant;
/**
* Create new {@link CollationLocale} for given language.
*
* @param language must not be {@literal null}.
* @return
*/
public static CollationLocale of(String language) {
Assert.notNull(language, "Code must not be null!");
return new CollationLocale(language, Optional.empty());
}
/**
* Define language variant.
*
* @param variant must not be {@literal null}.
* @return new {@link CollationLocale}.
*/
public CollationLocale variant(String variant) {
Assert.notNull(variant, "Variant must not be null!");
return new CollationLocale(language, Optional.of(variant));
}
/**
* Get the string representation.
*
* @return
*/
public String asString() {
StringBuilder sb = new StringBuilder(language);
variant.filter(it -> !it.isEmpty()).ifPresent(val -> {
// Mongo requires variant rendered as ICU keyword (@key=value;key=value…)
sb.append("@collation=").append(val);
});
return sb.toString();
}
}
private static Converter<Collation, Document> toMongoDocumentConverter() {
return source -> {
Document document = new Document();
document.append("locale", source.locale.asString());
source.strength.ifPresent(strength -> {
document.append("strength", strength.getLevel());
strength.getCaseLevel().ifPresent(it -> document.append("caseLevel", it));
strength.getCaseFirst().ifPresent(it -> document.append("caseFirst", it.state));
});
source.numericOrdering.ifPresent(val -> document.append("numericOrdering", val));
source.alternate.ifPresent(it -> {
document.append("alternate", it.alternate);
it.maxVariable.ifPresent(maxVariable -> document.append("maxVariable", maxVariable));
});
source.backwards.ifPresent(it -> document.append("backwards", it));
source.normalization.ifPresent(it -> document.append("normalization", it));
source.version.ifPresent(it -> document.append("version", it));
return document;
};
}
private static Converter<Collation, com.mongodb.client.model.Collation> toMongoCollationConverter() {
return source -> {
Builder builder = com.mongodb.client.model.Collation.builder();
builder.locale(source.locale.asString());
source.strength.ifPresent(strength -> {
builder.collationStrength(CollationStrength.fromInt(strength.getLevel()));
strength.getCaseLevel().ifPresent(builder::caseLevel);
strength.getCaseFirst().ifPresent(it -> builder.collationCaseFirst(CollationCaseFirst.fromString(it.state)));
});
source.numericOrdering.ifPresent(builder::numericOrdering);
source.alternate.ifPresent(it -> {
builder.collationAlternate(CollationAlternate.fromString(it.alternate));
it.maxVariable
.ifPresent(maxVariable -> builder.collationMaxVariable(CollationMaxVariable.fromString(maxVariable)));
});
source.backwards.ifPresent(builder::backwards);
source.normalization.ifPresent(builder::normalization);
return builder.build();
};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2011 the original author or authors.
* Copyright 2010-2017 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,56 +15,149 @@
*/
package org.springframework.data.mongodb.core;
import java.util.Optional;
import org.springframework.util.Assert;
/**
* Provides a simple wrapper to encapsulate the variety of settings you can use when creating a collection.
*
* @author Thomas Risberg
* @author Christoph Strobl
* @author Mark Paluch
*/
public class CollectionOptions {
private Integer maxDocuments;
private Integer size;
private Long maxDocuments;
private Long size;
private Boolean capped;
private Collation collation;
/**
* Constructs a new <code>CollectionOptions</code> instance.
*
* @param size the collection size in bytes, this data space is preallocated
* @param size the collection size in bytes, this data space is preallocated.
* @param maxDocuments the maximum number of documents in the collection.
* @param capped true to created a "capped" collection (fixed size with auto-FIFO behavior based on insertion order),
* false otherwise.
* @deprecated since 2.0 please use {@link CollectionOptions#empty()} as entry point.
*/
public CollectionOptions(Integer size, Integer maxDocuments, Boolean capped) {
super();
@Deprecated
public CollectionOptions(Long size, Long maxDocuments, Boolean capped) {
this(size, maxDocuments, capped, null);
}
private CollectionOptions(Long size, Long maxDocuments, Boolean capped, Collation collation) {
this.maxDocuments = maxDocuments;
this.size = size;
this.capped = capped;
this.collation = collation;
}
public Integer getMaxDocuments() {
return maxDocuments;
/**
* Create new {@link CollectionOptions} by just providing the {@link Collation} to use.
*
* @param collation must not be {@literal null}.
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public static CollectionOptions just(Collation collation) {
Assert.notNull(collation, "Collation must not be null!");
return new CollectionOptions(null, null, null, collation);
}
public void setMaxDocuments(Integer maxDocuments) {
this.maxDocuments = maxDocuments;
/**
* Create new empty {@link CollectionOptions}.
*
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public static CollectionOptions empty() {
return new CollectionOptions(null, null, null, null);
}
public Integer getSize() {
return size;
/**
* Create new {@link CollectionOptions} with already given settings and capped set to {@literal true}. <br />
* <strong>NOTE</strong> Using capped collections requires defining {@link #size(int)}.
*
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public CollectionOptions capped() {
return new CollectionOptions(size, maxDocuments, true, collation);
}
public void setSize(Integer size) {
this.size = size;
/**
* Create new {@link CollectionOptions} with already given settings and {@code maxDocuments} set to given value.
*
* @param maxDocuments can be {@literal null}.
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public CollectionOptions maxDocuments(long maxDocuments) {
return new CollectionOptions(size, maxDocuments, capped, collation);
}
public Boolean getCapped() {
return capped;
/**
* Create new {@link CollectionOptions} with already given settings and {@code size} set to given value.
*
* @param size can be {@literal null}.
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public CollectionOptions size(long size) {
return new CollectionOptions(size, maxDocuments, capped, collation);
}
public void setCapped(Boolean capped) {
this.capped = capped;
/**
* Create new {@link CollectionOptions} with already given settings and {@code collation} set to given value.
*
* @param collation can be {@literal null}.
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public CollectionOptions collation(Collation collation) {
return new CollectionOptions(size, maxDocuments, capped, collation);
}
/**
* Get the max number of documents the collection should be limited to.
*
* @return {@link Optional#empty()} if not set.
*/
public Optional<Long> getMaxDocuments() {
return Optional.ofNullable(maxDocuments);
}
/**
* Get the {@literal size} in bytes the collection should be limited to.
*
* @return {@link Optional#empty()} if not set.
*/
public Optional<Long> getSize() {
return Optional.ofNullable(size);
}
/**
* Get if the collection should be capped.
*
* @return {@link Optional#empty()} if not set.
* @since 2.0
*/
public Optional<Boolean> getCapped() {
return Optional.ofNullable(capped);
}
/**
* Get the {@link Collation} settings.
*
* @return {@link Optional#empty()} if not set.
* @since 2.0
*/
public Optional<Collation> getCollation() {
return Optional.ofNullable(collation);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2017 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,6 +19,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.mongodb.client.model.DeleteOptions;
import org.bson.Document;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
@@ -58,7 +59,7 @@ class DefaultBulkOperations implements BulkOperations {
private BulkWriteOptions bulkOptions;
List<WriteModel<Document>> models = new ArrayList<WriteModel<Document>>();
List<WriteModel<Document>> models = new ArrayList<>();
/**
* Creates a new {@link DefaultBulkOperations} for the given {@link MongoOperations}, {@link BulkMode}, collection
@@ -123,7 +124,16 @@ class DefaultBulkOperations implements BulkOperations {
Assert.notNull(document, "Document must not be null!");
models.add(new InsertOneModel<Document>((Document) mongoOperations.getConverter().convertToMongoType(document)));
if (document instanceof Document) {
models.add(new InsertOneModel<>((Document) document));
return this;
}
Document sink = new Document();
mongoOperations.getConverter().write(document, sink);
models.add(new InsertOneModel<>(sink));
return this;
}
@@ -235,7 +245,10 @@ class DefaultBulkOperations implements BulkOperations {
Assert.notNull(query, "Query must not be null!");
models.add(new DeleteManyModel<Document>(query.getQueryObject()));
DeleteOptions deleteOptions = new DeleteOptions();
query.getCollation().map(Collation::toMongoCollation).ifPresent(deleteOptions::collation);
models.add(new DeleteManyModel(query.getQueryObject(), deleteOptions));
return this;
}
@@ -297,11 +310,12 @@ class DefaultBulkOperations implements BulkOperations {
UpdateOptions options = new UpdateOptions();
options.upsert(upsert);
query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
if (multi) {
models.add(new UpdateManyModel<Document>(query.getQueryObject(), update.getUpdateObject(), options));
models.add(new UpdateManyModel<>(query.getQueryObject(), update.getUpdateObject(), options));
} else {
models.add(new UpdateOneModel<Document>(query.getQueryObject(), update.getUpdateObject(), options));
models.add(new UpdateOneModel<>(query.getQueryObject(), update.getUpdateObject(), options));
}
return this;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,13 +18,16 @@ package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.core.MongoTemplate.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.bson.Document;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.util.Assert;
import com.mongodb.MongoException;
@@ -43,22 +46,45 @@ import com.mongodb.client.model.IndexOptions;
*/
public class DefaultIndexOperations implements IndexOperations {
private static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression";
private final MongoDbFactory mongoDbFactory;
private final String collectionName;
private final QueryMapper mapper;
private final Class<?> type;
/**
* Creates a new {@link DefaultIndexOperations}.
*
* @param mongoDbFactory must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @param queryMapper must not be {@literal null}.
*/
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName) {
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName, QueryMapper queryMapper) {
this(mongoDbFactory, collectionName, queryMapper, null);
}
/**
* Creates a new {@link DefaultIndexOperations}.
*
* @param mongoDbFactory must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @param queryMapper must not be {@literal null}.
* @param type Type used for mapping potential partial index filter expression. Can be {@literal null}.
* @since 1.10
*/
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName, QueryMapper queryMapper,
Class<?> type) {
Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!");
Assert.notNull(collectionName, "Collection name can not be null!");
Assert.notNull(queryMapper, "QueryMapper must not be null!");
this.mongoDbFactory = mongoDbFactory;
this.collectionName = collectionName;
this.mapper = queryMapper;
this.type = type;
}
/*
@@ -74,10 +100,38 @@ public class DefaultIndexOperations implements IndexOperations {
if (indexOptions != null) {
IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
ops.partialFilterExpression( mapper.getMappedObject(
(Document) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), lookupPersistentEntity(type, collectionName)));
}
return collection.createIndex(indexDefinition.getIndexKeys(), ops);
}
return collection.createIndex(indexDefinition.getIndexKeys());
});
}
);
}
private MongoPersistentEntity<?> lookupPersistentEntity(Class<?> entityType, String collection) {
if (entityType != null) {
return mapper.getMappingContext().getRequiredPersistentEntity(entityType);
}
Collection<? extends MongoPersistentEntity<?>> entities = mapper.getMappingContext().getPersistentEntities();
for (MongoPersistentEntity<?> entity : entities) {
if (entity.getCollection().equals(collection)) {
return entity;
}
}
return null;
}
/*
@@ -134,7 +188,7 @@ public class DefaultIndexOperations implements IndexOperations {
public <T> T execute(CollectionCallback<T> callback) {
Assert.notNull(callback);
Assert.notNull(callback, "CollectionCallback must not be null!");
try {
MongoCollection<Document> collection = mongoDbFactory.getDb().getCollection(collectionName);

View File

@@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.QueryMapper;
/**
* {@link IndexOperationsProvider} to obtain {@link IndexOperations} from a given {@link MongoDbFactory}. TODO: Review
@@ -27,12 +28,13 @@ import org.springframework.data.mongodb.MongoDbFactory;
class DefaultIndexOperationsProvider implements IndexOperationsProvider {
private final MongoDbFactory mongoDbFactory;
private final QueryMapper mapper;
/**
* @param mongoDbFactory must not be {@literal null}.
*/
DefaultIndexOperationsProvider(MongoDbFactory mongoDbFactory) {
this.mongoDbFactory = mongoDbFactory;
DefaultIndexOperationsProvider(MongoDbFactory mongoDbFactory, QueryMapper mapper) {
this.mongoDbFactory = mongoDbFactory; this.mapper = mapper;
}
/* (non-Javadoc)
@@ -40,6 +42,6 @@ class DefaultIndexOperationsProvider implements IndexOperationsProvider {
*/
@Override
public IndexOperations indexOps(String collectionName) {
return new DefaultIndexOperations(mongoDbFactory, collectionName);
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper);
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2017 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.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.util.CloseableIterator;
/**
* {@link ExecutableAggregationOperation} allows creation and execution of MongoDB aggregation operations in a fluent
* API style. <br />
* The starting {@literal domainType} is used for mapping the {@link Aggregation} provided via {@code by} into the
* MongoDB specific representation, as well as mapping back the resulting {@link org.bson.Document}. An alternative
* input type for mapping the {@link Aggregation} can be provided by using
* {@link org.springframework.data.mongodb.core.aggregation.TypedAggregation}.
*
* <pre>
* <code>
* aggregateAndReturn(Jedi.class)
* .by(newAggregation(Human.class, project("These are not the droids you are looking for")))
* .get();
* </code>
* </pre>
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.0
*/
public interface ExecutableAggregationOperation {
/**
* Start creating an aggregation operation that returns results mapped to the given domain type. <br />
* Use {@link org.springframework.data.mongodb.core.aggregation.TypedAggregation} to specify a potentially different
* input type for he aggregation.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link AggregationOperation}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> AggregationOperation<T> aggregateAndReturn(Class<T> domainType);
/**
* Collection override (Optional).
*
* @author Christoph Strobl
* @since 2.0
*/
interface AggregationOperationWithCollection<T> {
/**
* Explicitly set the name of the collection to perform the query on. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link AggregationOperationWithAggregation}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
AggregationOperationWithAggregation<T> inCollection(String collection);
}
/**
* Trigger execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingAggregationOperation<T> {
/**
* Apply pipeline operations as specified and get all matching elements.
*
* @return never {@literal null}.
*/
AggregationResults<T> all();
/**
* Apply pipeline operations as specified and stream all matching elements. <br />
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.Cursor}
*
* @return a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.Cursor} that needs to be closed.
* Never {@literal null}.
*/
CloseableIterator<T> stream();
}
/**
* Define the aggregation with pipeline stages.
*
* @author Christoph Strobl
* @since 2.0
*/
interface AggregationOperationWithAggregation<T> {
/**
* Set the aggregation to be used.
*
* @param aggregation must not be {@literal null}.
* @return new instance of {@link TerminatingAggregationOperation}.
* @throws IllegalArgumentException if aggregation is {@literal null}.
*/
TerminatingAggregationOperation<T> by(Aggregation aggregation);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface AggregationOperation<T>
extends AggregationOperationWithCollection<T>, AggregationOperationWithAggregation<T> {}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2017 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 lombok.RequiredArgsConstructor;
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.util.CloseableIterator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Implementation of {@link ExecutableAggregationOperation} operating directly on {@link MongoTemplate}.
*
* @author Christoph Strobl
* @since 2.0
*/
class ExecutableAggregationOperationSupport implements ExecutableAggregationOperation {
private final MongoTemplate template;
/**
* Create new instance of {@link ExecutableAggregationOperationSupport}.
*
* @param template must not be {@literal null}.
* @throws IllegalArgumentException if template is {@literal null}.
*/
ExecutableAggregationOperationSupport(MongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.template = template;
}
@Override
public <T> AggregationOperation<T> aggregateAndReturn(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new AggregationOperationSupport<>(template, null, domainType, null);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
static class AggregationOperationSupport<T>
implements AggregationOperationWithAggregation<T>, AggregationOperation<T>, TerminatingAggregationOperation<T> {
private final MongoTemplate template;
private final Aggregation aggregation;
private final Class<T> domainType;
private final String collection;
@Override
public AggregationOperationWithAggregation<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new AggregationOperationSupport<>(template, aggregation, domainType, collection);
}
@Override
public TerminatingAggregationOperation<T> by(Aggregation aggregation) {
Assert.notNull(aggregation, "Aggregation must not be null!");
return new AggregationOperationSupport<>(template, aggregation, domainType, collection);
}
@Override
public AggregationResults<T> all() {
return template.aggregate(aggregation, getCollectionName(aggregation), domainType);
}
@Override
public CloseableIterator<T> stream() {
return template.aggregateStream(aggregation, getCollectionName(aggregation), domainType);
}
private String getCollectionName(Aggregation aggregation) {
if (StringUtils.hasText(collection)) {
return collection;
}
if (aggregation instanceof TypedAggregation) {
TypedAggregation<?> typedAggregation = (TypedAggregation<?>) aggregation;
if (typedAggregation.getInputType() != null) {
return template.determineCollectionName(typedAggregation.getInputType());
}
}
return template.determineCollectionName(domainType);
}
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import java.util.List;
import java.util.Optional;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.util.CloseableIterator;
/**
* {@link ExecutableFindOperation} allows creation and execution of MongoDB find operations in a fluent API style.
* <br />
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching} into the
* MongoDB specific representation. By default, the originating {@literal domainType} is also used for mapping back the
* result from the {@link org.bson.Document}. However, it is possible to define an different {@literal returnType} via
* {@code as} to mapping the result.<br />
* The collection to operate on is by default derived from the initial {@literal domainType} and can be defined there
* via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the
* collection name for the execution.
*
* <pre>
* <code>
* query(Human.class)
* .inCollection("star-wars")
* .as(Jedi.class)
* .matching(query(where("firstname").is("luke")))
* .all();
* </code>
* </pre>
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.0
*/
public interface ExecutableFindOperation {
/**
* Start creating a find operation for the given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link FindOperation}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> FindOperation<T> query(Class<T> domainType);
/**
* Trigger find execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingFindOperation<T> {
/**
* Get exactly zero or one result.
*
* @return {@link Optional#empty()} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
Optional<T> one();
/**
* Get the first or no result.
*
* @return {@link Optional#empty()} if no match found.
*/
Optional<T> first();
/**
* Get all matching elements.
*
* @return never {@literal null}.
*/
List<T> all();
/**
* Stream all matching elements.
*
* @return a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.Cursor} that needs to be closed.
* Never {@literal null}.
*/
CloseableIterator<T> stream();
}
/**
* Trigger geonear execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingFindNearOperation<T> {
/**
* Find all matching elements and return them as {@link org.springframework.data.geo.GeoResult}.
*
* @return never {@literal null}.
*/
GeoResults<T> all();
}
/**
* Terminating operations invoking the actual query execution.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindOperationWithQuery<T> extends TerminatingFindOperation<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFindOperation}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFindOperation<T> matching(Query query);
/**
* Set the filter query for the geoNear execution.
*
* @param nearQuery must not be {@literal null}.
* @return new instance of {@link TerminatingFindNearOperation}.
* @throws IllegalArgumentException if nearQuery is {@literal null}.
*/
TerminatingFindNearOperation<T> near(NearQuery nearQuery);
}
/**
* Collection override (Optional).
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindOperationWithCollection<T> extends FindOperationWithQuery<T> {
/**
* Explicitly set the name of the collection to perform the query on. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link FindOperationWithProjection}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
FindOperationWithProjection<T> inCollection(String collection);
}
/**
* Result type override (Optional).
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindOperationWithProjection<T> extends FindOperationWithQuery<T> {
/**
* Define the target type fields should be mapped to. <br />
* Skip this step if you are anyway only interested in the original domain type.
*
* @param resultType must not be {@literal null}.
* @param <R> result type.
* @return new instance of {@link FindOperationWithProjection}.
* @throws IllegalArgumentException if resultType is {@literal null}.
*/
<R> FindOperationWithQuery<R> as(Class<R> resultType);
}
/**
* {@link FindOperation} provides methods for constructing lookup operations in a fluent way.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindOperation<T> extends FindOperationWithCollection<T>, FindOperationWithProjection<T> {}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2017 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 lombok.RequiredArgsConstructor;
import java.util.List;
import java.util.Optional;
import org.bson.Document;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.SerializationUtils;
import org.springframework.data.util.CloseableIterator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.mongodb.client.FindIterable;
/**
* Implementation of {@link ExecutableFindOperation}.
*
* @author Christoph Strobl
* @since 2.0
*/
class ExecutableFindOperationSupport implements ExecutableFindOperation {
private final MongoTemplate template;
/**
* Create new {@link ExecutableFindOperationSupport}.
*
* @param template must not be {@literal null}.
* @throws IllegalArgumentException if template is {@literal null}.
*/
ExecutableFindOperationSupport(MongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.template = template;
}
@Override
public <T> FindOperation<T> query(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new FindOperationSupport<>(template, domainType, domainType, null, null);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
static class FindOperationSupport<T> implements FindOperation<T>, FindOperationWithCollection<T>,
FindOperationWithProjection<T>, FindOperationWithQuery<T> {
private final MongoTemplate template;
private final Class<?> domainType;
private final Class<T> returnType;
private final String collection;
private final Query query;
@Override
public FindOperationWithProjection<T> inCollection(String collection) {
Assert.hasText(collection, "Collection name must not be null nor empty!");
return new FindOperationSupport<>(template, domainType, returnType, collection, query);
}
@Override
public <T1> FindOperationWithQuery<T1> as(Class<T1> returnType) {
Assert.notNull(returnType, "ReturnType must not be null!");
return new FindOperationSupport<>(template, domainType, returnType, collection, query);
}
@Override
public TerminatingFindOperation<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new FindOperationSupport<>(template, domainType, returnType, collection, query);
}
@Override
public Optional<T> one() {
List<T> result = doFind(new DelegatingQueryCursorPreparer(getCursorPreparer(query, null)).limit(2));
if (ObjectUtils.isEmpty(result)) {
return Optional.empty();
}
if (result.size() > 1) {
throw new IncorrectResultSizeDataAccessException("Query " + asString() + " returned non unique result.", 1);
}
return Optional.of(result.iterator().next());
}
@Override
public Optional<T> first() {
List<T> result = doFind(new DelegatingQueryCursorPreparer(getCursorPreparer(query, null)).limit(1));
return ObjectUtils.isEmpty(result) ? Optional.empty() : Optional.of(result.iterator().next());
}
@Override
public List<T> all() {
return doFind(null);
}
@Override
public CloseableIterator<T> stream() {
return doStream();
}
@Override
public TerminatingFindNearOperation<T> near(NearQuery nearQuery) {
return () -> template.geoNear(nearQuery, domainType, getCollectionName(), returnType);
}
private List<T> doFind(CursorPreparer preparer) {
Document queryObject = query != null ? query.getQueryObject() : new Document();
Document fieldsObject = query != null ? query.getFieldsObject() : new Document();
return template.doFind(getCollectionName(), queryObject, fieldsObject, domainType, returnType,
getCursorPreparer(query, preparer));
}
private CloseableIterator<T> doStream() {
return template.doStream(query != null ? query : new BasicQuery(new Document()), domainType, getCollectionName(),
returnType);
}
private CursorPreparer getCursorPreparer(Query query, CursorPreparer preparer) {
return query == null || preparer != null ? preparer : template.new QueryCursorPreparer(query, domainType);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
private String asString() {
return SerializationUtils.serializeToJsonSafely(query);
}
}
/**
* @author Christoph Strobl
* @since 2.0
*/
static class DelegatingQueryCursorPreparer implements CursorPreparer {
private final CursorPreparer delegate;
private Optional<Integer> limit = Optional.empty();
DelegatingQueryCursorPreparer(CursorPreparer delegate) {
this.delegate = delegate;
}
@Override
public FindIterable<Document> prepare(FindIterable<Document> cursor) {
FindIterable<Document> target = delegate.prepare(cursor);
return limit.map(target::limit).orElse(target);
}
CursorPreparer limit(int limit) {
this.limit = Optional.of(limit);
return this;
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import java.util.Collection;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import com.mongodb.bulk.BulkWriteResult;
/**
* {@link ExecutableInsertOperation} allows creation and execution of MongoDB insert and bulk insert operations in a
* fluent API style. <br />
* The collection to operate on is by default derived from the initial {@literal domainType} and can be defined there
* via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the
* collection name for the execution.
*
* <pre>
* <code>
* insert(Jedi.class)
* .inCollection("star-wars")
* .one(luke);
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableInsertOperation {
/**
* Start creating an insert operation for given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link InsertOperation}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> InsertOperation<T> insert(Class<T> domainType);
/**
* Trigger insert execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingInsertOperation<T> extends TerminatingBulkInsertOperation<T> {
/**
* Insert exactly one object.
*
* @param object must not be {@literal null}.
* @throws IllegalArgumentException if object is {@literal null}.
*/
void one(T object);
/**
* Insert a collection of objects.
*
* @param objects must not be {@literal null}.
* @throws IllegalArgumentException if objects is {@literal null}.
*/
void all(Collection<? extends T> objects);
}
/**
* Trigger bulk insert execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingBulkInsertOperation<T> {
/**
* Bulk write collection of objects.
*
* @param objects must not be {@literal null}.
* @return resulting {@link BulkWriteResult}.
* @throws IllegalArgumentException if objects is {@literal null}.
*/
BulkWriteResult bulk(Collection<? extends T> objects);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface InsertOperation<T>
extends TerminatingInsertOperation<T>, InsertOperationWithCollection<T>, InsertOperationWithBulkMode<T> {}
/**
* Collection override (Optional).
*
* @author Christoph Strobl
* @since 2.0
*/
interface InsertOperationWithCollection<T> {
/**
* Explicitly set the name of the collection. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link InsertOperationWithBulkMode}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
InsertOperationWithBulkMode<T> inCollection(String collection);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface InsertOperationWithBulkMode<T> extends TerminatingInsertOperation<T> {
/**
* Define the {@link BulkMode} to use for bulk insert operation.
*
* @param bulkMode must not be {@literal null}.
* @return new instance of {@link TerminatingBulkInsertOperation}.
* @throws IllegalArgumentException if bulkMode is {@literal null}.
*/
TerminatingBulkInsertOperation<T> withBulkMode(BulkMode bulkMode);
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2017 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 lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.bulk.BulkWriteResult;
/**
* Implementation of {@link ExecutableInsertOperation}.
*
* @author Christoph Strobl
* @since 2.0
*/
class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
private final MongoTemplate template;
/**
* Create new {@link ExecutableInsertOperationSupport}.
*
* @param template must not be {@literal null}.
* @throws IllegalArgumentException if template is {@literal null}.
*/
ExecutableInsertOperationSupport(MongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.template = template;
}
@Override
public <T> InsertOperation<T> insert(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new InsertOperationSupport<>(template, domainType, null, null);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
static class InsertOperationSupport<T> implements InsertOperation<T> {
private final MongoTemplate template;
private final Class<T> domainType;
private final String collection;
private final BulkMode bulkMode;
@Override
public void one(T object) {
Assert.notNull(object, "Object must not be null!");
template.insert(object, getCollectionName());
}
@Override
public void all(Collection<? extends T> objects) {
Assert.notNull(objects, "Objects must not be null!");
template.insert(objects, getCollectionName());
}
@Override
public BulkWriteResult bulk(Collection<? extends T> objects) {
Assert.notNull(objects, "Objects must not be null!");
return template.bulkOps(bulkMode != null ? bulkMode : BulkMode.ORDERED, domainType, getCollectionName())
.insert(new ArrayList<>(objects)).execute();
}
@Override
public InsertOperationWithBulkMode<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new InsertOperationSupport<>(template, domainType, collection, bulkMode);
}
@Override
public TerminatingBulkInsertOperation<T> withBulkMode(BulkMode bulkMode) {
Assert.notNull(bulkMode, "BulkMode must not be null!");
return new InsertOperationSupport<>(template, domainType, collection, bulkMode);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import java.util.List;
import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.client.result.DeleteResult;
/**
* {@link ExecutableRemoveOperation} allows creation and execution of MongoDB remove / findAndRemove operations in a
* fluent API style. <br />
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching} into the
* MongoDB specific representation. The collection to operate on is by default derived from the initial
* {@literal domainType} and can be defined there via {@link org.springframework.data.mongodb.core.mapping.Document}.
* Using {@code inCollection} allows to override the collection name for the execution.
*
* <pre>
* <code>
* remove(Jedi.class)
* .inCollection("star-wars")
* .matching(query(where("firstname").is("luke")))
* .all();
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableRemoveOperation {
/**
* Start creating a remove operation for the given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link RemoveOperation}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> RemoveOperation<T> remove(Class<T> domainType);
/**
* Collection override (Optional).
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface RemoveOperationWithCollection<T> extends RemoveOperationWithQuery<T> {
/**
* Explicitly set the name of the collection to perform the query on. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link RemoveOperationWithCollection}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
RemoveOperationWithQuery<T> inCollection(String collection);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingRemoveOperation<T> {
/**
* Remove all documents matching.
*
* @return the {@link DeleteResult}. Never {@literal null}.
*/
DeleteResult all();
/**
* Remove and return all matching documents. <br/>
* <strong>NOTE</strong> The entire list of documents will be fetched before sending the actual delete commands.
* Also, {@link org.springframework.context.ApplicationEvent}s will be published for each and every delete
* operation.
*
* @return empty {@link List} if no match found. Never {@literal null}.
*/
List<T> findAndRemove();
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface RemoveOperationWithQuery<T> extends TerminatingRemoveOperation<T> {
/**
* Define the query filtering elements.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingRemoveOperation}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingRemoveOperation<T> matching(Query query);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface RemoveOperation<T> extends RemoveOperationWithCollection<T> {}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2017 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 lombok.RequiredArgsConstructor;
import java.util.List;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.client.result.DeleteResult;
/**
* Implementation of {@link ExecutableRemoveOperation}.
*
* @author Christoph Strobl
* @since 2.0
*/
class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
private final MongoTemplate tempate;
/**
* Create new {@link ExecutableRemoveOperationSupport}.
*
* @param template must not be {@literal null}.
* @throws IllegalArgumentException if template is {@literal null}.
*/
ExecutableRemoveOperationSupport(MongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.tempate = template;
}
@Override
public <T> RemoveOperation<T> remove(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new RemoveOperationSupport<>(tempate, null, domainType, null);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
static class RemoveOperationSupport<T> implements RemoveOperation<T>, RemoveOperationWithCollection<T> {
private final MongoTemplate template;
private final Query query;
private final Class<T> domainType;
private final String collection;
@Override
public RemoveOperationWithQuery<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new RemoveOperationSupport<>(template, query, domainType, collection);
}
@Override
public TerminatingRemoveOperation<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new RemoveOperationSupport<>(template, query, domainType, collection);
}
@Override
public DeleteResult all() {
String collectionName = getCollectionName();
return template.doRemove(collectionName, getQuery(), domainType);
}
@Override
public List<T> findAndRemove() {
String collectionName = getCollectionName();
return template.doFindAndDelete(collectionName, getQuery(), domainType);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
private Query getQuery() {
return query != null ? query : new BasicQuery(new Document());
}
}
}

View File

@@ -0,0 +1,180 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import java.util.Optional;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import com.mongodb.client.result.UpdateResult;
/**
* {@link ExecutableUpdateOperation} allows creation and execution of MongoDB update / findAndModify operations in a
* fluent API style. <br />
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}, as well as
* the {@link Update} via {@code apply} into the MongoDB specific representations. The collection to operate on is by
* default derived from the initial {@literal domainType} and can be defined there via
* {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the
* collection name for the execution.
*
* <pre>
* <code>
* update(Jedi.class)
* .inCollection("star-wars")
* .matching(query(where("firstname").is("luke")))
* .apply(new Update().set("lastname", "skywalker"))
* .upsert();
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableUpdateOperation {
/**
* Start creating an update operation for the given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link UpdateOperation}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> UpdateOperation<T> update(Class<T> domainType);
/**
* @author Christoph Strobl
* @since 2.0
*/
interface UpdateOperation<T>
extends UpdateOperationWithCollection<T>, UpdateOperationWithQuery<T>, UpdateOperationWithUpdate<T> {}
/**
* Declare the {@link Update} to apply.
*
* @author Christoph Strobl
* @since 2.0
*/
interface UpdateOperationWithUpdate<T> {
/**
* Set the {@link Update} to be applied.
*
* @param update must not be {@literal null}.
* @return new instance of {@link TerminatingUpdateOperation}.
* @throws IllegalArgumentException if update is {@literal null}.
*/
TerminatingUpdateOperation<T> apply(Update update);
}
/**
* Explicitly define the name of the collection to perform operation in.
*
* @author Christoph Strobl
* @since 2.0
*/
interface UpdateOperationWithCollection<T> {
/**
* Explicitly set the name of the collection to perform the query on. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link UpdateOperationWithCollection}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
UpdateOperationWithQuery<T> inCollection(String collection);
}
/**
* Define a filter query for the {@link Update}.
*
* @author Christoph Strobl
* @since 2.0
*/
interface UpdateOperationWithQuery<T> extends UpdateOperationWithUpdate<T> {
/**
* Filter documents by given {@literal query}.
*
* @param query must not be {@literal null}.
* @return new instance of {@link UpdateOperationWithQuery}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
UpdateOperationWithUpdate<T> matching(Query query);
}
/**
* Define {@link FindAndModifyOptions}.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindAndModifyWithOptions<T> {
/**
* Explicitly define {@link FindAndModifyOptions} for the {@link Update}.
*
* @param options must not be {@literal null}.
* @return new instance of {@link FindAndModifyWithOptions}.
* @throws IllegalArgumentException if options is {@literal null}.
*/
TerminatingFindAndModifyOperation<T> withOptions(FindAndModifyOptions options);
}
/**
* Trigger findAndModify execution by calling one of the terminating methods.
*/
interface TerminatingFindAndModifyOperation<T> {
/**
* Find, modify and return the first matching document.
*
* @return {@link Optional#empty()} if nothing found.
*/
Optional<T> findAndModify();
}
/**
* Trigger update execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingUpdateOperation<T> extends TerminatingFindAndModifyOperation<T>, FindAndModifyWithOptions<T> {
/**
* Update all matching documents in the collection.
*
* @return never {@literal null}.
*/
UpdateResult all();
/**
* Update the first document in the collection.
*
* @return never {@literal null}.
*/
UpdateResult first();
/**
* Creates a new document if no documents match the filter query or updates the matching ones.
*
* @return never {@literal null}.
*/
UpdateResult upsert();
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2017 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 lombok.RequiredArgsConstructor;
import java.util.Optional;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.client.result.UpdateResult;
/**
* Implementation of {@link ExecutableUpdateOperation}.
*
* @author Christoph Strobl
* @since 2.0
*/
class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
private final MongoTemplate template;
/**
* Creates new {@link ExecutableUpdateOperationSupport}.
*
* @param template must not be {@literal null}.
*/
ExecutableUpdateOperationSupport(MongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.template = template;
}
@Override
public <T> UpdateOperation<T> update(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new UpdateOperationSupport<>(template, null, domainType, null, null, null);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
static class UpdateOperationSupport<T> implements UpdateOperation<T>, UpdateOperationWithCollection<T>,
UpdateOperationWithQuery<T>, TerminatingUpdateOperation<T> {
private final MongoTemplate template;
private final Query query;
private final Class<T> domainType;
private final Update update;
private final String collection;
private final FindAndModifyOptions options;
@Override
public TerminatingUpdateOperation<T> apply(Update update) {
Assert.notNull(update, "Update must not be null!");
return new UpdateOperationSupport<>(template, query, domainType, update, collection, options);
}
@Override
public UpdateOperationWithQuery<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new UpdateOperationSupport<>(template, query, domainType, update, collection, options);
}
@Override
public UpdateResult first() {
return doUpdate(false, false);
}
@Override
public UpdateResult upsert() {
return doUpdate(true, true);
}
@Override
public Optional<T> findAndModify() {
String collectionName = getCollectionName();
return Optional.ofNullable(template.findAndModify(query != null ? query : new BasicQuery(new Document()), update,
options, domainType, collectionName));
}
@Override
public UpdateOperationWithUpdate<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new UpdateOperationSupport<>(template, query, domainType, update, collection, options);
}
@Override
public UpdateResult all() {
return doUpdate(true, false);
}
@Override
public TerminatingFindAndModifyOperation<T> withOptions(FindAndModifyOptions options) {
Assert.notNull(options, "Options must not be null!");
return new UpdateOperationSupport<>(template, query, domainType, update, collection, options);
}
private UpdateResult doUpdate(boolean multi, boolean upsert) {
String collectionName = getCollectionName();
Query query = this.query != null ? this.query : new BasicQuery(new Document());
return template.doUpdate(collectionName, query, update, domainType, upsert, multi);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2011 the original author or authors.
* Copyright 2010-2017 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,13 +15,20 @@
*/
package org.springframework.data.mongodb.core;
import java.util.Optional;
/**
* @author Mark Pollak
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class FindAndModifyOptions {
boolean returnNew;
private boolean returnNew;
private boolean upsert;
private boolean remove;
boolean upsert;
boolean remove;
private Collation collation;
/**
* Static factory method to create a FindAndModifyOptions instance
@@ -32,6 +39,26 @@ public class FindAndModifyOptions {
return new FindAndModifyOptions();
}
/**
* @param options
* @return
* @since 2.0
*/
public static FindAndModifyOptions of(FindAndModifyOptions source) {
FindAndModifyOptions options = new FindAndModifyOptions();
if (source == null) {
return options;
}
options.returnNew = source.returnNew;
options.upsert = source.upsert;
options.remove = source.remove;
options.collation = source.collation;
return options;
}
public FindAndModifyOptions returnNew(boolean returnNew) {
this.returnNew = returnNew;
return this;
@@ -47,6 +74,19 @@ public class FindAndModifyOptions {
return this;
}
/**
* Define the {@link Collation} specifying language-specific rules for string comparison.
*
* @param collation
* @return
* @since 2.0
*/
public FindAndModifyOptions collation(Collation collation) {
this.collation = collation;
return this;
}
public boolean isReturnNew() {
return returnNew;
}
@@ -59,4 +99,14 @@ public class FindAndModifyOptions {
return remove;
}
/**
* Get the {@link Collation} specifying language-specific rules for string comparison.
*
* @return
* @since 2.0
*/
public Optional<Collation> getCollation() {
return Optional.ofNullable(collation);
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2017 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;
/**
* Stripped down interface providing access to a fluent API that specifies a basic set of MongoDB operations.
*
* @author Christoph Strobl
* @since 2.0
*/
public interface FluentMongoOperations extends ExecutableFindOperation, ExecutableInsertOperation,
ExecutableUpdateOperation, ExecutableRemoveOperation, ExecutableAggregationOperation {}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -62,7 +62,7 @@ class GeoCommandStatistics {
* didn't return any result introduced in MongoDB 3.2 RC1.
*
* @return
* @see https://jira.mongodb.org/browse/SERVER-21024
* @see <a href="https://jira.mongodb.org/browse/SERVER-21024">MongoDB Jira SERVER-21024</a>
*/
public double getAverageDistance() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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,15 +17,14 @@ package org.springframework.data.mongodb.core;
import org.springframework.context.annotation.Bean;
import org.springframework.data.mongodb.core.geo.GeoJsonModule;
import org.springframework.data.web.config.SpringDataWebConfigurationMixin;
import org.springframework.data.web.config.SpringDataJacksonModules;
/**
* Configuration class to expose {@link GeoJsonModule} as a Spring bean.
*
* @author Oliver Gierke
*/
@SpringDataWebConfigurationMixin
public class GeoJsonConfiguration {
public class GeoJsonConfiguration implements SpringDataJacksonModules {
@Bean
public GeoJsonModule geoJsonModule() {

View File

@@ -16,21 +16,15 @@
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 java.util.concurrent.TimeUnit;
import org.bson.Document;
import org.springframework.core.convert.converter.Converter;
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.util.ObjectUtils;
import com.mongodb.client.model.Collation;
import com.mongodb.client.model.IndexOptions;
/**
@@ -45,10 +39,6 @@ abstract class IndexConverters {
private static final Converter<IndexDefinition, IndexOptions> DEFINITION_TO_MONGO_INDEX_OPTIONS;
private static final Converter<Document, IndexInfo> DOCUMENT_INDEX_INFO;
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");
static {
DEFINITION_TO_MONGO_INDEX_OPTIONS = getIndexDefinitionIndexOptionsConverter();
@@ -117,50 +107,29 @@ abstract class IndexConverters {
}
}
if (indexOptions.containsKey("partialFilterExpression")) {
ops = ops.partialFilterExpression((org.bson.Document) indexOptions.get("partialFilterExpression"));
}
if (indexOptions.containsKey("collation")) {
ops = ops.collation(fromDocument(indexOptions.get("collation", Document.class)));
}
return ops;
};
}
private static Converter<Document, IndexInfo> getDocumentIndexInfoConverter() {
public static Collation fromDocument(Document source) {
return ix -> {
Document keyDocument = (Document) ix.get("key");
int numberOfElements = keyDocument.keySet().size();
if (source == null) {
return null;
}
List<IndexField> indexFields = new ArrayList<IndexField>(numberOfElements);
for (String key : keyDocument.keySet()) {
Object value = keyDocument.get(key);
if (TWO_D_IDENTIFIERS.contains(value)) {
indexFields.add(IndexField.geo(key));
} else if ("text".equals(value)) {
Document weights = (Document) 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));
}
}
}
String name = ix.get("name").toString();
boolean unique = ix.containsKey("unique") ? (Boolean) ix.get("unique") : false;
boolean sparse = ix.containsKey("sparse") ? (Boolean) ix.get("sparse") : false;
String language = ix.containsKey("default_language") ? (String) ix.get("default_language") : "";
return new IndexInfo(indexFields, name, unique, sparse, language);
};
return org.springframework.data.mongodb.core.Collation.from(source).toMongoCollation();
}
private static Converter<Document, IndexInfo> getDocumentIndexInfoConverter() {
return IndexInfo::indexInfoOf;
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.data.mongodb.core;
import org.springframework.data.mongodb.core.convert.QueryMapper;
/**
* TODO: Revisit for a better pattern.
* @author Mark Paluch

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 the original author or authors.
* Copyright 2011-2017 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,31 +15,30 @@
*/
package org.springframework.data.mongodb.core;
import org.springframework.data.authentication.UserCredentials;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.util.Assert;
import com.mongodb.DB;
import com.mongodb.Mongo;
/**
* Mongo server administration exposed via JMX annotations
*
* @author Mark Pollack
* @author Thomas Darimont
* @author Mark Paluch
* @author Christoph Strobl
*/
@ManagedResource(description = "Mongo Admin Operations")
public class MongoAdmin implements MongoAdminOperations {
private final Mongo mongo;
private String username;
private String password;
private String authenticationDatabaseName;
private final MongoClient mongoClient;
public MongoAdmin(Mongo mongo) {
Assert.notNull(mongo);
this.mongo = mongo;
public MongoAdmin(MongoClient mongoClient) {
Assert.notNull(mongoClient, "MongoClient must not be null!");
this.mongoClient = mongoClient;
}
/* (non-Javadoc)
@@ -47,7 +46,7 @@ public class MongoAdmin implements MongoAdminOperations {
*/
@ManagedOperation
public void dropDatabase(String databaseName) {
getDB(databaseName).dropDatabase();
getDB(databaseName).drop();
}
/* (non-Javadoc)
@@ -63,37 +62,16 @@ public class MongoAdmin implements MongoAdminOperations {
*/
@ManagedOperation
public String getDatabaseStats(String databaseName) {
return getDB(databaseName).getStats().toString();
return getDB(databaseName).runCommand(new Document("dbStats", 1).append("scale" , 1024)).toJson();
}
/**
* Sets the username to use to connect to the Mongo database
*
* @param username The username to use
*/
public void setUsername(String username) {
this.username = username;
@ManagedOperation
public String getServerStatus() {
return getDB("admin").runCommand(new Document("serverStatus", 1).append("rangeDeleter", 1).append("repl", 1)).toJson();
}
/**
* Sets the password to use to authenticate with the Mongo database.
*
* @param password The password to use
*/
public void setPassword(String password) {
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), authenticationDatabaseName);
MongoDatabase getDB(String databaseName) {
return mongoClient.getDatabase(databaseName);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2017 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,7 +26,6 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
@@ -38,7 +37,7 @@ import com.mongodb.ServerAddress;
* @author Christoph Strobl
* @since 1.7
*/
public class MongoClientFactoryBean extends AbstractFactoryBean<Mongo> implements PersistenceExceptionTranslator {
public class MongoClientFactoryBean extends AbstractFactoryBean<MongoClient> implements PersistenceExceptionTranslator {
private static final PersistenceExceptionTranslator DEFAULT_EXCEPTION_TRANSLATOR = new MongoExceptionTranslator();
@@ -108,8 +107,8 @@ public class MongoClientFactoryBean extends AbstractFactoryBean<Mongo> implement
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<? extends Mongo> getObjectType() {
return Mongo.class;
public Class<? extends MongoClient> getObjectType() {
return MongoClient.class;
}
/*
@@ -125,7 +124,7 @@ public class MongoClientFactoryBean extends AbstractFactoryBean<Mongo> implement
* @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance()
*/
@Override
protected Mongo createInstance() throws Exception {
protected MongoClient createInstance() throws Exception {
if (mongoClientOptions == null) {
mongoClientOptions = MongoClientOptions.builder().build();
@@ -143,7 +142,7 @@ public class MongoClientFactoryBean extends AbstractFactoryBean<Mongo> implement
* @see org.springframework.beans.factory.config.AbstractFactoryBean#destroyInstance(java.lang.Object)
*/
@Override
protected void destroyInstance(Mongo instance) throws Exception {
protected void destroyInstance(MongoClient instance) throws Exception {
instance.close();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2017 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 com.mongodb.WriteConcern;
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Mark Paluch
* @since 1.7
*/
public class MongoClientOptionsFactoryBean extends AbstractFactoryBean<MongoClientOptions> {
@@ -62,6 +63,7 @@ public class MongoClientOptionsFactoryBean extends AbstractFactoryBean<MongoClie
private int heartbeatConnectTimeout = DEFAULT_MONGO_OPTIONS.getHeartbeatConnectTimeout();
private int heartbeatSocketTimeout = DEFAULT_MONGO_OPTIONS.getHeartbeatSocketTimeout();
private String requiredReplicaSetName = DEFAULT_MONGO_OPTIONS.getRequiredReplicaSetName();
private int serverSelectionTimeout = DEFAULT_MONGO_OPTIONS.getServerSelectionTimeout();
private boolean ssl;
private SSLSocketFactory sslSocketFactory;
@@ -247,7 +249,20 @@ public class MongoClientOptionsFactoryBean extends AbstractFactoryBean<MongoClie
* @param sslSocketFactory
*/
public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
this.sslSocketFactory = sslSocketFactory;
this.ssl = sslSocketFactory != null;
}
/**
* Set the {@literal server selection timeout} in msec for a 3.x MongoDB Java driver. If not set the default value of
* 30 sec will be used. A value of 0 means that it will timeout immediately if no server is available. A negative
* value means to wait indefinitely.
*
* @param serverSelectionTimeout in msec.
*/
public void setServerSelectionTimeout(int serverSelectionTimeout) {
this.serverSelectionTimeout = serverSelectionTimeout;
}
/*
@@ -257,8 +272,8 @@ public class MongoClientOptionsFactoryBean extends AbstractFactoryBean<MongoClie
@Override
protected MongoClientOptions createInstance() throws Exception {
SocketFactory socketFactoryToUse = ssl ? (sslSocketFactory != null ? sslSocketFactory : SSLSocketFactory
.getDefault()) : this.socketFactory;
SocketFactory socketFactoryToUse = ssl
? (sslSocketFactory != null ? sslSocketFactory : SSLSocketFactory.getDefault()) : this.socketFactory;
return MongoClientOptions.builder() //
.alwaysUseMBeans(this.alwaysUseMBeans) //
@@ -278,6 +293,7 @@ public class MongoClientOptionsFactoryBean extends AbstractFactoryBean<MongoClie
.minHeartbeatFrequency(minHeartbeatFrequency) //
.readPreference(readPreference) //
.requiredReplicaSetName(requiredReplicaSetName) //
.serverSelectionTimeout(serverSelectionTimeout) //
.socketFactory(socketFactoryToUse) //
.socketKeepAlive(socketKeepAlive) //
.socketTimeout(socketTimeout) //

View File

@@ -1,216 +0,0 @@
/*
* Copyright 2010-2015 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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.util.MongoClientVersion;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import com.mongodb.DB;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
/**
* 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
* @author Christoph Strobl
* @since 1.0
*/
public abstract class MongoDbUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoDbUtils.class);
/**
* Private constructor to prevent instantiation.
*/
private MongoDbUtils() {}
/**
* Obtains a {@link DB} connection for the given {@link Mongo} instance and database name
*
* @param mongo the {@link Mongo} instance, must not be {@literal null}.
* @param databaseName the database name, must not be {@literal null} or empty.
* @return the {@link DB} connection
*/
public static DB getDB(Mongo mongo, String databaseName) {
return doGetDB(mongo, databaseName, UserCredentials.NO_CREDENTIALS, true, databaseName);
}
/**
* Obtains a {@link DB} connection for the given {@link Mongo} instance and database name
*
* @param mongo the {@link Mongo} instance, must not be {@literal null}.
* @param databaseName the database name, must not be {@literal null} or empty.
* @param credentials the credentials to use, must not be {@literal null}.
* @return the {@link DB} connection
* @deprecated since 1.7. The {@link MongoClient} itself should hold credentials within
* {@link MongoClient#getCredentialsList()}.
*/
@Deprecated
public static DB getDB(Mongo mongo, String databaseName, UserCredentials credentials) {
return getDB(mongo, databaseName, credentials, databaseName);
}
/**
* @param mongo
* @param databaseName
* @param credentials
* @param authenticationDatabaseName
* @return
* @deprecated since 1.7. The {@link MongoClient} itself should hold credentials within
* {@link MongoClient#getCredentialsList()}.
*/
@Deprecated
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, authenticationDatabaseName);
}
private static DB doGetDB(Mongo mongo, String databaseName, UserCredentials credentials, boolean allowCreate,
String authenticationDatabaseName) {
DbHolder dbHolder = (DbHolder) TransactionSynchronizationManager.getResource(mongo);
// Do we have a populated holder and TX sync active?
if (dbHolder != null && !dbHolder.isEmpty() && TransactionSynchronizationManager.isSynchronizationActive()) {
DB db = dbHolder.getDB(databaseName);
// DB found but not yet synchronized
if (db != null && !dbHolder.isSynchronizedWithTransaction()) {
LOGGER.debug("Registering Spring transaction synchronization for existing MongoDB {}.", databaseName);
TransactionSynchronizationManager.registerSynchronization(new MongoSynchronization(dbHolder, mongo));
dbHolder.setSynchronizedWithTransaction(true);
}
if (db != null) {
return db;
}
}
// Lookup fresh database instance
LOGGER.debug("Getting Mongo Database name=[{}]", databaseName);
DB db = mongo.getDB(databaseName);
if (!(mongo instanceof MongoClient) && requiresAuthDbAuthentication(credentials)) {
ReflectiveDbInvoker.authenticate(mongo, db, credentials, authenticationDatabaseName);
}
// TX sync active, bind new database to thread
if (TransactionSynchronizationManager.isSynchronizationActive()) {
LOGGER.debug("Registering Spring transaction synchronization for MongoDB instance {}.", databaseName);
DbHolder holderToUse = dbHolder;
if (holderToUse == null) {
holderToUse = new DbHolder(databaseName, db);
} else {
holderToUse.addDB(databaseName, db);
}
// 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);
}
}
// Check whether we are allowed to return the DB.
if (!allowCreate && !isDBTransactional(db, mongo)) {
throw new IllegalStateException("No Mongo DB bound to thread, "
+ "and configuration does not allow creation of non-transactional one here");
}
return db;
}
/**
* Return whether the given DB instance is transactional, that is, bound to the current thread by Spring's transaction
* facilities.
*
* @param db the DB to check
* @param mongo the Mongo instance that the DB was created with (may be <code>null</code>)
* @return whether the DB is transactional
*/
public static boolean isDBTransactional(DB db, Mongo mongo) {
if (mongo == null) {
return false;
}
DbHolder dbHolder = (DbHolder) TransactionSynchronizationManager.getResource(mongo);
return dbHolder != null && dbHolder.containsDB(db);
}
/**
* Perform actual closing of the Mongo DB object, catching and logging any cleanup exceptions thrown.
*
* @param db the DB to close (may be <code>null</code>)
* @deprecated since 1.7. The main use case for this method is to ensure that applications can read their own
* unacknowledged writes, but this is no longer so prevalent since the MongoDB Java driver version 3
* started defaulting to acknowledged writes.
*/
@Deprecated
public static void closeDB(DB db) {
if (db != null) {
LOGGER.debug("Closing Mongo DB object");
try {
ReflectiveDbInvoker.requestDone(db);
} catch (Throwable ex) {
LOGGER.debug("Unexpected exception on closing Mongo DB object", ex);
}
}
}
/**
* Check if credentials present. In case we're using a mongo-java-driver version 3 or above we do not have the need
* for authentication as the auth data has to be provided within the MongoClient
*
* @param credentials
* @return
*/
private static boolean requiresAuthDbAuthentication(UserCredentials credentials) {
if (credentials == null || !credentials.hasUsername()) {
return false;
}
return !MongoClientVersion.isMongo3Driver();
}
}

View File

@@ -1,199 +0,0 @@
/*
* Copyright 2010-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.CannotGetMongoDbConnectionException;
import org.springframework.util.StringUtils;
import com.mongodb.Mongo;
import com.mongodb.MongoOptions;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;
/**
* Convenient factory for configuring MongoDB.
*
* @author Thomas Risberg
* @author Graeme Rocher
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @since 1.0
* @deprecated since 1.7. Please use {@link MongoClientFactoryBean} instead.
*/
@Deprecated
public class MongoFactoryBean extends AbstractFactoryBean<Mongo> implements PersistenceExceptionTranslator {
private static final PersistenceExceptionTranslator DEFAULT_EXCEPTION_TRANSLATOR = new MongoExceptionTranslator();
private MongoOptions mongoOptions;
private String host;
private Integer port;
private WriteConcern writeConcern;
private List<ServerAddress> replicaSetSeeds;
private List<ServerAddress> replicaPair;
private PersistenceExceptionTranslator exceptionTranslator = DEFAULT_EXCEPTION_TRANSLATOR;
/**
* @param mongoOptions
*/
public void setMongoOptions(MongoOptions mongoOptions) {
this.mongoOptions = mongoOptions;
}
public void setReplicaSetSeeds(ServerAddress[] replicaSetSeeds) {
this.replicaSetSeeds = filterNonNullElementsAsList(replicaSetSeeds);
}
/**
* @deprecated use {@link #setReplicaSetSeeds(ServerAddress[])} instead
* @param replicaPair
*/
@Deprecated
public void setReplicaPair(ServerAddress[] replicaPair) {
this.replicaPair = filterNonNullElementsAsList(replicaPair);
}
/**
* Configures the host to connect to.
*
* @param host
*/
public void setHost(String host) {
this.host = host;
}
/**
* Configures the port to connect to.
*
* @param port
*/
public void setPort(int port) {
this.port = port;
}
/**
* Sets the {@link WriteConcern} to be configured for the {@link Mongo} instance to be created.
*
* @param writeConcern
*/
public void setWriteConcern(WriteConcern writeConcern) {
this.writeConcern = writeConcern;
}
/**
* Configures the {@link PersistenceExceptionTranslator} to use.
*
* @param exceptionTranslator can be {@literal null}.
*/
public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator == null ? DEFAULT_EXCEPTION_TRANSLATOR : exceptionTranslator;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<? extends Mongo> getObjectType() {
return Mongo.class;
}
/*
* (non-Javadoc)
* @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException)
*/
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return exceptionTranslator.translateExceptionIfPossible(ex);
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance()
*/
@Override
protected Mongo createInstance() throws Exception {
Mongo mongo;
ServerAddress defaultOptions = new ServerAddress();
if (mongoOptions == null) {
mongoOptions = new MongoOptions();
}
if (!isNullOrEmpty(replicaPair)) {
if (replicaPair.size() < 2) {
throw new CannotGetMongoDbConnectionException("A replica pair must have two server entries");
}
mongo = new Mongo(replicaPair.get(0), replicaPair.get(1), mongoOptions);
} else if (!isNullOrEmpty(replicaSetSeeds)) {
mongo = new Mongo(replicaSetSeeds, mongoOptions);
} else {
String mongoHost = StringUtils.hasText(host) ? host : defaultOptions.getHost();
mongo = port != null ? new Mongo(new ServerAddress(mongoHost, port), mongoOptions) : new Mongo(mongoHost,
mongoOptions);
}
if (writeConcern != null) {
mongo.setWriteConcern(writeConcern);
}
return mongo;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.AbstractFactoryBean#destroyInstance(java.lang.Object)
*/
@Override
protected void destroyInstance(Mongo mongo) throws Exception {
mongo.close();
}
private static boolean isNullOrEmpty(Collection<?> elements) {
return elements == null || elements.isEmpty();
}
/**
* Returns the given array as {@link List} with all {@literal null} elements removed.
*
* @param elements the elements to filter <T>
* @return a new unmodifiable {@link List#} from the given elements without nulls
*/
private static <T> List<T> filterNonNullElementsAsList(T[] elements) {
if (elements == null) {
return Collections.emptyList();
}
List<T> candidateElements = new ArrayList<T>();
for (T element : elements) {
if (element != null) {
candidateElements.add(element);
}
}
return Collections.unmodifiableList(candidateElements);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 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,7 @@ import org.bson.Document;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.convert.MongoConverter;
@@ -38,7 +39,6 @@ import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.util.CloseableIterator;
import com.mongodb.Cursor;
import com.mongodb.DB;
import com.mongodb.ReadPreference;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.result.DeleteResult;
@@ -56,8 +56,9 @@ import com.mongodb.client.result.UpdateResult;
* @author Chuong Ngo
* @author Christoph Strobl
* @author Thomas Darimont
* @author Maninder Singh
*/
public interface MongoOperations {
public interface MongoOperations extends FluentMongoOperations {
/**
* The collection name used for the specified class by this template.
@@ -417,6 +418,81 @@ public interface MongoOperations {
*/
<O> AggregationResults<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType);
/**
* Execute an aggregation operation backed by a Mongo DB {@link Cursor}.
* <p>
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. The raw
* results will be mapped to the given entity class. The name of the inputCollection is derived from the inputType of
* the aggregation.
* <p>
* Aggregation streaming can't be used with {@link AggregationOptions#isExplain() aggregation explain}. Enabling
* explanation mode will throw an {@link IllegalArgumentException}.
*
* @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be
* {@literal null}.
* @param collectionName The name of the input collection to use for the aggreation.
* @param outputType The parameterized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 2.0
*/
<O> CloseableIterator<O> aggregateStream(TypedAggregation<?> aggregation, String collectionName, Class<O> outputType);
/**
* Execute an aggregation operation backed by a Mongo DB {@link Cursor}.
* <p/>
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. The raw
* results will be mapped to the given entity class and are returned as stream. The name of the inputCollection is
* derived from the inputType of the aggregation.
* <p/>
* Aggregation streaming can't be used with {@link AggregationOptions#isExplain() aggregation explain}. Enabling
* explanation mode will throw an {@link IllegalArgumentException}.
*
* @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be
* {@literal null}.
* @param outputType The parameterized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 2.0
*/
<O> CloseableIterator<O> aggregateStream(TypedAggregation<?> aggregation, Class<O> outputType);
/**
* Execute an aggregation operation backed by a Mongo DB {@link Cursor}.
* <p/>
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. The raw
* results will be mapped to the given entity class.
* <p/>
* Aggregation streaming can't be used with {@link AggregationOptions#isExplain() aggregation explain}. Enabling
* explanation mode will throw an {@link IllegalArgumentException}.
*
* @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be
* {@literal null}.
* @param inputType the inputType where the aggregation operation will read from, must not be {@literal null} or
* empty.
* @param outputType The parameterized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 2.0
*/
<O> CloseableIterator<O> aggregateStream(Aggregation aggregation, Class<?> inputType, Class<O> outputType);
/**
* Execute an aggregation operation backed by a Mongo DB {@link Cursor}.
* <p/>
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. The raw
* results will be mapped to the given entity class.
* <p/>
* Aggregation streaming can't be used with {@link AggregationOptions#isExplain() aggregation explain}. Enabling
* explanation mode will throw an {@link IllegalArgumentException}.
*
* @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be
* {@literal null}.
* @param collectionName the collection where the aggregation operation will read from, must not be {@literal null} or
* empty.
* @param outputType The parameterized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 2.0
*/
<O> CloseableIterator<O> aggregateStream(Aggregation aggregation, String collectionName, Class<O> outputType);
/**
* Execute a map-reduce operation. The map-reduce operation will be formed with an output type of INLINE
*
@@ -616,8 +692,8 @@ 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}.
* 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.
@@ -628,8 +704,8 @@ public interface MongoOperations {
<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}.
* 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.
@@ -641,8 +717,8 @@ public interface MongoOperations {
<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
* 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
@@ -655,8 +731,8 @@ public interface MongoOperations {
<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
* 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

View File

@@ -1,255 +0,0 @@
/*
* Copyright 2010-2015 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 javax.net.ssl.SSLSocketFactory;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.data.mongodb.util.MongoClientVersion;
import com.mongodb.MongoOptions;
/**
* A factory bean for construction of a {@link MongoOptions} instance. In case used with MongoDB Java driver version 3
* porperties not suppprted by the driver will be ignored.
*
* @author Graeme Rocher
* @author Mark Pollack
* @author Mike Saavedra
* @author Thomas Darimont
* @author Christoph Strobl
* @deprecated since 1.7. Please use {@link MongoClientOptionsFactoryBean} instead.
*/
@Deprecated
public class MongoOptionsFactoryBean extends AbstractFactoryBean<MongoOptions> {
private static final MongoOptions DEFAULT_MONGO_OPTIONS = new MongoOptions();
private int connectionsPerHost = DEFAULT_MONGO_OPTIONS.getConnectionsPerHost();
private int threadsAllowedToBlockForConnectionMultiplier = DEFAULT_MONGO_OPTIONS
.getThreadsAllowedToBlockForConnectionMultiplier();
private int maxWaitTime = DEFAULT_MONGO_OPTIONS.getMaxWaitTime();
private int connectTimeout = DEFAULT_MONGO_OPTIONS.getConnectTimeout();
private int socketTimeout = DEFAULT_MONGO_OPTIONS.getSocketTimeout();
private boolean socketKeepAlive = DEFAULT_MONGO_OPTIONS.isSocketKeepAlive();
private int writeNumber = DEFAULT_MONGO_OPTIONS.getW();
private int writeTimeout = DEFAULT_MONGO_OPTIONS.getWtimeout();
private boolean writeFsync = DEFAULT_MONGO_OPTIONS.isFsync();
private boolean autoConnectRetry = !MongoClientVersion.isMongo3Driver() ? ReflectiveMongoOptionsInvoker
.getAutoConnectRetry(DEFAULT_MONGO_OPTIONS) : false;
private long maxAutoConnectRetryTime = !MongoClientVersion.isMongo3Driver() ? ReflectiveMongoOptionsInvoker
.getMaxAutoConnectRetryTime(DEFAULT_MONGO_OPTIONS) : -1;
private boolean slaveOk = !MongoClientVersion.isMongo3Driver() ? ReflectiveMongoOptionsInvoker
.getSlaveOk(DEFAULT_MONGO_OPTIONS) : false;
private boolean ssl;
private SSLSocketFactory sslSocketFactory;
/**
* Configures the maximum number of connections allowed per host until we will block.
*
* @param connectionsPerHost
*/
public void setConnectionsPerHost(int connectionsPerHost) {
this.connectionsPerHost = connectionsPerHost;
}
/**
* 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.
*
* @param maxWaitTime
*/
public void setMaxWaitTime(int maxWaitTime) {
this.maxWaitTime = maxWaitTime;
}
/**
* Configures the connect timeout in milliseconds. Defaults to 0 (infinite time).
*
* @param connectTimeout
*/
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
/**
* Configures the socket timeout. Defaults to 0 (infinite time).
*
* @param socketTimeout
*/
public void setSocketTimeout(int socketTimeout) {
this.socketTimeout = socketTimeout;
}
/**
* Configures whether or not to have socket keep alive turned on (SO_KEEPALIVE). Defaults to {@literal false}.
*
* @param socketKeepAlive
*/
public void setSocketKeepAlive(boolean socketKeepAlive) {
this.socketKeepAlive = socketKeepAlive;
}
/**
* This specifies the number of servers to wait for on the write operation, and exception raising behavior. The 'w'
* option to the getlasterror command. Defaults to 0.
* <ul>
* <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>
* </ul>
*
* @param writeNumber the number of servers to wait for on the write operation, and exception raising behavior.
*/
public void setWriteNumber(int writeNumber) {
this.writeNumber = writeNumber;
}
/**
* Configures the timeout for write operations in milliseconds. This defaults to {@literal 0} (indefinite).
*
* @param writeTimeout
*/
public void setWriteTimeout(int writeTimeout) {
this.writeTimeout = writeTimeout;
}
/**
* Configures whether or not to fsync. The 'fsync' option to the getlasterror command. Defaults to {@literal false}.
*
* @param writeFsync to fsync on <code>write (true)<code>, otherwise {@literal false}.
*/
public void setWriteFsync(boolean writeFsync) {
this.writeFsync = writeFsync;
}
/**
* Configures whether or not the system retries automatically on a failed connect. This defaults to {@literal false}.
*
* @deprecated since 1.7.
*/
@Deprecated
public void setAutoConnectRetry(boolean autoConnectRetry) {
this.autoConnectRetry = autoConnectRetry;
}
/**
* 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
* @deprecated since 1.7
*/
@Deprecated
public void setMaxAutoConnectRetryTime(long maxAutoConnectRetryTime) {
this.maxAutoConnectRetryTime = maxAutoConnectRetryTime;
}
/**
* 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.
* @deprecated since 1.7
*/
@Deprecated
public void setSlaveOk(boolean slaveOk) {
this.slaveOk = slaveOk;
}
/**
* 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.config.AbstractFactoryBean#createInstance()
*/
@Override
protected MongoOptions createInstance() throws Exception {
if (MongoClientVersion.isMongo3Driver()) {
throw new IllegalArgumentException(
String
.format("Usage of 'mongo-options' is no longer supported for MongoDB Java driver version 3 and above. Please use 'mongo-client-options' and refer to chapter 'MongoDB 3.0 Support' for details."));
}
MongoOptions options = new MongoOptions();
options.setConnectionsPerHost(connectionsPerHost);
options.setThreadsAllowedToBlockForConnectionMultiplier(threadsAllowedToBlockForConnectionMultiplier);
options.setMaxWaitTime(maxWaitTime);
options.setConnectTimeout(connectTimeout);
options.setSocketTimeout(socketTimeout);
options.setSocketKeepAlive(socketKeepAlive);
options.setW(writeNumber);
options.setWtimeout(writeTimeout);
options.setFsync(writeFsync);
if (ssl) {
options.setSocketFactory(sslSocketFactory != null ? sslSocketFactory : SSLSocketFactory.getDefault());
}
ReflectiveMongoOptionsInvoker.setAutoConnectRetry(options, autoConnectRetry);
ReflectiveMongoOptionsInvoker.setMaxAutoConnectRetryTime(options, maxAutoConnectRetryTime);
ReflectiveMongoOptionsInvoker.setSlaveOk(options, slaveOk);
return options;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<?> getObjectType() {
return MongoOptions.class;
}
}

View File

@@ -46,10 +46,10 @@ import reactor.core.publisher.Mono;
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
* @see Flux
* @see Mono
* @see http://projectreactor.io/docs/
* @since 2.0
* @see <a href="http://projectreactor.io/docs/">Project Reactor</a>
*/
public interface ReactiveMongoOperations {
@@ -393,7 +393,7 @@ public interface ReactiveMongoOperations {
<T> Flux<GeoResult<T>> geoNear(NearQuery near, Class<T> entityClass, String collectionName);
/**
* Triggers <a href="http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
* Triggers <a href="https://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
@@ -405,7 +405,7 @@ public interface ReactiveMongoOperations {
<T> Mono<T> findAndModify(Query query, Update update, Class<T> entityClass);
/**
* Triggers <a href="http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
* Triggers <a href="https://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
@@ -418,7 +418,7 @@ public interface ReactiveMongoOperations {
<T> Mono<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/>
* Triggers <a href="https://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.
*
@@ -432,7 +432,7 @@ public interface ReactiveMongoOperations {
<T> Mono<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/>
* Triggers <a href="https://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.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,10 @@ package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -27,6 +31,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -54,6 +59,7 @@ import org.springframework.data.convert.EntityReader;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.Metric;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
@@ -87,6 +93,7 @@ import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.util.MongoClientVersion;
import org.springframework.data.util.Optionals;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -115,10 +122,6 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
import com.mongodb.reactivestreams.client.Success;
import com.mongodb.util.JSONParseException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
/**
* Primary implementation of {@link ReactiveMongoOperations}. It simplifies the use of Reactive MongoDB usage and helps
* to avoid common errors. It executes core MongoDB workflow, leaving application code to provide {@link Document} and
@@ -335,11 +338,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#executeCommand(org.bson.Document)
*/
public Mono<Document> executeCommand(final Document command) {
Assert.notNull(command, "Command must not be null!");
return createFlux(db -> readPreference != null ? db.runCommand(command, readPreference) : db.runCommand(command))
.next();
return executeCommand(command, null);
}
/* (non-Javadoc)
@@ -349,8 +348,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
Assert.notNull(command, "Command must not be null!");
return createFlux(db -> readPreference != null ? db.runCommand(command, readPreference) : db.runCommand(command))
.next();
return createFlux(db -> readPreference != null ? db.runCommand(command, readPreference, Document.class)
: db.runCommand(command, Document.class)).next();
}
/* (non-Javadoc)
@@ -373,7 +372,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#execute(java.lang.String, org.springframework.data.mongodb.core.ReactiveCollectionCallback)
*/
public <T> Flux<T> execute(String collectionName, ReactiveCollectionCallback<T> callback) {
Assert.notNull(callback);
Assert.notNull(callback, "ReactiveCollectionCallback must not be null!");
return createFlux(collectionName, callback);
}
@@ -386,9 +386,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public <T> Flux<T> createFlux(ReactiveDatabaseCallback<T> callback) {
Assert.notNull(callback);
Assert.notNull(callback, "ReactiveDatabaseCallback must not be null!");
return Flux.defer(() -> callback.doInDB(getMongoDatabase())).onErrorResumeWith(translateFluxException());
return Flux.defer(() -> callback.doInDB(getMongoDatabase())).onErrorMap(translateException());
}
/**
@@ -400,9 +400,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public <T> Mono<T> createMono(final ReactiveDatabaseCallback<T> callback) {
Assert.notNull(callback);
Assert.notNull(callback, "ReactiveDatabaseCallback must not be null!");
return Mono.defer(() -> Mono.from(callback.doInDB(getMongoDatabase()))).otherwise(translateMonoException());
return Mono.defer(() -> Mono.from(callback.doInDB(getMongoDatabase()))).onErrorMap(translateException());
}
/**
@@ -414,13 +414,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public <T> Flux<T> createFlux(String collectionName, ReactiveCollectionCallback<T> callback) {
Assert.hasText(collectionName);
Assert.notNull(callback);
Assert.hasText(collectionName, "Collection name must not be null or empty!");
Assert.notNull(callback, "ReactiveDatabaseCallback must not be null!");
Mono<MongoCollection<Document>> collectionPublisher = Mono
.fromCallable(() -> getAndPrepareCollection(getMongoDatabase(), collectionName));
return collectionPublisher.flatMap(callback::doInCollection).onErrorResumeWith(translateFluxException());
return collectionPublisher.flatMapMany(callback::doInCollection).onErrorMap(translateException());
}
/**
@@ -433,14 +433,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public <T> Mono<T> createMono(String collectionName, ReactiveCollectionCallback<T> callback) {
Assert.hasText(collectionName);
Assert.notNull(callback);
Assert.hasText(collectionName, "Collection name must not be null or empty!");
Assert.notNull(callback, "ReactiveCollectionCallback must not be null!");
Mono<MongoCollection<Document>> collectionPublisher = Mono
.fromCallable(() -> getAndPrepareCollection(getMongoDatabase(), collectionName));
return collectionPublisher.then(collection -> Mono.from(callback.doInCollection(collection)))
.otherwise(translateMonoException());
return collectionPublisher.flatMap(collection -> Mono.from(callback.doInCollection(collection)))
.onErrorMap(translateException());
}
/* (non-Javadoc)
@@ -539,8 +539,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public <T> Mono<T> findOne(Query query, Class<T> entityClass, String collectionName) {
if (query.getSortObject() == null) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass);
if (ObjectUtils.isEmpty(query.getSortObject())) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
query.getCollation().orElse(null));
}
query.limit(1);
@@ -573,7 +574,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return createFlux(collectionName, collection -> {
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass));
return collection.find(mappedQuery).limit(1);
FindPublisher<Document> findPublisher = collection.find(mappedQuery).projection(new Document("_id", 1));
findPublisher = query.getCollation().map(Collation::toMongoCollation).map(findPublisher::collation)
.orElse(findPublisher);
return findPublisher.limit(1);
}).hasElements();
}
@@ -609,12 +615,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public <T> Mono<T> findById(Object id, Class<T> entityClass, String collectionName) {
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityClass);
MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty();
Optional<? extends MongoPersistentEntity<?>> persistentEntity = mappingContext.getPersistentEntity(entityClass);
MongoPersistentProperty idProperty = persistentEntity.flatMap(PersistentEntity::getIdProperty).orElse(null);
String idKey = idProperty == null ? ID_FIELD : idProperty.getName();
return doFindOne(collectionName, new Document(idKey, id), null, entityClass);
return doFindOne(collectionName, new Document(idKey, id), null, entityClass, null);
}
/*
@@ -663,19 +669,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
GeoNearResultDbObjectCallback<T> callback = new GeoNearResultDbObjectCallback<T>(
new ReadDocumentCallback<T>(mongoConverter, entityClass, collectionName), near.getMetric());
return executeCommand(command, this.readPreference).flatMap(document -> {
return executeCommand(command, this.readPreference).flatMapMany(document -> {
List<Document> l = document.get("results", List.class);
if (l == null) {
return Flux.empty();
}
return Flux.fromIterable(l);
}).skip(near.getSkip() != null ? near.getSkip() : 0).map(new Function<Document, GeoResult<T>>() {
@Override
public GeoResult<T> apply(Document object) {
return callback.doWith(object);
}
});
}).skip(near.getSkip() != null ? near.getSkip() : 0).map(callback::doWith);
});
}
@@ -705,8 +706,18 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public <T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
String collectionName) {
FindAndModifyOptions optionsToUse = FindAndModifyOptions.of(options);
Optionals.ifAllPresent(query.getCollation(), optionsToUse.getCollation(), (l, r) -> {
throw new IllegalArgumentException(
"Both Query and FindAndModifyOptions define a collation. Please provide the collation only via one of the two.");
});
query.getCollation().ifPresent(optionsToUse::collation);
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), entityClass, update, options);
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
}
/* (non-Javadoc)
@@ -722,14 +733,16 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
public <T> Mono<T> findAndRemove(Query query, Class<T> entityClass, String collectionName) {
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), entityClass);
getMappedSortObject(query, entityClass), query.getCollation().orElse(null), entityClass);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class)
*/
public Mono<Long> count(Query query, Class<?> entityClass) {
Assert.notNull(entityClass);
Assert.notNull(entityClass, "Entity class must not be null!");
return count(query, entityClass, determineCollectionName(entityClass));
}
@@ -745,13 +758,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public Mono<Long> count(final Query query, final Class<?> entityClass, String collectionName) {
Assert.hasText(collectionName);
Assert.hasText(collectionName, "Collection name must not be null or empty!");
return createMono(collectionName, collection -> {
final Document Document = query == null ? null
: queryMapper.getMappedObject(query.getQueryObject(),
entityClass == null ? null : mappingContext.getPersistentEntity(entityClass));
entityClass == null ? Optional.empty() : mappingContext.getPersistentEntity(entityClass));
return collection.count(Document);
});
@@ -762,7 +775,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
@Override
public <T> Mono<T> insert(Mono<? extends T> objectToSave) {
return objectToSave.then(this::insert);
return objectToSave.flatMap(this::insert);
}
/* (non-Javadoc)
@@ -812,7 +825,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
maybeEmitEvent(new BeforeSaveEvent<T>(objectToSave, dbDoc, collectionName));
Mono<T> afterInsert = insertDBObject(collectionName, dbDoc, objectToSave.getClass()).then(id -> {
Mono<T> afterInsert = insertDBObject(collectionName, dbDoc, objectToSave.getClass()).flatMap(id -> {
populateIdIfNecessary(objectToSave, id);
maybeEmitEvent(new AfterSaveEvent<T>(objectToSave, dbDoc, collectionName));
return Mono.just(objectToSave);
@@ -856,11 +869,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
final Map<String, List<T>> elementsByCollection = new HashMap<String, List<T>>();
listToSave.forEach(element -> {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(element.getClass());
if (entity == null) {
throw new InvalidDataAccessApiUsageException("No PersistentEntity information found for " + element.getClass());
}
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(element.getClass());
String collection = entity.getCollection();
List<T> collectionElements = elementsByCollection.get(collection);
@@ -880,7 +889,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
protected <T> Flux<T> doInsertBatch(final String collectionName, final Collection<? extends T> batchToSave,
final MongoWriter<Object> writer) {
Assert.notNull(writer);
Assert.notNull(writer, "MongoWriter must not be null!");
Mono<List<Tuple2<T, Document>>> prepareDocuments = Flux.fromIterable(batchToSave)
.flatMap(new Function<T, Flux<Tuple2<T, Document>>>() {
@@ -897,7 +906,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
}).collectList();
Flux<Tuple2<T, Document>> insertDocuments = prepareDocuments.flatMap(tuples -> {
Flux<Tuple2<T, Document>> insertDocuments = prepareDocuments.flatMapMany(tuples -> {
List<Document> dbObjects = tuples.stream().map(Tuple2::getT2).collect(Collectors.toList());
@@ -917,7 +926,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
@Override
public <T> Mono<T> save(Mono<? extends T> objectToSave) {
return objectToSave.then(this::save);
return objectToSave.flatMap(this::save);
}
/* (non-Javadoc)
@@ -925,7 +934,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
@Override
public <T> Mono<T> save(Mono<? extends T> objectToSave, String collectionName) {
return objectToSave.then(o -> save(o, collectionName));
return objectToSave.flatMap(o -> save(o, collectionName));
}
/* (non-Javadoc)
@@ -933,7 +942,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public <T> Mono<T> save(T objectToSave) {
Assert.notNull(objectToSave);
Assert.notNull(objectToSave, "Object to save must not be null!");
return save(objectToSave, determineEntityCollectionName(objectToSave));
}
@@ -942,8 +951,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public <T> Mono<T> save(T objectToSave, String collectionName) {
Assert.notNull(objectToSave);
Assert.hasText(collectionName);
Assert.notNull(objectToSave, "Object to save must not be null!");
Assert.hasText(collectionName, "Collection name must not be null or empty!");
MongoPersistentEntity<?> mongoPersistentEntity = getPersistentEntity(objectToSave.getClass());
@@ -962,25 +971,29 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor(
entity.getPropertyAccessor(objectToSave), mongoConverter.getConversionService());
MongoPersistentProperty idProperty = entity.getIdProperty();
MongoPersistentProperty versionProperty = entity.getVersionProperty();
MongoPersistentProperty idProperty = entity.getIdProperty()
.orElseThrow(() -> new IllegalArgumentException("No id property present!"));
MongoPersistentProperty versionProperty = entity.getVersionProperty()
.orElseThrow(() -> new IllegalArgumentException("No version property present!"));
;
Object version = convertingAccessor.getProperty(versionProperty);
Number versionNumber = convertingAccessor.getProperty(versionProperty, Number.class);
Optional<Object> version = convertingAccessor.getProperty(versionProperty);
Optional<Number> versionNumber = convertingAccessor.getProperty(versionProperty, Number.class);
// Fresh instance -> initialize version property
if (version == null) {
if (!version.isPresent()) {
return doInsert(collectionName, objectToSave, mongoConverter);
}
ReactiveMongoTemplate.this.assertUpdateableIdIfNotSet(objectToSave);
// Create query for entity with the id and old version
Object id = convertingAccessor.getProperty(idProperty);
Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version));
Optional<Object> id = convertingAccessor.getProperty(idProperty);
Query query = new Query(
Criteria.where(idProperty.getName()).is(id.get()).and(versionProperty.getName()).is(version.get()));
// Bump version number
convertingAccessor.setProperty(versionProperty, versionNumber.longValue() + 1);
convertingAccessor.setProperty(versionProperty, Optional.of(versionNumber.orElse(0).longValue() + 1));
ReactiveMongoTemplate.this.maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave, collectionName));
@@ -1077,34 +1090,35 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return collectionToUse;
}
protected Mono<Object> saveDocument(final String collectionName, final Document dbDoc, final Class<?> entityClass) {
protected Mono<Object> saveDocument(final String collectionName, final Document document,
final Class<?> entityClass) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Saving Document containing fields: " + dbDoc.keySet());
LOGGER.debug("Saving Document containing fields: " + document.keySet());
}
return createMono(collectionName, collection -> {
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass,
dbDoc, null);
document, null);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
Publisher<?> publisher;
if (!dbDoc.containsKey(ID_FIELD)) {
if (!document.containsKey(ID_FIELD)) {
if (writeConcernToUse == null) {
publisher = collection.insertOne(dbDoc);
publisher = collection.insertOne(document);
} else {
publisher = collection.withWriteConcern(writeConcernToUse).insertOne(dbDoc);
publisher = collection.withWriteConcern(writeConcernToUse).insertOne(document);
}
} else if (writeConcernToUse == null) {
publisher = collection.replaceOne(Filters.eq(ID_FIELD, dbDoc.get(ID_FIELD)), dbDoc,
publisher = collection.replaceOne(Filters.eq(ID_FIELD, document.get(ID_FIELD)), document,
new UpdateOptions().upsert(true));
} else {
publisher = collection.withWriteConcern(writeConcernToUse).replaceOne(Filters.eq(ID_FIELD, dbDoc.get(ID_FIELD)),
dbDoc, new UpdateOptions().upsert(true));
publisher = collection.withWriteConcern(writeConcernToUse)
.replaceOne(Filters.eq(ID_FIELD, document.get(ID_FIELD)), document, new UpdateOptions().upsert(true));
}
return Mono.from(publisher).map(o -> dbDoc.get(ID_FIELD));
return Mono.from(publisher).map(o -> document.get(ID_FIELD));
});
}
@@ -1196,6 +1210,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
MongoCollection<Document> collectionToUse = prepareCollection(collection, writeConcernToUse);
UpdateOptions updateOptions = new UpdateOptions().upsert(upsert);
query.getCollation().map(Collation::toMongoCollation).ifPresent(updateOptions::collation);
if (!UpdateMapper.isUpdateObject(updateObj)) {
return collectionToUse.replaceOne(queryObj, updateObj, updateOptions);
@@ -1226,7 +1241,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
private void increaseVersionForUpdateIfNecessary(MongoPersistentEntity<?> persistentEntity, Update update) {
if (persistentEntity != null && persistentEntity.hasVersionProperty()) {
String versionFieldName = persistentEntity.getVersionProperty().getFieldName();
String versionFieldName = persistentEntity.getVersionProperty().get().getFieldName();
if (!update.modifies(versionFieldName)) {
update.inc(versionFieldName, 1L);
}
@@ -1239,7 +1254,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return false;
}
return document.containsKey(persistentEntity.getVersionProperty().getFieldName());
return document.containsKey(persistentEntity.getVersionProperty().get().getFieldName());
}
/* (non-Javadoc)
@@ -1247,7 +1262,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
@Override
public Mono<DeleteResult> remove(Mono<? extends Object> objectToRemove) {
return objectToRemove.then(this::remove);
return objectToRemove.flatMap(this::remove);
}
/* (non-Javadoc)
@@ -1255,7 +1270,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
@Override
public Mono<DeleteResult> remove(Mono<? extends Object> objectToRemove, String collection) {
return objectToRemove.then(o -> remove(objectToRemove, collection));
return objectToRemove.flatMap(o -> remove(objectToRemove, collection));
}
/* (non-Javadoc)
@@ -1275,7 +1290,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public Mono<DeleteResult> remove(Object object, String collection) {
Assert.hasText(collection);
Assert.hasText(collection, "Collection name must not be null or empty!");
if (object == null) {
return null;
@@ -1301,15 +1316,15 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return Collections.singletonMap(ID_FIELD, ((Document) object).get(ID_FIELD)).entrySet().iterator().next();
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(objectType);
MongoPersistentProperty idProp = entity == null ? null : entity.getIdProperty();
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(objectType);
MongoPersistentProperty idProp = entity.flatMap(PersistentEntity::getIdProperty).orElse(null);
if (idProp == 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();
Optional<Object> idValue = entity.get().getPropertyAccessor(object).getProperty(idProp);
return Collections.singletonMap(idProp.getFieldName(), idValue.get()).entrySet().iterator().next();
}
/**
@@ -1349,19 +1364,21 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
private void assertUpdateableIdIfNotSet(Object entity) {
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entity.getClass());
MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty();
Optional<? extends MongoPersistentEntity<?>> persistentEntity = mappingContext
.getPersistentEntity(entity.getClass());
Optional<MongoPersistentProperty> idProperty = persistentEntity.isPresent() ? persistentEntity.get().getIdProperty()
: Optional.empty();
if (idProperty == null) {
if (!idProperty.isPresent()) {
return;
}
Object idValue = persistentEntity.getPropertyAccessor(entity).getProperty(idProperty);
Optional<Object> idValue = persistentEntity.get().getPropertyAccessor(entity).getProperty(idProperty.get());
if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) {
if (!idValue.isPresent() && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.get().getType())) {
throw new InvalidDataAccessApiUsageException(
String.format("Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(),
entity.getClass().getName()));
String.format("Cannot autogenerate id of type %s for entity of type %s!",
idProperty.get().getType().getName(), entity.getClass().getName()));
}
}
@@ -1414,7 +1431,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
new Object[] { serializeToJsonSafely(dboq), collectionName });
}
query.getCollation().ifPresent(val -> {
// TODO: add collation support as soon as it's there! See https://jira.mongodb.org/browse/JAVARS-27
throw new IllegalArgumentException("DeleteMany does currently not accept collation settings.");
});
return collectionToUse.deleteMany(dboq);
}).doOnNext(deleteResult -> maybeEmitEvent(new AfterDeleteEvent<T>(queryObject, entityClass, collectionName)))
.next();
}
@@ -1496,7 +1520,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
Flux<T> flux = find(query, entityClass, collectionName);
return Flux.from(flux).collectList()
.flatMap(list -> Flux.from(remove(getIdInQueryFor(list), entityClass, collectionName))
.flatMapMany(list -> Flux.from(remove(getIdInQueryFor(list), entityClass, collectionName))
.flatMap(deleteResult -> Flux.fromIterable(list)));
}
@@ -1530,9 +1554,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @param entityClass the parameterized type of the returned list.
* @return the {@link List} of converted objects.
*/
protected <T> Mono<T> doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass) {
protected <T> Mono<T> doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass,
Collation collation) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
Document mappedQuery = queryMapper.getMappedObject(query, entity);
Document mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity);
@@ -1541,7 +1566,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
serializeToJsonSafely(query), mappedFields, entityClass, collectionName));
}
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields),
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, collation),
new ReadDocumentCallback<T>(this.mongoConverter, entityClass, collectionName), collectionName);
}
@@ -1582,7 +1607,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
protected <S, T> Flux<T> doFind(String collectionName, Document query, Document fields, Class<S> entityClass,
FindPublisherPreparer preparer, DocumentCallback<T> objectCallback) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
Document mappedFields = queryMapper.getMappedFields(fields, entity);
Document mappedQuery = queryMapper.getMappedObject(query, entity);
@@ -1601,17 +1626,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
CreateCollectionOptions result = new CreateCollectionOptions();
if (collectionOptions != null) {
if (collectionOptions.getCapped() != null) {
result = result.capped(collectionOptions.getCapped());
}
if (collectionOptions.getSize() != null) {
result = result.sizeInBytes(collectionOptions.getSize());
}
if (collectionOptions.getMaxDocuments() != null) {
result = result.maxDocuments(collectionOptions.getMaxDocuments());
}
collectionOptions.getCapped().ifPresent(result::capped);
collectionOptions.getSize().ifPresent(result::sizeInBytes);
collectionOptions.getMaxDocuments().ifPresent(result::maxDocuments);
collectionOptions.getCollation().map(Collation::toMongoCollation).ifPresent(result::collation);
}
return result;
}
@@ -1624,38 +1642,35 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*
* @param collectionName name of the collection to retrieve the objects from
* @param query the query document that specifies the criteria used to find a record
* @param collation collation
* @param entityClass the parameterized type of the returned list.
* @return the List of converted objects.
*/
protected <T> Mono<T> doFindAndRemove(String collectionName, Document query, Document fields, Document sort,
Class<T> entityClass) {
Collation collation, Class<T> entityClass) {
if (LOGGER.isDebugEnabled()) {
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);
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
return executeFindOneInternal(new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort),
return executeFindOneInternal(
new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort, collation),
new ReadDocumentCallback<T>(this.mongoConverter, entityClass, collectionName), collectionName);
}
protected <T> Mono<T> doFindAndModify(String collectionName, Document query, Document fields, Document sort,
Class<T> entityClass, Update update, FindAndModifyOptions options) {
FindAndModifyOptions optionsToUse;
if (options == null) {
optionsToUse = new FindAndModifyOptions();
} else {
optionsToUse = options;
}
FindAndModifyOptions optionsToUse = options != null ? options : new FindAndModifyOptions();
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
return Mono.defer(() -> {
increaseVersionForUpdateIfNecessary(entity, update);
increaseVersionForUpdateIfNecessary(entity.get(), update);
Document mappedQuery = queryMapper.getMappedObject(query, entity);
Document mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
@@ -1703,14 +1718,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
ConversionService conversionService = mongoConverter.getConversionService();
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(savedObject.getClass());
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(savedObject.getClass());
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(savedObject);
if (accessor.getProperty(idProp) != null) {
if (accessor.getProperty(idProp).isPresent()) {
return;
}
new ConvertingPropertyAccessor(accessor, conversionService).setProperty(idProp, id);
new ConvertingPropertyAccessor(accessor, conversionService).setProperty(idProp, Optional.ofNullable(id));
}
private MongoCollection<Document> getAndPrepareCollection(MongoDatabase db, String collectionName) {
@@ -1839,7 +1854,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
private <T> T execute(MongoDatabaseCallback<T> action) {
Assert.notNull(action);
Assert.notNull(action, "MongoDatabaseCallback must not be null!");
try {
MongoDatabase db = this.getMongoDatabase();
@@ -1850,36 +1865,19 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
/**
* Exception translation {@link Function} intended for {@link Flux#onErrorResumeWith(Function)} usage.
* Exception translation {@link Function} intended for {@link Flux#mapError(Function)}} usage.
*
* @return the exception translation {@link Function}
*/
private <T> Function<Throwable, Publisher<? extends T>> translateFluxException() {
private Function<Throwable, Throwable> translateException() {
return throwable -> {
if (throwable instanceof RuntimeException) {
return Flux.error(potentiallyConvertRuntimeException((RuntimeException) throwable, exceptionTranslator));
return potentiallyConvertRuntimeException((RuntimeException) throwable, exceptionTranslator);
}
return Flux.error(throwable);
};
}
/**
* Exception translation {@link Function} intended for {@link Mono#otherwise(Function)} usage.
*
* @return the exception translation {@link Function}
*/
private <T> Function<Throwable, Mono<? extends T>> translateMonoException() {
return throwable -> {
if (throwable instanceof RuntimeException) {
return Mono.error(potentiallyConvertRuntimeException((RuntimeException) throwable, exceptionTranslator));
}
return Mono.error(throwable);
return throwable;
};
}
@@ -1898,12 +1896,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
private MongoPersistentEntity<?> getPersistentEntity(Class<?> type) {
return type == null ? null : mappingContext.getPersistentEntity(type);
return type == null ? null : mappingContext.getPersistentEntity(type).orElse(null);
}
private MongoPersistentProperty getIdPropertyFor(Class<?> type) {
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(type);
return persistentEntity == null ? null : persistentEntity.getIdProperty();
Optional<? extends MongoPersistentEntity<?>> persistentEntity = mappingContext.getPersistentEntity(type);
return persistentEntity.flatMap(PersistentEntity::getIdProperty).orElse(null);
}
private <T> String determineEntityCollectionName(T obj) {
@@ -1922,13 +1921,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
"No class parameter provided, entity collection can't be determined!");
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
if (entity == null) {
throw new InvalidDataAccessApiUsageException(
"No Persistent Entity information found for the class " + entityClass.getName());
}
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(entityClass);
return entity.getCollection();
}
@@ -1983,7 +1976,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
if (mongoPersistentEntity != null && mongoPersistentEntity.hasVersionProperty()) {
ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(
mongoPersistentEntity.getPropertyAccessor(entity), mongoConverter.getConversionService());
accessor.setProperty(mongoPersistentEntity.getVersionProperty(), 0);
accessor.setProperty(mongoPersistentEntity.getVersionProperty().get(), Optional.of(0));
}
}
@@ -1999,34 +1992,34 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
private static class FindOneCallback implements ReactiveCollectionCallback<Document> {
private final Document query;
private final Document fields;
private final Optional<Document> fields;
private final Optional<Collation> collation;
FindOneCallback(Document query, Document fields) {
FindOneCallback(Document query, Document fields, Collation collation) {
this.query = query;
this.fields = fields;
this.fields = Optional.ofNullable(fields);
this.collation = Optional.ofNullable(collation);
}
@Override
public Publisher<Document> doInCollection(MongoCollection<Document> collection)
throws MongoException, DataAccessException {
if (fields == null) {
FindPublisher<Document> publisher = collection.find(query);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("findOne using query: %s in db.collection: %s", serializeToJsonSafely(query),
collection.getNamespace().getFullName()));
}
if (LOGGER.isDebugEnabled()) {
return collection.find(query).limit(1).first();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("findOne using query: %s fields: %s in db.collection: %s",
serializeToJsonSafely(query), fields, collection.getNamespace().getFullName()));
}
return collection.find(query).projection(fields).limit(1);
LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query),
serializeToJsonSafely(fields.orElseGet(Document::new)), collection.getNamespace().getFullName());
}
if (fields.isPresent()) {
publisher = publisher.projection(fields.get());
}
publisher = collation.map(Collation::toMongoCollation).map(publisher::collation).orElse(publisher);
return publisher.limit(1).first();
}
}
@@ -2079,12 +2072,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
private final Document query;
private final Document fields;
private final Document sort;
private final Optional<Collation> collation;
FindAndRemoveCallback(Document query, Document fields, Document sort) {
FindAndRemoveCallback(Document query, Document fields, Document sort, Collation collation) {
this.query = query;
this.fields = fields;
this.sort = sort;
this.collation = Optional.ofNullable(collation);
}
@Override
@@ -2092,6 +2087,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
throws MongoException, DataAccessException {
FindOneAndDeleteOptions findOneAndDeleteOptions = convertToFindOneAndDeleteOptions(fields, sort);
collation.map(Collation::toMongoCollation).ifPresent(findOneAndDeleteOptions::collation);
return collection.findOneAndDelete(query, findOneAndDeleteOptions);
}
}
@@ -2123,6 +2120,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
if (options.isRemove()) {
FindOneAndDeleteOptions findOneAndDeleteOptions = convertToFindOneAndDeleteOptions(fields, sort);
findOneAndDeleteOptions = options.getCollation().map(Collation::toMongoCollation)
.map(findOneAndDeleteOptions::collation).orElse(findOneAndDeleteOptions);
return collection.findOneAndDelete(query, findOneAndDeleteOptions);
}
@@ -2143,6 +2144,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
result = result.returnDocument(ReturnDocument.BEFORE);
}
result = options.getCollation().map(Collation::toMongoCollation).map(result::collation).orElse(result);
return result;
}
}
@@ -2202,8 +2205,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
ReadDocumentCallback(EntityReader<? super T, Bson> reader, Class<T> type, String collectionName) {
Assert.notNull(reader);
Assert.notNull(type);
Assert.notNull(reader, "EntityReader must not be null!");
Assert.notNull(type, "Entity type must not be null!");
this.reader = reader;
this.type = type;
this.collectionName = collectionName;
@@ -2240,7 +2244,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
GeoNearResultDbObjectCallback(DocumentCallback<T> delegate, Metric metric) {
Assert.notNull(delegate);
Assert.notNull(delegate, "DocumentCallback must not be null!");
this.delegate = delegate;
this.metric = metric;
}
@@ -2276,21 +2281,24 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return findPublisher;
}
FindPublisher<T> findPublisherToUse;
findPublisherToUse = query.getCollation().map(Collation::toMongoCollation).map(findPublisher::collation)
.orElse(findPublisher);
if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null
&& !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) {
return findPublisher;
return findPublisherToUse;
}
FindPublisher<T> findPublisherToUse = findPublisher;
try {
if (query.getSkip() > 0) {
findPublisherToUse = findPublisherToUse.skip(query.getSkip());
findPublisherToUse = findPublisherToUse.skip((int) query.getSkip());
}
if (query.getLimit() > 0) {
findPublisherToUse = findPublisherToUse.limit(query.getLimit());
}
if (query.getSortObject() != null) {
if (!ObjectUtils.isEmpty(query.getSortObject())) {
Document sort = type != null ? getMappedSortObject(query, type) : query.getSortObject();
findPublisherToUse = findPublisherToUse.sort(sort);
}
@@ -2341,9 +2349,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
static class NoOpDbRefResolver implements DbRefResolver {
@Override
public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
public Optional<Object> resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
DbRefProxyHandler proxyHandler) {
return null;
return Optional.empty();
}
@Override

View File

@@ -1,109 +0,0 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.util.MongoClientVersion.*;
import static org.springframework.util.ReflectionUtils.*;
import java.lang.reflect.Method;
import org.bson.Document;
import org.springframework.data.mongodb.util.MongoClientVersion;
import com.mongodb.DBCollection;
/**
* {@link ReflectiveDBCollectionInvoker} provides reflective access to {@link DBCollection} API that is not consistently
* available for various driver versions.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
*/
class ReflectiveDBCollectionInvoker {
private static final Method GEN_INDEX_NAME_METHOD;
private static final Method RESET_INDEX_CHACHE_METHOD;
static {
GEN_INDEX_NAME_METHOD = findMethod(DBCollection.class, "genIndexName", Document.class);
RESET_INDEX_CHACHE_METHOD = findMethod(DBCollection.class, "resetIndexCache");
}
private ReflectiveDBCollectionInvoker() {}
/**
* Convenience method to generate an index name from the set of fields it is over. Will fall back to a MongoDB Java
* driver version 2 compatible way of generating index name in case of {@link MongoClientVersion#isMongo3Driver()}.
*
* @param keys the names of the fields used in this index
* @return
*/
public static String generateIndexName(Document keys) {
if (isMongo3Driver()) {
return genIndexName(keys);
}
return (String) invokeMethod(GEN_INDEX_NAME_METHOD, null, keys);
}
/**
* In case of MongoDB Java driver version 2 all indices that have not yet been applied to this collection will be
* cleared. Since this method is not available for the MongoDB Java driver version 3 the operation will throw
* {@link UnsupportedOperationException}.
*
* @param dbCollection
* @throws UnsupportedOperationException
*/
public static void resetIndexCache(DBCollection dbCollection) {
if (isMongo3Driver()) {
throw new UnsupportedOperationException("The mongo java driver 3 does no loger support resetIndexCache!");
}
invokeMethod(RESET_INDEX_CHACHE_METHOD, dbCollection);
}
/**
* Borrowed from MongoDB Java driver version 2. See
* <a href="http://github.com/mongodb/mongo-java-driver/blob/r2.13.0/src/main/com/mongodb/DBCollection.java#L754" >
* http://github.com/mongodb/mongo-java-driver/blob/r2.13.0/src/main/com/mongodb/DBCollection.java#L754</a>
*
* @param keys
* @return
*/
private static String genIndexName(Document keys) {
StringBuilder name = new StringBuilder();
for (String s : keys.keySet()) {
if (name.length() > 0) {
name.append('_');
}
name.append(s).append('_');
Object val = keys.get(s);
if (val instanceof Number || val instanceof String) {
name.append(val.toString().replace(' ', '_'));
}
}
return name.toString();
}
}

View File

@@ -1,134 +0,0 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.util.MongoClientVersion.*;
import static org.springframework.util.ReflectionUtils.*;
import java.lang.reflect.Method;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.CannotGetMongoDbConnectionException;
import org.springframework.data.mongodb.util.MongoClientVersion;
import com.mongodb.DB;
import com.mongodb.Mongo;
/**
* {@link ReflectiveDbInvoker} provides reflective access to {@link DB} API that is not consistently available for
* various driver versions.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
*/
final class ReflectiveDbInvoker {
private static final Method DB_IS_AUTHENTICATED_METHOD;
private static final Method DB_AUTHENTICATE_METHOD;
private static final Method DB_REQUEST_DONE_METHOD;
private static final Method DB_ADD_USER_METHOD;
private static final Method DB_REQUEST_START_METHOD;
static {
DB_IS_AUTHENTICATED_METHOD = findMethod(DB.class, "isAuthenticated");
DB_AUTHENTICATE_METHOD = findMethod(DB.class, "authenticate", String.class, char[].class);
DB_REQUEST_DONE_METHOD = findMethod(DB.class, "requestDone");
DB_ADD_USER_METHOD = findMethod(DB.class, "addUser", String.class, char[].class);
DB_REQUEST_START_METHOD = findMethod(DB.class, "requestStart");
}
private ReflectiveDbInvoker() {}
/**
* Authenticate against database using provided credentials in case of a MongoDB Java driver version 2.
*
* @param mongo must not be {@literal null}.
* @param db must not be {@literal null}.
* @param credentials must not be {@literal null}.
* @param authenticationDatabaseName
*/
public static void authenticate(Mongo mongo, DB db, UserCredentials credentials, String authenticationDatabaseName) {
String databaseName = db.getName();
DB authDb = databaseName.equals(authenticationDatabaseName) ? db : mongo.getDB(authenticationDatabaseName);
synchronized (authDb) {
Boolean isAuthenticated = (Boolean) invokeMethod(DB_IS_AUTHENTICATED_METHOD, authDb);
if (!isAuthenticated) {
String username = credentials.getUsername();
String password = credentials.hasPassword() ? credentials.getPassword() : null;
Boolean authenticated = (Boolean) invokeMethod(DB_AUTHENTICATE_METHOD, authDb, username,
password == null ? null : password.toCharArray());
if (!authenticated) {
throw new CannotGetMongoDbConnectionException("Failed to authenticate to database [" + databaseName + "], "
+ credentials.toString(), databaseName, credentials);
}
}
}
}
/**
* Starts a new 'consistent request' in case of MongoDB Java driver version 2. Will do nothing for MongoDB Java driver
* version 3 since the operation is no longer available.
*
* @param db
*/
public static void requestStart(DB db) {
if (isMongo3Driver()) {
return;
}
invokeMethod(DB_REQUEST_START_METHOD, db);
}
/**
* Ends the current 'consistent request'. a new 'consistent request' in case of MongoDB Java driver version 2. Will do
* nothing for MongoDB Java driver version 3 since the operation is no longer available
*
* @param db
*/
public static void requestDone(DB db) {
if (MongoClientVersion.isMongo3Driver()) {
return;
}
invokeMethod(DB_REQUEST_DONE_METHOD, db);
}
/**
* @param db
* @param username
* @param password
* @throws UnsupportedOperationException
*/
public static void addUser(DB db, String username, char[] password) {
if (isMongo3Driver()) {
throw new UnsupportedOperationException(
"Please use DB.command(…) to call either the createUser or updateUser command!");
}
invokeMethod(DB_ADD_USER_METHOD, db, username, password);
}
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.util.MongoClientVersion.*;
import static org.springframework.util.ReflectionUtils.*;
import java.lang.reflect.Method;
import org.springframework.util.Assert;
import com.mongodb.MapReduceCommand;
/**
* {@link ReflectiveMapReduceInvoker} provides reflective access to {@link MapReduceCommand} API that is not
* consistently available for various driver versions.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
*/
final class ReflectiveMapReduceInvoker {
private static final Method ADD_EXTRA_OPTION_METHOD;
static {
ADD_EXTRA_OPTION_METHOD = findMethod(MapReduceCommand.class, "addExtraOption", String.class, Object.class);
}
private ReflectiveMapReduceInvoker() {}
/**
* Sets the extra option for MongoDB Java driver version 2. Will do nothing for MongoDB Java driver version 2.
*
* @param cmd can be {@literal null} for MongoDB Java driver version 2.
* @param key
* @param value
*/
public static void addExtraOption(MapReduceCommand cmd, String key, Object value) {
if (isMongo3Driver()) {
return;
}
Assert.notNull(cmd, "MapReduceCommand must not be null!");
invokeMethod(ADD_EXTRA_OPTION_METHOD, cmd, key, value);
}
}

View File

@@ -1,158 +0,0 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.util.MongoClientVersion.*;
import static org.springframework.util.ReflectionUtils.*;
import java.lang.reflect.Method;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.util.ReflectionUtils;
import com.mongodb.MongoOptions;
/**
* {@link ReflectiveMongoOptionsInvoker} provides reflective access to {@link MongoOptions} API that is not consistently
* available for various driver versions.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
*/
@SuppressWarnings("deprecation")
class ReflectiveMongoOptionsInvoker {
private static final Method GET_AUTO_CONNECT_RETRY_METHOD;
private static final Method SET_AUTO_CONNECT_RETRY_METHOD;
private static final Method GET_MAX_AUTO_CONNECT_RETRY_TIME_METHOD;
private static final Method SET_MAX_AUTO_CONNECT_RETRY_TIME_METHOD;
static {
SET_AUTO_CONNECT_RETRY_METHOD = ReflectionUtils
.findMethod(MongoOptions.class, "setAutoConnectRetry", boolean.class);
GET_AUTO_CONNECT_RETRY_METHOD = ReflectionUtils.findMethod(MongoOptions.class, "isAutoConnectRetry");
SET_MAX_AUTO_CONNECT_RETRY_TIME_METHOD = ReflectionUtils.findMethod(MongoOptions.class,
"setMaxAutoConnectRetryTime", long.class);
GET_MAX_AUTO_CONNECT_RETRY_TIME_METHOD = ReflectionUtils.findMethod(MongoOptions.class,
"getMaxAutoConnectRetryTime");
}
private ReflectiveMongoOptionsInvoker() {}
/**
* Sets the retry connection flag for MongoDB Java driver version 2. Will do nothing for MongoDB Java driver version 3
* since the method has been removed.
*
* @param options can be {@literal null} for MongoDB Java driver version 3.
* @param autoConnectRetry
*/
public static void setAutoConnectRetry(MongoOptions options, boolean autoConnectRetry) {
if (isMongo3Driver()) {
return;
}
invokeMethod(SET_AUTO_CONNECT_RETRY_METHOD, options, autoConnectRetry);
}
/**
* Sets the maxAutoConnectRetryTime attribute for MongoDB Java driver version 2. Will do nothing for MongoDB Java
* driver version 3 since the method has been removed.
*
* @param options can be {@literal null} for MongoDB Java driver version 3.
* @param maxAutoConnectRetryTime
*/
public static void setMaxAutoConnectRetryTime(MongoOptions options, long maxAutoConnectRetryTime) {
if (isMongo3Driver()) {
return;
}
invokeMethod(SET_MAX_AUTO_CONNECT_RETRY_TIME_METHOD, options, maxAutoConnectRetryTime);
}
/**
* Sets the slaveOk attribute for MongoDB Java driver version 2. Will do nothing for MongoDB Java driver version 3
* since the method has been removed.
*
* @param options can be {@literal null} for MongoDB Java driver version 3.
* @param slaveOk
*/
public static void setSlaveOk(MongoOptions options, boolean slaveOk) {
if (isMongo3Driver()) {
return;
}
new DirectFieldAccessor(options).setPropertyValue("slaveOk", slaveOk);
}
/**
* Gets the slaveOk attribute for MongoDB Java driver version 2. Throws {@link UnsupportedOperationException} for
* MongoDB Java driver version 3 since the method has been removed.
*
* @param options can be {@literal null} for MongoDB Java driver version 3.
* @return
* @throws UnsupportedOperationException
*/
public static boolean getSlaveOk(MongoOptions options) {
if (isMongo3Driver()) {
throw new UnsupportedOperationException(
"Cannot get value for autoConnectRetry which has been removed in MongoDB Java driver version 3.");
}
return ((Boolean) new DirectFieldAccessor(options).getPropertyValue("slaveOk")).booleanValue();
}
/**
* Gets the autoConnectRetry attribute for MongoDB Java driver version 2. Throws {@link UnsupportedOperationException}
* for MongoDB Java driver version 3 since the method has been removed.
*
* @param options can be {@literal null} for MongoDB Java driver version 3.
* @return
* @throws UnsupportedOperationException
*/
public static boolean getAutoConnectRetry(MongoOptions options) {
if (isMongo3Driver()) {
throw new UnsupportedOperationException(
"Cannot get value for autoConnectRetry which has been removed in MongoDB Java driver version 3.");
}
return ((Boolean) invokeMethod(GET_AUTO_CONNECT_RETRY_METHOD, options)).booleanValue();
}
/**
* Gets the maxAutoConnectRetryTime attribute for MongoDB Java driver version 2. Throws
* {@link UnsupportedOperationException} for MongoDB Java driver version 3 since the method has been removed.
*
* @param options can be {@literal null} for MongoDB Java driver version 3.
* @return
* @throws UnsupportedOperationException
*/
public static long getMaxAutoConnectRetryTime(MongoOptions options) {
if (isMongo3Driver()) {
throw new UnsupportedOperationException(
"Cannot get value for maxAutoConnectRetryTime which has been removed in MongoDB Java driver version 3.");
}
return ((Long) invokeMethod(GET_MAX_AUTO_CONNECT_RETRY_TIME_METHOD, options)).longValue();
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.util.MongoClientVersion.*;
import org.springframework.beans.DirectFieldAccessor;
import com.mongodb.WriteConcern;
/**
* {@link ReflectiveWriteConcernInvoker} provides reflective access to {@link WriteConcern} API that is not consistently
* available for various driver versions.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
*/
class ReflectiveWriteConcernInvoker {
private static final WriteConcern NONE_OR_UNACKNOWLEDGED;
static {
NONE_OR_UNACKNOWLEDGED = isMongo3Driver() ? WriteConcern.UNACKNOWLEDGED : (WriteConcern) new DirectFieldAccessor(
new WriteConcern()).getPropertyValue("NONE");
}
/**
* @return {@link WriteConcern#NONE} for MongoDB Java driver version 2, otherwise {@link WriteConcern#UNACKNOWLEDGED}.
*/
public static WriteConcern noneOrUnacknowledged() {
return NONE_OR_UNACKNOWLEDGED;
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.util.MongoClientVersion.*;
import static org.springframework.util.ReflectionUtils.*;
import java.lang.reflect.Method;
import com.mongodb.MongoException;
import com.mongodb.WriteResult;
import com.mongodb.client.result.UpdateResult;
/**
* {@link ReflectiveWriteResultInvoker} provides reflective access to {@link WriteResult} API that is not consistently
* available for various driver versions.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
*/
final class ReflectiveWriteResultInvoker {
private static final Method GET_ERROR_METHOD;
private static final Method WAS_ACKNOWLEDGED_METHOD;
private static final Method WAS_ACKNOWLEDGED_METHOD_UR;
private ReflectiveWriteResultInvoker() {}
static {
GET_ERROR_METHOD = findMethod(WriteResult.class, "getError");
WAS_ACKNOWLEDGED_METHOD = findMethod(WriteResult.class, "wasAcknowledged");
WAS_ACKNOWLEDGED_METHOD_UR = findMethod(UpdateResult.class, "wasAcknowledged");
}
/**
* @param writeResult can be {@literal null} for MongoDB Java driver version 3.
* @return null in case of MongoDB Java driver version 3 since errors are thrown as {@link MongoException}.
*/
public static String getError(WriteResult writeResult) {
if (isMongo3Driver()) {
return null;
}
return (String) invokeMethod(GET_ERROR_METHOD, writeResult);
}
/**
* @param writeResult
* @return return in case of MongoDB Java driver version 2.
*/
public static boolean wasAcknowledged(WriteResult writeResult) {
return isMongo3Driver() ? ((Boolean) invokeMethod(WAS_ACKNOWLEDGED_METHOD, writeResult)).booleanValue() : true;
}
/**
* @param writeResult
* @return return in case of MongoDB Java driver version 2.
*/
public static boolean wasAcknowledged(UpdateResult writeResult) {
return isMongo3Driver() ? ((Boolean) invokeMethod(WAS_ACKNOWLEDGED_METHOD_UR, writeResult)).booleanValue() : true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 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,24 +19,18 @@ import java.net.UnknownHostException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
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;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoException;
import com.mongodb.MongoURI;
import com.mongodb.WriteConcern;
import com.mongodb.client.MongoDatabase;
/**
* Factory to create {@link DB} instances from a {@link Mongo} instance.
* Factory to create {@link DB} instances from a {@link MongoClient} instance.
*
* @author Mark Pollack
* @author Oliver Gierke
@@ -45,70 +39,13 @@ import com.mongodb.client.MongoDatabase;
*/
public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
private final Mongo mongo;
private final MongoClient mongoClient;
private final String databaseName;
private final boolean mongoInstanceCreated;
private final UserCredentials credentials;
private final PersistenceExceptionTranslator exceptionTranslator;
private final String authenticationDatabaseName;
private WriteConcern writeConcern;
/**
* Create an instance of {@link SimpleMongoDbFactory} given the {@link Mongo} instance and database name.
*
* @param mongo Mongo instance, must not be {@literal null}.
* @param databaseName database name, not be {@literal null} or empty.
* @deprecated since 1.7. Please use {@link #SimpleMongoDbFactory(MongoClient, String)}.
*/
@Deprecated
public SimpleMongoDbFactory(Mongo mongo, String databaseName) {
this(mongo, databaseName, 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.
* @deprecated since 1.7. The credentials used should be provided by {@link MongoClient#getCredentialsList()}.
*/
@Deprecated
public SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials) {
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
* @deprecated since 1.7. The credentials used should be provided by {@link MongoClient#getCredentialsList()}.
*/
@Deprecated
public SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials,
String authenticationDatabaseName) {
this(mongo, databaseName, credentials, false, authenticationDatabaseName);
}
/**
* Creates a new {@link SimpleMongoDbFactory} instance from the given {@link MongoURI}.
*
* @param uri must not be {@literal null}.
* @throws MongoException
* @throws UnknownHostException
* @see MongoURI
* @deprecated since 1.7. Please use {@link #SimpleMongoDbFactory(MongoClientURI)} instead.
*/
@Deprecated
public SimpleMongoDbFactory(MongoURI uri) throws MongoException, UnknownHostException {
this(new Mongo(uri), uri.getDatabase(), new UserCredentials(uri.getUsername(), parseChars(uri.getPassword())), true,
uri.getDatabase());
}
/**
* Creates a new {@link SimpleMongoDbFactory} instance from the given {@link MongoClientURI}.
*
@@ -116,7 +53,7 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
* @throws UnknownHostException
* @since 1.7
*/
public SimpleMongoDbFactory(MongoClientURI uri) throws UnknownHostException {
public SimpleMongoDbFactory(MongoClientURI uri) {
this(new MongoClient(uri), uri.getDatabase(), true);
}
@@ -131,48 +68,23 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
this(mongoClient, databaseName, false);
}
private SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials,
boolean mongoInstanceCreated, String authenticationDatabaseName) {
if (mongo instanceof MongoClient && (credentials != null && !UserCredentials.NO_CREDENTIALS.equals(credentials))) {
throw new InvalidDataAccessApiUsageException(
"Usage of 'UserCredentials' with 'MongoClient' is no longer supported. Please use 'MongoCredential' for 'MongoClient' or just 'Mongo'.");
}
Assert.notNull(mongo, "Mongo must not be null");
Assert.hasText(databaseName, "Database name must not be empty");
Assert.isTrue(databaseName.matches("[\\w-]+"),
"Database name must only contain letters, numbers, underscores and dashes!");
this.mongo = mongo;
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!");
}
/**
* @param client
* @param databaseName
* @param mongoInstanceCreated
* @since 1.7
*/
private SimpleMongoDbFactory(MongoClient client, String databaseName, boolean mongoInstanceCreated) {
private SimpleMongoDbFactory(MongoClient mongoClient, String databaseName, boolean mongoInstanceCreated) {
Assert.notNull(client, "MongoClient must not be null!");
Assert.notNull(mongoClient, "MongoClient must not be null!");
Assert.hasText(databaseName, "Database name must not be empty!");
Assert.isTrue(databaseName.matches("[\\w-]+"),
"Database name must only contain letters, numbers, underscores and dashes!");
this.mongo = client;
this.mongoClient = mongoClient;
this.databaseName = databaseName;
this.mongoInstanceCreated = mongoInstanceCreated;
this.exceptionTranslator = new MongoExceptionTranslator();
this.credentials = UserCredentials.NO_CREDENTIALS;
this.authenticationDatabaseName = databaseName;
}
/**
@@ -200,7 +112,7 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
Assert.hasText(dbName, "Database name must not be empty.");
MongoDatabase db = ((MongoClient) mongo).getDatabase(dbName);
MongoDatabase db = mongoClient.getDatabase(dbName);
if (writeConcern == null) {
return db;
@@ -216,14 +128,10 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
*/
public void destroy() throws Exception {
if (mongoInstanceCreated) {
mongo.close();
mongoClient.close();
}
}
private static String parseChars(char[] chars) {
return chars == null ? null : String.valueOf(chars);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.MongoDbFactory#getExceptionTranslator()
@@ -236,6 +144,6 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
@SuppressWarnings("deprecation")
@Override
public DB getLegacyDb() {
return mongo.getDB(databaseName);
return mongoClient.getDB(databaseName);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2017 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,12 @@ package org.springframework.data.mongodb.core;
/**
* Enum to represent how strict the check of {@link com.mongodb.WriteResult} shall be. It can either be skipped entirely
* (use {@link #NONE}), or errors can be logged ({@link #LOG}) or cause an exception to be thrown {@link #EXCEPTION}.
* (use {@link #NONE}) or cause an exception to be thrown {@link #EXCEPTION}.
*
* @author Thomas Risberg
* @author Oliver Gierke
*/
public enum WriteResultChecking {
NONE, LOG, EXCEPTION
NONE, EXCEPTION
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2016. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import org.bson.Document;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
* @since 1.10
*/
abstract class AbstractAggregationExpression implements AggregationExpression {
private final Object value;
protected AbstractAggregationExpression(Object value) {
this.value = value;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
return toDocument(this.value, context);
}
@SuppressWarnings("unchecked")
public Document toDocument(Object value, AggregationOperationContext context) {
Object valueToUse;
if (value instanceof List) {
List<Object> arguments = (List<Object>) value;
List<Object> args = new ArrayList<Object>(arguments.size());
for (Object val : arguments) {
args.add(unpack(val, context));
}
valueToUse = args;
} else if (value instanceof java.util.Map) {
Document dbo = new Document();
for (java.util.Map.Entry<String, Object> entry : ((java.util.Map<String, Object>) value).entrySet()) {
dbo.put(entry.getKey(), unpack(entry.getValue(), context));
}
valueToUse = dbo;
} else {
valueToUse = unpack(value, context);
}
return new Document(getMongoMethod(), valueToUse);
}
protected static List<Field> asFields(String... fieldRefs) {
if (ObjectUtils.isEmpty(fieldRefs)) {
return Collections.emptyList();
}
return Fields.fields(fieldRefs).asList();
}
@SuppressWarnings("unchecked")
private Object unpack(Object value, AggregationOperationContext context) {
if (value instanceof AggregationExpression) {
return ((AggregationExpression) value).toDocument(context);
}
if (value instanceof Field) {
return context.getReference((Field) value).toString();
}
if (value instanceof List) {
List<Object> sourceList = (List<Object>) value;
List<Object> mappedList = new ArrayList<Object>(sourceList.size());
for (Object item : sourceList) {
mappedList.add(unpack(item, context));
}
return mappedList;
}
return value;
}
protected List<Object> append(Object value) {
if (this.value instanceof List) {
List<Object> clone = new ArrayList<Object>((List) this.value);
if (value instanceof List) {
for (Object val : (List) value) {
clone.add(val);
}
} else {
clone.add(value);
}
return clone;
}
return Arrays.asList(this.value, value);
}
@SuppressWarnings("unchecked")
protected java.util.Map<String, Object> append(String key, Object value) {
if (!(this.value instanceof java.util.Map)) {
throw new IllegalArgumentException("o_O");
}
java.util.Map<String, Object> clone = new LinkedHashMap<String, Object>((java.util.Map<String, Object>) this.value);
clone.put(key, value);
return clone;
}
protected List<Object> values() {
if (value instanceof List) {
return new ArrayList<Object>((List) value);
}
if (value instanceof java.util.Map) {
return new ArrayList<Object>(((java.util.Map) value).values());
}
return new ArrayList<Object>(Collections.singletonList(value));
}
protected abstract String getMongoMethod();
}

View File

@@ -0,0 +1,647 @@
/*
* Copyright 2016. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.Collections;
import java.util.List;
import org.bson.Document;
import org.springframework.util.Assert;
/**
* Gateway to {@literal accumulator} aggregation operations.
*
* @author Christoph Strobl
* @since 1.10
* @soundtrack Rage Against The Machine - Killing In The Name
*/
public class AccumulatorOperators {
/**
* Take the numeric value referenced by given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static AccumulatorOperatorFactory valueOf(String fieldReference) {
return new AccumulatorOperatorFactory(fieldReference);
}
/**
* Take the numeric value referenced resulting from given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static AccumulatorOperatorFactory valueOf(AggregationExpression expression) {
return new AccumulatorOperatorFactory(expression);
}
/**
* @author Christoph Strobl
*/
public static class AccumulatorOperatorFactory {
private final String fieldReference;
private final AggregationExpression expression;
/**
* Creates new {@link AccumulatorOperatorFactory} for given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
*/
public AccumulatorOperatorFactory(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
this.fieldReference = fieldReference;
this.expression = null;
}
/**
* Creates new {@link AccumulatorOperatorFactory} for given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
*/
public AccumulatorOperatorFactory(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
this.fieldReference = null;
this.expression = expression;
}
/**
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates and
* returns the sum.
*
* @return
*/
public Sum sum() {
return usesFieldRef() ? Sum.sumOf(fieldReference) : Sum.sumOf(expression);
}
/**
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the
* average value.
*
* @return
*/
public Avg avg() {
return usesFieldRef() ? Avg.avgOf(fieldReference) : Avg.avgOf(expression);
}
/**
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the
* maximum value.
*
* @return
*/
public Max max() {
return usesFieldRef() ? Max.maxOf(fieldReference) : Max.maxOf(expression);
}
/**
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the
* minimum value.
*
* @return
*/
public Min min() {
return usesFieldRef() ? Min.minOf(fieldReference) : Min.minOf(expression);
}
/**
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates the
* population standard deviation of the input values.
*
* @return
*/
public StdDevPop stdDevPop() {
return usesFieldRef() ? StdDevPop.stdDevPopOf(fieldReference) : StdDevPop.stdDevPopOf(expression);
}
/**
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates the
* sample standard deviation of the input values.
*
* @return
*/
public StdDevSamp stdDevSamp() {
return usesFieldRef() ? StdDevSamp.stdDevSampOf(fieldReference) : StdDevSamp.stdDevSampOf(expression);
}
private boolean usesFieldRef() {
return fieldReference != null;
}
}
/**
* {@link AggregationExpression} for {@code $sum}.
*
* @author Christoph Strobl
*/
public static class Sum extends AbstractAggregationExpression {
private Sum(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$sum";
}
/**
* Creates new {@link Sum}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Sum sumOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Sum(asFields(fieldReference));
}
/**
* Creates new {@link Sum}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Sum sumOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Sum(Collections.singletonList(expression));
}
/**
* Creates new {@link Sum} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Sum and(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Sum(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Sum} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param expression must not be {@literal null}.
* @return
*/
public Sum and(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Sum(append(expression));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDocument(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
@SuppressWarnings("unchecked")
public Document toDocument(Object value, AggregationOperationContext context) {
if (value instanceof List) {
if (((List) value).size() == 1) {
return super.toDocument(((List<Object>) value).iterator().next(), context);
}
}
return super.toDocument(value, context);
}
}
/**
* {@link AggregationExpression} for {@code $avg}.
*
* @author Christoph Strobl
*/
public static class Avg extends AbstractAggregationExpression {
private Avg(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$avg";
}
/**
* Creates new {@link Avg}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Avg avgOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Avg(asFields(fieldReference));
}
/**
* Creates new {@link Avg}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Avg avgOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Avg(Collections.singletonList(expression));
}
/**
* Creates new {@link Avg} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Avg and(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Avg(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Avg} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param expression must not be {@literal null}.
* @return
*/
public Avg and(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Avg(append(expression));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDocument(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
@SuppressWarnings("unchecked")
public Document toDocument(Object value, AggregationOperationContext context) {
if (value instanceof List) {
if (((List) value).size() == 1) {
return super.toDocument(((List<Object>) value).iterator().next(), context);
}
}
return super.toDocument(value, context);
}
}
/**
* {@link AggregationExpression} for {@code $max}.
*
* @author Christoph Strobl
*/
public static class Max extends AbstractAggregationExpression {
private Max(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$max";
}
/**
* Creates new {@link Max}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Max maxOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Max(asFields(fieldReference));
}
/**
* Creates new {@link Max}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Max maxOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Max(Collections.singletonList(expression));
}
/**
* Creates new {@link Max} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Max and(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Max(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Max} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param expression must not be {@literal null}.
* @return
*/
public Max and(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Max(append(expression));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDocument(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
@SuppressWarnings("unchecked")
public Document toDocument(Object value, AggregationOperationContext context) {
if (value instanceof List) {
if (((List) value).size() == 1) {
return super.toDocument(((List<Object>) value).iterator().next(), context);
}
}
return super.toDocument(value, context);
}
}
/**
* {@link AggregationExpression} for {@code $min}.
*
* @author Christoph Strobl
*/
public static class Min extends AbstractAggregationExpression {
private Min(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$min";
}
/**
* Creates new {@link Min}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Min minOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Min(asFields(fieldReference));
}
/**
* Creates new {@link Min}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Min minOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Min(Collections.singletonList(expression));
}
/**
* Creates new {@link Min} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Min and(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Min(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Min} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param expression must not be {@literal null}.
* @return
*/
public Min and(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Min(append(expression));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDocument(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
@SuppressWarnings("unchecked")
public Document toDocument(Object value, AggregationOperationContext context) {
if (value instanceof List) {
if (((List) value).size() == 1) {
return super.toDocument(((List<Object>) value).iterator().next(), context);
}
}
return super.toDocument(value, context);
}
}
/**
* {@link AggregationExpression} for {@code $stdDevPop}.
*
* @author Christoph Strobl
*/
public static class StdDevPop extends AbstractAggregationExpression {
private StdDevPop(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$stdDevPop";
}
/**
* Creates new {@link StdDevPop}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static StdDevPop stdDevPopOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new StdDevPop(asFields(fieldReference));
}
/**
* Creates new {@link StdDevPop} with all previously added arguments appending the given one.
*
* @param expression must not be {@literal null}.
* @return
*/
public static StdDevPop stdDevPopOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new StdDevPop(Collections.singletonList(expression));
}
/**
* Creates new {@link StdDevPop} with all previously added arguments appending the given one. <br/>
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public StdDevPop and(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new StdDevPop(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link StdDevSamp} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param expression must not be {@literal null}.
* @return
*/
public StdDevPop and(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new StdDevPop(append(expression));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDocument(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
@SuppressWarnings("unchecked")
public Document toDocument(Object value, AggregationOperationContext context) {
if (value instanceof List) {
if (((List) value).size() == 1) {
return super.toDocument(((List<Object>) value).iterator().next(), context);
}
}
return super.toDocument(value, context);
}
}
/**
* {@link AggregationExpression} for {@code $stdDevSamp}.
*
* @author Christoph Strobl
*/
public static class StdDevSamp extends AbstractAggregationExpression {
private StdDevSamp(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$stdDevSamp";
}
/**
* Creates new {@link StdDevSamp}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static StdDevSamp stdDevSampOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new StdDevSamp(asFields(fieldReference));
}
/**
* Creates new {@link StdDevSamp}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static StdDevSamp stdDevSampOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new StdDevSamp(Collections.singletonList(expression));
}
/**
* Creates new {@link StdDevSamp} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public StdDevSamp and(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new StdDevSamp(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link StdDevSamp} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
* @param expression must not be {@literal null}.
* @return
*/
public StdDevSamp and(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new StdDevSamp(append(expression));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDocument(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
@SuppressWarnings("unchecked")
public Document toDocument(Object value, AggregationOperationContext context) {
if (value instanceof List) {
if (((List) value).size() == 1) {
return super.toDocument(((List<Object>) value).iterator().next(), context);
}
}
return super.toDocument(value, context);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2017 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,19 @@ package org.springframework.data.mongodb.core.aggregation;
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.bson.Document;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
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.Fields.AggregationField;
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
import org.springframework.data.mongodb.core.aggregation.CountOperation.CountOperationBuilder;
import org.springframework.data.mongodb.core.aggregation.FacetOperation.FacetOperationBuilder;
import org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder;
import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootDocumentOperationBuilder;
import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootOperationBuilder;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.SerializationUtils;
import org.springframework.util.Assert;
@@ -44,6 +45,7 @@ import org.springframework.util.Assert;
* @author Alessio Fachechi
* @author Christoph Strobl
* @author Nikolay Bogdanov
* @author Gustavo de Geus
* @since 1.3
*/
public class Aggregation {
@@ -60,7 +62,7 @@ public class Aggregation {
*/
public static final String CURRENT = SystemVariable.CURRENT.toString();
public static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext();
public static final AggregationOperationContext DEFAULT_CONTEXT = AggregationOperationRenderer.DEFAULT_CONTEXT;
public static final AggregationOptions DEFAULT_OPTIONS = newAggregationOptions().build();
protected final List<AggregationOperation> operations;
@@ -194,7 +196,7 @@ public class Aggregation {
}
/**
* Creates a new {@link ProjectionOperation} includeing the given {@link Fields}.
* Creates a new {@link ProjectionOperation} including the given {@link Fields}.
*
* @param fields must not be {@literal null}.
* @return
@@ -213,6 +215,40 @@ public class Aggregation {
return new UnwindOperation(field(field));
}
/**
* Factory method to create a new {@link ReplaceRootOperation} for the field with the given name.
*
* @param fieldName must not be {@literal null} or empty.
* @return
* @since 1.10
*/
public static ReplaceRootOperation replaceRoot(String fieldName) {
return ReplaceRootOperation.builder().withValueOf(fieldName);
}
/**
* Factory method to create a new {@link ReplaceRootOperation} for the field with the given
* {@link AggregationExpression}.
*
* @param aggregationExpression must not be {@literal null}.
* @return
* @since 1.10
*/
public static ReplaceRootOperation replaceRoot(AggregationExpression aggregationExpression) {
return ReplaceRootOperation.builder().withValueOf(aggregationExpression);
}
/**
* Factory method to create a new {@link ReplaceRootDocumentOperationBuilder} to configure a
* {@link ReplaceRootOperation}.
*
* @return the {@literal ReplaceRootDocumentOperationBuilder}.
* @since 1.10
*/
public static ReplaceRootOperationBuilder replaceRoot() {
return ReplaceRootOperation.builder();
}
/**
* Factory method to create a new {@link UnwindOperation} for the field with the given name and
* {@code preserveNullAndEmptyArrays}. Note that extended unwind is supported in MongoDB version 3.2+.
@@ -277,6 +313,18 @@ public class Aggregation {
return new GroupOperation(fields);
}
/**
* Creates a new {@link GraphLookupOperation.GraphLookupOperationFromBuilder} to construct a
* {@link GraphLookupOperation} given {@literal fromCollection}.
*
* @param fromCollection must not be {@literal null} or empty.
* @return
* @since 1.10
*/
public static StartWithBuilder graphLookup(String fromCollection) {
return GraphLookupOperation.builder().from(fromCollection);
}
/**
* Factory method to create a new {@link SortOperation} for the given {@link Sort}.
*
@@ -295,7 +343,7 @@ public class Aggregation {
* @return
*/
public static SortOperation sort(Direction direction, String... fields) {
return new SortOperation(new Sort(direction, fields));
return new SortOperation(Sort.by(direction, fields));
}
/**
@@ -329,6 +377,17 @@ public class Aggregation {
return new LimitOperation(maxElements);
}
/**
* Creates a new {@link SampleOperation} to select the specified number of documents from its input randomly.
*
* @param sampleSize must not be less than zero.
* @return
* @since 2.0
*/
public static SampleOperation sample(long sampleSize) {
return new SampleOperation(sampleSize);
}
/**
* Creates a new {@link MatchOperation} using the given {@link Criteria}.
*
@@ -339,6 +398,17 @@ public class Aggregation {
return new MatchOperation(criteria);
}
/**
* Creates a new {@link MatchOperation} using the given {@link CriteriaDefinition}.
*
* @param criteria must not be {@literal null}.
* @return
* @since 1.10
*/
public static MatchOperation match(CriteriaDefinition criteria) {
return new MatchOperation(criteria);
}
/**
* Creates a new {@link OutOperation} using the given collection name. This operation must be the last operation in
* the pipeline.
@@ -353,6 +423,73 @@ public class Aggregation {
return new OutOperation(outCollectionName);
}
/**
* Creates a new {@link BucketOperation} given {@literal groupByField}.
*
* @param groupByField must not be {@literal null} or empty.
* @return
* @since 1.10
*/
public static BucketOperation bucket(String groupByField) {
return new BucketOperation(field(groupByField));
}
/**
* Creates a new {@link BucketOperation} given {@link AggregationExpression group-by expression}.
*
* @param groupByExpression must not be {@literal null}.
* @return
* @since 1.10
*/
public static BucketOperation bucket(AggregationExpression groupByExpression) {
return new BucketOperation(groupByExpression);
}
/**
* Creates a new {@link BucketAutoOperation} given {@literal groupByField}.
*
* @param groupByField must not be {@literal null} or empty.
* @param buckets number of buckets, must be a positive integer.
* @return
* @since 1.10
*/
public static BucketAutoOperation bucketAuto(String groupByField, int buckets) {
return new BucketAutoOperation(field(groupByField), buckets);
}
/**
* Creates a new {@link BucketAutoOperation} given {@link AggregationExpression group-by expression}.
*
* @param groupByExpression must not be {@literal null}.
* @param buckets number of buckets, must be a positive integer.
* @return
* @since 1.10
*/
public static BucketAutoOperation bucketAuto(AggregationExpression groupByExpression, int buckets) {
return new BucketAutoOperation(groupByExpression, buckets);
}
/**
* Creates a new {@link FacetOperation}.
*
* @return
* @since 1.10
*/
public static FacetOperation facet() {
return FacetOperation.EMPTY;
}
/**
* Creates a new {@link FacetOperationBuilder} given {@link Aggregation}.
*
* @param aggregationOperations the sub-pipeline, must not be {@literal null}.
* @return
* @since 1.10
*/
public static FacetOperationBuilder facet(AggregationOperation... aggregationOperations) {
return facet().and(aggregationOperations);
}
/**
* Creates a new {@link LookupOperation}.
*
@@ -382,65 +519,13 @@ public class Aggregation {
}
/**
* Creates a new {@link IfNullOperator} for the given {@code field} and {@code replacement} value.
* Creates a new {@link CountOperationBuilder}.
*
* @param field must not be {@literal null}.
* @param replacement must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public static IfNullOperator ifNull(String field, Object replacement) {
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
}
/**
* Creates a new {@link IfNullOperator} for the given {@link Field} and {@link Field} to obtain a value from.
*
* @param field must not be {@literal null}.
* @param replacement must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public static IfNullOperator ifNull(Field field, Field replacement) {
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
}
/**
* Creates a new {@link IfNullOperator} for the given {@link Field} and {@code replacement} value.
*
* @param field must not be {@literal null}.
* @param replacement must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public static IfNullOperator ifNull(Field field, Object replacement) {
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
}
/**
* Creates a new {@link ConditionalOperator} for the given {@link Field} that holds a {@literal boolean} value.
*
* @param booleanField must not be {@literal null}.
* @param then must not be {@literal null}.
* @param otherwise must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public static ConditionalOperator conditional(Field booleanField, Object then, Object otherwise) {
return ConditionalOperator.newBuilder().when(booleanField).then(then).otherwise(otherwise);
}
/**
* Creates a new {@link ConditionalOperator} for the given {@link Criteria}.
*
* @param criteria must not be {@literal null}.
* @param then must not be {@literal null}.
* @param otherwise must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public static ConditionalOperator conditional(Criteria criteria, Object then, Object otherwise) {
return ConditionalOperator.newBuilder().when(criteria).then(then).otherwise(otherwise);
public static CountOperationBuilder count() {
return new CountOperationBuilder();
}
/**
@@ -496,24 +581,7 @@ public class Aggregation {
*/
public Document toDocument(String inputCollectionName, AggregationOperationContext rootContext) {
AggregationOperationContext context = rootContext;
List<Document> operationDocuments = new ArrayList<Document>(operations.size());
for (AggregationOperation operation : operations) {
operationDocuments.add(operation.toDocument(context));
if (operation instanceof FieldsExposingAggregationOperation) {
FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation;
if (operation instanceof InheritsFieldsAggregationOperation) {
context = new InheritingExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), context);
} else {
context = new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), context);
}
}
}
List<Document> operationDocuments = AggregationOperationRenderer.toDocument(operations, rootContext);
Document command = new Document("aggregate", inputCollectionName);
command.put("pipeline", operationDocuments);
@@ -529,50 +597,14 @@ public class Aggregation {
*/
@Override
public String toString() {
return SerializationUtils
.serializeToJsonSafely(toDocument("__collection__", new NoOpAggregationOperationContext()));
}
/**
* Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is.
*
* @author Oliver Gierke
*/
private static class NoOpAggregationOperationContext implements AggregationOperationContext {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.Document)
*/
@Override
public Document getMappedObject(Document document) {
return document;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.ExposedFields.AvailableField)
*/
@Override
public FieldReference getReference(Field field) {
return new FieldReference(new ExposedField(field, true));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String)
*/
@Override
public FieldReference getReference(String name) {
return new FieldReference(new ExposedField(new AggregationField(name), true));
}
return SerializationUtils.serializeToJsonSafely(toDocument("__collection__", DEFAULT_CONTEXT));
}
/**
* Describes the system variables available in MongoDB aggregation framework pipeline expressions.
*
* @author Thomas Darimont
* @see http://docs.mongodb.org/manual/reference/aggregation-variables
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation-variables">Aggregation Variables</a>
*/
enum SystemVariable {

View File

@@ -30,10 +30,12 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.7
* @deprecated since 1.10. Please use {@link ArithmeticOperators} and {@link ComparisonOperators} instead.
*/
@Deprecated
public enum AggregationFunctionExpressions {
SIZE;
SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT, ADD, MULTIPLY;
/**
* Returns an {@link AggregationExpression} build from the current {@link Enum} name and the given parameters.
@@ -52,7 +54,7 @@ public enum AggregationFunctionExpressions {
*
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.10
* @since 1.7
*/
static class FunctionExpression implements AggregationExpression {

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.List;
import org.bson.Document;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
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.Fields.AggregationField;
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
/**
* Rendering support for {@link AggregationOperation} into a {@link List} of {@link org.bson.Document}.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
*/
class AggregationOperationRenderer {
static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext();
/**
* Render a {@link List} of {@link AggregationOperation} given {@link AggregationOperationContext} into their
* {@link Document} representation.
*
* @param operations must not be {@literal null}.
* @param context must not be {@literal null}.
* @return the {@link List} of {@link Document}.
*/
static List<Document> toDocument(List<AggregationOperation> operations, AggregationOperationContext rootContext) {
List<Document> operationDocuments = new ArrayList<Document>(operations.size());
AggregationOperationContext contextToUse = rootContext;
for (AggregationOperation operation : operations) {
operationDocuments.add(operation.toDocument(contextToUse));
if (operation instanceof FieldsExposingAggregationOperation) {
FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation;
ExposedFields fields = exposedFieldsOperation.getFields();
if (operation instanceof InheritsFieldsAggregationOperation) {
contextToUse = new InheritingExposedFieldsAggregationOperationContext(fields, contextToUse);
} else {
contextToUse = fields.exposesNoFields() ? DEFAULT_CONTEXT
: new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), contextToUse);
}
}
}
return operationDocuments;
}
/**
* Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is.
*
* @author Oliver Gierke
*/
private static class NoOpAggregationOperationContext implements AggregationOperationContext {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document)
*/
@Override
public Document getMappedObject(Document document) {
return document;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.ExposedFields.AvailableField)
*/
@Override
public FieldReference getReference(Field field) {
return new DirectFieldReference(new ExposedField(field, true));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String)
*/
@Override
public FieldReference getReference(String name) {
return new DirectFieldReference(new ExposedField(new AggregationField(name), true));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 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,29 +15,39 @@
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.Optional;
import org.bson.Document;
import org.springframework.data.mongodb.core.Collation;
import org.springframework.util.Assert;
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
* https://docs.mongodb.org/manual/reference/command/aggregate/#aggregate
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
* @see Aggregation#withOptions(AggregationOptions)
* @see TypedAggregation#withOptions(AggregationOptions)
* @since 1.6
*/
public class AggregationOptions {
private static final String BATCH_SIZE = "batchSize";
private static final String CURSOR = "cursor";
private static final String EXPLAIN = "explain";
private static final String ALLOW_DISK_USE = "allowDiskUse";
private static final String COLLATION = "collation";
private final boolean allowDiskUse;
private final boolean explain;
private final Document cursor;
private final Optional<Document> cursor;
private final Optional<Collation> collation;
/**
* Creates a new {@link AggregationOptions}.
@@ -47,10 +57,57 @@ public class AggregationOptions {
* @param cursor can be {@literal null}, used to pass additional options to the aggregation.
*/
public AggregationOptions(boolean allowDiskUse, boolean explain, Document cursor) {
this(allowDiskUse, explain, cursor, null);
}
/**
* 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 (such as {@code batchSize}) to the
* aggregation.
* @param collation collation for string comparison. Can be {@literal null}.
* @since 2.0
*/
public AggregationOptions(boolean allowDiskUse, boolean explain, Document cursor, Collation collation) {
this.allowDiskUse = allowDiskUse;
this.explain = explain;
this.cursor = cursor;
this.cursor = Optional.ofNullable(cursor);
this.collation = Optional.ofNullable(collation);
}
/**
* 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 cursorBatchSize initial cursor batch size.
* @since 2.0
*/
public AggregationOptions(boolean allowDiskUse, boolean explain, int cursorBatchSize) {
this(allowDiskUse, explain, createCursor(cursorBatchSize), null);
}
/**
* Creates new {@link AggregationOptions} given {@link DBObject} containing aggregation options.
*
* @param document must not be {@literal null}.
* @return the {@link AggregationOptions}.
* @since 2.0
*/
public static AggregationOptions fromDocument(Document document) {
Assert.notNull(document, "Document must not be null!");
boolean allowDiskUse = document.getBoolean(ALLOW_DISK_USE, false);
boolean explain = document.getBoolean(EXPLAIN, false);
Document cursor = document.get(CURSOR, Document.class);
Collation collation = document.containsKey(COLLATION) ? Collation.from(document.get(COLLATION, Document.class))
: null;
return new AggregationOptions(allowDiskUse, explain, cursor, collation);
}
/**
@@ -72,15 +129,40 @@ public class AggregationOptions {
return explain;
}
/**
* The initial cursor batch size, if available, otherwise {@literal null}.
*
* @return the batch size or {@literal null}.
* @since 2.0
*/
public Integer getCursorBatchSize() {
if (cursor.filter(val -> val.containsKey(BATCH_SIZE)).isPresent()) {
return cursor.get().get(BATCH_SIZE, Integer.class);
}
return null;
}
/**
* Specify a document that contains options that control the creation of the cursor object.
*
* @return
*/
public Document getCursor() {
public Optional<Document> getCursor() {
return cursor;
}
/**
* Get collation settings for string comparison.
*
* @return
* @since 2.0
*/
public Optional<Collation> getCollation() {
return collation;
}
/**
* Returns a new potentially adjusted copy for the given {@code aggregationCommandObject} with the configuration
* applied.
@@ -100,8 +182,12 @@ public class AggregationOptions {
result.put(EXPLAIN, explain);
}
if (cursor != null && !result.containsKey(CURSOR)) {
result.put("cursor", cursor);
if (!result.containsKey(CURSOR)) {
cursor.ifPresent(val -> result.put(CURSOR, val));
}
if (!result.containsKey(COLLATION)) {
collation.map(Collation::toDocument).ifPresent(val -> result.append(COLLATION, val));
}
return result;
@@ -117,7 +203,9 @@ public class AggregationOptions {
Document document = new Document();
document.put(ALLOW_DISK_USE, allowDiskUse);
document.put(EXPLAIN, explain);
document.put(CURSOR, cursor);
cursor.ifPresent(val -> document.put(CURSOR, val));
collation.ifPresent(val -> document.append(COLLATION, val.toDocument()));
return document;
}
@@ -130,16 +218,22 @@ public class AggregationOptions {
return toDocument().toJson();
}
static Document createCursor(int cursorBatchSize) {
return new Document("batchSize", cursorBatchSize);
}
/**
* A Builder for {@link AggregationOptions}.
*
* @author Thomas Darimont
* @author Mark Paluch
*/
public static class Builder {
private boolean allowDiskUse;
private boolean explain;
private Document cursor;
private Collation collation;
/**
* Defines whether to off-load intensive sort-operations to disk.
@@ -177,13 +271,38 @@ public class AggregationOptions {
return this;
}
/**
* Define the initial cursor batch size.
*
* @param batchSize
* @return
* @since 2.0
*/
public Builder cursorBatchSize(int batchSize) {
this.cursor = createCursor(batchSize);
return this;
}
/**
* Define collation settings for string comparison.
*
* @param collation can be {@literal null}.
* @return
*/
public Builder collation(Collation collation) {
this.collation = collation;
return this;
}
/**
* Returns a new {@link AggregationOptions} instance with the given configuration.
*
* @return
*/
public AggregationOptions build() {
return new AggregationOptions(allowDiskUse, explain, cursor);
return new AggregationOptions(allowDiskUse, explain, cursor, collation);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
* @param <T> The class in which the results are mapped onto.
* @since 1.3
*/
@@ -46,8 +47,8 @@ public class AggregationResults<T> implements Iterable<T> {
*/
public AggregationResults(List<T> mappedResults, Document rawResults) {
Assert.notNull(mappedResults);
Assert.notNull(rawResults);
Assert.notNull(mappedResults, "List of mapped results must not be null!");
Assert.notNull(rawResults, "Raw results must not be null!");
this.mappedResults = Collections.unmodifiableList(mappedResults);
this.rawResults = rawResults;

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2016. 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.bson.Document;
import org.springframework.util.Assert;
/**
* An {@link AggregationExpression} that renders a MongoDB Aggregation Framework expression from the AST of a
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html">SpEL
* expression</a>. <br />
* <br />
* <strong>Samples:</strong> <br />
* <code>
* <pre>
* // { $and: [ { $gt: [ "$qty", 100 ] }, { $lt: [ "$qty", 250 ] } ] }
* expressionOf("qty > 100 && qty < 250);
*
* // { $cond : { if : { $gte : [ "$a", 42 ]}, then : "answer", else : "no-answer" } }
* expressionOf("cond(a >= 42, 'answer', 'no-answer')");
* </pre>
* </code>
*
* @author Christoph Strobl
* @see SpelExpressionTransformer
* @since 1.10
*/
public class AggregationSpELExpression implements AggregationExpression {
private static final SpelExpressionTransformer TRANSFORMER = new SpelExpressionTransformer();
private final String rawExpression;
private final Object[] parameters;
private AggregationSpELExpression(String rawExpression, Object[] parameters) {
this.rawExpression = rawExpression;
this.parameters = parameters;
}
/**
* Creates new {@link AggregationSpELExpression} for the given {@literal expressionString} and {@literal parameters}.
*
* @param expressionString must not be {@literal null}.
* @param parameters can be empty.
* @return
*/
public static AggregationSpELExpression expressionOf(String expressionString, Object... parameters) {
Assert.notNull(expressionString, "ExpressionString must not be null!");
return new AggregationSpELExpression(expressionString, parameters);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
return (Document) TRANSFORMER.transform(rawExpression, context, parameters);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.Range;
import org.springframework.util.Assert;
/**
* Utility methods for aggregation operation implementations.
*
* @author Oliver Gierke
*/
interface AggregationUtils {
/**
* Converts the given {@link Range} into an array of values.
*
* @param range must not be {@literal null}.
* @return
*/
public static List<Long> toRangeValues(Range<Long> range) {
Assert.notNull(range, "Range must not be null!");
List<Long> result = new ArrayList<Long>(2);
result.add(range.getLowerBound().getValue()
.orElseThrow(() -> new IllegalArgumentException("Lower bound of range must be bounded!")));
range.getUpperBound().getValue().ifPresent(it -> result.add(it));
return result;
}
}

View File

@@ -0,0 +1,353 @@
/*
* Copyright 2016. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.util.Assert;
/**
* Gateway to {@literal boolean expressions} that evaluate their argument expressions as booleans and return a boolean
* as the result.
*
* @author Christoph Strobl
* @since 1.10
*/
public class BooleanOperators {
/**
* Take the array referenced by given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static BooleanOperatorFactory valueOf(String fieldReference) {
return new BooleanOperatorFactory(fieldReference);
}
/**
* Take the value resulting of the given {@link AggregationExpression}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static BooleanOperatorFactory valueOf(AggregationExpression fieldReference) {
return new BooleanOperatorFactory(fieldReference);
}
/**
* Creates new {@link AggregationExpression} that evaluates the boolean value of the referenced field and returns the
* opposite boolean value.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Not not(String fieldReference) {
return Not.not(fieldReference);
}
/**
* Creates new {@link AggregationExpression} that evaluates the boolean value of {@link AggregationExpression} result
* and returns the opposite boolean value.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Not not(AggregationExpression expression) {
return Not.not(expression);
}
/**
* @author Christoph Strobl
*/
public static class BooleanOperatorFactory {
private final String fieldReference;
private final AggregationExpression expression;
/**
* Creates new {@link BooleanOperatorFactory} for given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
*/
public BooleanOperatorFactory(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
this.fieldReference = fieldReference;
this.expression = null;
}
/**
* Creates new {@link BooleanOperatorFactory} for given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
*/
public BooleanOperatorFactory(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
this.fieldReference = null;
this.expression = expression;
}
/**
* Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if
* all of the expressions are {@literal true}.
*
* @param expression must not be {@literal null}.
* @return
*/
public And and(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return createAnd().andExpression(expression);
}
/**
* Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if
* all of the expressions are {@literal true}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public And and(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return createAnd().andField(fieldReference);
}
private And createAnd() {
return usesFieldRef() ? And.and(Fields.field(fieldReference)) : And.and(expression);
}
/**
* Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if
* any of the expressions are {@literal true}.
*
* @param expression must not be {@literal null}.
* @return
*/
public Or or(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return createOr().orExpression(expression);
}
/**
* Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if
* any of the expressions are {@literal true}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Or or(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return createOr().orField(fieldReference);
}
private Or createOr() {
return usesFieldRef() ? Or.or(Fields.field(fieldReference)) : Or.or(expression);
}
/**
* Creates new {@link AggregationExpression} that evaluates a boolean and returns the opposite boolean value.
*
* @return
*/
public Not not() {
return usesFieldRef() ? Not.not(fieldReference) : Not.not(expression);
}
private boolean usesFieldRef() {
return this.fieldReference != null;
}
}
/**
* {@link AggregationExpression} for {@code $and}.
*
* @author Christoph Strobl
*/
public static class And extends AbstractAggregationExpression {
private And(List<?> values) {
super(values);
}
@Override
protected String getMongoMethod() {
return "$and";
}
/**
* Creates new {@link And} that evaluates one or more expressions and returns {@literal true} if all of the
* expressions are {@literal true}.
*
* @param expressions
* @return
*/
public static And and(Object... expressions) {
return new And(Arrays.asList(expressions));
}
/**
* Creates new {@link And} with all previously added arguments appending the given one.
*
* @param expression must not be {@literal null}.
* @return
*/
public And andExpression(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new And(append(expression));
}
/**
* Creates new {@link And} with all previously added arguments appending the given one.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public And andField(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new And(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link And} with all previously added arguments appending the given one.
*
* @param value must not be {@literal null}.
* @return
*/
public And andValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new And(append(value));
}
}
/**
* {@link AggregationExpression} for {@code $or}.
*
* @author Christoph Strobl
*/
public static class Or extends AbstractAggregationExpression {
private Or(List<?> values) {
super(values);
}
@Override
protected String getMongoMethod() {
return "$or";
}
/**
* Creates new {@link Or} that evaluates one or more expressions and returns {@literal true} if any of the
* expressions are {@literal true}.
*
* @param expressions must not be {@literal null}.
* @return
*/
public static Or or(Object... expressions) {
Assert.notNull(expressions, "Expressions must not be null!");
return new Or(Arrays.asList(expressions));
}
/**
* Creates new {@link Or} with all previously added arguments appending the given one.
*
* @param expression must not be {@literal null}.
* @return
*/
public Or orExpression(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Or(append(expression));
}
/**
* Creates new {@link Or} with all previously added arguments appending the given one.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Or orField(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Or(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Or} with all previously added arguments appending the given one.
*
* @param value must not be {@literal null}.
* @return
*/
public Or orValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Or(append(value));
}
}
/**
* {@link AggregationExpression} for {@code $not}.
*
* @author Christoph Strobl
*/
public static class Not extends AbstractAggregationExpression {
private Not(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$not";
}
/**
* Creates new {@link Not} that evaluates the boolean value of the referenced field and returns the opposite boolean
* value.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Not not(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Not(asFields(fieldReference));
}
/**
* Creates new {@link Not} that evaluates the resulting boolean value of the given {@link AggregationExpression} and
* returns the opposite boolean value.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Not not(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Not(Collections.singletonList(expression));
}
}
}

View File

@@ -0,0 +1,275 @@
/*
* Copyright 2016 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.BucketAutoOperation.BucketAutoOperationOutputBuilder;
import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder;
import org.springframework.util.Assert;
import org.bson.Document;
/**
* Encapsulates the aggregation framework {@code $bucketAuto}-operation. <br />
* Bucket stage is typically used with {@link Aggregation} and {@code $facet}. Categorizes incoming documents into a
* specific number of groups, called buckets, based on a specified expression. Bucket boundaries are automatically
* determined in an attempt to evenly distribute the documents into the specified number of buckets. <br />
* We recommend to use the static factory method {@link Aggregation#bucketAuto(String, int)} instead of creating
* instances of this class directly.
*
* @see <a href=
* "https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/">https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/</a>
* @see BucketOperationSupport
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
*/
public class BucketAutoOperation extends BucketOperationSupport<BucketAutoOperation, BucketAutoOperationOutputBuilder>
implements FieldsExposingAggregationOperation {
private final int buckets;
private final String granularity;
/**
* Creates a new {@link BucketAutoOperation} given a {@link Field group-by field}.
*
* @param groupByField must not be {@literal null}.
* @param buckets number of buckets, must be a positive integer.
*/
public BucketAutoOperation(Field groupByField, int buckets) {
super(groupByField);
Assert.isTrue(buckets > 0, "Number of buckets must be greater 0!");
this.buckets = buckets;
this.granularity = null;
}
/**
* Creates a new {@link BucketAutoOperation} given a {@link AggregationExpression group-by expression}.
*
* @param groupByExpression must not be {@literal null}.
* @param buckets number of buckets, must be a positive integer.
*/
public BucketAutoOperation(AggregationExpression groupByExpression, int buckets) {
super(groupByExpression);
Assert.isTrue(buckets > 0, "Number of buckets must be greater 0!");
this.buckets = buckets;
this.granularity = null;
}
private BucketAutoOperation(BucketAutoOperation bucketOperation, Outputs outputs) {
super(bucketOperation, outputs);
this.buckets = bucketOperation.buckets;
this.granularity = bucketOperation.granularity;
}
private BucketAutoOperation(BucketAutoOperation bucketOperation, int buckets, String granularity) {
super(bucketOperation);
this.buckets = buckets;
this.granularity = granularity;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
Document options = new Document();
options.put("buckets", buckets);
if (granularity != null) {
options.put("granularity", granularity);
}
options.putAll(super.toDocument(context));
return new Document("$bucketAuto", options);
}
/**
* Configures a number of bucket {@literal buckets} and return a new {@link BucketAutoOperation}.
*
* @param buckets must be a positive number.
* @return
*/
public BucketAutoOperation withBuckets(int buckets) {
Assert.isTrue(buckets > 0, "Number of buckets must be greater 0!");
return new BucketAutoOperation(this, buckets, granularity);
}
/**
* Configures {@link Granularity granularity} that specifies the preferred number series to use to ensure that the
* calculated boundary edges end on preferred round numbers or their powers of 10 and return a new
* {@link BucketAutoOperation}. <br />
* Use either predefined {@link Granularities} or provide a own one.
*
* @param granularity must not be {@literal null}.
* @return
*/
public BucketAutoOperation withGranularity(Granularity granularity) {
Assert.notNull(granularity, "Granularity must not be null!");
return new BucketAutoOperation(this, buckets, granularity.getMongoRepresentation());
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#newBucketOperation(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Outputs)
*/
@Override
protected BucketAutoOperation newBucketOperation(Outputs outputs) {
return new BucketAutoOperation(this, outputs);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutputExpression(java.lang.String, java.lang.Object[])
*/
@Override
public ExpressionBucketAutoOperationBuilder andOutputExpression(String expression, Object... params) {
return new ExpressionBucketAutoOperationBuilder(expression, this, params);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
@Override
public BucketAutoOperationOutputBuilder andOutput(AggregationExpression expression) {
return new BucketAutoOperationOutputBuilder(expression, this);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(java.lang.String)
*/
@Override
public BucketAutoOperationOutputBuilder andOutput(String fieldName) {
return new BucketAutoOperationOutputBuilder(Fields.field(fieldName), this);
}
/**
* {@link OutputBuilder} implementation for {@link BucketAutoOperation}.
*/
public static class BucketAutoOperationOutputBuilder
extends OutputBuilder<BucketAutoOperationOutputBuilder, BucketAutoOperation> {
/**
* Creates a new {@link BucketAutoOperationOutputBuilder} fot the given value and {@link BucketAutoOperation}.
*
* @param value must not be {@literal null}.
* @param operation must not be {@literal null}.
*/
protected BucketAutoOperationOutputBuilder(Object value, BucketAutoOperation operation) {
super(value, operation);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput)
*/
@Override
protected BucketAutoOperationOutputBuilder apply(OperationOutput operationOutput) {
return new BucketAutoOperationOutputBuilder(operationOutput, this.operation);
}
}
/**
* {@link ExpressionBucketOperationBuilderSupport} implementation for {@link BucketAutoOperation} using SpEL
* expression based {@link Output}.
*
* @author Mark Paluch
*/
public static class ExpressionBucketAutoOperationBuilder
extends ExpressionBucketOperationBuilderSupport<BucketAutoOperationOutputBuilder, BucketAutoOperation> {
/**
* Creates a new {@link ExpressionBucketAutoOperationBuilder} for the given value, {@link BucketAutoOperation} and
* parameters.
*
* @param expression must not be {@literal null}.
* @param operation must not be {@literal null}.
* @param parameters
*/
protected ExpressionBucketAutoOperationBuilder(String expression, BucketAutoOperation operation,
Object[] parameters) {
super(expression, operation, parameters);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput)
*/
@Override
protected BucketAutoOperationOutputBuilder apply(OperationOutput operationOutput) {
return new BucketAutoOperationOutputBuilder(operationOutput, this.operation);
}
}
/**
* @author Mark Paluch
*/
public interface Granularity {
/**
* @return a String that represents a MongoDB granularity to be used with {@link BucketAutoOperation}. Never
* {@literal null}.
*/
String getMongoRepresentation();
}
/**
* Supported MongoDB granularities.
*
* @see <a
* href="https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity>https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity</a>
* @author Mark Paluch
*/
public enum Granularities implements Granularity {
R5, R10, R20, R40, R80, //
SERIES_1_2_5("1-2-5"), //
E6, E12, E24, E48, E96, E192, //
POWERSOF2;
private final String granularity;
Granularities() {
this.granularity = name();
}
Granularities(String granularity) {
this.granularity = granularity;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.GranularitytoMongoGranularity()
*/
@Override
public String getMongoRepresentation() {
return granularity;
}
}
}

View File

@@ -0,0 +1,226 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.data.mongodb.core.aggregation.BucketOperation.BucketOperationOutputBuilder;
import org.springframework.util.Assert;
import org.bson.Document;
/**
* Encapsulates the aggregation framework {@code $bucket}-operation. <br />
*
* Bucket stage is typically used with {@link Aggregation} and {@code $facet}. Categorizes incoming documents into
* groups, called buckets, based on a specified expression and bucket boundaries. <br />
*
* We recommend to use the static factory method {@link Aggregation#bucket(String)} instead of creating instances of
* this class directly.
*
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation/bucket/">https://docs.mongodb.org/manual/reference/aggregation/bucket/</a>
* @see BucketOperationSupport
* @author Mark Paluch
* @since 1.10
*/
public class BucketOperation extends BucketOperationSupport<BucketOperation, BucketOperationOutputBuilder>
implements FieldsExposingAggregationOperation {
private final List<Object> boundaries;
private final Object defaultBucket;
/**
* Creates a new {@link BucketOperation} given a {@link Field group-by field}.
*
* @param groupByField must not be {@literal null}.
*/
public BucketOperation(Field groupByField) {
super(groupByField);
this.boundaries = Collections.emptyList();
this.defaultBucket = null;
}
/**
* Creates a new {@link BucketOperation} given a {@link AggregationExpression group-by expression}.
*
* @param groupByExpression must not be {@literal null}.
*/
public BucketOperation(AggregationExpression groupByExpression) {
super(groupByExpression);
this.boundaries = Collections.emptyList();
this.defaultBucket = null;
}
private BucketOperation(BucketOperation bucketOperation, Outputs outputs) {
super(bucketOperation, outputs);
this.boundaries = bucketOperation.boundaries;
this.defaultBucket = bucketOperation.defaultBucket;
}
private BucketOperation(BucketOperation bucketOperation, List<Object> boundaries, Object defaultBucket) {
super(bucketOperation);
this.boundaries = new ArrayList<Object>(boundaries);
this.defaultBucket = defaultBucket;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
Document options = new Document();
options.put("boundaries", context.getMappedObject(new Document("$set", boundaries)).get("$set"));
if (defaultBucket != null) {
options.put("default", context.getMappedObject(new Document("$set", defaultBucket)).get("$set"));
}
options.putAll(super.toDocument(context));
return new Document("$bucket", options);
}
/**
* Configures a default bucket {@literal literal} and return a new {@link BucketOperation}.
*
* @param literal must not be {@literal null}.
* @return
*/
public BucketOperation withDefaultBucket(Object literal) {
Assert.notNull(literal, "Default bucket literal must not be null!");
return new BucketOperation(this, boundaries, literal);
}
/**
* Configures {@literal boundaries} and return a new {@link BucketOperation}. Existing {@literal boundaries} are
* preserved and the new {@literal boundaries} are appended.
*
* @param boundaries must not be {@literal null}.
* @return
*/
public BucketOperation withBoundaries(Object... boundaries) {
Assert.notNull(boundaries, "Boundaries must not be null!");
Assert.noNullElements(boundaries, "Boundaries must not contain null values!");
List<Object> newBoundaries = new ArrayList<Object>(this.boundaries.size() + boundaries.length);
newBoundaries.addAll(this.boundaries);
newBoundaries.addAll(Arrays.asList(boundaries));
return new BucketOperation(this, newBoundaries, defaultBucket);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#newBucketOperation(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Outputs)
*/
@Override
protected BucketOperation newBucketOperation(Outputs outputs) {
return new BucketOperation(this, outputs);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutputExpression(java.lang.String, java.lang.Object[])
*/
@Override
public ExpressionBucketOperationBuilder andOutputExpression(String expression, Object... params) {
return new ExpressionBucketOperationBuilder(expression, this, params);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
@Override
public BucketOperationOutputBuilder andOutput(AggregationExpression expression) {
return new BucketOperationOutputBuilder(expression, this);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(java.lang.String)
*/
@Override
public BucketOperationOutputBuilder andOutput(String fieldName) {
return new BucketOperationOutputBuilder(Fields.field(fieldName), this);
}
/**
* {@link OutputBuilder} implementation for {@link BucketOperation}.
*/
public static class BucketOperationOutputBuilder
extends BucketOperationSupport.OutputBuilder<BucketOperationOutputBuilder, BucketOperation> {
/**
* Creates a new {@link BucketOperationOutputBuilder} fot the given value and {@link BucketOperation}.
*
* @param value must not be {@literal null}.
* @param operation must not be {@literal null}.
*/
protected BucketOperationOutputBuilder(Object value, BucketOperation operation) {
super(value, operation);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput)
*/
@Override
protected BucketOperationOutputBuilder apply(OperationOutput operationOutput) {
return new BucketOperationOutputBuilder(operationOutput, this.operation);
}
}
/**
* {@link ExpressionBucketOperationBuilderSupport} implementation for {@link BucketOperation} using SpEL expression
* based {@link Output}.
*
* @author Mark Paluch
*/
public static class ExpressionBucketOperationBuilder
extends ExpressionBucketOperationBuilderSupport<BucketOperationOutputBuilder, BucketOperation> {
/**
* Creates a new {@link ExpressionBucketOperationBuilderSupport} for the given value, {@link BucketOperation}
* and parameters.
*
* @param expression must not be {@literal null}.
* @param operation must not be {@literal null}.
* @param parameters
*/
protected ExpressionBucketOperationBuilder(String expression, BucketOperation operation, Object[] parameters) {
super(expression, operation, parameters);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput)
*/
@Override
protected BucketOperationOutputBuilder apply(OperationOutput operationOutput) {
return new BucketOperationOutputBuilder(operationOutput, this.operation);
}
}
}

View File

@@ -0,0 +1,679 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder;
import org.springframework.expression.spel.ast.Projection;
import org.springframework.util.Assert;
import org.bson.Document;
/**
* Base class for bucket operations that support output expressions the aggregation framework. <br />
* Bucket stages collect documents into buckets and can contribute output fields. <br />
* Implementing classes are required to provide an {@link OutputBuilder}.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
*/
public abstract class BucketOperationSupport<T extends BucketOperationSupport<T, B>, B extends OutputBuilder<B, T>>
implements FieldsExposingAggregationOperation {
private final Field groupByField;
private final AggregationExpression groupByExpression;
private final Outputs outputs;
/**
* Creates a new {@link BucketOperationSupport} given a {@link Field group-by field}.
*
* @param groupByField must not be {@literal null}.
*/
protected BucketOperationSupport(Field groupByField) {
Assert.notNull(groupByField, "Group by field must not be null!");
this.groupByField = groupByField;
this.groupByExpression = null;
this.outputs = Outputs.EMPTY;
}
/**
* Creates a new {@link BucketOperationSupport} given a {@link AggregationExpression group-by expression}.
*
* @param groupByExpression must not be {@literal null}.
*/
protected BucketOperationSupport(AggregationExpression groupByExpression) {
Assert.notNull(groupByExpression, "Group by AggregationExpression must not be null!");
this.groupByExpression = groupByExpression;
this.groupByField = null;
this.outputs = Outputs.EMPTY;
}
/**
* Creates a copy of {@link BucketOperationSupport}.
*
* @param operationSupport must not be {@literal null}.
*/
protected BucketOperationSupport(BucketOperationSupport<?, ?> operationSupport) {
this(operationSupport, operationSupport.outputs);
}
/**
* Creates a copy of {@link BucketOperationSupport} and applies the new {@link Outputs}.
*
* @param operationSupport must not be {@literal null}.
* @param outputs must not be {@literal null}.
*/
protected BucketOperationSupport(BucketOperationSupport<?, ?> operationSupport, Outputs outputs) {
Assert.notNull(operationSupport, "BucketOperationSupport must not be null!");
Assert.notNull(outputs, "Outputs must not be null!");
this.groupByField = operationSupport.groupByField;
this.groupByExpression = operationSupport.groupByExpression;
this.outputs = outputs;
}
/**
* Creates a new {@link ExpressionBucketOperationBuilderSupport} given a SpEL {@literal expression} and optional
* {@literal params} to add an output field to the resulting bucket documents.
*
* @param expression the SpEL expression, must not be {@literal null} or empty.
* @param params must not be {@literal null}
* @return
*/
public abstract ExpressionBucketOperationBuilderSupport<B, T> andOutputExpression(String expression,
Object... params);
/**
* Creates a new {@link BucketOperationSupport} given an {@link AggregationExpression} to add an output field to the
* resulting bucket documents.
*
* @param expression the SpEL expression, must not be {@literal null} or empty.
* @return
*/
public abstract B andOutput(AggregationExpression expression);
/**
* Creates a new {@link BucketOperationSupport} given {@literal fieldName} to add an output field to the resulting
* bucket documents. {@link BucketOperationSupport} exposes accumulation operations that can be applied to
* {@literal fieldName}.
*
* @param fieldName must not be {@literal null} or empty.
* @return
*/
public abstract B andOutput(String fieldName);
/**
* Creates a new {@link BucketOperationSupport} given to add a count field to the resulting bucket documents.
*
* @return
*/
public B andOutputCount() {
return andOutput(new AggregationExpression() {
@Override
public Document toDocument(AggregationOperationContext context) {
return new Document("$sum", 1);
}
});
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
Document document = new Document();
document.put("groupBy", groupByExpression == null ? context.getReference(groupByField).toString()
: groupByExpression.toDocument(context));
if (!outputs.isEmpty()) {
document.put("output", outputs.toDocument(context));
}
return document;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields()
*/
@Override
public ExposedFields getFields() {
return outputs.asExposedFields();
}
/**
* Implementation hook to create a new bucket operation.
*
* @param outputs the outputs
* @return the new bucket operation.
*/
protected abstract T newBucketOperation(Outputs outputs);
protected T andOutput(Output output) {
return newBucketOperation(outputs.and(output));
}
/**
* Builder for SpEL expression-based {@link Output}.
*
* @author Mark Paluch
*/
public abstract static class ExpressionBucketOperationBuilderSupport<B extends OutputBuilder<B, T>, T extends BucketOperationSupport<T, B>>
extends OutputBuilder<B, T> {
/**
* Creates a new {@link ExpressionBucketOperationBuilderSupport} for the given value, {@link BucketOperationSupport}
* and parameters.
*
* @param expression must not be {@literal null}.
* @param operation must not be {@literal null}.
* @param parameters
*/
protected ExpressionBucketOperationBuilderSupport(String expression, T operation, Object[] parameters) {
super(new SpelExpressionOutput(expression, parameters), operation);
}
}
/**
* Base class for {@link Output} builders that result in a {@link BucketOperationSupport} providing the built
* {@link Output}.
*
* @author Mark Paluch
*/
public abstract static class OutputBuilder<B extends OutputBuilder<B, T>, T extends BucketOperationSupport<T, B>> {
protected final Object value;
protected final T operation;
/**
* Creates a new {@link OutputBuilder} for the given value and {@link BucketOperationSupport}.
*
* @param value must not be {@literal null}.
* @param operation must not be {@literal null}.
*/
protected OutputBuilder(Object value, T 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;
}
/**
* Generates a builder for a {@code $sum}-expression. <br />
* Count expressions are emulated via {@code $sum: 1}.
*
* @return
*/
public B count() {
return sum(1);
}
/**
* Generates a builder for a {@code $sum}-expression for the current value.
*
* @return
*/
public B sum() {
return apply(Accumulators.SUM);
}
/**
* Generates a builder for a {@code $sum}-expression for the given {@literal value}.
*
* @param value
* @return
*/
public B sum(Number value) {
return apply(new OperationOutput(Accumulators.SUM.getMongoOperator(), Collections.singleton(value)));
}
/**
* Generates a builder for an {@code $last}-expression for the current value..
*
* @return
*/
public B last() {
return apply(Accumulators.LAST);
}
/**
* Generates a builder for a {@code $first}-expression the current value.
*
* @return
*/
public B first() {
return apply(Accumulators.FIRST);
}
/**
* Generates a builder for an {@code $avg}-expression for the current value.
*
* @param reference
* @return
*/
public B avg() {
return apply(Accumulators.AVG);
}
/**
* Generates a builder for an {@code $min}-expression for the current value.
*
* @return
*/
public B min() {
return apply(Accumulators.MIN);
}
/**
* Generates a builder for an {@code $max}-expression for the current value.
*
* @return
*/
public B max() {
return apply(Accumulators.MAX);
}
/**
* Generates a builder for an {@code $push}-expression for the current value.
*
* @return
*/
public B push() {
return apply(Accumulators.PUSH);
}
/**
* Generates a builder for an {@code $addToSet}-expression for the current value.
*
* @return
*/
public B addToSet() {
return apply(Accumulators.ADDTOSET);
}
/**
* Apply an operator to the current value.
*
* @param operation the operation name, must not be {@literal null} or empty.
* @param values must not be {@literal null}.
* @return
*/
public B apply(String operation, Object... values) {
Assert.hasText(operation, "Operation must not be empty or null!");
Assert.notNull(value, "Values must not be null!");
List<Object> objects = new ArrayList<Object>(values.length + 1);
objects.add(value);
objects.addAll(Arrays.asList(values));
return apply(new OperationOutput(operation, objects));
}
/**
* Apply an {@link OperationOutput} to this output.
*
* @param operationOutput must not be {@literal null}.
* @return
*/
protected abstract B apply(OperationOutput operationOutput);
private B apply(Accumulators operation) {
return this.apply(operation.getMongoOperator());
}
/**
* Returns the finally to be applied {@link BucketOperation} with the given alias.
*
* @param alias will never be {@literal null} or empty.
* @return
*/
public T as(String alias) {
if (value instanceof OperationOutput) {
return this.operation.andOutput(((OperationOutput) this.value).withAlias(alias));
}
if (value instanceof Field) {
throw new IllegalStateException("Cannot add a field as top-level output. Use accumulator expressions.");
}
return this.operation
.andOutput(new AggregationExpressionOutput(Fields.field(alias), (AggregationExpression) value));
}
}
private enum Accumulators {
SUM("$sum"), AVG("$avg"), FIRST("$first"), LAST("$last"), MAX("$max"), MIN("$min"), PUSH("$push"), ADDTOSET(
"$addToSet");
private String mongoOperator;
Accumulators(String mongoOperator) {
this.mongoOperator = mongoOperator;
}
public String getMongoOperator() {
return mongoOperator;
}
}
/**
* Encapsulates {@link Output}s.
*
* @author Mark Paluch
*/
protected static class Outputs implements AggregationExpression {
protected static final Outputs EMPTY = new Outputs();
private List<Output> outputs;
/**
* Creates a new, empty {@link Outputs}.
*/
private Outputs() {
this.outputs = new ArrayList<Output>();
}
/**
* Creates new {@link Outputs} containing all given {@link Output}s.
*
* @param current
* @param output
*/
private Outputs(Collection<Output> current, Output output) {
this.outputs = new ArrayList<Output>(current.size() + 1);
this.outputs.addAll(current);
this.outputs.add(output);
}
/**
* @return the {@link ExposedFields} derived from {@link Output}.
*/
protected ExposedFields asExposedFields() {
// The count field is included by default when the output is not specified.
if (isEmpty()) {
return ExposedFields.from(new ExposedField("count", true));
}
ExposedFields fields = ExposedFields.from();
for (Output output : outputs) {
fields = fields.and(output.getExposedField());
}
return fields;
}
/**
* Create a new {@link Outputs} that contains the new {@link Output}.
*
* @param output must not be {@literal null}.
* @return the new {@link Outputs} that contains the new {@link Output}
*/
protected Outputs and(Output output) {
Assert.notNull(output, "BucketOutput must not be null!");
return new Outputs(this.outputs, output);
}
/**
* @return {@literal true} if {@link Outputs} contains no {@link Output}.
*/
protected boolean isEmpty() {
return outputs.isEmpty();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
Document document = new Document();
for (Output output : outputs) {
document.put(output.getExposedField().getName(), output.toDocument(context));
}
return document;
}
}
/**
* Encapsulates an output field in a bucket aggregation stage. <br />
* Output fields can be either top-level fields that define a valid field name or nested output fields using
* operators.
*
* @author Mark Paluch
*/
protected abstract static class Output implements AggregationExpression {
private final ExposedField field;
/**
* Creates new {@link Projection} for the given {@link Field}.
*
* @param field must not be {@literal null}.
*/
protected Output(Field field) {
Assert.notNull(field, "Field must not be null!");
this.field = new ExposedField(field, true);
}
/**
* Returns the field exposed by the {@link Output}.
*
* @return will never be {@literal null}.
*/
protected ExposedField getExposedField() {
return field;
}
}
/**
* Output field that uses a Mongo operation (expression object) to generate an output field value. <br />
* {@link OperationOutput} is used either with a regular field name or an operation keyword (e.g.
* {@literal $sum, $count}).
*
* @author Mark Paluch
*/
protected static class OperationOutput extends Output {
private final String operation;
private final List<Object> values;
/**
* Creates a new {@link Output} for the given field.
*
* @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}.
*/
public OperationOutput(String operation, Collection<? extends Object> values) {
super(Fields.field(operation));
Assert.hasText(operation, "Operation must not be null or empty!");
Assert.notNull(values, "Values must not be null!");
this.operation = operation;
this.values = new ArrayList<Object>(values);
}
private OperationOutput(Field field, OperationOutput operationOutput) {
super(field);
this.operation = operationOutput.operation;
this.values = operationOutput.values;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
List<Object> operationArguments = getOperationArguments(context);
return new Document(operation,
operationArguments.size() == 1 ? operationArguments.get(0) : operationArguments);
}
protected List<Object> getOperationArguments(AggregationOperationContext context) {
List<Object> result = new ArrayList<Object>(values != null ? values.size() : 1);
for (Object element : values) {
if (element instanceof Field) {
result.add(context.getReference((Field) element).toString());
} else if (element instanceof Fields) {
for (Field field : (Fields) element) {
result.add(context.getReference(field).toString());
}
} else if (element instanceof AggregationExpression) {
result.add(((AggregationExpression) element).toDocument(context));
} else {
result.add(element);
}
}
return result;
}
/**
* Returns the field that holds the {@link ProjectionOperationBuilder.OperationProjection}.
*
* @return
*/
protected Field getField() {
return getExposedField();
}
/**
* Creates a new instance of this {@link OperationOutput} with the given alias.
*
* @param alias the alias to set
* @return
*/
public OperationOutput withAlias(String alias) {
final Field aliasedField = Fields.field(alias);
return new OperationOutput(aliasedField, this) {
@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" OperationOutput that we replace
// with this new instance.
return OperationOutput.this.getOperationArguments(context);
}
};
}
}
/**
* A {@link Output} based on a SpEL expression.
*/
private static class SpelExpressionOutput extends Output {
private static final SpelExpressionTransformer TRANSFORMER = new SpelExpressionTransformer();
private final String expression;
private final Object[] params;
/**
* Creates a new {@link SpelExpressionOutput} for the given field, SpEL expression and parameters.
*
* @param expression must not be {@literal null} or empty.
* @param parameters must not be {@literal null}.
*/
public SpelExpressionOutput(String expression, Object[] parameters) {
super(Fields.field(expression));
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.BucketOperationSupport.Output#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
return (Document) TRANSFORMER.transform(expression, context, params);
}
}
/**
* @author Mark Paluch
*/
private static class AggregationExpressionOutput extends Output {
private final AggregationExpression expression;
/**
* Creates a new {@link AggregationExpressionOutput}.
*
* @param field
* @param expression
*/
protected AggregationExpressionOutput(Field field, AggregationExpression expression) {
super(field);
this.expression = expression;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Output#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
return expression.toDocument(context);
}
}
}

View File

@@ -0,0 +1,879 @@
/*
* Copyright 2016. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.Collections;
import java.util.List;
import org.springframework.util.Assert;
/**
* Gateway to {@literal comparison expressions}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class ComparisonOperators {
/**
* Take the field referenced by given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static ComparisonOperatorFactory valueOf(String fieldReference) {
return new ComparisonOperatorFactory(fieldReference);
}
/**
* Take the value resulting from the given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static ComparisonOperatorFactory valueOf(AggregationExpression expression) {
return new ComparisonOperatorFactory(expression);
}
public static class ComparisonOperatorFactory {
private final String fieldReference;
private final AggregationExpression expression;
/**
* Creates new {@link ComparisonOperatorFactory} for given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
*/
public ComparisonOperatorFactory(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
this.fieldReference = fieldReference;
this.expression = null;
}
/**
* Creates new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
*/
public ComparisonOperatorFactory(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
this.fieldReference = null;
this.expression = expression;
}
/**
* Creates new {@link AggregationExpression} that compares two values.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Cmp compareTo(String fieldReference) {
return createCmp().compareTo(fieldReference);
}
/**
* Creates new {@link AggregationExpression} that compares two values.
*
* @param expression must not be {@literal null}.
* @return
*/
public Cmp compareTo(AggregationExpression expression) {
return createCmp().compareTo(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values.
*
* @param value must not be {@literal null}.
* @return
*/
public Cmp compareToValue(Object value) {
return createCmp().compareToValue(value);
}
private Cmp createCmp() {
return usesFieldRef() ? Cmp.valueOf(fieldReference) : Cmp.valueOf(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is equal to the value of the referenced field.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Eq equalTo(String fieldReference) {
return createEq().equalTo(fieldReference);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is equal to the expression result.
*
* @param expression must not be {@literal null}.
* @return
*/
public Eq equalTo(AggregationExpression expression) {
return createEq().equalTo(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is equal to the given value.
*
* @param value must not be {@literal null}.
* @return
*/
public Eq equalToValue(Object value) {
return createEq().equalToValue(value);
}
private Eq createEq() {
return usesFieldRef() ? Eq.valueOf(fieldReference) : Eq.valueOf(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is greater than the value of the referenced field.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Gt greaterThan(String fieldReference) {
return createGt().greaterThan(fieldReference);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is greater than the expression result.
*
* @param expression must not be {@literal null}.
* @return
*/
public Gt greaterThan(AggregationExpression expression) {
return createGt().greaterThan(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is greater than the given value.
*
* @param value must not be {@literal null}.
* @return
*/
public Gt greaterThanValue(Object value) {
return createGt().greaterThanValue(value);
}
private Gt createGt() {
return usesFieldRef() ? Gt.valueOf(fieldReference) : Gt.valueOf(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is greater than or equivalent to the value of the referenced field.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Gte greaterThanEqualTo(String fieldReference) {
return createGte().greaterThanEqualTo(fieldReference);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is greater than or equivalent to the expression result.
*
* @param expression must not be {@literal null}.
* @return
*/
public Gte greaterThanEqualTo(AggregationExpression expression) {
return createGte().greaterThanEqualTo(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is greater than or equivalent to the given value.
*
* @param value must not be {@literal null}.
* @return
*/
public Gte greaterThanEqualToValue(Object value) {
return createGte().greaterThanEqualToValue(value);
}
private Gte createGte() {
return usesFieldRef() ? Gte.valueOf(fieldReference) : Gte.valueOf(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is less than the value of the referenced field.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Lt lessThan(String fieldReference) {
return createLt().lessThan(fieldReference);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is less than the expression result.
*
* @param expression must not be {@literal null}.
* @return
*/
public Lt lessThan(AggregationExpression expression) {
return createLt().lessThan(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is less than to the given value.
*
* @param value must not be {@literal null}.
* @return
*/
public Lt lessThanValue(Object value) {
return createLt().lessThanValue(value);
}
private Lt createLt() {
return usesFieldRef() ? Lt.valueOf(fieldReference) : Lt.valueOf(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is less than or equivalent to the value of the referenced field.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Lte lessThanEqualTo(String fieldReference) {
return createLte().lessThanEqualTo(fieldReference);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is less than or equivalent to the expression result.
*
* @param expression must not be {@literal null}.
* @return
*/
public Lte lessThanEqualTo(AggregationExpression expression) {
return createLte().lessThanEqualTo(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first
* value is less than or equivalent to the given value.
*
* @param value
* @return
*/
public Lte lessThanEqualToValue(Object value) {
return createLte().lessThanEqualToValue(value);
}
private Lte createLte() {
return usesFieldRef() ? Lte.valueOf(fieldReference) : Lte.valueOf(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the values
* are not equivalent.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Ne notEqualTo(String fieldReference) {
return createNe().notEqualTo(fieldReference);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the values
* are not equivalent.
*
* @param expression must not be {@literal null}.
* @return
*/
public Ne notEqualTo(AggregationExpression expression) {
return createNe().notEqualTo(expression);
}
/**
* Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the values
* are not equivalent.
*
* @param value must not be {@literal null}.
* @return
*/
public Ne notEqualToValue(Object value) {
return createNe().notEqualToValue(value);
}
private Ne createNe() {
return usesFieldRef() ? Ne.valueOf(fieldReference) : Ne.valueOf(expression);
}
private boolean usesFieldRef() {
return fieldReference != null;
}
}
/**
* {@link AggregationExpression} for {@code $cmp}.
*
* @author Christoph Strobl
*/
public static class Cmp extends AbstractAggregationExpression {
private Cmp(List<?> value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$cmp";
}
/**
* Creates new {@link Cmp}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Cmp valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Cmp(asFields(fieldReference));
}
/**
* Creates new {@link Cmp}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Cmp valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Cmp(Collections.singletonList(expression));
}
/**
* Creates new {@link Cmp} with all previously added arguments appending the given one.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Cmp compareTo(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Cmp(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Cmp} with all previously added arguments appending the given one.
*
* @param expression must not be {@literal null}.
* @return
*/
public Cmp compareTo(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Cmp(append(expression));
}
/**
* Creates new {@link Cmp} with all previously added arguments appending the given one.
*
* @param value must not be {@literal null}.
* @return
*/
public Cmp compareToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Cmp(append(value));
}
}
/**
* {@link AggregationExpression} for {@code $eq}.
*
* @author Christoph Strobl
*/
public static class Eq extends AbstractAggregationExpression {
private Eq(List<?> value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$eq";
}
/**
* Creates new {@link Eq}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Eq valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Eq(asFields(fieldReference));
}
/**
* Creates new {@link Eq}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Eq valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Eq(Collections.singletonList(expression));
}
/**
* Creates new {@link Eq} with all previously added arguments appending the given one.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Eq equalTo(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Eq(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Eq} with all previously added arguments appending the given one.
*
* @param expression must not be {@literal null}.
* @return
*/
public Eq equalTo(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Eq(append(expression));
}
/**
* Creates new {@link Eq} with all previously added arguments appending the given one.
*
* @param value must not be {@literal null}.
* @return
*/
public Eq equalToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Eq(append(value));
}
}
/**
* {@link AggregationExpression} for {@code $gt}.
*
* @author Christoph Strobl
*/
public static class Gt extends AbstractAggregationExpression {
private Gt(List<?> value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$gt";
}
/**
* Creates new {@link Gt}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Gt valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Gt(asFields(fieldReference));
}
/**
* Creates new {@link Gt}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Gt valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Gt(Collections.singletonList(expression));
}
/**
* Creates new {@link Gt} with all previously added arguments appending the given one.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Gt greaterThan(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Gt(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Gt} with all previously added arguments appending the given one.
*
* @param expression must not be {@literal null}.
* @return
*/
public Gt greaterThan(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Gt(append(expression));
}
/**
* Creates new {@link Gt} with all previously added arguments appending the given one.
*
* @param value must not be {@literal null}.
* @return
*/
public Gt greaterThanValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Gt(append(value));
}
}
/**
* {@link AggregationExpression} for {@code $lt}.
*
* @author Christoph Strobl
*/
public static class Lt extends AbstractAggregationExpression {
private Lt(List<?> value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$lt";
}
/**
* Creates new {@link Lt}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Lt valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Lt(asFields(fieldReference));
}
/**
* Creates new {@link Lt}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Lt valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Lt(Collections.singletonList(expression));
}
/**
* Creates new {@link Lt} with all previously added arguments appending the given one.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Lt lessThan(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Lt(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Lt} with all previously added arguments appending the given one.
*
* @param expression must not be {@literal null}.
* @return
*/
public Lt lessThan(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Lt(append(expression));
}
/**
* Creates new {@link Lt} with all previously added arguments appending the given one.
*
* @param value must not be {@literal null}.
* @return
*/
public Lt lessThanValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Lt(append(value));
}
}
/**
* {@link AggregationExpression} for {@code $gte}.
*
* @author Christoph Strobl
*/
public static class Gte extends AbstractAggregationExpression {
private Gte(List<?> value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$gte";
}
/**
* Creates new {@link Gte}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Gte valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Gte(asFields(fieldReference));
}
/**
* Creates new {@link Gte}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Gte valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Gte(Collections.singletonList(expression));
}
/**
* Creates new {@link Gte} with all previously added arguments appending the given one.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Gte greaterThanEqualTo(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Gte(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Gte} with all previously added arguments appending the given one.
*
* @param expression must not be {@literal null}.
* @return
*/
public Gte greaterThanEqualTo(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Gte(append(expression));
}
/**
* Creates new {@link Gte} with all previously added arguments appending the given one.
*
* @param value must not be {@literal null}.
* @return
*/
public Gte greaterThanEqualToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Gte(append(value));
}
}
/**
* {@link AggregationExpression} for {@code $lte}.
*
* @author Christoph Strobl
*/
public static class Lte extends AbstractAggregationExpression {
private Lte(List<?> value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$lte";
}
/**
* Creates new {@link Lte}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Lte valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Lte(asFields(fieldReference));
}
/**
* Creates new {@link Lte}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Lte valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Lte(Collections.singletonList(expression));
}
/**
* Creates new {@link Lte} with all previously added arguments appending the given one.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Lte lessThanEqualTo(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Lte(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Lte} with all previously added arguments appending the given one.
*
* @param expression must not be {@literal null}.
* @return
*/
public Lte lessThanEqualTo(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Lte(append(expression));
}
/**
* Creates new {@link Lte} with all previously added arguments appending the given one.
*
* @param value must not be {@literal null}.
* @return
*/
public Lte lessThanEqualToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Lte(append(value));
}
}
/**
* {@link AggregationExpression} for {@code $ne}.
*
* @author Christoph Strobl
*/
public static class Ne extends AbstractAggregationExpression {
private Ne(List<?> value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$ne";
}
/**
* Creates new {@link Ne}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Ne valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Ne(asFields(fieldReference));
}
/**
* Creates new {@link Ne}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Ne valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Ne(Collections.singletonList(expression));
}
/**
* Creates new {@link Ne} with all previously added arguments appending the given one.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public Ne notEqualTo(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Ne(append(Fields.field(fieldReference)));
}
/**
* Creates new {@link Ne} with all previously added arguments appending the given one.
*
* @param expression must not be {@literal null}.
* @return
*/
public Ne notEqualTo(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Ne(append(expression));
}
/**
* Creates new {@link Eq} with all previously added arguments appending the given one.
*
* @param value must not be {@literal null}.
* @return
*/
public Ne notEqualToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Ne(append(value));
}
}
}

View File

@@ -1,392 +0,0 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.List;
import org.bson.Document;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Encapsulates the aggregation framework {@code $cond} operator. A {@link ConditionalOperator} allows nested conditions
* {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition} or a {@link Document custom}
* condition. Replacement values can be either {@link Field field references}, values of simple MongoDB types or values
* that can be converted to a simple MongoDB type.
*
* @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
*/
public class ConditionalOperator implements AggregationExpression {
private final Object condition;
private final Object thenValue;
private final Object otherwiseValue;
/**
* Creates a new {@link ConditionalOperator} for a given {@link Field} and {@code then}/{@code otherwise} values.
*
* @param condition must not be {@literal null}.
* @param thenValue must not be {@literal null}.
* @param otherwiseValue must not be {@literal null}.
*/
public ConditionalOperator(Field condition, Object thenValue, Object otherwiseValue) {
this((Object) condition, thenValue, otherwiseValue);
}
/**
* Creates a new {@link ConditionalOperator} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise}
* values.
*
* @param condition must not be {@literal null}.
* @param thenValue must not be {@literal null}.
* @param otherwiseValue must not be {@literal null}.
*/
public ConditionalOperator(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) {
this((Object) condition, thenValue, otherwiseValue);
}
/**
* Creates a new {@link ConditionalOperator} for a given {@link Document criteria} and {@code then}/{@code otherwise}
* values.
*
* @param condition must not be {@literal null}.
* @param thenValue must not be {@literal null}.
* @param otherwiseValue must not be {@literal null}.
*/
public ConditionalOperator(Document condition, Object thenValue, Object otherwiseValue) {
this((Object) condition, thenValue, otherwiseValue);
}
private ConditionalOperator(Object condition, Object thenValue, Object otherwiseValue) {
Assert.notNull(condition, "Condition must not be null!");
Assert.notNull(thenValue, "'Then value' must not be null!");
Assert.notNull(otherwiseValue, "'Otherwise value' must not be null!");
assertNotBuilder(condition, "Condition");
assertNotBuilder(thenValue, "'Then value'");
assertNotBuilder(otherwiseValue, "'Otherwise value'");
this.condition = condition;
this.thenValue = thenValue;
this.otherwiseValue = otherwiseValue;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
Document condObject = new Document();
condObject.append("if", resolveCriteria(context, condition));
condObject.append("then", resolveValue(context, thenValue));
condObject.append("else", resolveValue(context, otherwiseValue));
return new Document("$cond", condObject);
}
private Object resolveValue(AggregationOperationContext context, Object value) {
if (value instanceof Document || value instanceof Field) {
return resolve(context, value);
}
if (value instanceof ConditionalOperator) {
return ((ConditionalOperator) value).toDocument(context);
}
return context.getMappedObject(new Document("$set", value)).get("$set");
}
private Object resolveCriteria(AggregationOperationContext context, Object value) {
if (value instanceof Document || value instanceof Field) {
return resolve(context, value);
}
if (value instanceof CriteriaDefinition) {
Document mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject());
List<Object> clauses = new ArrayList<Object>();
clauses.addAll(getClauses(context, mappedObject));
if (clauses.size() == 1) {
return clauses.get(0);
}
return clauses;
}
throw new InvalidDataAccessApiUsageException(
String.format("Invalid value in condition. Supported: Document, Field references, Criteria, got: %s", value));
}
private List<Object> getClauses(AggregationOperationContext context, Document mappedObject) {
List<Object> clauses = new ArrayList<Object>();
for (String key : mappedObject.keySet()) {
Object predicate = mappedObject.get(key);
clauses.addAll(getClauses(context, key, predicate));
}
return clauses;
}
private List<Object> getClauses(AggregationOperationContext context, String key, Object predicate) {
List<Object> clauses = new ArrayList<Object>();
if (predicate instanceof List) {
List<Object> args = new ArrayList<Object>();
for (Object clause : (List<?>) predicate) {
if (clause instanceof Document) {
args.addAll(getClauses(context, (Document) clause));
}
}
clauses.add(new Document(key, args));
} else if (predicate instanceof Document) {
Document nested = (Document) predicate;
for (String s : nested.keySet()) {
if (!isKeyword(s)) {
continue;
}
List<Object> args = new ArrayList<Object>();
args.add("$" + key);
args.add(nested.get(s));
clauses.add(new Document(s, args));
}
} else if (!isKeyword(key)) {
List<Object> args = new ArrayList<Object>();
args.add("$" + key);
args.add(predicate);
clauses.add(new Document("$eq", args));
}
return clauses;
}
/**
* Returns whether the given {@link String} is a MongoDB keyword.
*
* @param candidate
* @return
*/
private boolean isKeyword(String candidate) {
return candidate.startsWith("$");
}
private Object resolve(AggregationOperationContext context, Object value) {
if (value instanceof Document) {
return context.getMappedObject((Document) value);
}
return context.getReference((Field) value).toString();
}
private void assertNotBuilder(Object toCheck, String name) {
Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck),
String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName()));
}
/**
* Get a builder that allows fluent creation of {@link ConditionalOperator}.
*
* @return a new {@link ConditionalExpressionBuilder}.
*/
public static ConditionalExpressionBuilder newBuilder() {
return ConditionalExpressionBuilder.newBuilder();
}
/**
* @since 1.10
*/
public static interface WhenBuilder {
/**
* @param booleanExpression expression that yields in a boolean result, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(Document booleanExpression);
/**
* @param booleanField reference to a field holding a boolean value, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(Field booleanField);
/**
* @param booleanField name of a field holding a boolean value, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(String booleanField);
/**
* @param criteria criteria to evaluate, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(CriteriaDefinition criteria);
}
/**
* @since 1.10
*/
public static interface ThenBuilder {
/**
* @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link Document}, a value
* that is supported by MongoDB or a value that can be converted to a MongoDB representation but must not
* be {@literal null}.
* @return the {@link OtherwiseBuilder}
*/
OtherwiseBuilder then(Object value);
}
/**
* @since 1.10
*/
public static interface OtherwiseBuilder {
/**
* @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link Document}, a value
* that is supported by MongoDB or a value that can be converted to a MongoDB representation but must not
* be {@literal null}.
* @return the {@link ConditionalOperator}
*/
ConditionalOperator otherwise(Object value);
}
/**
* Builder for fluent {@link ConditionalOperator} creation.
*
* @author Mark Paluch
* @since 1.10
*/
public static final class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder {
private Object condition;
private Object thenValue;
private ConditionalExpressionBuilder() {}
/**
* Creates a new builder for {@link ConditionalOperator}.
*
* @return never {@literal null}.
*/
public static ConditionalExpressionBuilder newBuilder() {
return new ConditionalExpressionBuilder();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.bson.Document)
*/
@Override
public ConditionalExpressionBuilder when(Document booleanExpression) {
Assert.notNull(booleanExpression, "'Boolean expression' must not be null!");
this.condition = booleanExpression;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition)
*/
@Override
public ThenBuilder when(CriteriaDefinition criteria) {
Assert.notNull(criteria, "Criteria must not be null!");
this.condition = criteria;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.Field)
*/
@Override
public ThenBuilder when(Field booleanField) {
Assert.notNull(booleanField, "Boolean field must not be null!");
this.condition = booleanField;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(java.lang.String)
*/
@Override
public ThenBuilder when(String booleanField) {
Assert.hasText(booleanField, "Boolean field name must not be null or empty!");
this.condition = Fields.field(booleanField);
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.ThenBuilder#then(java.lang.Object)
*/
@Override
public OtherwiseBuilder then(Object thenValue) {
Assert.notNull(thenValue, "'Then-value' must not be null!");
this.thenValue = thenValue;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.OtherwiseBuilder#otherwise(java.lang.Object)
*/
@Override
public ConditionalOperator otherwise(Object otherwiseValue) {
Assert.notNull(otherwiseValue, "'Otherwise-value' must not be null!");
return new ConditionalOperator(condition, thenValue, otherwiseValue);
}
}
}

View File

@@ -0,0 +1,976 @@
/*
* Copyright 2016. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.bson.Document;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder;
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder;
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Gateway to {@literal conditional expressions} that evaluate their argument expressions as booleans to a value.
*
* @author Mark Paluch
* @since 1.10
*/
public class ConditionalOperators {
/**
* Take the field referenced by given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static ConditionalOperatorFactory when(String fieldReference) {
return new ConditionalOperatorFactory(fieldReference);
}
/**
* Take the value resulting from the given {@literal expression}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static ConditionalOperatorFactory when(AggregationExpression expression) {
return new ConditionalOperatorFactory(expression);
}
/**
* Take the value resulting from the given {@literal criteriaDefinition}.
*
* @param criteriaDefinition must not be {@literal null}.
* @return
*/
public static ConditionalOperatorFactory when(CriteriaDefinition criteriaDefinition) {
return new ConditionalOperatorFactory(criteriaDefinition);
}
/**
* Creates new {@link AggregationExpression} that evaluates an expression and returns the value of the expression if
* the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, including
* instances of undefined values or missing fields, returns the value of the replacement expression.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static IfNull.ThenBuilder ifNull(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return IfNull.ifNull(fieldReference);
}
/**
* Creates new {@link AggregationExpression} that evaluates an expression and returns the value of the expression if
* the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, including
* instances of undefined values or missing fields, returns the value of the replacement expression.
*
* @param expression must not be {@literal null}.
* @return
*/
public static IfNull.ThenBuilder ifNull(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return IfNull.ifNull(expression);
}
/**
* Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it
* finds an expression which evaluates to {@literal true}, {@code $switch} executes a specified expression and breaks
* out of the control flow.
*
* @param conditions must not be {@literal null}.
* @return
*/
public static Switch switchCases(CaseOperator... conditions) {
return Switch.switchCases(conditions);
}
/**
* Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it
* finds an expression which evaluates to {@literal true}, {@code $switch} executes a specified expression and breaks
* out of the control flow.
*
* @param conditions must not be {@literal null}.
* @return
*/
public static Switch switchCases(List<CaseOperator> conditions) {
return Switch.switchCases(conditions);
}
public static class ConditionalOperatorFactory {
private final String fieldReference;
private final AggregationExpression expression;
private final CriteriaDefinition criteriaDefinition;
/**
* Creates new {@link ConditionalOperatorFactory} for given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
*/
public ConditionalOperatorFactory(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
this.fieldReference = fieldReference;
this.expression = null;
this.criteriaDefinition = null;
}
/**
* Creates new {@link ConditionalOperatorFactory} for given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
*/
public ConditionalOperatorFactory(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
this.fieldReference = null;
this.expression = expression;
this.criteriaDefinition = null;
}
/**
* Creates new {@link ConditionalOperatorFactory} for given {@link CriteriaDefinition}.
*
* @param criteriaDefinition must not be {@literal null}.
*/
public ConditionalOperatorFactory(CriteriaDefinition criteriaDefinition) {
Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!");
this.fieldReference = null;
this.expression = null;
this.criteriaDefinition = criteriaDefinition;
}
/**
* Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two specified
* return expressions.
*
* @param value must not be {@literal null}.
* @return
*/
public OtherwiseBuilder then(Object value) {
Assert.notNull(value, "Value must not be null!");
return createThenBuilder().then(value);
}
/**
* Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two specified
* return expressions.
*
* @param expression must not be {@literal null}.
* @return
*/
public OtherwiseBuilder thenValueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return createThenBuilder().then(expression);
}
/**
* Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two specified
* return expressions.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public OtherwiseBuilder thenValueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return createThenBuilder().then(fieldReference);
}
private ThenBuilder createThenBuilder() {
if (usesFieldRef()) {
return Cond.newBuilder().when(fieldReference);
}
return usesCriteriaDefinition() ? Cond.newBuilder().when(criteriaDefinition) : Cond.newBuilder().when(expression);
}
private boolean usesFieldRef() {
return this.fieldReference != null;
}
private boolean usesCriteriaDefinition() {
return this.criteriaDefinition != null;
}
}
/**
* Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field
* field references}, {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be
* converted to a simple MongoDB type.
*
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/">https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/</a>
* @author Mark Paluch
*/
public static class IfNull implements AggregationExpression {
private final Object condition;
private final Object value;
private IfNull(Object condition, Object value) {
this.condition = condition;
this.value = value;
}
/**
* Creates new {@link IfNull}.
*
* @param fieldReference the field to check for a {@literal null} value, field reference must not be {@literal null}
* .
* @return
*/
public static ThenBuilder ifNull(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new IfNullOperatorBuilder().ifNull(fieldReference);
}
/**
* Creates new {@link IfNull}.
*
* @param expression the expression to check for a {@literal null} value, field reference must not be
* {@literal null}.
* @return
*/
public static ThenBuilder ifNull(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new IfNullOperatorBuilder().ifNull(expression);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
List<Object> list = new ArrayList<Object>();
if (condition instanceof Field) {
list.add(context.getReference((Field) condition).toString());
} else if (condition instanceof AggregationExpression) {
list.add(((AggregationExpression) condition).toDocument(context));
} else {
list.add(condition);
}
list.add(resolve(value, context));
return new Document("$ifNull", list);
}
private Object resolve(Object value, AggregationOperationContext context) {
if (value instanceof Field) {
return context.getReference((Field) value).toString();
} else if (value instanceof AggregationExpression) {
return ((AggregationExpression) value).toDocument(context);
} else if (value instanceof Document) {
return value;
}
return context.getMappedObject(new Document("$set", value)).get("$set");
}
/**
* @author Mark Paluch
*/
public interface IfNullBuilder {
/**
* @param fieldReference the field to check for a {@literal null} value, field reference must not be
* {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder ifNull(String fieldReference);
/**
* @param expression the expression to check for a {@literal null} value, field name must not be {@literal null}
* or empty.
* @return the {@link ThenBuilder}
*/
ThenBuilder ifNull(AggregationExpression expression);
}
/**
* @author Mark Paluch
*/
public interface ThenBuilder {
/**
* @param value the value to be used if the {@code $ifNull} condition evaluates {@literal true}. Can be a
* {@link Document}, a value that is supported by MongoDB or a value that can be converted to a MongoDB
* representation but must not be {@literal null}.
* @return
*/
IfNull then(Object value);
/**
* @param fieldReference the field holding the replacement value, must not be {@literal null}.
* @return
*/
IfNull thenValueOf(String fieldReference);
/**
* @param expression the expression yielding to the replacement value, must not be {@literal null}.
* @return
*/
IfNull thenValueOf(AggregationExpression expression);
}
/**
* Builder for fluent {@link IfNull} creation.
*
* @author Mark Paluch
*/
static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder {
private Object condition;
private IfNullOperatorBuilder() {}
/**
* Creates a new builder for {@link IfNull}.
*
* @return never {@literal null}.
*/
public static IfNullOperatorBuilder newBuilder() {
return new IfNullOperatorBuilder();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.IfNullBuilder#ifNull(java.lang.String)
*/
public ThenBuilder ifNull(String fieldReference) {
Assert.hasText(fieldReference, "FieldReference name must not be null or empty!");
this.condition = Fields.field(fieldReference);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
@Override
public ThenBuilder ifNull(AggregationExpression expression) {
Assert.notNull(expression, "AggregationExpression name must not be null or empty!");
this.condition = expression;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#then(java.lang.Object)
*/
public IfNull then(Object value) {
return new IfNull(condition, value);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#thenValueOf(java.lang.String)
*/
public IfNull thenValueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new IfNull(condition, Fields.field(fieldReference));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
public IfNull thenValueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new IfNull(condition, expression);
}
}
}
/**
* {@link AggregationExpression} for {@code $switch}.
*
* @author Christoph Strobl
*/
public static class Switch extends AbstractAggregationExpression {
private Switch(java.util.Map<String, Object> values) {
super(values);
}
@Override
protected String getMongoMethod() {
return "$switch";
}
/**
* Creates new {@link Switch}.
*
* @param conditions must not be {@literal null}.
*/
public static Switch switchCases(CaseOperator... conditions) {
Assert.notNull(conditions, "Conditions must not be null!");
return switchCases(Arrays.asList(conditions));
}
/**
* Creates new {@link Switch}.
*
* @param conditions must not be {@literal null}.
*/
public static Switch switchCases(List<CaseOperator> conditions) {
Assert.notNull(conditions, "Conditions must not be null!");
return new Switch(Collections.<String, Object> singletonMap("branches", new ArrayList<CaseOperator>(conditions)));
}
public Switch defaultTo(Object value) {
return new Switch(append("default", value));
}
/**
* Encapsulates the aggregation framework case document inside a {@code $switch}-operation.
*/
public static class CaseOperator implements AggregationExpression {
private final AggregationExpression when;
private final Object then;
private CaseOperator(AggregationExpression when, Object then) {
this.when = when;
this.then = then;
}
public static ThenBuilder when(final AggregationExpression condition) {
Assert.notNull(condition, "Condition must not be null!");
return new ThenBuilder() {
@Override
public CaseOperator then(Object value) {
Assert.notNull(value, "Value must not be null!");
return new CaseOperator(condition, value);
}
};
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
Document dbo = new Document("case", when.toDocument(context));
if (then instanceof AggregationExpression) {
dbo.put("then", ((AggregationExpression) then).toDocument(context));
} else if (then instanceof Field) {
dbo.put("then", context.getReference((Field) then).toString());
} else {
dbo.put("then", then);
}
return dbo;
}
/**
* @author Christoph Strobl
*/
public interface ThenBuilder {
/**
* Set the then {@literal value}.
*
* @param value must not be {@literal null}.
* @return
*/
CaseOperator then(Object value);
}
}
}
/**
* Encapsulates the aggregation framework {@code $cond} operator. A {@link Cond} allows nested conditions
* {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition}, {@link AggregationExpression}
* or a {@link Document custom} condition. Replacement values can be either {@link Field field references},
* {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be converted to a
* simple MongoDB type.
*
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/cond/">https://docs.mongodb.com/manual/reference/operator/aggregation/cond/</a>
* @author Mark Paluch
* @author Christoph Strobl
*/
public static class Cond implements AggregationExpression {
private final Object condition;
private final Object thenValue;
private final Object otherwiseValue;
/**
* Creates a new {@link Cond} for a given {@link Field} and {@code then}/{@code otherwise} values.
*
* @param condition must not be {@literal null}.
* @param thenValue must not be {@literal null}.
* @param otherwiseValue must not be {@literal null}.
*/
private Cond(Field condition, Object thenValue, Object otherwiseValue) {
this((Object) condition, thenValue, otherwiseValue);
}
/**
* Creates a new {@link Cond} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise} values.
*
* @param condition must not be {@literal null}.
* @param thenValue must not be {@literal null}.
* @param otherwiseValue must not be {@literal null}.
*/
private Cond(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) {
this((Object) condition, thenValue, otherwiseValue);
}
private Cond(Object condition, Object thenValue, Object otherwiseValue) {
Assert.notNull(condition, "Condition must not be null!");
Assert.notNull(thenValue, "Then value must not be null!");
Assert.notNull(otherwiseValue, "Otherwise value must not be null!");
assertNotBuilder(condition, "Condition");
assertNotBuilder(thenValue, "Then value");
assertNotBuilder(otherwiseValue, "Otherwise value");
this.condition = condition;
this.thenValue = thenValue;
this.otherwiseValue = otherwiseValue;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
Document condObject = new Document();
condObject.append("if", resolveCriteria(context, condition));
condObject.append("then", resolveValue(context, thenValue));
condObject.append("else", resolveValue(context, otherwiseValue));
return new Document("$cond", condObject);
}
private Object resolveValue(AggregationOperationContext context, Object value) {
if (value instanceof Document || value instanceof Field) {
return resolve(context, value);
}
if (value instanceof AggregationExpression) {
return ((AggregationExpression) value).toDocument(context);
}
return context.getMappedObject(new Document("$set", value)).get("$set");
}
private Object resolveCriteria(AggregationOperationContext context, Object value) {
if (value instanceof Document || value instanceof Field) {
return resolve(context, value);
}
if (value instanceof AggregationExpression) {
return ((AggregationExpression) value).toDocument(context);
}
if (value instanceof CriteriaDefinition) {
Document mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject());
List<Object> clauses = new ArrayList<Object>();
clauses.addAll(getClauses(context, mappedObject));
return clauses.size() == 1 ? clauses.get(0) : clauses;
}
throw new InvalidDataAccessApiUsageException(
String.format("Invalid value in condition. Supported: Document, Field references, Criteria, got: %s", value));
}
private List<Object> getClauses(AggregationOperationContext context, Document mappedObject) {
List<Object> clauses = new ArrayList<Object>();
for (String key : mappedObject.keySet()) {
Object predicate = mappedObject.get(key);
clauses.addAll(getClauses(context, key, predicate));
}
return clauses;
}
private List<Object> getClauses(AggregationOperationContext context, String key, Object predicate) {
List<Object> clauses = new ArrayList<Object>();
if (predicate instanceof List) {
List<Object> args = new ArrayList<Object>();
for (Object clause : (List<?>) predicate) {
if (clause instanceof Document) {
args.addAll(getClauses(context, (Document) clause));
}
}
clauses.add(new Document(key, args));
} else if (predicate instanceof Document) {
Document nested = (Document) predicate;
for (String s : nested.keySet()) {
if (!isKeyword(s)) {
continue;
}
List<Object> args = new ArrayList<Object>();
args.add("$" + key);
args.add(nested.get(s));
clauses.add(new Document(s, args));
}
} else if (!isKeyword(key)) {
List<Object> args = new ArrayList<Object>();
args.add("$" + key);
args.add(predicate);
clauses.add(new Document("$eq", args));
}
return clauses;
}
/**
* Returns whether the given {@link String} is a MongoDB keyword.
*
* @param candidate
* @return
*/
private boolean isKeyword(String candidate) {
return candidate.startsWith("$");
}
private Object resolve(AggregationOperationContext context, Object value) {
if (value instanceof Document) {
return context.getMappedObject((Document) value);
}
return context.getReference((Field) value).toString();
}
private void assertNotBuilder(Object toCheck, String name) {
Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck),
String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName()));
}
/**
* Get a builder that allows fluent creation of {@link Cond}.
*
* @return never {@literal null}.
*/
public static WhenBuilder newBuilder() {
return ConditionalExpressionBuilder.newBuilder();
}
/**
* Start creating new {@link Cond} by providing the boolean expression used in {@code if}.
*
* @param booleanExpression must not be {@literal null}.
* @return never {@literal null}.
*/
public static ThenBuilder when(Document booleanExpression) {
return ConditionalExpressionBuilder.newBuilder().when(booleanExpression);
}
/**
* Start creating new {@link Cond} by providing the {@link AggregationExpression} used in {@code if}.
*
* @param expression expression that yields in a boolean result, must not be {@literal null}.
* @return never {@literal null}.
*/
public static ThenBuilder when(AggregationExpression expression) {
return ConditionalExpressionBuilder.newBuilder().when(expression);
}
/**
* Start creating new {@link Cond} by providing the field reference used in {@code if}.
*
* @param booleanField name of a field holding a boolean value, must not be {@literal null}.
* @return never {@literal null}.
*/
public static ThenBuilder when(String booleanField) {
return ConditionalExpressionBuilder.newBuilder().when(booleanField);
}
/**
* Start creating new {@link Cond} by providing the {@link CriteriaDefinition} used in {@code if}.
*
* @param criteria criteria to evaluate, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
public static ThenBuilder when(CriteriaDefinition criteria) {
return ConditionalExpressionBuilder.newBuilder().when(criteria);
}
/**
* @author Mark Paluch
*/
public interface WhenBuilder {
/**
* @param booleanExpression expression that yields in a boolean result, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(Document booleanExpression);
/**
* @param expression expression that yields in a boolean result, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(AggregationExpression expression);
/**
* @param booleanField name of a field holding a boolean value, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(String booleanField);
/**
* @param criteria criteria to evaluate, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(CriteriaDefinition criteria);
}
/**
* @author Mark Paluch
*/
public interface ThenBuilder {
/**
* @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link Document}, a
* value that is supported by MongoDB or a value that can be converted to a MongoDB representation but
* must not be {@literal null}.
* @return the {@link OtherwiseBuilder}
*/
OtherwiseBuilder then(Object value);
/**
* @param fieldReference must not be {@literal null}.
* @return the {@link OtherwiseBuilder}
*/
OtherwiseBuilder thenValueOf(String fieldReference);
/**
* @param expression must not be {@literal null}.
* @return the {@link OtherwiseBuilder}
*/
OtherwiseBuilder thenValueOf(AggregationExpression expression);
}
/**
* @author Mark Paluch
*/
public interface OtherwiseBuilder {
/**
* @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link Document}, a
* value that is supported by MongoDB or a value that can be converted to a MongoDB representation but
* must not be {@literal null}.
* @return the {@link Cond}
*/
Cond otherwise(Object value);
/**
* @param fieldReference must not be {@literal null}.
* @return the {@link Cond}
*/
Cond otherwiseValueOf(String fieldReference);
/**
* @param expression must not be {@literal null}.
* @return the {@link Cond}
*/
Cond otherwiseValueOf(AggregationExpression expression);
}
/**
* Builder for fluent {@link Cond} creation.
*
* @author Mark Paluch
*/
static class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder {
private Object condition;
private Object thenValue;
private ConditionalExpressionBuilder() {}
/**
* Creates a new builder for {@link Cond}.
*
* @return never {@literal null}.
*/
public static ConditionalExpressionBuilder newBuilder() {
return new ConditionalExpressionBuilder();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(org.bson.Document)
*/
@Override
public ConditionalExpressionBuilder when(Document booleanExpression) {
Assert.notNull(booleanExpression, "'Boolean expression' must not be null!");
this.condition = booleanExpression;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition)
*/
@Override
public ThenBuilder when(CriteriaDefinition criteria) {
Assert.notNull(criteria, "Criteria must not be null!");
this.condition = criteria;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
@Override
public ThenBuilder when(AggregationExpression expression) {
Assert.notNull(expression, "AggregationExpression field must not be null!");
this.condition = expression;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(java.lang.String)
*/
@Override
public ThenBuilder when(String booleanField) {
Assert.hasText(booleanField, "Boolean field name must not be null or empty!");
this.condition = Fields.field(booleanField);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder#then(java.lang.Object)
*/
@Override
public OtherwiseBuilder then(Object thenValue) {
Assert.notNull(thenValue, "Then-value must not be null!");
this.thenValue = thenValue;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder#thenValueOf(java.lang.String)
*/
@Override
public OtherwiseBuilder thenValueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
this.thenValue = Fields.field(fieldReference);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
@Override
public OtherwiseBuilder thenValueOf(AggregationExpression expression) {
Assert.notNull(expression, "AggregationExpression must not be null!");
this.thenValue = expression;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder#otherwise(java.lang.Object)
*/
@Override
public Cond otherwise(Object otherwiseValue) {
Assert.notNull(otherwiseValue, "Value must not be null!");
return new Cond(condition, thenValue, otherwiseValue);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder#otherwiseValueOf(java.lang.String)
*/
@Override
public Cond otherwiseValueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Cond(condition, thenValue, Fields.field(fieldReference));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder#otherwiseValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
@Override
public Cond otherwiseValueOf(AggregationExpression expression) {
Assert.notNull(expression, "AggregationExpression must not be null!");
return new Cond(condition, thenValue, expression);
}
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2016 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.bson.Document;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.util.Assert;
/**
* Encapsulates the aggregation framework {@code $count}-operation. <br />
* We recommend to use the static factory method {@link Aggregation#count()} instead of creating instances of this class
* directly.
*
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/count/#pipe._S_count">https://docs.mongodb.com/manual/reference/operator/aggregation/count/</a>
* @author Mark Paluch
* @since 1.10
*/
public class CountOperation implements FieldsExposingAggregationOperation {
private final String fieldName;
/**
* Creates a new {@link CountOperation} given the {@link fieldName} field name.
*
* @param asFieldName must not be {@literal null} or empty.
*/
public CountOperation(String fieldName) {
Assert.hasText(fieldName, "Field name must not be null or empty!");
this.fieldName = fieldName;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
return new Document("$count", fieldName);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields()
*/
@Override
public ExposedFields getFields() {
return ExposedFields.from(new ExposedField(fieldName, true));
}
/**
* Builder for {@link CountOperation}.
*
* @author Mark Paluch
*/
public static class CountOperationBuilder {
/**
* Returns the finally to be applied {@link CountOperation} with the given alias.
*
* @param fieldName must not be {@literal null} or empty.
* @return
*/
public CountOperation as(String fieldName) {
return new CountOperation(fieldName);
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2016. 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.util.Assert;
/**
* Gateway to {@literal data type} expressions.
*
* @author Christoph Strobl
* @since 1.10
* @soundtrack Clawfinger - Catch Me
*/
public class DataTypeOperators {
/**
* Return the BSON data type of the given {@literal field}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Type typeOf(String fieldReference) {
return Type.typeOf(fieldReference);
}
/**
* {@link AggregationExpression} for {@code $type}.
*
* @author Christoph Strobl
*/
public static class Type extends AbstractAggregationExpression {
private Type(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$type";
}
/**
* Creates new {@link Type}.
*
* @param field must not be {@literal null}.
* @return
*/
public static Type typeOf(String field) {
Assert.notNull(field, "Field must not be null!");
return new Type(Fields.field(field));
}
}
}

View File

@@ -0,0 +1,837 @@
/*
* Copyright 2016. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.LinkedHashMap;
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators.ArithmeticOperatorFactory;
import org.springframework.util.Assert;
/**
* Gateway to {@literal Date} aggregation operations.
*
* @author Christoph Strobl
* @since 1.10
*/
public class DateOperators {
/**
* Take the date referenced by given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static DateOperatorFactory dateOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new DateOperatorFactory(fieldReference);
}
/**
* Take the date resulting from the given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static DateOperatorFactory dateOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new DateOperatorFactory(expression);
}
/**
* @author Christoph Strobl
*/
public static class DateOperatorFactory {
private final String fieldReference;
private final AggregationExpression expression;
/**
* Creates new {@link ArithmeticOperatorFactory} for given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
*/
public DateOperatorFactory(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
this.fieldReference = fieldReference;
this.expression = null;
}
/**
* Creates new {@link ArithmeticOperatorFactory} for given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
*/
public DateOperatorFactory(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
this.fieldReference = null;
this.expression = expression;
}
/**
* Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and
* 366.
*
* @return
*/
public DayOfYear dayOfYear() {
return usesFieldRef() ? DayOfYear.dayOfYear(fieldReference) : DayOfYear.dayOfYear(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the day of the month for a date as a number between 1 and
* 31.
*
* @return
*/
public DayOfMonth dayOfMonth() {
return usesFieldRef() ? DayOfMonth.dayOfMonth(fieldReference) : DayOfMonth.dayOfMonth(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the day of the week for a date as a number between 1
* (Sunday) and 7 (Saturday).
*
* @return
*/
public DayOfWeek dayOfWeek() {
return usesFieldRef() ? DayOfWeek.dayOfWeek(fieldReference) : DayOfWeek.dayOfWeek(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the year portion of a date.
*
* @return
*/
public Year year() {
return usesFieldRef() ? Year.yearOf(fieldReference) : Year.yearOf(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the month of a date as a number between 1 and 12.
*
* @return
*/
public Month month() {
return usesFieldRef() ? Month.monthOf(fieldReference) : Month.monthOf(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the week of the year for a date as a number between 0 and
* 53.
*
* @return
*/
public Week week() {
return usesFieldRef() ? Week.weekOf(fieldReference) : Week.weekOf(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the hour portion of a date as a number between 0 and 23.
*
* @return
*/
public Hour hour() {
return usesFieldRef() ? Hour.hourOf(fieldReference) : Hour.hourOf(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the minute portion of a date as a number between 0 and 59.
*
* @return
*/
public Minute minute() {
return usesFieldRef() ? Minute.minuteOf(fieldReference) : Minute.minuteOf(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the second portion of a date as a number between 0 and 59,
* but can be 60 to account for leap seconds.
*
* @return
*/
public Second second() {
return usesFieldRef() ? Second.secondOf(fieldReference) : Second.secondOf(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the millisecond portion of a date as an integer between 0
* and 999.
*
* @return
*/
public Millisecond millisecond() {
return usesFieldRef() ? Millisecond.millisecondOf(fieldReference) : Millisecond.millisecondOf(expression);
}
/**
* Creates new {@link AggregationExpression} that converts a date object to a string according to a user-specified
* {@literal format}.
*
* @param format must not be {@literal null}.
* @return
*/
public DateToString toString(String format) {
return (usesFieldRef() ? DateToString.dateOf(fieldReference) : DateToString.dateOf(expression)).toString(format);
}
/**
* Creates new {@link AggregationExpression} that returns the weekday number in ISO 8601 format, ranging from 1 (for
* Monday) to 7 (for Sunday).
*
* @return
*/
public IsoDayOfWeek isoDayOfWeek() {
return usesFieldRef() ? IsoDayOfWeek.isoDayOfWeek(fieldReference) : IsoDayOfWeek.isoDayOfWeek(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the week number in ISO 8601 format, ranging from 1 to 53.
*
* @return
*/
public IsoWeek isoWeek() {
return usesFieldRef() ? IsoWeek.isoWeekOf(fieldReference) : IsoWeek.isoWeekOf(expression);
}
/**
* Creates new {@link AggregationExpression} that returns the year number in ISO 8601 format.
*
* @return
*/
public IsoWeekYear isoWeekYear() {
return usesFieldRef() ? IsoWeekYear.isoWeekYearOf(fieldReference) : IsoWeekYear.isoWeekYearOf(expression);
}
private boolean usesFieldRef() {
return fieldReference != null;
}
}
/**
* {@link AggregationExpression} for {@code $dayOfYear}.
*
* @author Christoph Strobl
*/
public static class DayOfYear extends AbstractAggregationExpression {
private DayOfYear(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$dayOfYear";
}
/**
* Creates new {@link DayOfYear}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static DayOfYear dayOfYear(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new DayOfYear(Fields.field(fieldReference));
}
/**
* Creates new {@link DayOfYear}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static DayOfYear dayOfYear(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new DayOfYear(expression);
}
}
/**
* {@link AggregationExpression} for {@code $dayOfMonth}.
*
* @author Christoph Strobl
*/
public static class DayOfMonth extends AbstractAggregationExpression {
private DayOfMonth(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$dayOfMonth";
}
/**
* Creates new {@link DayOfMonth}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static DayOfMonth dayOfMonth(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new DayOfMonth(Fields.field(fieldReference));
}
/**
* Creates new {@link DayOfMonth}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static DayOfMonth dayOfMonth(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new DayOfMonth(expression);
}
}
/**
* {@link AggregationExpression} for {@code $dayOfWeek}.
*
* @author Christoph Strobl
*/
public static class DayOfWeek extends AbstractAggregationExpression {
private DayOfWeek(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$dayOfWeek";
}
/**
* Creates new {@link DayOfWeek}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static DayOfWeek dayOfWeek(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new DayOfWeek(Fields.field(fieldReference));
}
/**
* Creates new {@link DayOfWeek}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static DayOfWeek dayOfWeek(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new DayOfWeek(expression);
}
}
/**
* {@link AggregationExpression} for {@code $year}.
*
* @author Christoph Strobl
*/
public static class Year extends AbstractAggregationExpression {
private Year(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$year";
}
/**
* Creates new {@link Year}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Year yearOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Year(Fields.field(fieldReference));
}
/**
* Creates new {@link Year}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Year yearOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Year(expression);
}
}
/**
* {@link AggregationExpression} for {@code $month}.
*
* @author Christoph Strobl
*/
public static class Month extends AbstractAggregationExpression {
private Month(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$month";
}
/**
* Creates new {@link Month}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Month monthOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Month(Fields.field(fieldReference));
}
/**
* Creates new {@link Month}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Month monthOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Month(expression);
}
}
/**
* {@link AggregationExpression} for {@code $week}.
*
* @author Christoph Strobl
*/
public static class Week extends AbstractAggregationExpression {
private Week(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$week";
}
/**
* Creates new {@link Week}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Week weekOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Week(Fields.field(fieldReference));
}
/**
* Creates new {@link Week}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Week weekOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Week(expression);
}
}
/**
* {@link AggregationExpression} for {@code $hour}.
*
* @author Christoph Strobl
*/
public static class Hour extends AbstractAggregationExpression {
private Hour(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$hour";
}
/**
* Creates new {@link Hour}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Hour hourOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Hour(Fields.field(fieldReference));
}
/**
* Creates new {@link Hour}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Hour hourOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Hour(expression);
}
}
/**
* {@link AggregationExpression} for {@code $minute}.
*
* @author Christoph Strobl
*/
public static class Minute extends AbstractAggregationExpression {
private Minute(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$minute";
}
/**
* Creates new {@link Minute}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Minute minuteOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Minute(Fields.field(fieldReference));
}
/**
* Creates new {@link Minute}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Minute minuteOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Minute(expression);
}
}
/**
* {@link AggregationExpression} for {@code $second}.
*
* @author Christoph Strobl
*/
public static class Second extends AbstractAggregationExpression {
private Second(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$second";
}
/**
* Creates new {@link Second}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Second secondOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Second(Fields.field(fieldReference));
}
/**
* Creates new {@link Second}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Second secondOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Second(expression);
}
}
/**
* {@link AggregationExpression} for {@code $millisecond}.
*
* @author Christoph Strobl
*/
public static class Millisecond extends AbstractAggregationExpression {
private Millisecond(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$millisecond";
}
/**
* Creates new {@link Millisecond}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Millisecond millisecondOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Millisecond(Fields.field(fieldReference));
}
/**
* Creates new {@link Millisecond}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Millisecond millisecondOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Millisecond(expression);
}
}
/**
* {@link AggregationExpression} for {@code $dateToString}.
*
* @author Christoph Strobl
*/
public static class DateToString extends AbstractAggregationExpression {
private DateToString(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$dateToString";
}
/**
* Creates new {@link FormatBuilder} allowing to define the date format to apply.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static FormatBuilder dateOf(final String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new FormatBuilder() {
@Override
public DateToString toString(String format) {
Assert.notNull(format, "Format must not be null!");
return new DateToString(argumentMap(Fields.field(fieldReference), format));
}
};
}
/**
* Creates new {@link FormatBuilder} allowing to define the date format to apply.
*
* @param expression must not be {@literal null}.
* @return
*/
public static FormatBuilder dateOf(final AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new FormatBuilder() {
@Override
public DateToString toString(String format) {
Assert.notNull(format, "Format must not be null!");
return new DateToString(argumentMap(expression, format));
}
};
}
private static java.util.Map<String, Object> argumentMap(Object date, String format) {
java.util.Map<String, Object> args = new LinkedHashMap<String, Object>(2);
args.put("format", format);
args.put("date", date);
return args;
}
public interface FormatBuilder {
/**
* Creates new {@link DateToString} with all previously added arguments appending the given one.
*
* @param format must not be {@literal null}.
* @return
*/
DateToString toString(String format);
}
}
/**
* {@link AggregationExpression} for {@code $isoDayOfWeek}.
*
* @author Christoph Strobl
*/
public static class IsoDayOfWeek extends AbstractAggregationExpression {
private IsoDayOfWeek(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$isoDayOfWeek";
}
/**
* Creates new {@link IsoDayOfWeek}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static IsoDayOfWeek isoDayOfWeek(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new IsoDayOfWeek(Fields.field(fieldReference));
}
/**
* Creates new {@link IsoDayOfWeek}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static IsoDayOfWeek isoDayOfWeek(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new IsoDayOfWeek(expression);
}
}
/**
* {@link AggregationExpression} for {@code $isoWeek}.
*
* @author Christoph Strobl
*/
public static class IsoWeek extends AbstractAggregationExpression {
private IsoWeek(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$isoWeek";
}
/**
* Creates new {@link IsoWeek}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static IsoWeek isoWeekOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new IsoWeek(Fields.field(fieldReference));
}
/**
* Creates new {@link IsoWeek}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static IsoWeek isoWeekOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new IsoWeek(expression);
}
}
/**
* {@link AggregationExpression} for {@code $isoWeekYear}.
*
* @author Christoph Strobl
*/
public static class IsoWeekYear extends AbstractAggregationExpression {
private IsoWeekYear(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$isoWeekYear";
}
/**
* Creates new {@link IsoWeekYear}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static IsoWeekYear isoWeekYearOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new IsoWeekYear(Fields.field(fieldReference));
}
/**
* Creates new {@link Millisecond}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static IsoWeekYear isoWeekYearOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new IsoWeekYear(expression);
}
}
}

View File

@@ -22,8 +22,10 @@ import java.util.Iterator;
import java.util.List;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
import org.springframework.util.Assert;
import org.springframework.util.CompositeIterator;
import org.springframework.util.ObjectUtils;
/**
* Value object to capture the fields exposed by an {@link AggregationOperation}.
@@ -104,7 +106,7 @@ public final class ExposedFields implements Iterable<ExposedField> {
result.add(new ExposedField(field, synthetic));
}
return ExposedFields.from(result);
return from(result);
}
/**
@@ -336,12 +338,36 @@ public final class ExposedFields implements Iterable<ExposedField> {
}
}
/**
* A reference to an {@link ExposedField}.
*
* @author Christoph Strobl
* @since 1.10
*/
interface FieldReference {
/**
* Returns the raw, unqualified reference, i.e. the field reference without a {@literal $} prefix.
*
* @return
*/
String getRaw();
/**
* Returns the reference value for the given field reference. Will return 1 for a synthetic, unaliased field or the
* raw rendering of the reference otherwise.
*
* @return
*/
Object getReferenceValue();
}
/**
* A reference to an {@link ExposedField}.
*
* @author Oliver Gierke
*/
static class FieldReference {
static class DirectFieldReference implements FieldReference {
private final ExposedField field;
@@ -350,17 +376,16 @@ public final class ExposedFields implements Iterable<ExposedField> {
*
* @param field must not be {@literal null}.
*/
public FieldReference(ExposedField field) {
public DirectFieldReference(ExposedField field) {
Assert.notNull(field, "ExposedField must not be null!");
this.field = field;
}
/**
* Returns the raw, unqualified reference, i.e. the field reference without a {@literal $} prefix.
*
* @return
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getRaw()
*/
public String getRaw() {
@@ -368,11 +393,9 @@ public final class ExposedFields implements Iterable<ExposedField> {
return field.synthetic ? target : String.format("%s.%s", Fields.UNDERSCORE_ID, target);
}
/**
* Returns the reference value for the given field reference. Will return 1 for a synthetic, unaliased field or the
* raw rendering of the reference otherwise.
*
* @return
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getReferenceValue()
*/
public Object getReferenceValue() {
return field.synthetic && !field.isAliased() ? 1 : toString();
@@ -384,6 +407,11 @@ public final class ExposedFields implements Iterable<ExposedField> {
*/
@Override
public String toString() {
if(getRaw().startsWith("$")) {
return getRaw();
}
return String.format("$%s", getRaw());
}
@@ -398,11 +426,11 @@ public final class ExposedFields implements Iterable<ExposedField> {
return true;
}
if (!(obj instanceof FieldReference)) {
if (!(obj instanceof DirectFieldReference)) {
return false;
}
FieldReference that = (FieldReference) obj;
DirectFieldReference that = (DirectFieldReference) obj;
return this.field.equals(that.field);
}
@@ -416,4 +444,78 @@ public final class ExposedFields implements Iterable<ExposedField> {
return field.hashCode();
}
}
/**
* A {@link FieldReference} to a {@link Field} used within a nested {@link AggregationExpression}.
*
* @author Christoph Strobl
* @since 1.10
*/
static class ExpressionFieldReference implements FieldReference {
private FieldReference delegate;
/**
* Creates a new {@link FieldReference} for the given {@link ExposedField}.
*
* @param field must not be {@literal null}.
*/
public ExpressionFieldReference(FieldReference field) {
delegate = field;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getRaw()
*/
@Override
public String getRaw() {
return delegate.getRaw();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getReferenceValue()
*/
@Override
public Object getReferenceValue() {
return delegate.getReferenceValue();
}
@Override
public String toString() {
String fieldRef = delegate.toString();
if (fieldRef.startsWith("$$")) {
return fieldRef;
}
if (fieldRef.startsWith("$")) {
return "$" + fieldRef;
}
return fieldRef;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ExpressionFieldReference)) {
return false;
}
ExpressionFieldReference that = (ExpressionFieldReference) obj;
return ObjectUtils.nullSafeEquals(this.delegate, that.delegate);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.aggregation;
import org.bson.Document;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.util.Assert;
@@ -54,7 +55,7 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.Document)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document)
*/
@Override
public Document getMappedObject(Document document) {
@@ -112,10 +113,10 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
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 DirectFieldReference(new ExposedField(field, exposedField.isSynthetic()));
}
return new FieldReference(exposedField);
return new DirectFieldReference(exposedField);
}
if (name.contains(".")) {
@@ -126,7 +127,7 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
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));
return new DirectFieldReference(new ExposedField(name, true));
}
}
return null;

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Output;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.util.Assert;
import org.bson.Document;
/**
* Encapsulates the aggregation framework {@code $facet}-operation. <br />
* Facet of {@link AggregationOperation}s to be used in an {@link Aggregation}. Processes multiple
* {@link AggregationOperation} pipelines within a single stage on the same set of input documents. Each sub-pipeline
* has its own field in the output document where its results are stored as an array of documents.
* {@link FacetOperation} enables various aggregations on the same set of input documents, without needing to retrieve
* the input documents multiple times. <br />
* As of MongoDB 3.4, {@link FacetOperation} cannot be used with nested pipelines containing {@link GeoNearOperation},
* {@link OutOperation} and {@link FacetOperation}. <br />
* We recommend to use the static factory method {@link Aggregation#facet()} instead of creating instances of this class
* directly.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation/facet/">MongoDB Aggregation Framework: $facet</a>
*/
public class FacetOperation implements FieldsExposingAggregationOperation {
/**
* Empty (initial) {@link FacetOperation}.
*/
public static final FacetOperation EMPTY = new FacetOperation();
private final Facets facets;
/**
* Creates a new {@link FacetOperation}.
*/
public FacetOperation() {
this(Facets.EMPTY);
}
private FacetOperation(Facets facets) {
this.facets = facets;
}
/**
* Creates a new {@link FacetOperationBuilder} to append a new facet using {@literal operations}. <br />
* {@link FacetOperationBuilder} takes a pipeline of {@link AggregationOperation} to categorize documents into a
* single facet.
*
* @param operations must not be {@literal null} or empty.
* @return
*/
public FacetOperationBuilder and(AggregationOperation... operations) {
Assert.notNull(operations, "AggregationOperations must not be null!");
Assert.notEmpty(operations, "AggregationOperations must not be empty!");
return new FacetOperationBuilder(facets, Arrays.asList(operations));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
return new Document("$facet", facets.toDocument(context));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields()
*/
@Override
public ExposedFields getFields() {
return facets.asExposedFields();
}
/**
* Builder for {@link FacetOperation} by adding existing and the new pipeline of {@link AggregationOperation} to the
* new {@link FacetOperation}.
*
* @author Mark Paluch
*/
public static class FacetOperationBuilder {
private final Facets current;
private final List<AggregationOperation> operations;
private FacetOperationBuilder(Facets current, List<AggregationOperation> operations) {
this.current = current;
this.operations = operations;
}
/**
* Creates a new {@link FacetOperation} that contains the configured pipeline of {@link AggregationOperation}
* exposed as {@literal fieldName} in the resulting facet document.
*
* @param fieldName must not be {@literal null} or empty.
* @return
*/
public FacetOperation as(String fieldName) {
Assert.hasText(fieldName, "FieldName must not be null or empty!");
return new FacetOperation(current.and(fieldName, operations));
}
}
/**
* Encapsulates multiple {@link Facet}s
*
* @author Mark Paluch
*/
private static class Facets {
private static final Facets EMPTY = new Facets(Collections.<Facet> emptyList());
private List<Facet> facets;
/**
* Creates a new {@link Facets} given {@link List} of {@link Facet}.
*
* @param facets
*/
private Facets(List<Facet> facets) {
this.facets = facets;
}
/**
* @return the {@link ExposedFields} derived from {@link Output}.
*/
ExposedFields asExposedFields() {
ExposedFields fields = ExposedFields.from();
for (Facet facet : facets) {
fields = fields.and(facet.getExposedField());
}
return fields;
}
protected Document toDocument(AggregationOperationContext context) {
Document document = new Document();
for (Facet facet : facets) {
document.put(facet.getExposedField().getName(), facet.toDocuments(context));
}
return document;
}
/**
* Adds a facet to this {@link Facets}.
*
* @param fieldName must not be {@literal null}.
* @param operations must not be {@literal null}.
* @return the new {@link Facets}.
*/
Facets and(String fieldName, List<AggregationOperation> operations) {
Assert.hasText(fieldName, "FieldName must not be null or empty!");
Assert.notNull(operations, "AggregationOperations must not be null!");
List<Facet> facets = new ArrayList<Facet>(this.facets.size() + 1);
facets.addAll(this.facets);
facets.add(new Facet(new ExposedField(fieldName, true), operations));
return new Facets(facets);
}
}
/**
* A single facet with a {@link ExposedField} and its {@link AggregationOperation} pipeline.
*
* @author Mark Paluch
*/
private static class Facet {
private final ExposedField exposedField;
private final List<AggregationOperation> operations;
/**
* Creates a new {@link Facet} given {@link ExposedField} and {@link AggregationOperation} pipeline.
*
* @param exposedField must not be {@literal null}.
* @param operations must not be {@literal null}.
*/
Facet(ExposedField exposedField, List<AggregationOperation> operations) {
Assert.notNull(exposedField, "ExposedField must not be null!");
Assert.notNull(operations, "AggregationOperations must not be null!");
this.exposedField = exposedField;
this.operations = operations;
}
ExposedField getExposedField() {
return exposedField;
}
protected List<Document> toDocuments(AggregationOperationContext context) {
return AggregationOperationRenderer.toDocument(operations, context);
}
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -185,6 +186,14 @@ public final class Fields implements Iterable<Field> {
return fields.iterator();
}
/**
* @return
* @since 1.10
*/
public List<Field> asList() {
return Collections.unmodifiableList(fields);
}
/**
* Value object to encapsulate a field in an aggregation operation.
*
@@ -192,6 +201,7 @@ public final class Fields implements Iterable<Field> {
*/
static class AggregationField implements Field {
private final String raw;
private final String name;
private final String target;
@@ -216,6 +226,7 @@ public final class Fields implements Iterable<Field> {
*/
public AggregationField(String name, String target) {
raw = name;
String nameToSet = cleanUp(name);
String targetToSet = cleanUp(target);
@@ -257,6 +268,11 @@ public final class Fields implements Iterable<Field> {
* @see org.springframework.data.mongodb.core.aggregation.Field#getAlias()
*/
public String getTarget() {
if (isLocalVar()) {
return this.getRaw();
}
return StringUtils.hasText(this.target) ? this.target : this.name;
}
@@ -269,6 +285,22 @@ public final class Fields implements Iterable<Field> {
return !getName().equals(getTarget());
}
/**
* @return {@literal true} in case the field name starts with {@code $$}.
* @since 1.10
*/
public boolean isLocalVar() {
return raw.startsWith("$$") && !raw.startsWith("$$$");
}
/**
* @return
* @since 1.10
*/
public String getRaw() {
return raw;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()

View File

@@ -0,0 +1,409 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bson.Document;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Encapsulates the aggregation framework {@code $graphLookup}-operation. <br />
* Performs a recursive search on a collection, with options for restricting the search by recursion depth and query
* filter. <br />
* We recommend to use the static factory method {@link Aggregation#graphLookup(String)} instead of creating instances
* of this class directly.
*
* @see <a href=
* "https://docs.mongodb.org/manual/reference/aggregation/graphLookup/">https://docs.mongodb.org/manual/reference/aggregation/graphLookup/</a>
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
*/
public class GraphLookupOperation implements InheritsFieldsAggregationOperation {
private static final Set<Class<?>> ALLOWED_START_TYPES = new HashSet<Class<?>>(
Arrays.<Class<?>> asList(AggregationExpression.class, String.class, Field.class, Document.class));
private final String from;
private final List<Object> startWith;
private final Field connectFrom;
private final Field connectTo;
private final Field as;
private final Long maxDepth;
private final Field depthField;
private final CriteriaDefinition restrictSearchWithMatch;
private GraphLookupOperation(String from, List<Object> startWith, Field connectFrom, Field connectTo, Field as,
Long maxDepth, Field depthField, CriteriaDefinition restrictSearchWithMatch) {
this.from = from;
this.startWith = startWith;
this.connectFrom = connectFrom;
this.connectTo = connectTo;
this.as = as;
this.maxDepth = maxDepth;
this.depthField = depthField;
this.restrictSearchWithMatch = restrictSearchWithMatch;
}
/**
* Creates a new {@link FromBuilder} to build {@link GraphLookupOperation}.
*
* @return a new {@link FromBuilder}.
*/
public static FromBuilder builder() {
return new GraphLookupOperationFromBuilder();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
Document graphLookup = new Document();
graphLookup.put("from", from);
List<Object> mappedStartWith = new ArrayList<Object>(startWith.size());
for (Object startWithElement : startWith) {
if (startWithElement instanceof AggregationExpression) {
mappedStartWith.add(((AggregationExpression) startWithElement).toDocument(context));
} else if (startWithElement instanceof Field) {
mappedStartWith.add(context.getReference((Field) startWithElement).toString());
} else {
mappedStartWith.add(startWithElement);
}
}
graphLookup.put("startWith", mappedStartWith.size() == 1 ? mappedStartWith.iterator().next() : mappedStartWith);
graphLookup.put("connectFromField", connectFrom.getName());
graphLookup.put("connectToField", connectTo.getName());
graphLookup.put("as", as.getName());
if (maxDepth != null) {
graphLookup.put("maxDepth", maxDepth);
}
if (depthField != null) {
graphLookup.put("depthField", depthField.getName());
}
if (restrictSearchWithMatch != null) {
graphLookup.put("restrictSearchWithMatch", context.getMappedObject(restrictSearchWithMatch.getCriteriaObject()));
}
return new Document("$graphLookup", graphLookup);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields()
*/
@Override
public ExposedFields getFields() {
return ExposedFields.from(new ExposedField(as, true));
}
/**
* @author Mark Paluch
*/
public interface FromBuilder {
/**
* Set the {@literal collectionName} to apply the {@code $graphLookup} to.
*
* @param collectionName must not be {@literal null} or empty.
* @return
*/
StartWithBuilder from(String collectionName);
}
/**
* @author Mark Paluch
* @author Christoph Strobl
*/
public interface StartWithBuilder {
/**
* Set the startWith {@literal fieldReferences} to apply the {@code $graphLookup} to.
*
* @param fieldReferences must not be {@literal null}.
* @return
*/
ConnectFromBuilder startWith(String... fieldReferences);
/**
* Set the startWith {@literal expressions} to apply the {@code $graphLookup} to.
*
* @param expressions must not be {@literal null}.
* @return
*/
ConnectFromBuilder startWith(AggregationExpression... expressions);
/**
* Set the startWith as either {@literal fieldReferences}, {@link Fields}, {@link Document} or
* {@link AggregationExpression} to apply the {@code $graphLookup} to.
*
* @param expressions must not be {@literal null}.
* @return
* @throws IllegalArgumentException
*/
ConnectFromBuilder startWith(Object... expressions);
}
/**
* @author Mark Paluch
*/
public interface ConnectFromBuilder {
/**
* Set the connectFrom {@literal fieldName} to apply the {@code $graphLookup} to.
*
* @param fieldName must not be {@literal null} or empty.
* @return
*/
ConnectToBuilder connectFrom(String fieldName);
}
/**
* @author Mark Paluch
*/
public interface ConnectToBuilder {
/**
* Set the connectTo {@literal fieldName} to apply the {@code $graphLookup} to.
*
* @param fieldName must not be {@literal null} or empty.
* @return
*/
GraphLookupOperationBuilder connectTo(String fieldName);
}
/**
* Builder to build the initial {@link GraphLookupOperationBuilder} that configures the initial mandatory set of
* {@link GraphLookupOperation} properties.
*
* @author Mark Paluch
*/
static final class GraphLookupOperationFromBuilder
implements FromBuilder, StartWithBuilder, ConnectFromBuilder, ConnectToBuilder {
private String from;
private List<? extends Object> startWith;
private String connectFrom;
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.FromBuilder#from(java.lang.String)
*/
@Override
public StartWithBuilder from(String collectionName) {
Assert.hasText(collectionName, "CollectionName must not be null or empty!");
this.from = collectionName;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder#startWith(java.lang.String[])
*/
@Override
public ConnectFromBuilder startWith(String... fieldReferences) {
Assert.notNull(fieldReferences, "FieldReferences must not be null!");
Assert.noNullElements(fieldReferences, "FieldReferences must not contain null elements!");
List<Object> fields = new ArrayList<Object>(fieldReferences.length);
for (String fieldReference : fieldReferences) {
fields.add(Fields.field(fieldReference));
}
this.startWith = fields;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder#startWith(org.springframework.data.mongodb.core.aggregation.AggregationExpression[])
*/
@Override
public ConnectFromBuilder startWith(AggregationExpression... expressions) {
Assert.notNull(expressions, "AggregationExpressions must not be null!");
Assert.noNullElements(expressions, "AggregationExpressions must not contain null elements!");
this.startWith = Arrays.asList(expressions);
return this;
}
@Override
public ConnectFromBuilder startWith(Object... expressions) {
Assert.notNull(expressions, "Expressions must not be null!");
Assert.noNullElements(expressions, "Expressions must not contain null elements!");
this.startWith = verifyAndPotentiallyTransformStartsWithTypes(expressions);
return this;
}
private List<Object> verifyAndPotentiallyTransformStartsWithTypes(Object... expressions) {
List<Object> expressionsToUse = new ArrayList<Object>(expressions.length);
for (Object expression : expressions) {
assertStartWithType(expression);
if (expression instanceof String) {
expressionsToUse.add(Fields.field((String) expression));
} else {
expressionsToUse.add(expression);
}
}
return expressionsToUse;
}
private void assertStartWithType(Object expression) {
for (Class<?> type : ALLOWED_START_TYPES) {
if (ClassUtils.isAssignable(type, expression.getClass())) {
return;
}
}
throw new IllegalArgumentException(
String.format("Expression must be any of %s but was %s", ALLOWED_START_TYPES, expression.getClass()));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.ConnectFromBuilder#connectFrom(java.lang.String)
*/
@Override
public ConnectToBuilder connectFrom(String fieldName) {
Assert.hasText(fieldName, "ConnectFrom must not be null or empty!");
this.connectFrom = fieldName;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.ConnectToBuilder#connectTo(java.lang.String)
*/
@Override
public GraphLookupOperationBuilder connectTo(String fieldName) {
Assert.hasText(fieldName, "ConnectTo must not be null or empty!");
return new GraphLookupOperationBuilder(from, startWith, connectFrom, fieldName);
}
}
/**
* @author Mark Paluch
*/
public static final class GraphLookupOperationBuilder {
private final String from;
private final List<Object> startWith;
private final Field connectFrom;
private final Field connectTo;
private Long maxDepth;
private Field depthField;
private CriteriaDefinition restrictSearchWithMatch;
protected GraphLookupOperationBuilder(String from, List<? extends Object> startWith, String connectFrom,
String connectTo) {
this.from = from;
this.startWith = new ArrayList<Object>(startWith);
this.connectFrom = Fields.field(connectFrom);
this.connectTo = Fields.field(connectTo);
}
/**
* Optionally limit the number of recursions.
*
* @param numberOfRecursions must be greater or equal to zero.
* @return
*/
public GraphLookupOperationBuilder maxDepth(long numberOfRecursions) {
Assert.isTrue(numberOfRecursions >= 0, "Max depth must be >= 0!");
this.maxDepth = numberOfRecursions;
return this;
}
/**
* Optionally add a depth field {@literal fieldName} to each traversed document in the search path.
*
* @param fieldName must not be {@literal null} or empty.
* @return
*/
public GraphLookupOperationBuilder depthField(String fieldName) {
Assert.hasText(fieldName, "Depth field name must not be null or empty!");
this.depthField = Fields.field(fieldName);
return this;
}
/**
* Optionally add a query specifying conditions to the recursive search.
*
* @param criteriaDefinition must not be {@literal null}.
* @return
*/
public GraphLookupOperationBuilder restrict(CriteriaDefinition criteriaDefinition) {
Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!");
this.restrictSearchWithMatch = criteriaDefinition;
return this;
}
/**
* Set the name of the array field added to each output document and return the final {@link GraphLookupOperation}.
* Contains the documents traversed in the {@literal $graphLookup} stage to reach the document.
*
* @param fieldName must not be {@literal null} or empty.
* @return the final {@link GraphLookupOperation}.
*/
public GraphLookupOperation as(String fieldName) {
Assert.hasText(fieldName, "As field name must not be null or empty!");
return new GraphLookupOperation(from, startWith, connectFrom, connectTo, Fields.field(fieldName), maxDepth,
depthField, restrictSearchWithMatch);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2017 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,12 +33,13 @@ import org.springframework.util.StringUtils;
* We recommend to use the static factory method {@link Aggregation#group(Fields)} instead of creating instances of this
* class directly.
*
* @see http://docs.mongodb.org/manual/reference/aggregation/group/#stage._S_group
* @author Sebastian Herold
* @author Thomas Darimont
* @author Oliver Gierke
* @author Gustavo de Geus
* @author Christoph Strobl
* @since 1.3
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation/group/">MongoDB Aggregation Framework: $group</a>
*/
public class GroupOperation implements FieldsExposingAggregationOperation {
@@ -311,6 +312,51 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
return newBuilder(GroupOps.MAX, null, expr);
}
/**
* Generates an {@link GroupOperationBuilder} for an {@code $stdDevSamp}-expression that for the given
* field-reference.
*
* @param reference must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public GroupOperationBuilder stdDevSamp(String reference) {
return newBuilder(GroupOps.STD_DEV_SAMP, reference, null);
}
/**
* Generates an {@link GroupOperationBuilder} for an {@code $stdDevSamp}-expression that for the given {@link AggregationExpression}.
*
* @param expr must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public GroupOperationBuilder stdDevSamp(AggregationExpression expr) {
return newBuilder(GroupOps.STD_DEV_SAMP, null, expr);
}
/**
* Generates an {@link GroupOperationBuilder} for an {@code $stdDevPop}-expression that for the given field-reference.
*
* @param reference must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public GroupOperationBuilder stdDevPop(String reference) {
return newBuilder(GroupOps.STD_DEV_POP, reference, null);
}
/**
* Generates an {@link GroupOperationBuilder} for an {@code $stdDevPop}-expression that for the given {@link AggregationExpression}.
*
* @param expr must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public GroupOperationBuilder stdDevPop(AggregationExpression expr) {
return newBuilder(GroupOps.STD_DEV_POP, null, expr);
}
private GroupOperationBuilder newBuilder(Keyword keyword, String reference, Object value) {
return new GroupOperationBuilder(this, new Operation(keyword, null, reference, value));
}
@@ -375,21 +421,18 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
private static enum GroupOps implements Keyword {
SUM, LAST, FIRST, PUSH, AVG, MIN, MAX, ADD_TO_SET, COUNT;
SUM("$sum"), LAST("$last"), FIRST("$first"), PUSH("$push"), AVG("$avg"), MIN("$min"), MAX("$max"), ADD_TO_SET("$addToSet"), STD_DEV_POP("$stdDevPop"), STD_DEV_SAMP("$stdDevSamp");
private String mongoOperator;
GroupOps(String mongoOperator) {
this.mongoOperator = mongoOperator;
}
@Override
public String toString() {
String[] parts = name().split("_");
StringBuilder builder = new StringBuilder();
for (String part : parts) {
String lowerCase = part.toLowerCase(Locale.US);
builder.append(builder.length() == 0 ? lowerCase : StringUtils.capitalize(lowerCase));
}
return "$" + builder.toString();
return mongoOperator;
}
}

View File

@@ -1,195 +0,0 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.List;
import org.bson.Document;
import org.springframework.util.Assert;
/**
* Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field field
* references}, values of simple MongoDB types or values that can be converted to a simple MongoDB type.
*
* @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
*/
public class IfNullOperator implements AggregationExpression {
private final Field field;
private final Object value;
/**
* Creates a new {@link IfNullOperator} for the given {@link Field} and replacement {@code value}.
*
* @param field must not be {@literal null}.
* @param value must not be {@literal null}.
*/
public IfNullOperator(Field field, Object value) {
Assert.notNull(field, "Field must not be null!");
Assert.notNull(value, "'Replacement-value' must not be null!");
this.field = field;
this.value = value;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
List<Object> list = new ArrayList<Object>();
list.add(context.getReference(field).toString());
list.add(resolve(value, context));
return new Document("$ifNull", list);
}
private Object resolve(Object value, AggregationOperationContext context) {
if (value instanceof Field) {
return context.getReference((Field) value).toString();
} else if (value instanceof Document) {
return value;
}
return context.getMappedObject(new Document("$set", value)).get("$set");
}
/**
* Get a builder that allows fluent creation of {@link IfNullOperator}.
*
* @return a new {@link IfNullBuilder}.
*/
public static IfNullBuilder newBuilder() {
return IfNullOperatorBuilder.newBuilder();
}
/**
* @since 1.10
*/
public static interface IfNullBuilder {
/**
* @param field the field to check for a {@literal null} value, field reference must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder ifNull(Field field);
/**
* @param field the field to check for a {@literal null} value, field name must not be {@literal null} or empty.
* @return the {@link ThenBuilder}
*/
ThenBuilder ifNull(String field);
}
/**
* @since 1.10
*/
public static interface ThenBuilder {
/**
* @param field the field holding the replacement value, must not be {@literal null}.
* @return the {@link IfNullOperator}
*/
IfNullOperator thenReplaceWith(Field field);
/**
* @param value the value to be used if the {@code $ifNull }condition evaluates {@literal true}. Can be a
* {@link Document}, a value that is supported by MongoDB or a value that can be converted to a MongoDB
* representation but must not be {@literal null}.
* @return the {@link IfNullOperator}
*/
IfNullOperator thenReplaceWith(Object value);
}
/**
* Builder for fluent {@link IfNullOperator} creation.
*
* @author Mark Paluch
* @since 1.10
*/
public static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder {
private Field field;
private IfNullOperatorBuilder() {}
/**
* Creates a new builder for {@link IfNullOperator}.
*
* @return never {@literal null}.
*/
public static IfNullOperatorBuilder newBuilder() {
return new IfNullOperatorBuilder();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.Field)
*/
public ThenBuilder ifNull(Field field) {
Assert.notNull(field, "Field must not be null!");
this.field = field;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.IfNullBuilder#ifNull(java.lang.String)
*/
public ThenBuilder ifNull(String name) {
Assert.hasText(name, "Field name must not be null or empty!");
this.field = Fields.field(name);
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.ThenReplaceBuilder#thenReplaceWith(org.springframework.data.mongodb.core.aggregation.Field)
*/
@Override
public IfNullOperator thenReplaceWith(Field replacementField) {
Assert.notNull(replacementField, "Replacement field must not be null!");
return new IfNullOperator(this.field, replacementField);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.ThenReplaceBuilder#thenReplaceWith(java.lang.Object)
*/
@Override
public IfNullOperator thenReplaceWith(Object value) {
Assert.notNull(value, "'Replacement-value' must not be null!");
return new IfNullOperator(this.field, value);
}
}
}

View File

@@ -13,17 +13,16 @@
* 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.ExposedFields.FieldReference;
import org.springframework.util.Assert;
/**
* {@link ExposedFieldsAggregationOperationContext} that inherits fields from its parent
* {@link AggregationOperationContext}.
*
* @author Mark Paluch
* @since 1.9
*/
class InheritingExposedFieldsAggregationOperationContext extends ExposedFieldsAggregationOperationContext {
@@ -40,7 +39,7 @@ class InheritingExposedFieldsAggregationOperationContext extends ExposedFieldsAg
AggregationOperationContext previousContext) {
super(exposedFields, previousContext);
Assert.notNull(previousContext, "PreviousContext must not be null!");
this.previousContext = previousContext;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2017 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,11 +24,11 @@ import org.springframework.util.Assert;
* We recommend to use the static factory method {@link Aggregation#limit(long)} instead of creating instances of this
* class directly.
*
* @see http://docs.mongodb.org/manual/reference/aggregation/limit/
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
* @since 1.3
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation/limit/">MongoDB Aggregation Framework: $limit</a>
*/
public class LimitOperation implements AggregationOperation {

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2016. 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.util.Assert;
/**
* Gateway to {@literal literal} aggregation operations.
*
* @author Christoph Strobl
* @since 1.10
*/
public class LiteralOperators {
/**
* Take the value referenced by given {@literal value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static LiteralOperatorFactory valueOf(Object value) {
Assert.notNull(value, "Value must not be null!");
return new LiteralOperatorFactory(value);
}
/**
* @author Christoph Strobl
*/
public static class LiteralOperatorFactory {
private final Object value;
/**
* Creates new {@link LiteralOperatorFactory} for given {@literal value}.
*
* @param value must not be {@literal null}.
*/
public LiteralOperatorFactory(Object value) {
Assert.notNull(value, "Value must not be null!");
this.value = value;
}
/**
* Creates new {@link Literal} that returns the associated value without parsing.
*
* @return
*/
public Literal asLiteral() {
return Literal.asLiteral(value);
}
}
/**
* {@link AggregationExpression} for {@code $literal}.
*
* @author Christoph Strobl
*/
public static class Literal extends AbstractAggregationExpression {
private Literal(Object value) {
super(value);
}
@Override
protected String getMongoMethod() {
return "$literal";
}
/**
* Creates new {@link Literal}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Literal asLiteral(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Literal(value);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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,8 +27,8 @@ import org.springframework.util.Assert;
* @author Alessio Fachechi
* @author Christoph Strobl
* @author Mark Paluch
* @see http://docs.mongodb.org/manual/reference/aggregation/lookup/#stage._S_lookup
* @since 1.9
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/">MongoDB Aggregation Framework: $lookup</a>
*/
public class LookupOperation implements FieldsExposingAggregationOperation, InheritsFieldsAggregationOperation {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2017 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,11 +26,11 @@ import org.springframework.util.Assert;
* {@link Aggregation#match(org.springframework.data.mongodb.core.query.Criteria)} instead of creating instances of this
* class directly.
*
* @see http://docs.mongodb.org/manual/reference/aggregation/match/
* @author Sebastian Herold
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.3
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/match/">MongoDB Aggregation Framework: $match</a>
*/
public class MatchOperation implements AggregationOperation {

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2016. 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.bson.Document;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExpressionFieldReference;
import org.springframework.util.Assert;
/**
* {@link AggregationOperationContext} that delegates {@link FieldReference} resolution and mapping to a parent one, but
* assures {@link FieldReference} get converted into {@link ExpressionFieldReference} using {@code $$} to ref an inner
* variable.
*
* @author Christoph Strobl
* @since 1.10
*/
class NestedDelegatingExpressionAggregationOperationContext implements AggregationOperationContext {
private final AggregationOperationContext delegate;
/**
* Creates new {@link NestedDelegatingExpressionAggregationOperationContext}.
*
* @param referenceContext must not be {@literal null}.
*/
public NestedDelegatingExpressionAggregationOperationContext(AggregationOperationContext referenceContext) {
Assert.notNull(referenceContext, "Reference context must not be null!");
this.delegate = referenceContext;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(org.bson.Document)
*/
@Override
public Document getMappedObject(Document document) {
return delegate.getMappedObject(document);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.Field)
*/
@Override
public FieldReference getReference(Field field) {
return new ExpressionFieldReference(delegate.getReference(field));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String)
*/
@Override
public FieldReference getReference(String name) {
return new ExpressionFieldReference(delegate.getReference(name));
}
}

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