Compare commits

...

94 Commits

Author SHA1 Message Date
Oliver Gierke
f4db26ffd1 DATAMONGO-1717 - Release version 2.0 RC1 (Kay). 2017-07-25 09:48:49 +02:00
Oliver Gierke
b14a3166e3 DATAMONGO-1717 - Prepare 2.0 RC1 (Kay). 2017-07-25 09:48:01 +02:00
Oliver Gierke
bd7af4c55b DATAMONGO-1717 - Updated changelog. 2017-07-25 09:47:53 +02:00
Oliver Gierke
491304f2c7 DATAMONGO-1711 - Updated changelog. 2017-07-24 22:20:42 +02:00
Oliver Gierke
51d5a3b61a DATAMONGO-1720 - Make sure benchmark module is not included by default.
The benchmarks module does not produce a JAR by default which let's our Maven Central deployment fail as a module has to produce one according to their rules. We're now only including the benchmark module when the benchmarks profile is active.
2017-07-24 18:39:56 +02:00
Sebastien Deleuze
2c193ec325 DATAMONGO-1748 - Removed placeholder for Kotlin version as the build parent already has it.
Original pull request: #489.
2017-07-24 14:52:42 +02:00
Sebastien Deleuze
427d4f5bd5 DATAMONGO-1748 - Tweaked Kotlin extensions to add Collection variant of Criteria.inValues(…) alias.
Original pull request: #489.
2017-07-24 14:52:32 +02:00
Sebastien Deleuze
8f8d792b61 DATAMONGO-1748 - Fine-tuning of nullability expressions in Kotlin extension for Criteria.
The Kotlin extensions introduced for Criteria now define relaxed null requirements for both isEqualTo(…) and the elements of the varargs handed to inValues(…).

Original pull request: #488.
2017-07-24 11:07:29 +02:00
Oliver Gierke
f16809a363 DATAMONGO-1519 - Method signature change for MongoTemplate.insertDocumentList(…).
Implemented return type change for protected method insertDocumentList(String, List<Document>).

Related ticket: DATAMONGO-1513.
2017-07-24 10:12:58 +02:00
Christoph Strobl
58b33e949b DATAMONGO-1719 - Add support for open/closed interface projections to fluent reactive API.
Original Pull Request: #487
2017-07-24 09:03:00 +02:00
Christoph Strobl
47481c4597 DATAMONGO-1719 - Polishing.
Use empty query where possible to avoid null values and introduce non optional findAndModify alternative for imperative api.
Add missing ExecutableUpdateOperation Kotlin extension.
Update Javadoc and add non-Javadoc comments.

Original Pull Request: #487
2017-07-24 09:02:23 +02:00
Mark Paluch
1475cde337 DATAMONGO-1719 - Polishing.
Use mockito extensions to mimic static imports.
Use Lombok's FieldDefaults and NonNull in operations support.

Original Pull Request: #487
2017-07-24 09:01:48 +02:00
Mark Paluch
4734a2925c DATAMONGO-1719 - Rename blocking fluent entry interfaces from …Operation to Executable… and remove Operation from intermediate interfaces.
Original Pull Request: #487
2017-07-24 08:45:35 +02:00
Mark Paluch
a6a0bde6f2 DATAMONGO-1719 - Add fluent reactive operations.
We now provide a fluent API for find, insert, update, aggregate and delete operations that can be used as an alternative for their counterparts in ReactiveMongoOperations.

Original Pull Request: #487
2017-07-24 08:44:39 +02:00
Mark Paluch
30a8608135 DATAMONGO-1746 - Inherit Project Reactor version from dependency management.
Adapt to BOM import in Spring Data Build pom.
2017-07-20 15:48:37 +02:00
Oliver Gierke
c77facda90 DATAMONGO-1733 - Polishing.
Tiny improvements to the ProjectingReadCallback as we now skip null values completely. Made the ProjectionFactory an instance variable to make sure we propagate the BeanFactory and BeanClassLoader in setApplicationContext(…). Added unit test to verify instances aren't proxied unnecessarily if the interface asked for is already implemented by the target.

Original pull request: #486.
Related tickets: DATACMNS-1121.
2017-07-20 14:53:06 +02:00
Christoph Strobl
b4c213b8c2 DATAMONGO-1733 - Added benchmark for projections in FluentMongoOperations.
Original pull request: #486.
2017-07-20 14:53:03 +02:00
Christoph Strobl
2230b51a79 DATAMONGO-1733 - Added support for projections on FluentMongoOperations.
Interfaces based projections handed to queries built using the FluentMongoOperations APIs now get projected as expected and also apply querying optimizations so that only fields needed in the projection are read in the first place.

Original pull request: #486.
2017-07-20 14:52:35 +02:00
Oliver Gierke
7258cb8d1d DATAMONGO-1748 - Polishing.
Added missing license headers, formatting.

Original pull request: #473.
2017-07-20 13:28:28 +02:00
Sebastien Deleuze
e6bae5d124 DATAMONGO-1748 - Add Kotlin extensions for Criteria API.
This commit introduces two Criteria method aliases because in and is are reserved keywords in Kotlin:

 - isEqualTo(…) alias for is(…)
 - inValues(…) alias for in(…)

Original pull request: #473.
2017-07-20 13:28:28 +02:00
Christoph Strobl
41bb619dc7 DATAMONGO-1646 - Polishing.
Remove ReactiveMongoDbUtils, update JavaDoc and add missing tests.

Original Pull Request: #481
2017-07-20 09:37:10 +02:00
Mark Paluch
6c42c4c828 DATAMONGO-1646 - Polishing.
Add missing license header.

Original Pull Request: #481
2017-07-20 09:32:58 +02:00
Mark Paluch
58050405a3 DATAMONGO-1646 - Support reactive aggregation execution.
We now support reactive aggregation execution via ReactiveMongoOperations.aggregate(…).

Original Pull Request: #481
2017-07-20 09:32:16 +02:00
Oliver Gierke
8834c5e97d DATAMONGO-1702 - Polishing.
Removed obsolete constants from MongoRepositoryFactory.

Original pull request: #480.
2017-07-19 15:56:07 +02:00
Mark Paluch
7526f3bd2e DATAMONGO-1702 - Adopt to composable repositories.
Deprecate QuerydslMongoRepository, introduce QuerydslMongoPredicateExecutor instead without extending SimpleMongoRepository. Use RepositoryFragments to mix in requested repository aspects.

Original pull request: #480.
2017-07-19 15:56:03 +02:00
Oliver Gierke
3d623d8181 DATAMONGO-1744 - Improved setup of default MongoMappingContext instances created.
We now make sure that the SimpleTypeHolder produced by MongoCustomConversions is used to set up default MongoMappingContext instances in (Reactive)MongoTemplate and unit tests.
2017-07-19 15:04:24 +02:00
Mark Paluch
ada8c4ec8d DATAMONGO-1738 - Polishing.
Adapt stream execution to changes in fluent API. Use fluent API for GeoNear executions. Remove unused code, update JavaDoc. Remove superfluous exception declaration in tests.

Original pull request: #484.
2017-07-18 11:13:19 +02:00
Oliver Gierke
dd8fc1a591 DATAMONGO-1738 - Move repository query execution to fluent operations API.
We now use the fluent FindOperations API in AbstractMongoQuery and MongoQueryExecution instead of the MongoOperations. This allows us to eagerly resolve some general execution coordinates (which collection to query etc.) and thus simplify the eventual execution.

Got rid of a couple of very simple QueryExecution implementations that can be replace by a simple lambda. Removed the need to read into a partially filled domain object and then map to the projection DTO as we can now tell the operations to read into the DTO directly.

Adapted unit tests.

Original pull request: #484.
2017-07-18 11:13:05 +02:00
Oliver Gierke
e1f19f69bd DATAMONGO-1739 - Changed TerminatingFindOperation.stream() to return Stream.
TerminatingFindOperation.stream() now returns a Stream directly, leveraging Spring Data Commons' StreamUtils.createStreamFromIterator(…) to create a Stream and register a callback to forward calls to Stream.close() to the iterator.

Original pull request: #485.
2017-07-18 09:50:07 +02:00
Mark Paluch
566e69a825 DATAMONGO-1703 - Polishing.
Use lombok's Value for ObjectPathItem. Make methods accessible in DefaultDbRefResolver before calling. Use class.cast to avoid warnings. Update Javadoc.

Original pull request: #478.
2017-07-14 11:36:29 +02:00
Christoph Strobl
6342ef1806 DATAMONGO-1703 - Convert resolved DBRef's from source that do not match the requested property type.
We now check if already resolved DBRef's are assignable to the target property type. If not, we perform conversion again to prevent ClassCastException when trying to assign non matching types.

Remove non applicable public modifiers in ObjectPath.

Original pull request: #478.
2017-07-14 11:36:14 +02:00
Mark Paluch
9d4d47f503 DATAMONGO-1721 - Move ReactiveIndexOperations to core.index package.
Align with synchronous IndexOperations.

Related pull request: #470.
2017-07-14 09:49:13 +02:00
Mark Paluch
f22036851e DATAMONGO-1682 - Polishing.
Require non-null arguments in DefaultReactiveIndexOperations constructor. Remove superfluous publisher creation indirections. Use StepVerifier.verifyComplete() to verify the step sequence.

Use provided entity type in template API to construct index operations.

Original pull request: #474.
2017-07-13 16:11:00 +02:00
Christoph Strobl
d5006bb693 DATAMONGO-1682 - Add support partialFilterExpression for reactive index creation.
We now support partial filter expression on indexes via Index.partial(…) on the reactive API. This allows to create partial indexes that only index the documents in a collection that meet a specified filter expression.

Original pull request: #474.
2017-07-13 16:01:00 +02:00
Mark Paluch
82fdbe8cc2 DATAMONGO-1720 - Polishing.
Enhance benchmark statistics with Git/working tree details. Specify byte encoding for JSON to byte encoder.
Add status code check to HttpResultsWriter to verify that the results were accepted. Convert spaces to tabs in pom.xml.

Original pull request: #483.
2017-07-13 15:16:32 +02:00
Christoph Strobl
747625b5c3 DATAMONGO-1720 - Add JMH based benchmarks for MappingMongoConverter.
Run the benchmark via the maven profile "benchmarks":

    mvn -P benchmarks clean test

Or run them customized:

    mvn -P benchmarks -DwarmupIterations=2 -DmeasurementIterations=5 -Dforks=1 clean test

Origin pull request: #483.
2017-07-13 15:16:28 +02:00
Oliver Gierke
3dee8de66d DATAMONGO-1371 - Polishing.
Removed console output from unit tests.
2017-07-13 14:37:09 +02:00
Oliver Gierke
e3b98693d4 DATAMONGO-1721 - Polishing.
Removed deprecated types and adapt dependency tests accordingly.

Refactored MongoExampleMapper to revert to use StringMatcher from Spring Data Commons' ExampleMatcher. Introduced MongoRegexCreator specific MatchMode, which is basically a copy of StringMatcher. Adapted MongoExampleMapper and MongoQueryCreator to translate from StringMatcher and Part.Type to MatchMode.

Turned unit tests for MongoRegexCreator into parameterized ones.

Original pull request: #470.
2017-07-13 14:37:07 +02:00
Jens Schauder
80ff3760ef DATAMONGO-1721 - Fixed package dependencies.
Added a Degraph based tests to identify package cycles and violations in layering.

Moved Collation to the core.query package, fixing dependency cycles. Moved IndexOperations and IndexOperationsProvider to the core.index package. fixing dependency cycles. Moved GeoJsonConfiguration to config package. Replaced the original version of these interfaces/classes with a deprecated version extending the new one, in order to not break the existing API.

Removed all references to Part.Type, except for those to maintain the existing API. API using Part.Type is marked as deprecated. It violates the layering, because nothing but "config" should access "repository". Tests added to MongoRegexCreator in order to facilitate the removal of Part.Type dependencies. Using the moved/new ExampleMatcherAccessor.

Related Tickets: DATACMNS-1097.
Original pull request: #470.
2017-07-13 14:36:57 +02:00
Mark Paluch
d19ea88670 DATAMONGO-1735 - Query sort and field documents no longer allow null.
We now require Sort and Fields (Projection) documents in Query. Absent sorting and projection uses empty documents.

Original pull request: #479.
2017-07-07 14:23:21 +02:00
Christoph Strobl
d3b9f91478 DATAMONGO-1734 - Polish MongoTemplate.exists execution.
Optimize execution by using count() limited to 1 element.

Original pull request: #479.
2017-07-07 14:23:14 +02:00
Christoph Strobl
7fb5c7d97c DATAMONGO-1734 - Add exists() and count() to fluent API.
Original pull request: #479.
2017-07-07 14:22:53 +02:00
Mark Paluch
18487ef252 DATAMONGO-1713 - Polishing.
Fix typos. Migrate to diamond syntax where applicable. Use Arrays.stream(…) instead of Arrays.asList(…).stream(). Mention percent sign as required char for URL encoding and reference RFC 3986 in documentation.

Original pull request: #477.
2017-07-06 14:21:34 +02:00
Christoph Strobl
1aa2ee5f54 DATAMONGO-1713 - Allow using URL encoded username/password for <mongo-client credentials=… />.
We now URL decode username & password before creating MongoCredentials. This allows usage of reserved characters in the credentials string.

Original pull request: #477.
2017-07-06 14:21:12 +02:00
Christoph Strobl
b9282c8d32 DATAMONGO-1726 - Add oneValue() and firstValue() to FluentMongoOperations returning nullable types.
We leave the choice of using Optional open by also providing terminating find operation methods that return null instead of Optional.

Original pull request: #475.
2017-07-06 11:29:08 +02:00
Oliver Gierke
3ac379a4b8 DATAMONGO-1725 - Polishing. 2017-07-05 13:16:21 +02:00
Oliver Gierke
0470dd6268 DATAMONGO-1725 - Prevent NullPointerException in CloseableIterableCursorAdapter.close(). 2017-07-05 13:06:33 +02:00
Christoph Strobl
270d373083 DATAMONGO-1728 - Fix NPE in ExecutableFindOperation.first().
Original pull request: #476.
2017-07-05 09:50:52 +02:00
Mark Paluch
697f5ad7c6 DATAMONGO-1730 - Adapt to API changes in mapping subsystem. 2017-07-04 14:36:58 +02:00
Oliver Gierke
028aeb327f DATAMONGO-1729 - Open projections don't get field restrictions applied.
We now only apply a field restriction if the projection used for a query is closed.
2017-07-03 22:04:16 +02:00
Oliver Gierke
6568fa2d2e DATAMONGO-1723 - ConfigurationExtensionUnitTests now need to provide a BeanDefinitionRegistry. 2017-06-26 16:55:27 +02:00
Mark Paluch
34986df70b DATAMONGO-1678 - Polishing.
Use Lombok's Value annotation for immutable value objects. Use IllegalArgumentException for NonNull validation exceptions. Introduce missing generics. Use static methods where possible. Remove unused WriteConcernResolver. Trim whitespaces, formatting.

Original pull request: #472.
2017-06-26 13:23:30 +02:00
Christoph Strobl
c3383432f7 DATAMONGO-1678 - Run bulk update / remove documents through type mappers.
We now make sure to run any query / update object through the Query- / UpdateMapper. This ensures @Field annotations and potential custom conversions get processed correctly for update / remove operations.

Original pull request: #472.
2017-06-26 13:23:25 +02:00
Christoph Strobl
e9498c86ca DATAMONGO-1705 - Deprecate cross-store support.
We deprecate cross-store support to remove cross-store with a future release.

Original pull request: #471.
2017-06-26 10:34:37 +02:00
Christoph Strobl
09f8dc6843 DATAMONGO-1715 - Remove spring-data-mongodb-log4j module.
We no longer support spring-data-mongodb-log4j hence removing the code entirely.

Original pull request: #471.
2017-06-26 10:34:11 +02:00
Christoph Strobl
9f22195330 DATAMONGO-1697 - Update MongoOperations JavaDoc regarding mapping limitations.
We now explicitly mention mapping/support limitations for API variants like count(Query, String) not having domain type specific information that allows field specific mapping.
2017-06-19 10:40:19 +02:00
Christoph Strobl
dd944b0881 DATAMONGO-1718 - Polishing.
Add test and hand over Object.class as placeholder for required domain type.

Original Pull Request: #469
2017-06-16 13:25:19 +02:00
Borislav Rangelov
cf0f891c8b DATAMONGO-1718 - Fix MongoTemplate::findAllAndRemove(Query,String) delegating to wrong overload.
Original Pull Request: #469 (by Borislav Rangelov).
2017-06-16 13:25:11 +02:00
Mark Paluch
fbf84fed0e DATAMONGO-1688 - After release cleanups. 2017-06-14 17:14:08 +02:00
Mark Paluch
251a953957 DATAMONGO-1688 - Prepare next development iteration. 2017-06-14 17:14:07 +02:00
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
227 changed files with 13943 additions and 2778 deletions

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@ src/ant/.ant-targets-upload-dist.xml
atlassian-ide-plugin.xml
/.gradle/
/.idea/
*.graphml

View File

@@ -16,7 +16,9 @@ env:
addons:
apt:
sources:
- mongodb-3.4-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

2
lombok.config Normal file
View File

@@ -0,0 +1,2 @@
lombok.nonNull.exceptionType = IllegalArgumentException
lombok.log.fieldName = LOG

21
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.M3</version>
<version>2.0.0.RC1</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,22 +15,22 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.0.0.M3</version>
<version>2.0.0.RC1</version>
</parent>
<modules>
<module>spring-data-mongodb</module>
<module>spring-data-mongodb-cross-store</module>
<module>spring-data-mongodb-log4j</module>
<module>spring-data-mongodb-distribution</module>
</modules>
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>2.0.0.M3</springdata.commons>
<springdata.commons>2.0.0.RC1</springdata.commons>
<mongo>3.4.2</mongo>
<mongo.reactivestreams>1.3.0</mongo.reactivestreams>
<mongo.reactivestreams>1.5.0</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
</properties>
<developers>
@@ -160,6 +160,17 @@
</build>
</profile>
<profile>
<id>benchmarks</id>
<modules>
<module>spring-data-mongodb</module>
<module>spring-data-mongodb-cross-store</module>
<module>spring-data-mongodb-log4j</module>
<module>spring-data-mongodb-distribution</module>
<module>spring-data-mongodb-benchmarks</module>
</modules>
</profile>
</profiles>
<dependencies>

View File

@@ -0,0 +1,76 @@
# Benchmarks
Benchmarks are based on [JMH](http://openjdk.java.net/projects/code-tools/jmh/).
# Running Benchmarks
Running benchmarks is disabled by default and can be activated via the `benchmarks` profile.
To run the benchmarks with default settings use.
```bash
mvn -P benchmarks clean test
```
A basic report will be printed to the CLI.
```bash
# Run complete. Total time: 00:00:15
Benchmark Mode Cnt Score Error Units
MappingMongoConverterBenchmark.readObject thrpt 10 1920157,631 ± 64310,809 ops/s
MappingMongoConverterBenchmark.writeObject thrpt 10 782732,857 ± 53804,130 ops/s
```
## Running all Benchmarks of a specific class
To run all Benchmarks of a specific class, just provide its simple class name via the `benchmark` command line argument.
```bash
mvn -P benchmarks clean test -D benchmark=MappingMongoConverterBenchmark
```
## Running a single Benchmark
To run a single Benchmark provide its containing class simple name followed by `#` and the method name via the `benchmark` command line argument.
```bash
mvn -P benchmarks clean test -D benchmark=MappingMongoConverterBenchmark#readObjectWith2Properties
```
# Saving Benchmark Results
A detailed benchmark report is stored in JSON format in the `/target/reports/performance` directory.
To store the report in a different location use the `benchmarkReportDir` command line argument.
## MongoDB
Results can be directly piped to MongoDB by providing a valid [Connection String](https://docs.mongodb.com/manual/reference/connection-string/) via the `publishTo` command line argument.
```bash
mvn -P benchmarks clean test -D publishTo=mongodb://127.0.0.1:27017
```
NOTE: If the uri does not explicitly define a database the default `spring-data-mongodb-benchmarks` is used.
## HTTP Endpoint
The benchmark report can also be posted as `application/json` to an HTTP Endpoint by providing a valid URl via the `publishTo` command line argument.
```bash
mvn -P benchmarks clean test -D publishTo=http://127.0.0.1:8080/capture-benchmarks
```
# Customizing Benchmarks
Following options can be set via command line.
Option | Default Value
--- | ---
warmupIterations | 10
warmupTime | 1 (seconds)
measurementIterations | 10
measurementTime | 1 (seconds)
forks | 1
benchmarkReportDir | /target/reports/performance (always relative to project root dir)
benchmark | .* (single benchmark via `classname#benchmark`)
publishTo | \[not set\] (mongodb-uri or http-endpoint)

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.RC1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-data-mongodb-benchmarks</artifactId>
<packaging>jar</packaging>
<name>Spring Data MongoDB - Microbenchmarks</name>
<properties>
<!-- Skip tests by default; run only if -DskipTests=false is specified or benchmarks profile is activated -->
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>benchmarks</id>
<properties>
<skipTests>false</skipTests>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>2.2.2</version>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>default-jar</id>
<phase>never</phase>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<testSourceDirectory>${project.build.sourceDirectory}</testSourceDirectory>
<testClassesDirectory>${project.build.outputDirectory}</testClassesDirectory>
<excludes>
<exclude>**/AbstractMicrobenchmark.java</exclude>
<exclude>**/*$*.class</exclude>
<exclude>**/generated/*.class</exclude>
</excludes>
<includes>
<include>**/*Benchmark*</include>
</includes>
<systemPropertyVariables>
<benchmarkReportDir>${project.build.directory}/reports/performance</benchmarkReportDir>
<project.version>${project.version}</project.version>
<git.dirty>${git.dirty}</git.dirty>
<git.commit.id>${git.commit.id}</git.commit.id>
<git.branch>${git.branch}</git.branch>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>

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 org.bson.Document;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.TearDown;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.microbenchmark.AbstractMicrobenchmark;
import com.mongodb.MongoClient;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoCollection;
/**
* @author Christoph Strobl
*/
public class ProjectionsBenchmark extends AbstractMicrobenchmark {
private static final String DB_NAME = "projections-benchmark";
private static final String COLLECTION_NAME = "projections";
private MongoTemplate template;
private MongoClient client;
private MongoCollection<Document> mongoCollection;
private Person source;
private FindWithQuery<Person> asPerson;
private FindWithQuery<DtoProjection> asDtoProjection;
private FindWithQuery<ClosedProjection> asClosedProjection;
private FindWithQuery<OpenProjection> asOpenProjection;
private TerminatingFind<Person> asPersonWithFieldsRestriction;
private Document fields = new Document("firstname", 1);
@Setup
public void setUp() {
client = new MongoClient(new ServerAddress());
template = new MongoTemplate(client, DB_NAME);
source = new Person();
source.firstname = "luke";
source.lastname = "skywalker";
source.address = new Address();
source.address.street = "melenium falcon 1";
source.address.city = "deathstar";
template.save(source, COLLECTION_NAME);
asPerson = template.query(Person.class).inCollection(COLLECTION_NAME);
asDtoProjection = template.query(Person.class).inCollection(COLLECTION_NAME).as(DtoProjection.class);
asClosedProjection = template.query(Person.class).inCollection(COLLECTION_NAME).as(ClosedProjection.class);
asOpenProjection = template.query(Person.class).inCollection(COLLECTION_NAME).as(OpenProjection.class);
asPersonWithFieldsRestriction = template.query(Person.class).inCollection(COLLECTION_NAME)
.matching(new BasicQuery(new Document(), fields));
mongoCollection = client.getDatabase(DB_NAME).getCollection(COLLECTION_NAME);
}
@TearDown
public void tearDown() {
client.dropDatabase(DB_NAME);
client.close();
}
/**
* Set the baseline for comparison by using the plain MongoDB java driver api without any additional fluff.
*
* @return
*/
@Benchmark // DATAMONGO-1733
public Object baseline() {
return mongoCollection.find().first();
}
/**
* Read into the domain type including all fields.
*
* @return
*/
@Benchmark // DATAMONGO-1733
public Object readIntoDomainType() {
return asPerson.all();
}
/**
* Read into the domain type but restrict query to only return one field.
*
* @return
*/
@Benchmark // DATAMONGO-1733
public Object readIntoDomainTypeRestrictingToOneField() {
return asPersonWithFieldsRestriction.all();
}
/**
* Read into dto projection that only needs to map one field back.
*
* @return
*/
@Benchmark // DATAMONGO-1733
public Object readIntoDtoProjectionWithOneField() {
return asDtoProjection.all();
}
/**
* Read into closed interface projection.
*
* @return
*/
@Benchmark // DATAMONGO-1733
public Object readIntoClosedProjectionWithOneField() {
return asClosedProjection.all();
}
/**
* Read into an open projection backed by the mapped domain object.
*
* @return
*/
@Benchmark // DATAMONGO-1733
public Object readIntoOpenProjection() {
return asOpenProjection.all();
}
static class Person {
@Id String id;
String firstname;
String lastname;
Address address;
}
static class Address {
String city;
String street;
}
static class DtoProjection {
@Field("firstname") String name;
}
static interface ClosedProjection {
String getFirstname();
}
static interface OpenProjection {
@Value("#{target.firstname}")
String name();
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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.convert;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import org.bson.types.ObjectId;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.microbenchmark.AbstractMicrobenchmark;
import com.mongodb.MongoClient;
import com.mongodb.ServerAddress;
/**
* @author Christoph Strobl
*/
@State(Scope.Benchmark)
public class DbRefMappingBenchmark extends AbstractMicrobenchmark {
private static final String DB_NAME = "dbref-loading-benchmark";
private MongoClient client;
private MongoTemplate template;
private Query queryObjectWithDBRef;
private Query queryObjectWithDBRefList;
@Setup
public void setUp() throws Exception {
client = new MongoClient(new ServerAddress());
template = new MongoTemplate(client, DB_NAME);
List<RefObject> refObjects = new ArrayList<>();
for (int i = 0; i < 1; i++) {
RefObject o = new RefObject();
template.save(o);
refObjects.add(o);
}
ObjectWithDBRef singleDBRef = new ObjectWithDBRef();
singleDBRef.ref = refObjects.iterator().next();
template.save(singleDBRef);
ObjectWithDBRef multipleDBRefs = new ObjectWithDBRef();
multipleDBRefs.refList = refObjects;
template.save(multipleDBRefs);
queryObjectWithDBRef = query(where("id").is(singleDBRef.id));
queryObjectWithDBRefList = query(where("id").is(multipleDBRefs.id));
}
@TearDown
public void tearDown() {
client.dropDatabase(DB_NAME);
client.close();
}
@Benchmark // DATAMONGO-1720
public ObjectWithDBRef readSingleDbRef() {
return template.findOne(queryObjectWithDBRef, ObjectWithDBRef.class);
}
@Benchmark // DATAMONGO-1720
public ObjectWithDBRef readMultipleDbRefs() {
return template.findOne(queryObjectWithDBRefList, ObjectWithDBRef.class);
}
@Data
static class ObjectWithDBRef {
private @Id ObjectId id;
private @DBRef RefObject ref;
private @DBRef List<RefObject> refList;
}
@Data
static class RefObject {
private @Id String id;
private String someValue;
}
}

View File

@@ -0,0 +1,181 @@
/*
* 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.convert;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.springframework.data.annotation.Id;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.microbenchmark.AbstractMicrobenchmark;
import com.mongodb.MongoClient;
import com.mongodb.ServerAddress;
/**
* @author Christoph Strobl
*/
@State(Scope.Benchmark)
public class MappingMongoConverterBenchmark extends AbstractMicrobenchmark {
private static final String DB_NAME = "mapping-mongo-converter-benchmark";
private MongoClient client;
private MongoMappingContext mappingContext;
private MappingMongoConverter converter;
private Document documentWith2Properties, documentWith2PropertiesAnd1Nested;
private Customer objectWith2PropertiesAnd1Nested;
private Document documentWithFlatAndComplexPropertiesPlusListAndMap;
private SlightlyMoreComplexObject objectWithFlatAndComplexPropertiesPlusListAndMap;
@Setup
public void setUp() throws Exception {
client = new MongoClient(new ServerAddress());
this.mappingContext = new MongoMappingContext();
this.mappingContext.setInitialEntitySet(Collections.singleton(Customer.class));
this.mappingContext.afterPropertiesSet();
DbRefResolver dbRefResolver = new DefaultDbRefResolver(new SimpleMongoDbFactory(client, DB_NAME));
this.converter = new MappingMongoConverter(dbRefResolver, mappingContext);
this.converter.setCustomConversions(new MongoCustomConversions(Collections.emptyList()));
this.converter.afterPropertiesSet();
// just a flat document
this.documentWith2Properties = new Document("firstname", "Dave").append("lastname", "Matthews");
// document with a nested one
Document address = new Document("zipCode", "ABCDE").append("city", "Some Place");
this.documentWith2PropertiesAnd1Nested = new Document("firstname", "Dave").//
append("lastname", "Matthews").//
append("address", address);
// object equivalent of documentWith2PropertiesAnd1Nested
this.objectWith2PropertiesAnd1Nested = new Customer("Dave", "Matthews", new Address("zipCode", "City"));
// a bit more challenging object with list & map conversion.
objectWithFlatAndComplexPropertiesPlusListAndMap = new SlightlyMoreComplexObject();
objectWithFlatAndComplexPropertiesPlusListAndMap.id = UUID.randomUUID().toString();
objectWithFlatAndComplexPropertiesPlusListAndMap.addressList = Arrays.asList(new Address("zip-1", "city-1"),
new Address("zip-2", "city-2"));
objectWithFlatAndComplexPropertiesPlusListAndMap.customer = objectWith2PropertiesAnd1Nested;
objectWithFlatAndComplexPropertiesPlusListAndMap.customerMap = new LinkedHashMap<>();
objectWithFlatAndComplexPropertiesPlusListAndMap.customerMap.put("dave", objectWith2PropertiesAnd1Nested);
objectWithFlatAndComplexPropertiesPlusListAndMap.customerMap.put("deborah",
new Customer("Deborah Anne", "Dyer", new Address("?", "london")));
objectWithFlatAndComplexPropertiesPlusListAndMap.customerMap.put("eddie",
new Customer("Eddie", "Vedder", new Address("??", "Seattle")));
objectWithFlatAndComplexPropertiesPlusListAndMap.intOne = Integer.MIN_VALUE;
objectWithFlatAndComplexPropertiesPlusListAndMap.intTwo = Integer.MAX_VALUE;
objectWithFlatAndComplexPropertiesPlusListAndMap.location = new Point(-33.865143, 151.209900);
objectWithFlatAndComplexPropertiesPlusListAndMap.renamedField = "supercalifragilisticexpialidocious";
objectWithFlatAndComplexPropertiesPlusListAndMap.stringOne = "¯\\_(ツ)_/¯";
objectWithFlatAndComplexPropertiesPlusListAndMap.stringTwo = " (╯°□°)╯︵ ┻━┻";
// JSON equivalent of objectWithFlatAndComplexPropertiesPlusListAndMap
documentWithFlatAndComplexPropertiesPlusListAndMap = Document.parse(
"{ \"_id\" : \"517f6aee-e9e0-44f0-88ed-f3694a019f27\", \"intOne\" : -2147483648, \"intTwo\" : 2147483647, \"stringOne\" : \"¯\\\\_(ツ)_/¯\", \"stringTwo\" : \" (╯°□°)╯︵ ┻━┻\", \"explicit-field-name\" : \"supercalifragilisticexpialidocious\", \"location\" : { \"x\" : -33.865143, \"y\" : 151.2099 }, \"objectWith2PropertiesAnd1Nested\" : { \"firstname\" : \"Dave\", \"lastname\" : \"Matthews\", \"address\" : { \"zipCode\" : \"zipCode\", \"city\" : \"City\" } }, \"addressList\" : [{ \"zipCode\" : \"zip-1\", \"city\" : \"city-1\" }, { \"zipCode\" : \"zip-2\", \"city\" : \"city-2\" }], \"customerMap\" : { \"dave\" : { \"firstname\" : \"Dave\", \"lastname\" : \"Matthews\", \"address\" : { \"zipCode\" : \"zipCode\", \"city\" : \"City\" } }, \"deborah\" : { \"firstname\" : \"Deborah Anne\", \"lastname\" : \"Dyer\", \"address\" : { \"zipCode\" : \"?\", \"city\" : \"london\" } }, \"eddie\" : { \"firstname\" : \"Eddie\", \"lastname\" : \"Vedder\", \"address\" : { \"zipCode\" : \"??\", \"city\" : \"Seattle\" } } }, \"_class\" : \"org.springframework.data.mongodb.core.convert.MappingMongoConverterBenchmark$SlightlyMoreComplexObject\" }");
}
@TearDown
public void tearDown() {
client.dropDatabase(DB_NAME);
client.close();
}
@Benchmark // DATAMONGO-1720
public Customer readObjectWith2Properties() {
return converter.read(Customer.class, documentWith2Properties);
}
@Benchmark // DATAMONGO-1720
public Customer readObjectWith2PropertiesAnd1NestedObject() {
return converter.read(Customer.class, documentWith2PropertiesAnd1Nested);
}
@Benchmark // DATAMONGO-1720
public Document writeObjectWith2PropertiesAnd1NestedObject() {
Document sink = new Document();
converter.write(objectWith2PropertiesAnd1Nested, sink);
return sink;
}
@Benchmark // DATAMONGO-1720
public Object readObjectWithListAndMapsOfComplexType() {
return converter.read(SlightlyMoreComplexObject.class, documentWithFlatAndComplexPropertiesPlusListAndMap);
}
@Benchmark // DATAMONGO-1720
public Object writeObjectWithListAndMapsOfComplexType() {
Document sink = new Document();
converter.write(objectWithFlatAndComplexPropertiesPlusListAndMap, sink);
return sink;
}
@Getter
@RequiredArgsConstructor
static class Customer {
private @Id ObjectId id;
private final String firstname, lastname;
private final Address address;
}
@Getter
@AllArgsConstructor
static class Address {
private String zipCode, city;
}
@Data
static class SlightlyMoreComplexObject {
@Id String id;
int intOne, intTwo;
String stringOne, stringTwo;
@Field("explicit-field-name") String renamedField;
Point location;
Customer customer;
List<Address> addressList;
Map<String, Customer> customerMap;
}
}

View File

@@ -0,0 +1,328 @@
/*
* 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.microbenchmark;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import org.junit.Test;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* @author Christoph Strobl
*/
@Warmup(iterations = AbstractMicrobenchmark.WARMUP_ITERATIONS)
@Measurement(iterations = AbstractMicrobenchmark.MEASUREMENT_ITERATIONS)
@Fork(AbstractMicrobenchmark.FORKS)
@State(Scope.Thread)
public class AbstractMicrobenchmark {
static final int WARMUP_ITERATIONS = 5;
static final int MEASUREMENT_ITERATIONS = 10;
static final int FORKS = 1;
static final String[] JVM_ARGS = { "-server", "-XX:+HeapDumpOnOutOfMemoryError", "-Xms1024m", "-Xmx1024m",
"-XX:MaxDirectMemorySize=1024m" };
private final StandardEnvironment environment = new StandardEnvironment();
/**
* Run matching {@link org.openjdk.jmh.annotations.Benchmark} methods with options collected from
* {@link org.springframework.core.env.Environment}.
*
* @throws Exception
* @see #options(String)
*/
@Test
public void run() throws Exception {
String includes = includes();
if (!includes.contains(org.springframework.util.ClassUtils.getShortName(getClass()))) {
return;
}
publishResults(new Runner(options(includes).build()).run());
}
/**
* Get the regex for all benchmarks to be included in the run. By default every benchmark within classes matching the
* current ones short name. <br />
* The {@literal benchmark} command line argument allows overriding the defaults using {@code #} as class / method
* name separator.
*
* @return never {@literal null}.
* @see org.springframework.util.ClassUtils#getShortName(Class)
*/
protected String includes() {
String tests = environment.getProperty("benchmark", String.class);
if (!StringUtils.hasText(tests)) {
return ".*" + org.springframework.util.ClassUtils.getShortName(getClass()) + ".*";
}
if (!tests.contains("#")) {
return ".*" + tests + ".*";
}
String[] args = tests.split("#");
return ".*" + args[0] + "." + args[1];
}
/**
* Collect all options for the {@link Runner}.
*
* @param includes regex for matching benchmarks to be included in the run.
* @return never {@literal null}.
* @throws Exception
*/
protected ChainedOptionsBuilder options(String includes) throws Exception {
ChainedOptionsBuilder optionsBuilder = new OptionsBuilder().include(includes).jvmArgs(jvmArgs());
optionsBuilder = warmup(optionsBuilder);
optionsBuilder = measure(optionsBuilder);
optionsBuilder = forks(optionsBuilder);
optionsBuilder = report(optionsBuilder);
return optionsBuilder;
}
/**
* JVM args to apply to {@link Runner} via its {@link org.openjdk.jmh.runner.options.Options}.
*
* @return {@link #JVM_ARGS} by default.
*/
protected String[] jvmArgs() {
String[] args = new String[JVM_ARGS.length];
System.arraycopy(JVM_ARGS, 0, args, 0, JVM_ARGS.length);
return args;
}
/**
* Read {@code warmupIterations} property from {@link org.springframework.core.env.Environment}.
*
* @return -1 if not set.
*/
protected int getWarmupIterations() {
return environment.getProperty("warmupIterations", Integer.class, -1);
}
/**
* Read {@code measurementIterations} property from {@link org.springframework.core.env.Environment}.
*
* @return -1 if not set.
*/
protected int getMeasurementIterations() {
return environment.getProperty("measurementIterations", Integer.class, -1);
}
/**
* Read {@code forks} property from {@link org.springframework.core.env.Environment}.
*
* @return -1 if not set.
*/
protected int getForksCount() {
return environment.getProperty("forks", Integer.class, -1);
}
/**
* Read {@code benchmarkReportDir} property from {@link org.springframework.core.env.Environment}.
*
* @return {@literal null} if not set.
*/
protected String getReportDirectory() {
return environment.getProperty("benchmarkReportDir");
}
/**
* Read {@code measurementTime} property from {@link org.springframework.core.env.Environment}.
*
* @return -1 if not set.
*/
protected long getMeasurementTime() {
return environment.getProperty("measurementTime", Long.class, -1L);
}
/**
* Read {@code warmupTime} property from {@link org.springframework.core.env.Environment}.
*
* @return -1 if not set.
*/
protected long getWarmupTime() {
return environment.getProperty("warmupTime", Long.class, -1L);
}
/**
* {@code project.version_yyyy-MM-dd_ClassName.json} eg.
* {@literal 1.11.0.BUILD-SNAPSHOT_2017-03-07_MappingMongoConverterBenchmark.json}
*
* @return
*/
protected String reportFilename() {
StringBuilder sb = new StringBuilder();
if (environment.containsProperty("project.version")) {
sb.append(environment.getProperty("project.version"));
sb.append("_");
}
sb.append(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
sb.append("_");
sb.append(org.springframework.util.ClassUtils.getShortName(getClass()));
sb.append(".json");
return sb.toString();
}
/**
* Apply measurement options to {@link ChainedOptionsBuilder}.
*
* @param optionsBuilder must not be {@literal null}.
* @return {@link ChainedOptionsBuilder} with options applied.
* @see #getMeasurementIterations()
* @see #getMeasurementTime()
*/
private ChainedOptionsBuilder measure(ChainedOptionsBuilder optionsBuilder) {
int measurementIterations = getMeasurementIterations();
long measurementTime = getMeasurementTime();
if (measurementIterations > 0) {
optionsBuilder = optionsBuilder.measurementIterations(measurementIterations);
}
if (measurementTime > 0) {
optionsBuilder = optionsBuilder.measurementTime(TimeValue.seconds(measurementTime));
}
return optionsBuilder;
}
/**
* Apply warmup options to {@link ChainedOptionsBuilder}.
*
* @param optionsBuilder must not be {@literal null}.
* @return {@link ChainedOptionsBuilder} with options applied.
* @see #getWarmupIterations()
* @see #getWarmupTime()
*/
private ChainedOptionsBuilder warmup(ChainedOptionsBuilder optionsBuilder) {
int warmupIterations = getWarmupIterations();
long warmupTime = getWarmupTime();
if (warmupIterations > 0) {
optionsBuilder = optionsBuilder.warmupIterations(warmupIterations);
}
if (warmupTime > 0) {
optionsBuilder = optionsBuilder.warmupTime(TimeValue.seconds(warmupTime));
}
return optionsBuilder;
}
/**
* Apply forks option to {@link ChainedOptionsBuilder}.
*
* @param optionsBuilder must not be {@literal null}.
* @return {@link ChainedOptionsBuilder} with options applied.
* @see #getForksCount()
*/
private ChainedOptionsBuilder forks(ChainedOptionsBuilder optionsBuilder) {
int forks = getForksCount();
if (forks <= 0) {
return optionsBuilder;
}
return optionsBuilder.forks(forks);
}
/**
* Apply report option to {@link ChainedOptionsBuilder}.
*
* @param optionsBuilder must not be {@literal null}.
* @return {@link ChainedOptionsBuilder} with options applied.
* @throws IOException if report file cannot be created.
* @see #getReportDirectory()
*/
private ChainedOptionsBuilder report(ChainedOptionsBuilder optionsBuilder) throws IOException {
String reportDir = getReportDirectory();
if (!StringUtils.hasText(reportDir)) {
return optionsBuilder;
}
String reportFilePath = reportDir + (reportDir.endsWith(File.separator) ? "" : File.separator) + reportFilename();
File file = ResourceUtils.getFile(reportFilePath);
if (file.exists()) {
file.delete();
} else {
file.getParentFile().mkdirs();
file.createNewFile();
}
optionsBuilder.resultFormat(ResultFormatType.JSON);
optionsBuilder.result(reportFilePath);
return optionsBuilder;
}
/**
* Publish results to an external system.
*
* @param results must not be {@literal null}.
*/
private void publishResults(Collection<RunResult> results) {
if (CollectionUtils.isEmpty(results) || !environment.containsProperty("publishTo")) {
return;
}
String uri = environment.getProperty("publishTo");
try {
ResultsWriter.forUri(uri).write(results);
} catch (Exception e) {
System.err.println(String.format("Cannot save benchmark results to '%s'. Error was %s.", uri, e));
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.microbenchmark;
import lombok.SneakyThrows;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collection;
import org.openjdk.jmh.results.RunResult;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.CollectionUtils;
/**
* {@link ResultsWriter} implementation of {@link URLConnection}.
*
* @since 2.0
*/
class HttpResultsWriter implements ResultsWriter {
private final String url;
HttpResultsWriter(String url) {
this.url = url;
}
@Override
@SneakyThrows
public void write(Collection<RunResult> results) {
if (CollectionUtils.isEmpty(results)) {
return;
}
StandardEnvironment env = new StandardEnvironment();
String projectVersion = env.getProperty("project.version", "unknown");
String gitBranch = env.getProperty("git.branch", "unknown");
String gitDirty = env.getProperty("git.dirty", "no");
String gitCommitId = env.getProperty("git.commit.id", "unknown");
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setConnectTimeout((int) Duration.ofSeconds(1).toMillis());
connection.setReadTimeout((int) Duration.ofSeconds(1).toMillis());
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("X-Project-Version", projectVersion);
connection.addRequestProperty("X-Git-Branch", gitBranch);
connection.addRequestProperty("X-Git-Dirty", gitDirty);
connection.addRequestProperty("X-Git-Commit-Id", gitCommitId);
try (OutputStream output = connection.getOutputStream()) {
output.write(ResultsWriter.jsonifyResults(results).getBytes(StandardCharsets.UTF_8));
}
if (connection.getResponseCode() >= 400) {
throw new IllegalStateException(
String.format("Status %d %s", connection.getResponseCode(), connection.getResponseMessage()));
}
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.microbenchmark;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.bson.Document;
import org.openjdk.jmh.results.RunResult;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.client.MongoDatabase;
import com.mongodb.util.JSON;
/**
* MongoDB specific {@link ResultsWriter} implementation.
*
* @author Christoph Strobl
* @since 2.0
*/
class MongoResultsWriter implements ResultsWriter {
private final String uri;
MongoResultsWriter(String uri) {
this.uri = uri;
}
@Override
public void write(Collection<RunResult> results) {
Date now = new Date();
StandardEnvironment env = new StandardEnvironment();
String projectVersion = env.getProperty("project.version", "unknown");
String gitBranch = env.getProperty("git.branch", "unknown");
String gitDirty = env.getProperty("git.dirty", "no");
String gitCommitId = env.getProperty("git.commit.id", "unknown");
MongoClientURI uri = new MongoClientURI(this.uri);
MongoClient client = new MongoClient(uri);
String dbName = StringUtils.hasText(uri.getDatabase()) ? uri.getDatabase() : "spring-data-mongodb-benchmarks";
MongoDatabase db = client.getDatabase(dbName);
for (BasicDBObject dbo : (List<BasicDBObject>) JSON.parse(ResultsWriter.jsonifyResults(results))) {
String collectionName = extractClass(dbo.get("benchmark").toString());
Document sink = new Document();
sink.append("_version", projectVersion);
sink.append("_branch", gitBranch);
sink.append("_commit", gitCommitId);
sink.append("_dirty", gitDirty);
sink.append("_method", extractBenchmarkName(dbo.get("benchmark").toString()));
sink.append("_date", now);
sink.append("_snapshot", projectVersion.toLowerCase().contains("snapshot"));
sink.putAll(dbo);
db.getCollection(collectionName).insertOne(fixDocumentKeys(sink));
}
client.close();
}
/**
* Replace {@code .} by {@code ,}.
*
* @param doc
* @return
*/
private Document fixDocumentKeys(Document doc) {
Document sanitized = new Document();
for (Object key : doc.keySet()) {
Object value = doc.get(key);
if (value instanceof Document) {
value = fixDocumentKeys((Document) value);
} else if (value instanceof BasicDBObject) {
value = fixDocumentKeys(new Document((BasicDBObject) value));
}
if (key instanceof String) {
String newKey = (String) key;
if (newKey.contains(".")) {
newKey = newKey.replace('.', ',');
}
sanitized.put(newKey, value);
} else {
sanitized.put(ObjectUtils.nullSafeToString(key).replace('.', ','), value);
}
}
return sanitized;
}
private static String extractClass(String source) {
String tmp = source.substring(0, source.lastIndexOf('.'));
return tmp.substring(tmp.lastIndexOf(".") + 1);
}
private static String extractBenchmarkName(String source) {
return source.substring(source.lastIndexOf(".") + 1);
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.microbenchmark;
import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.results.format.ResultFormatFactory;
import org.openjdk.jmh.results.format.ResultFormatType;
/**
* @author Christoph Strobl
* @since 2.0
*/
interface ResultsWriter {
/**
* Write the {@link RunResult}s.
*
* @param results can be {@literal null}.
*/
void write(Collection<RunResult> results);
/**
* Get the uri specific {@link ResultsWriter}.
*
* @param uri must not be {@literal null}.
* @return
*/
static ResultsWriter forUri(String uri) {
return uri.startsWith("mongodb:") ? new MongoResultsWriter(uri) : new HttpResultsWriter(uri);
}
/**
* Convert {@link RunResult}s to JMH Json representation.
*
* @param results
* @return json string representation of results.
* @see org.openjdk.jmh.results.format.JSONResultFormat
*/
@SneakyThrows
static String jsonifyResults(Collection<RunResult> results) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ResultFormatFactory.getInstance(ResultFormatType.JSON, new PrintStream(baos, true, "UTF-8")).writeOut(results);
return new String(baos.toByteArray(), StandardCharsets.UTF_8);
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<root level="error">
<appender-ref ref="console" />
</root>
</configuration>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.M3</version>
<version>2.0.0.RC1</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -48,14 +48,13 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.0.0.M3</version>
<version>2.0.0.RC1</version>
</dependency>
<!-- reactive -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>${reactor}</version>
<optional>true</optional>
</dependency>

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.
@@ -17,6 +17,12 @@ package org.springframework.data.mongodb.crossstore;
import org.springframework.data.crossstore.ChangeSetBacked;
/**
* @author Thomas Risberg
* @author Oliver Gierke
* @deprecated will be removed without replacement.
*/
@Deprecated
public interface DocumentBacked extends ChangeSetBacked {
}

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.
@@ -40,7 +40,9 @@ import com.mongodb.client.result.DeleteResult;
* @author Oliver Gierke
* @author Alex Vengrovsk
* @author Mark Paluch
* @deprecated will be removed without replacement.
*/
@Deprecated
public class MongoChangeSetPersister implements ChangeSetPersister<Object> {
private static final String ENTITY_CLASS = "_entity_class";

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.
@@ -40,7 +40,9 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
* Aspect to turn an object annotated with @Document into a persistent document using Mongo.
*
* @author Thomas Risberg
* @deprecated will be removed without replacement.
*/
@Deprecated
public aspect MongoDocumentBacking {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoDocumentBacking.class);

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.
@@ -22,7 +22,9 @@ import java.lang.annotation.Target;
/**
* @author Thomas Risberg
* @deprecated will be removed without replacement.
*/
@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface RelatedDocument {

View File

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

View File

@@ -1,52 +0,0 @@
# MongoDB Log4J Appender
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.
To use it, configure a host, port, (optionally) applicationId, and database property in your Log4J configuration:
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 = %c
log4j.appender.stdout.applicationId = my.application
log4j.appender.stdout.warnOrHigherWriteConcern = FSYNC_SAFE
It will even support properties in your MDC (so long as they're Strings or support .toString()).
The collection name is configurable as well. If you don't specify anything, it will use the Category name.
If you want to specify a collection name, you can give it a Log4J pattern layout format string which will have
the following additional MDC variables in the context when the collection name is rendered:
"year" = Calendar.YEAR
"month" = Calendar.MONTH + 1
"day" = Calendar.DAY_OF_MONTH
"hour" = Calendar.HOUR_OF_DAY
"applicationId" = configured applicationId
An example log entry might look like:
{
"_id" : ObjectId("4d89341a8ef397e06940d5cd"),
"applicationId" : "my.application",
"name" : "org.springframework.data.mongodb.log4j.MongoLog4jAppenderIntegrationTests",
"level" : "DEBUG",
"timestamp" : ISODate("2011-03-23T16:53:46.778Z"),
"properties" : {
"property" : "one"
},
"message" : "DEBUG message"
}
To set WriteConcern levels for WARN or higher messages, set warnOrHigherWriteConcern to one of the following:
* FSYNC_SAFE
* NONE
* NORMAL
* REPLICAS_SAFE
* SAFE
[http://api.mongodb.org/java/2.5-pre-/com/mongodb/WriteConcern.html#field_detail](http://api.mongodb.org/java/2.5-pre-/com/mongodb/WriteConcern.html#field_detail)

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.M3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-data-mongodb-log4j</artifactId>
<name>Spring Data MongoDB - Log4J Appender</name>
<properties>
<log4j>1.2.16</log4j>
</properties>
<dependencies>
<!-- Logging -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j}</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,298 +0,0 @@
/*
* Copyright 2011-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 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;
import org.apache.log4j.Level;
import org.apache.log4j.MDC;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.spi.LoggingEvent;
import com.mongodb.BasicDBList;
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;
/**
* Log4j appender writing log entries into a MongoDB instance.
*
* @author Jon Brisbin
* @author Oliver Gierke
* @author Christoph Strobl
* @author Ricardo Espirito Santo
*/
public class MongoLog4jAppender extends AppenderSkeleton {
public static final String LEVEL = "level";
public static final String NAME = "name";
public static final String APP_ID = "applicationId";
public static final String TIMESTAMP = "timestamp";
public static final String PROPERTIES = "properties";
public static final String TRACEBACK = "traceback";
public static final String MESSAGE = "message";
public static final String YEAR = "year";
public static final String MONTH = "month";
public static final String DAY = "day";
public static final String HOUR = "hour";
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);
protected String applicationId = System.getProperty("APPLICATION_ID", null);
protected WriteConcern warnOrHigherWriteConcern = WriteConcern.ACKNOWLEDGED;
protected WriteConcern infoOrLowerWriteConcern = WriteConcern.UNACKNOWLEDGED;
protected Mongo mongo;
protected DB db;
public MongoLog4jAppender() {}
public MongoLog4jAppender(boolean isActive) {
super(isActive);
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
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;
}
public void setDatabase(String database) {
this.database = database;
}
public String getCollectionPattern() {
return collectionPattern;
}
public void setCollectionPattern(String collectionPattern) {
this.collectionPattern = collectionPattern;
this.collectionLayout = new PatternLayout(collectionPattern);
}
public String getApplicationId() {
return applicationId;
}
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
public String getWarnOrHigherWriteConcern() {
return warnOrHigherWriteConcern.toString();
}
public void setWarnOrHigherWriteConcern(String wc) {
this.warnOrHigherWriteConcern = WriteConcern.valueOf(wc);
}
public String getInfoOrLowerWriteConcern() {
return infoOrLowerWriteConcern.toString();
}
public void setInfoOrLowerWriteConcern(String wc) {
this.infoOrLowerWriteConcern = WriteConcern.valueOf(wc);
}
protected void connectToMongo() throws UnknownHostException {
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)
*/
@Override
@SuppressWarnings({ "unchecked" })
protected void append(final LoggingEvent event) {
if (null == db) {
try {
connectToMongo();
} catch (UnknownHostException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
BasicDBObject dbo = new BasicDBObject();
if (null != applicationId) {
dbo.put(APP_ID, applicationId);
MDC.put(APP_ID, applicationId);
}
dbo.put(NAME, event.getLogger().getName());
dbo.put(LEVEL, event.getLevel().toString());
Calendar tstamp = Calendar.getInstance();
tstamp.setTimeInMillis(event.getTimeStamp());
dbo.put(TIMESTAMP, tstamp.getTime());
// Copy properties into document
Map<Object, Object> props = event.getProperties();
if (null != props && !props.isEmpty()) {
BasicDBObject propsDbo = new BasicDBObject();
for (Map.Entry<Object, Object> entry : props.entrySet()) {
propsDbo.put(entry.getKey().toString(), entry.getValue().toString());
}
dbo.put(PROPERTIES, propsDbo);
}
// Copy traceback info (if there is any) into the document
String[] traceback = event.getThrowableStrRep();
if (null != traceback && traceback.length > 0) {
BasicDBList tbDbo = new BasicDBList();
tbDbo.addAll(Arrays.asList(traceback));
dbo.put(TRACEBACK, tbDbo);
}
// Put the rendered message into the document
dbo.put(MESSAGE, event.getRenderedMessage());
// Insert the document
Calendar now = Calendar.getInstance();
MDC.put(YEAR, now.get(Calendar.YEAR));
MDC.put(MONTH, String.format("%1$02d", now.get(Calendar.MONTH) + 1));
MDC.put(DAY, String.format("%1$02d", now.get(Calendar.DAY_OF_MONTH)));
MDC.put(HOUR, String.format("%1$02d", now.get(Calendar.HOUR_OF_DAY)));
String coll = collectionLayout.format(event);
MDC.remove(YEAR);
MDC.remove(MONTH);
MDC.remove(DAY);
MDC.remove(HOUR);
if (null != applicationId) {
MDC.remove(APP_ID);
}
WriteConcern wc;
if (event.getLevel().isGreaterOrEqual(Level.WARN)) {
wc = warnOrHigherWriteConcern;
} else {
wc = infoOrLowerWriteConcern;
}
db.getCollection(coll).insert(dbo, wc);
}
/*
* (non-Javadoc)
* @see org.apache.log4j.AppenderSkeleton#close()
*/
public void close() {
if (mongo != null) {
mongo.close();
}
}
/*
* (non-Javadoc)
* @see org.apache.log4j.AppenderSkeleton#requiresLayout()
*/
public boolean requiresLayout() {
return true;
}
}

View File

@@ -1,5 +0,0 @@
/**
* Infrastructure for to use MongoDB as a logging sink.
*/
package org.springframework.data.mongodb.log4j;

View File

@@ -1,114 +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.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

@@ -1,87 +0,0 @@
/*
* Copyright 2011-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 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.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCursor;
import com.mongodb.MongoClient;
import com.mongodb.ServerAddress;
/**
* Integration tests for {@link MongoLog4jAppender}.
*
* @author Jon Brisbin
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class MongoLog4jAppenderIntegrationTests {
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");
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
public void tearDown() {
db.getCollection(collection).remove(new BasicDBObject());
}
@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

@@ -1,16 +0,0 @@
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 +0,0 @@
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.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,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.M3</version>
<version>2.0.0.RC1</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -106,14 +106,12 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>${reactor}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor.addons</groupId>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>${reactor}</version>
<optional>true</optional>
</dependency>
@@ -231,11 +229,124 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.schauderhaft.degraph</groupId>
<artifactId>degraph-check</artifactId>
<version>0.1.4</version>
<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 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
package org.springframework.data.mongodb.config;
import org.springframework.context.annotation.Bean;
import org.springframework.data.mongodb.core.geo.GeoJsonModule;
@@ -23,6 +23,7 @@ import org.springframework.data.web.config.SpringDataJacksonModules;
* Configuration class to expose {@link GeoJsonModule} as a Spring bean.
*
* @author Oliver Gierke
* @author Jens Schauder
*/
public class GeoJsonConfiguration implements SpringDataJacksonModules {

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.
@@ -16,6 +16,8 @@
package org.springframework.data.mongodb.config;
import java.beans.PropertyEditorSupport;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -29,7 +31,7 @@ import com.mongodb.MongoCredential;
/**
* Parse a {@link String} to a Collection of {@link MongoCredential}.
*
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
@@ -39,10 +41,10 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
private static final Pattern GROUP_PATTERN = Pattern.compile("(\\\\?')(.*?)\\1");
private static final String AUTH_MECHANISM_KEY = "uri.authMechanism";
private static final String USERNAME_PASSWORD_DELIMINATOR = ":";
private static final String DATABASE_DELIMINATOR = "@";
private static final String OPTIONS_DELIMINATOR = "?";
private static final String OPTION_VALUE_DELIMINATOR = "&";
private static final String USERNAME_PASSWORD_DELIMITER = ":";
private static final String DATABASE_DELIMITER = "@";
private static final String OPTIONS_DELIMITER = "?";
private static final String OPTION_VALUE_DELIMITER = "&";
/*
* (non-Javadoc)
@@ -55,7 +57,7 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
return;
}
List<MongoCredential> credentials = new ArrayList<MongoCredential>();
List<MongoCredential> credentials = new ArrayList<>();
for (String credentialString : extractCredentialsString(text)) {
@@ -115,7 +117,7 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
private List<String> extractCredentialsString(String source) {
Matcher matcher = GROUP_PATTERN.matcher(source);
List<String> list = new ArrayList<String>();
List<String> list = new ArrayList<>();
while (matcher.find()) {
@@ -132,39 +134,44 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
private static String[] extractUserNameAndPassword(String text) {
int index = text.lastIndexOf(DATABASE_DELIMINATOR);
int index = text.lastIndexOf(DATABASE_DELIMITER);
index = index != -1 ? index : text.lastIndexOf(OPTIONS_DELIMINATOR);
index = index != -1 ? index : text.lastIndexOf(OPTIONS_DELIMITER);
return index == -1 ? new String[] {} : text.substring(0, index).split(USERNAME_PASSWORD_DELIMINATOR);
if (index == -1) {
return new String[] {};
}
return Arrays.stream(text.substring(0, index).split(USERNAME_PASSWORD_DELIMITER))
.map(MongoCredentialPropertyEditor::decodeParameter).toArray(String[]::new);
}
private static String extractDB(String text) {
int dbSeperationIndex = text.lastIndexOf(DATABASE_DELIMINATOR);
int dbSeparationIndex = text.lastIndexOf(DATABASE_DELIMITER);
if (dbSeperationIndex == -1) {
if (dbSeparationIndex == -1) {
return "";
}
String tmp = text.substring(dbSeperationIndex + 1);
int optionsSeperationIndex = tmp.lastIndexOf(OPTIONS_DELIMINATOR);
String tmp = text.substring(dbSeparationIndex + 1);
int optionsSeparationIndex = tmp.lastIndexOf(OPTIONS_DELIMITER);
return optionsSeperationIndex > -1 ? tmp.substring(0, optionsSeperationIndex) : tmp;
return optionsSeparationIndex > -1 ? tmp.substring(0, optionsSeparationIndex) : tmp;
}
private static Properties extractOptions(String text) {
int optionsSeperationIndex = text.lastIndexOf(OPTIONS_DELIMINATOR);
int dbSeperationIndex = text.lastIndexOf(OPTIONS_DELIMINATOR);
int optionsSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
int dbSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
if (optionsSeperationIndex == -1 || dbSeperationIndex > optionsSeperationIndex) {
if (optionsSeparationIndex == -1 || dbSeparationIndex > optionsSeparationIndex) {
return new Properties();
}
Properties properties = new Properties();
for (String option : text.substring(optionsSeperationIndex + 1).split(OPTION_VALUE_DELIMINATOR)) {
for (String option : text.substring(optionsSeparationIndex + 1).split(OPTION_VALUE_DELIMITER)) {
String[] optionArgs = option.split("=");
properties.put(optionArgs[0], optionArgs[1]);
}
@@ -195,4 +202,12 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
throw new IllegalArgumentException("Credentials need to specify username!");
}
}
private static String decodeParameter(String it) {
try {
return URLDecoder.decode(it, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("o_O UTF-8 not supported!", e);
}
}
}

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.
@@ -28,7 +28,7 @@ import com.mongodb.bulk.BulkWriteResult;
* 2.6 and make use of low level bulk commands on the protocol level. This interface defines a fluent API to add
* multiple single operations or list of similar operations in sequence which can then eventually be executed by calling
* {@link #execute()}.
*
*
* @author Tobias Trelle
* @author Oliver Gierke
* @since 1.9
@@ -49,7 +49,7 @@ public interface BulkOperations {
/**
* Add a single insert to the bulk operation.
*
*
* @param documents the document to insert, must not be {@literal null}.
* @return the current {@link BulkOperations} instance with the insert added, will never be {@literal null}.
*/
@@ -57,7 +57,7 @@ public interface BulkOperations {
/**
* Add a list of inserts to the bulk operation.
*
*
* @param documents List of documents to insert, must not be {@literal null}.
* @return the current {@link BulkOperations} instance with the insert added, will never be {@literal null}.
*/
@@ -65,7 +65,7 @@ public interface BulkOperations {
/**
* Add a single update to the bulk operation. For the update request, only the first matching document is updated.
*
*
* @param query update criteria, must not be {@literal null}.
* @param update {@link Update} operation to perform, must not be {@literal null}.
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
@@ -74,7 +74,7 @@ public interface BulkOperations {
/**
* Add a list of updates to the bulk operation. For each update request, only the first matching document is updated.
*
*
* @param updates Update operations to perform.
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
*/
@@ -82,7 +82,7 @@ public interface BulkOperations {
/**
* Add a single update to the bulk operation. For the update request, all matching documents are updated.
*
*
* @param query Update criteria.
* @param update Update operation to perform.
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
@@ -91,7 +91,7 @@ public interface BulkOperations {
/**
* Add a list of updates to the bulk operation. For each update request, all matching documents are updated.
*
*
* @param updates Update operations to perform.
* @return The bulk operation.
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
@@ -101,7 +101,7 @@ public interface BulkOperations {
/**
* Add a single upsert to the bulk operation. An upsert is an update if the set of matching documents is not empty,
* else an insert.
*
*
* @param query Update criteria.
* @param update Update operation to perform.
* @return The bulk operation.
@@ -112,7 +112,7 @@ public interface BulkOperations {
/**
* Add a list of upserts to the bulk operation. An upsert is an update if the set of matching documents is not empty,
* else an insert.
*
*
* @param updates Updates/insert operations to perform.
* @return The bulk operation.
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
@@ -121,7 +121,7 @@ public interface BulkOperations {
/**
* Add a single remove operation to the bulk operation.
*
*
* @param remove the {@link Query} to select the documents to be removed, must not be {@literal null}.
* @return the current {@link BulkOperations} instance with the removal added, will never be {@literal null}.
*/
@@ -129,7 +129,7 @@ public interface BulkOperations {
/**
* Add a list of remove operations to the bulk operation.
*
*
* @param removes the remove operations to perform, must not be {@literal null}.
* @return the current {@link BulkOperations} instance with the removal added, will never be {@literal null}.
*/
@@ -137,9 +137,9 @@ public interface BulkOperations {
/**
* Execute all bulk operations using the default write concern.
*
*
* @return Result of the bulk operation providing counters for inserts/updates etc.
* @throws {@link BulkOperationException} if an error occurred during bulk processing.
* @throws org.springframework.data.mongodb.BulkOperationException if an error occurred during bulk processing.
*/
BulkWriteResult execute();
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core;
import java.util.Optional;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.util.Assert;
/**
@@ -28,10 +29,10 @@ import org.springframework.util.Assert;
*/
public class CollectionOptions {
private Integer maxDocuments;
private Integer size;
private Long maxDocuments;
private Long size;
private Boolean capped;
private Optional<Collation> collation;
private Collation collation;
/**
* Constructs a new <code>CollectionOptions</code> instance.
@@ -40,12 +41,14 @@ public class CollectionOptions {
* @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) {
this(size, maxDocuments, capped, Optional.empty());
@Deprecated
public CollectionOptions(Long size, Long maxDocuments, Boolean capped) {
this(size, maxDocuments, capped, null);
}
private CollectionOptions(Integer size, Integer maxDocuments, Boolean capped, Optional<Collation> collation) {
private CollectionOptions(Long size, Long maxDocuments, Boolean capped, Collation collation) {
this.maxDocuments = maxDocuments;
this.size = size;
@@ -53,8 +56,6 @@ public class CollectionOptions {
this.collation = collation;
}
private CollectionOptions() {}
/**
* Create new {@link CollectionOptions} by just providing the {@link Collation} to use.
*
@@ -66,9 +67,7 @@ public class CollectionOptions {
Assert.notNull(collation, "Collation must not be null!");
CollectionOptions options = new CollectionOptions();
options.setCollation(collation);
return options;
return new CollectionOptions(null, null, null, collation);
}
/**
@@ -78,17 +77,17 @@ public class CollectionOptions {
* @since 2.0
*/
public static CollectionOptions empty() {
return new CollectionOptions();
return new CollectionOptions(null, null, null, null);
}
/**
* Create new {@link CollectionOptions} with already given settings and capped set to {@literal true}.
* 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)}.
*
* @param size the collection size in bytes, this data space is preallocated.
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public CollectionOptions capped(int size) {
public CollectionOptions capped() {
return new CollectionOptions(size, maxDocuments, true, collation);
}
@@ -99,7 +98,7 @@ public class CollectionOptions {
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public CollectionOptions maxDocuments(Integer maxDocuments) {
public CollectionOptions maxDocuments(long maxDocuments) {
return new CollectionOptions(size, maxDocuments, capped, collation);
}
@@ -110,7 +109,7 @@ public class CollectionOptions {
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public CollectionOptions size(int size) {
public CollectionOptions size(long size) {
return new CollectionOptions(size, maxDocuments, capped, collation);
}
@@ -122,50 +121,44 @@ public class CollectionOptions {
* @since 2.0
*/
public CollectionOptions collation(Collation collation) {
return new CollectionOptions(size, maxDocuments, capped, Optional.ofNullable(collation));
}
public Integer getMaxDocuments() {
return maxDocuments;
}
public void setMaxDocuments(Integer maxDocuments) {
this.maxDocuments = maxDocuments;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public Boolean getCapped() {
return capped;
}
public void setCapped(Boolean capped) {
this.capped = capped;
return new CollectionOptions(size, maxDocuments, capped, collation);
}
/**
* Set {@link Collation} options.
* Get the max number of documents the collection should be limited to.
*
* @param collation
* @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 void setCollation(Collation collation) {
this.collation = Optional.ofNullable(collation);
public Optional<Boolean> getCapped() {
return Optional.ofNullable(capped);
}
/**
* Get the {@link Collation} settings.
*
* @return
* @return {@link Optional#empty()} if not set.
* @since 2.0
*/
public Optional<Collation> getCollation() {
return collation;
return Optional.ofNullable(collation);
}
}

View File

@@ -15,14 +15,23 @@
*/
package org.springframework.data.mongodb.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import lombok.NonNull;
import lombok.Value;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import com.mongodb.client.model.DeleteOptions;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.util.Pair;
@@ -33,6 +42,8 @@ import com.mongodb.WriteConcern;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.BulkWriteOptions;
import com.mongodb.client.model.DeleteManyModel;
import com.mongodb.client.model.DeleteOneModel;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.InsertOneModel;
import com.mongodb.client.model.UpdateManyModel;
import com.mongodb.client.model.UpdateOneModel;
@@ -41,50 +52,46 @@ import com.mongodb.client.model.WriteModel;
/**
* Default implementation for {@link BulkOperations}.
*
*
* @author Tobias Trelle
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.9
*/
class DefaultBulkOperations implements BulkOperations {
private final MongoOperations mongoOperations;
private final BulkMode bulkMode;
private final String collectionName;
private final BulkOperationContext bulkOperationContext;
private final List<WriteModel<Document>> models = new ArrayList<>();
private PersistenceExceptionTranslator exceptionTranslator;
private WriteConcernResolver writeConcernResolver;
private WriteConcern defaultWriteConcern;
private BulkWriteOptions bulkOptions;
List<WriteModel<Document>> models = new ArrayList<>();
/**
* Creates a new {@link DefaultBulkOperations} for the given {@link MongoOperations}, {@link BulkMode}, collection
* name and {@link WriteConcern}.
* Creates a new {@link DefaultBulkOperations} for the given {@link MongoOperations}, collection name and
* {@link BulkOperationContext}.
*
* @param mongoOperations The underlying {@link MongoOperations}, must not be {@literal null}.
* @param bulkMode must not be {@literal null}.
* @param collectionName Name of the collection to work on, must not be {@literal null} or empty.
* @param entityType the entity type, can be {@literal null}.
* @param mongoOperations must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @param bulkOperationContext must not be {@literal null}.
* @since 2.0
*/
DefaultBulkOperations(MongoOperations mongoOperations, BulkMode bulkMode, String collectionName,
Class<?> entityType) {
DefaultBulkOperations(MongoOperations mongoOperations, String collectionName,
BulkOperationContext bulkOperationContext) {
Assert.notNull(mongoOperations, "MongoOperations must not be null!");
Assert.notNull(bulkMode, "BulkMode must not be null!");
Assert.hasText(collectionName, "Collection name must not be null or empty!");
Assert.hasText(collectionName, "CollectionName must not be null nor empty!");
Assert.notNull(bulkOperationContext, "BulkOperationContext must not be null!");
this.mongoOperations = mongoOperations;
this.bulkMode = bulkMode;
this.collectionName = collectionName;
this.bulkOperationContext = bulkOperationContext;
this.exceptionTranslator = new MongoExceptionTranslator();
this.writeConcernResolver = DefaultWriteConcernResolver.INSTANCE;
this.bulkOptions = initBulkOperation();
this.bulkOptions = getBulkWriteOptions(bulkOperationContext.getBulkMode());
}
/**
@@ -96,22 +103,12 @@ class DefaultBulkOperations implements BulkOperations {
this.exceptionTranslator = exceptionTranslator == null ? new MongoExceptionTranslator() : exceptionTranslator;
}
/**
* Configures the {@link WriteConcernResolver} to be used. Defaults to {@link DefaultWriteConcernResolver}.
*
* @param writeConcernResolver can be {@literal null}.
*/
public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) {
this.writeConcernResolver = writeConcernResolver == null ? DefaultWriteConcernResolver.INSTANCE
: writeConcernResolver;
}
/**
* Configures the default {@link WriteConcern} to be used. Defaults to {@literal null}.
*
* @param defaultWriteConcern can be {@literal null}.
*/
public void setDefaultWriteConcern(WriteConcern defaultWriteConcern) {
void setDefaultWriteConcern(WriteConcern defaultWriteConcern) {
this.defaultWriteConcern = defaultWriteConcern;
}
@@ -134,6 +131,7 @@ class DefaultBulkOperations implements BulkOperations {
mongoOperations.getConverter().write(document, sink);
models.add(new InsertOneModel<>(sink));
return this;
}
@@ -146,9 +144,7 @@ class DefaultBulkOperations implements BulkOperations {
Assert.notNull(documents, "Documents must not be null!");
for (Object document : documents) {
insert(document);
}
documents.forEach(this::insert);
return this;
}
@@ -164,7 +160,7 @@ class DefaultBulkOperations implements BulkOperations {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(update, "Update must not be null!");
return updateOne(Arrays.asList(Pair.of(query, update)));
return updateOne(Collections.singletonList(Pair.of(query, update)));
}
/*
@@ -194,7 +190,7 @@ class DefaultBulkOperations implements BulkOperations {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(update, "Update must not be null!");
return updateMulti(Arrays.asList(Pair.of(query, update)));
return updateMulti(Collections.singletonList(Pair.of(query, update)));
}
/*
@@ -248,7 +244,8 @@ class DefaultBulkOperations implements BulkOperations {
DeleteOptions deleteOptions = new DeleteOptions();
query.getCollation().map(Collation::toMongoCollation).ifPresent(deleteOptions::collation);
models.add(new DeleteManyModel(query.getQueryObject(), deleteOptions));
models.add(new DeleteManyModel<>(query.getQueryObject(), deleteOptions));
return this;
}
@@ -282,7 +279,7 @@ class DefaultBulkOperations implements BulkOperations {
collection = collection.withWriteConcern(defaultWriteConcern);
}
return collection.bulkWrite(models, bulkOptions);
return collection.bulkWrite(models.stream().map(this::mapWriteModel).collect(Collectors.toList()), bulkOptions);
} catch (BulkWriteException o_O) {
@@ -290,13 +287,13 @@ class DefaultBulkOperations implements BulkOperations {
throw toThrow == null ? o_O : toThrow;
} finally {
this.bulkOptions = initBulkOperation();
this.bulkOptions = getBulkWriteOptions(bulkOperationContext.getBulkMode());
}
}
/**
* Performs update and upsert bulk operations.
*
*
* @param query the {@link Query} to determine documents to update.
* @param update the {@link Update} to perform, must not be {@literal null}.
* @param upsert whether to upsert.
@@ -317,18 +314,81 @@ class DefaultBulkOperations implements BulkOperations {
} else {
models.add(new UpdateOneModel<>(query.getQueryObject(), update.getUpdateObject(), options));
}
return this;
}
private final BulkWriteOptions initBulkOperation() {
private WriteModel<Document> mapWriteModel(WriteModel<Document> writeModel) {
if (writeModel instanceof UpdateOneModel) {
UpdateOneModel<Document> model = (UpdateOneModel<Document>) writeModel;
return new UpdateOneModel<>(getMappedQuery(model.getFilter()), getMappedUpdate(model.getUpdate()),
model.getOptions());
}
if (writeModel instanceof UpdateManyModel) {
UpdateManyModel<Document> model = (UpdateManyModel<Document>) writeModel;
return new UpdateManyModel<>(getMappedQuery(model.getFilter()), getMappedUpdate(model.getUpdate()),
model.getOptions());
}
if (writeModel instanceof DeleteOneModel) {
DeleteOneModel<Document> model = (DeleteOneModel<Document>) writeModel;
return new DeleteOneModel<>(getMappedQuery(model.getFilter()), model.getOptions());
}
if (writeModel instanceof DeleteManyModel) {
DeleteManyModel<Document> model = (DeleteManyModel<Document>) writeModel;
return new DeleteManyModel<>(getMappedQuery(model.getFilter()), model.getOptions());
}
return writeModel;
}
private Bson getMappedUpdate(Bson update) {
return bulkOperationContext.getUpdateMapper().getMappedObject(update, bulkOperationContext.getEntity());
}
private Bson getMappedQuery(Bson query) {
return bulkOperationContext.getQueryMapper().getMappedObject(query, bulkOperationContext.getEntity());
}
private static BulkWriteOptions getBulkWriteOptions(BulkMode bulkMode) {
BulkWriteOptions options = new BulkWriteOptions();
switch (bulkMode) {
case ORDERED:
return options.ordered(true);
case UNORDERED:
return options.ordered(false);
}
throw new IllegalStateException("BulkMode was null!");
}
/**
* {@link BulkOperationContext} holds information about
* {@link org.springframework.data.mongodb.core.BulkOperations.BulkMode} the entity in use as well as references to
* {@link QueryMapper} and {@link UpdateMapper}.
*
* @author Christoph Strobl
* @since 2.0
*/
@Value
static class BulkOperationContext {
@NonNull BulkMode bulkMode;
@NonNull Optional<? extends MongoPersistentEntity<?>> entity;
@NonNull QueryMapper queryMapper;
@NonNull UpdateMapper updateMapper;
}
}

View File

@@ -23,10 +23,11 @@ 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.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.util.Assert;
@@ -37,7 +38,7 @@ import com.mongodb.client.model.IndexOptions;
/**
* Default implementation of {@link IndexOperations}.
*
*
* @author Mark Pollack
* @author Oliver Gierke
* @author Komi Innocent
@@ -55,7 +56,7 @@ public class DefaultIndexOperations implements IndexOperations {
/**
* 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}.
@@ -89,7 +90,7 @@ public class DefaultIndexOperations implements IndexOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.IndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition)
* @see org.springframework.data.mongodb.core.index.IndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition)
*/
public String ensureIndex(final IndexDefinition indexDefinition) {
@@ -97,24 +98,22 @@ public class DefaultIndexOperations implements IndexOperations {
Document indexOptions = indexDefinition.getIndexOptions();
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);
if (indexOptions == null) {
return collection.createIndex(indexDefinition.getIndexKeys());
}
return collection.createIndex(indexDefinition.getIndexKeys());
}
);
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);
});
}
private MongoPersistentEntity<?> lookupPersistentEntity(Class<?> entityType, String collection) {
@@ -136,7 +135,7 @@ public class DefaultIndexOperations implements IndexOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.IndexOperations#dropIndex(java.lang.String)
* @see org.springframework.data.mongodb.core.index.IndexOperations#dropIndex(java.lang.String)
*/
public void dropIndex(final String name) {
@@ -149,7 +148,7 @@ public class DefaultIndexOperations implements IndexOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.IndexOperations#dropAllIndexes()
* @see org.springframework.data.mongodb.core.index.IndexOperations#dropAllIndexes()
*/
public void dropAllIndexes() {
dropIndex("*");
@@ -157,7 +156,7 @@ public class DefaultIndexOperations implements IndexOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.IndexOperations#getIndexInfo()
* @see org.springframework.data.mongodb.core.index.IndexOperations#getIndexInfo()
*/
public List<IndexInfo> getIndexInfo() {

View File

@@ -17,6 +17,8 @@ package org.springframework.data.mongodb.core;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
/**
* {@link IndexOperationsProvider} to obtain {@link IndexOperations} from a given {@link MongoDbFactory}. TODO: Review
@@ -38,7 +40,7 @@ class DefaultIndexOperationsProvider implements IndexOperationsProvider {
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.IndexOperationsProvider#reactiveIndexOps(java.lang.String)
* @see org.springframework.data.mongodb.core.index.IndexOperationsProvider#reactiveIndexOps(java.lang.String)
*/
@Override
public IndexOperations indexOps(String collectionName) {

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.
@@ -15,88 +15,138 @@
*/
package org.springframework.data.mongodb.core;
import org.bson.Document;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.util.Assert;
import com.mongodb.reactivestreams.client.ListIndexesPublisher;
import org.springframework.data.mongodb.core.index.ReactiveIndexOperations;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.Optional;
import org.bson.Document;
import org.springframework.data.mongodb.core.convert.QueryMapper;
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.client.model.IndexOptions;
/**
* Default implementation of {@link IndexOperations}.
* Default implementation of {@link ReactiveIndexOperations}.
*
* @author Mark Paluch
* @since 1.11
* @author Christoph Strobl
* @since 2.0
*/
public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
private static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression";
private final ReactiveMongoOperations mongoOperations;
private final String collectionName;
private final QueryMapper queryMapper;
private final Optional<Class<?>> type;
/**
* Creates a new {@link DefaultReactiveIndexOperations}.
*
* @param mongoOperations must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @param queryMapper must not be {@literal null}.
*/
public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName) {
public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName,
QueryMapper queryMapper) {
this(mongoOperations, collectionName, queryMapper, Optional.empty());
}
/**
* Creates a new {@link DefaultReactiveIndexOperations}.
*
* @param mongoOperations must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @param queryMapper must not be {@literal null}.
* @param type used for mapping potential partial index filter expression, must not be {@literal null}.
*/
public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName,
QueryMapper queryMapper, Class<?> type) {
this(mongoOperations, collectionName, queryMapper, Optional.of(type));
}
private DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName,
QueryMapper queryMapper, Optional<Class<?>> type) {
Assert.notNull(mongoOperations, "ReactiveMongoOperations must not be null!");
Assert.notNull(collectionName, "Collection must not be null!");
Assert.notNull(queryMapper, "QueryMapper must not be null!");
this.mongoOperations = mongoOperations;
this.collectionName = collectionName;
this.queryMapper = queryMapper;
this.type = type;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveIndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition)
* @see org.springframework.data.mongodb.core.index.ReactiveIndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition)
*/
public Mono<String> ensureIndex(final IndexDefinition indexDefinition) {
return mongoOperations.execute(collectionName, (ReactiveCollectionCallback<String>) collection -> {
return mongoOperations.execute(collectionName, collection -> {
Document indexOptions = indexDefinition.getIndexOptions();
if (indexOptions != null) {
return collection.createIndex(indexDefinition.getIndexKeys(),
IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition));
if (indexOptions == null) {
return collection.createIndex(indexDefinition.getIndexKeys());
}
return collection.createIndex(indexDefinition.getIndexKeys());
IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
MongoPersistentEntity<?> entity = type
.map(val -> (MongoPersistentEntity) queryMapper.getMappingContext().getRequiredPersistentEntity(val))
.orElseGet(() -> lookupPersistentEntity(collectionName));
ops = ops.partialFilterExpression(
queryMapper.getMappedObject(indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY, Document.class), entity));
}
return collection.createIndex(indexDefinition.getIndexKeys(), ops);
}).next();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveIndexOperations#dropIndex(java.lang.String)
*/
public Mono<Void> dropIndex(final String name) {
private MongoPersistentEntity<?> lookupPersistentEntity(String collection) {
return mongoOperations.execute(collectionName, collection -> {
Collection<? extends MongoPersistentEntity<?>> entities = queryMapper.getMappingContext().getPersistentEntities();
return Mono.from(collection.dropIndex(name));
}).flatMap(success -> Mono.<Void>empty()).next();
return entities.stream() //
.filter(entity -> entity.getCollection().equals(collection)) //
.findFirst() //
.orElse(null);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveIndexOperations#dropAllIndexes()
* @see org.springframework.data.mongodb.core.index.ReactiveIndexOperations#dropIndex(java.lang.String)
*/
public Mono<Void> dropIndex(final String name) {
return mongoOperations.execute(collectionName, collection -> collection.dropIndex(name)).then();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.ReactiveIndexOperations#dropAllIndexes()
*/
public Mono<Void> dropAllIndexes() {
return dropIndex("*");
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveIndexOperations#getIndexInfo()
* @see org.springframework.data.mongodb.core.index.ReactiveIndexOperations#getIndexInfo()
*/
public Flux<IndexInfo> getIndexInfo() {
return mongoOperations.execute(collectionName, collection -> {
ListIndexesPublisher<Document> indexesPublisher = collection.listIndexes(Document.class);
return Flux.from(indexesPublisher).map(IndexConverters.documentToIndexInfoConverter()::convert);
});
return mongoOperations.execute(collectionName, collection -> collection.listIndexes(Document.class)) //
.map(IndexConverters.documentToIndexInfoConverter()::convert);
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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")))
* .all();
* </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 ExecutableAggregation}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> ExecutableAggregation<T> aggregateAndReturn(Class<T> domainType);
/**
* Collection override (Optional).
*
* @author Christoph Strobl
* @since 2.0
*/
interface AggregationWithCollection<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 AggregationWithAggregation}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
AggregationWithAggregation<T> inCollection(String collection);
}
/**
* Trigger execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingAggregation<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 AggregationWithAggregation<T> {
/**
* Set the aggregation to be used.
*
* @param aggregation must not be {@literal null}.
* @return new instance of {@link TerminatingAggregation}.
* @throws IllegalArgumentException if aggregation is {@literal null}.
*/
TerminatingAggregation<T> by(Aggregation aggregation);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface ExecutableAggregation<T> extends AggregationWithCollection<T>, AggregationWithAggregation<T> {}
}

View File

@@ -0,0 +1,140 @@
/*
* 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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
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
* @author Mark Paluch
* @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;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableAggregationOperation#aggregateAndReturn(java.lang.Class)
*/
@Override
public <T> ExecutableAggregation<T> aggregateAndReturn(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableAggregationSupport<>(template, domainType, null, null);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
static class ExecutableAggregationSupport<T>
implements AggregationWithAggregation<T>, ExecutableAggregation<T>, TerminatingAggregation<T> {
@NonNull MongoTemplate template;
@NonNull Class<T> domainType;
Aggregation aggregation;
String collection;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableAggregationOperation.AggregationWithCollection#inCollection(java.lang.String)
*/
@Override
public AggregationWithAggregation<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new ExecutableAggregationSupport<>(template, domainType, aggregation, collection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableAggregationOperation.AggregationWithAggregation#by(org.springframework.data.mongodb.core.aggregation.Aggregation)
*/
@Override
public TerminatingAggregation<T> by(Aggregation aggregation) {
Assert.notNull(aggregation, "Aggregation must not be null!");
return new ExecutableAggregationSupport<>(template, domainType, aggregation, collection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableAggregationOperation.TerminatingAggregation#all()
*/
@Override
public AggregationResults<T> all() {
return template.aggregate(aggregation, getCollectionName(aggregation), domainType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableAggregationOperation.TerminatingAggregation#stream()
*/
@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,223 @@
/*
* 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 java.util.stream.Stream;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
/**
* {@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 ExecutableFind}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> ExecutableFind<T> query(Class<T> domainType);
/**
* Trigger find execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingFind<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.
*/
default Optional<T> one() {
return Optional.ofNullable(oneValue());
}
/**
* Get exactly zero or one result.
*
* @return {@literal null} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
T oneValue();
/**
* Get the first or no result.
*
* @return {@link Optional#empty()} if no match found.
*/
default Optional<T> first() {
return Optional.ofNullable(firstValue());
}
/**
* Get the first or no result.
*
* @return {@literal null} if no match found.
*/
T firstValue();
/**
* Get all matching elements.
*
* @return never {@literal null}.
*/
List<T> all();
/**
* Stream all matching elements.
*
* @return a {@link Stream} that wraps the a Mongo DB {@link com.mongodb.Cursor} that needs to be closed. Never
* {@literal null}.
*/
Stream<T> stream();
/**
* Get the number of matching elements.
*
* @return total number of matching elements.
*/
long count();
/**
* Check for the presence of matching elements.
*
* @return {@literal true} if at least one matching element exists.
*/
boolean exists();
}
/**
* Trigger geonear execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingFindNear<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 FindWithQuery<T> extends TerminatingFind<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFind}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFind<T> matching(Query query);
/**
* Set the filter query for the geoNear execution.
*
* @param nearQuery must not be {@literal null}.
* @return new instance of {@link TerminatingFindNear}.
* @throws IllegalArgumentException if nearQuery is {@literal null}.
*/
TerminatingFindNear<T> near(NearQuery nearQuery);
}
/**
* Collection override (Optional).
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindWithCollection<T> extends FindWithQuery<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 FindWithProjection}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
FindWithProjection<T> inCollection(String collection);
}
/**
* Result type override (Optional).
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindWithProjection<T> extends FindWithQuery<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 FindWithProjection}.
* @throws IllegalArgumentException if resultType is {@literal null}.
*/
<R> FindWithQuery<R> as(Class<R> resultType);
}
/**
* {@link ExecutableFind} provides methods for constructing lookup operations in a fluent way.
*
* @author Christoph Strobl
* @since 2.0
*/
interface ExecutableFind<T> extends FindWithCollection<T>, FindWithProjection<T> {}
}

View File

@@ -0,0 +1,263 @@
/*
* 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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.bson.Document;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
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.data.util.StreamUtils;
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
* @author Mark Paluch
* @since 2.0
*/
class ExecutableFindOperationSupport implements ExecutableFindOperation {
private static final Query ALL_QUERY = new Query();
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;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation#query(java.lang.Class)
*/
@Override
public <T> ExecutableFind<T> query(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableFindSupport<>(template, domainType, domainType, null, ALL_QUERY);
}
/**
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
static class ExecutableFindSupport<T>
implements ExecutableFind<T>, FindWithCollection<T>, FindWithProjection<T>, FindWithQuery<T> {
@NonNull MongoTemplate template;
@NonNull Class<?> domainType;
Class<T> returnType;
String collection;
Query query;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithCollection#inCollection(java.lang.String)
*/
@Override
public FindWithProjection<T> inCollection(String collection) {
Assert.hasText(collection, "Collection name must not be null nor empty!");
return new ExecutableFindSupport<>(template, domainType, returnType, collection, query);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithProjection#as(Class)
*/
@Override
public <T1> FindWithQuery<T1> as(Class<T1> returnType) {
Assert.notNull(returnType, "ReturnType must not be null!");
return new ExecutableFindSupport<>(template, domainType, returnType, collection, query);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery#matching(org.springframework.data.mongodb.core.query.Query)
*/
@Override
public TerminatingFind<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new ExecutableFindSupport<>(template, domainType, returnType, collection, query);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind#oneValue()
*/
@Override
public T oneValue() {
List<T> result = doFind(new DelegatingQueryCursorPreparer(getCursorPreparer(query, null)).limit(2));
if (ObjectUtils.isEmpty(result)) {
return null;
}
if (result.size() > 1) {
throw new IncorrectResultSizeDataAccessException("Query " + asString() + " returned non unique result.", 1);
}
return result.iterator().next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind#firstValue()
*/
@Override
public T firstValue() {
List<T> result = doFind(new DelegatingQueryCursorPreparer(getCursorPreparer(query, null)).limit(1));
return ObjectUtils.isEmpty(result) ? null : result.iterator().next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind#all()
*/
@Override
public List<T> all() {
return doFind(null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind#stream()
*/
@Override
public Stream<T> stream() {
return StreamUtils.createStreamFromIterator(doStream());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery#near(org.springframework.data.mongodb.core.query.NearQuery)
*/
@Override
public TerminatingFindNear<T> near(NearQuery nearQuery) {
return () -> template.geoNear(nearQuery, domainType, getCollectionName(), returnType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind#count()
*/
@Override
public long count() {
return template.count(query, domainType, getCollectionName());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind#exists()
*/
@Override
public boolean exists() {
return template.exists(query, domainType, getCollectionName());
}
private List<T> doFind(CursorPreparer preparer) {
Document queryObject = query.getQueryObject();
Document fieldsObject = query.getFieldsObject();
return template.doFind(getCollectionName(), queryObject, fieldsObject, domainType, returnType,
getCursorPreparer(query, preparer));
}
private CloseableIterator<T> doStream() {
return template.doStream(query, 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;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.CursorPreparer#prepare(com.mongodb.clientFindIterable)
*/
@Override
public FindIterable<Document> prepare(FindIterable<Document> cursor) {
FindIterable<Document> target = delegate != null ? delegate.prepare(cursor) : 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
* @author Mark Paluch
* @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 ExecutableInsert}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> ExecutableInsert<T> insert(Class<T> domainType);
/**
* Trigger insert execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingInsert<T> extends TerminatingBulkInsert<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 TerminatingBulkInsert<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);
}
/**
* Collection override (optional).
*
* @author Christoph Strobl
* @since 2.0
*/
interface InsertWithCollection<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 InsertWithBulkMode}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
InsertWithBulkMode<T> inCollection(String collection);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface InsertWithBulkMode<T> extends TerminatingInsert<T> {
/**
* Define the {@link BulkMode} to use for bulk insert operation.
*
* @param bulkMode must not be {@literal null}.
* @return new instance of {@link TerminatingBulkInsert}.
* @throws IllegalArgumentException if bulkMode is {@literal null}.
*/
TerminatingBulkInsert<T> withBulkMode(BulkMode bulkMode);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface ExecutableInsert<T> extends TerminatingInsert<T>, InsertWithCollection<T>, InsertWithBulkMode<T> {}
}

View File

@@ -0,0 +1,146 @@
/*
* 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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
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
* @author Mark Paluch
* @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;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.coreExecutableInsertOperation#insert(java.lan.Class)
*/
@Override
public <T> ExecutableInsert<T> insert(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableInsertSupport<>(template, domainType, null, null);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
static class ExecutableInsertSupport<T> implements ExecutableInsert<T> {
@NonNull MongoTemplate template;
@NonNull Class<T> domainType;
String collection;
BulkMode bulkMode;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation.TerminatingInsert#insert(java.lang.Class)
*/
@Override
public void one(T object) {
Assert.notNull(object, "Object must not be null!");
template.insert(object, getCollectionName());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation.TerminatingInsert#all(java.util.Collection)
*/
@Override
public void all(Collection<? extends T> objects) {
Assert.notNull(objects, "Objects must not be null!");
template.insert(objects, getCollectionName());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation.TerminatingBulkInsert#bulk(java.util.Collection)
*/
@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();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation.InsertWithCollection#inCollection(java.lang.String)
*/
@Override
public InsertWithBulkMode<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableInsertSupport<>(template, domainType, collection, bulkMode);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation.InsertWithBulkMode#withBulkMode(org.springframework.data.mongodb.core.BulkMode)
*/
@Override
public TerminatingBulkInsert<T> withBulkMode(BulkMode bulkMode) {
Assert.notNull(bulkMode, "BulkMode must not be null!");
return new ExecutableInsertSupport<>(template, domainType, collection, bulkMode);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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
* @author Mark Paluch
* @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 ExecutableRemove}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> ExecutableRemove<T> remove(Class<T> domainType);
/**
* Collection override (optional).
*
* @param <T>
* @author Christoph Strobl
* @since 2.0
*/
interface RemoveWithCollection<T> extends RemoveWithQuery<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 RemoveWithCollection}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
RemoveWithQuery<T> inCollection(String collection);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingRemove<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 RemoveWithQuery<T> extends TerminatingRemove<T> {
/**
* Define the query filtering elements.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingRemove}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingRemove<T> matching(Query query);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface ExecutableRemove<T> extends RemoveWithCollection<T> {}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.util.List;
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
* @author Mark Paluch
* @since 2.0
*/
class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
private static final Query ALL_QUERY = new Query();
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;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableRemoveOperation#remove(java.lang.Class)
*/
@Override
public <T> ExecutableRemove<T> remove(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableRemoveSupport<>(tempate, domainType, ALL_QUERY, null);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
static class ExecutableRemoveSupport<T> implements ExecutableRemove<T>, RemoveWithCollection<T> {
@NonNull MongoTemplate template;
@NonNull Class<T> domainType;
Query query;
String collection;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableRemoveOperation.RemoveWithCollection#inCollection(java.lang.String)
*/
@Override
public RemoveWithQuery<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new ExecutableRemoveSupport<>(template, domainType, query, collection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableRemoveOperation.RemoveWithQuery#matching(org.springframework.data.mongodb.core.query.Query)
*/
@Override
public TerminatingRemove<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new ExecutableRemoveSupport<>(template, domainType, query, collection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableRemoveOperation.TerminatingRemove#all()
*/
@Override
public DeleteResult all() {
String collectionName = getCollectionName();
return template.doRemove(collectionName, query, domainType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableRemoveOperation.TerminatingRemove#findAndRemove()
*/
@Override
public List<T> findAndRemove() {
String collectionName = getCollectionName();
return template.doFindAndDelete(collectionName, query, domainType);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
}
}

View File

@@ -0,0 +1,189 @@
/*
* 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
* @author Mark Paluch
* @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 ExecutableUpdate}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> ExecutableUpdate<T> update(Class<T> domainType);
/**
* Declare the {@link Update} to apply.
*
* @author Christoph Strobl
* @since 2.0
*/
interface UpdateWithUpdate<T> {
/**
* Set the {@link Update} to be applied.
*
* @param update must not be {@literal null}.
* @return new instance of {@link TerminatingUpdate}.
* @throws IllegalArgumentException if update is {@literal null}.
*/
TerminatingUpdate<T> apply(Update update);
}
/**
* Explicitly define the name of the collection to perform operation in.
*
* @author Christoph Strobl
* @since 2.0
*/
interface UpdateWithCollection<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 UpdateWithCollection}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
UpdateWithQuery<T> inCollection(String collection);
}
/**
* Define a filter query for the {@link Update}.
*
* @author Christoph Strobl
* @since 2.0
*/
interface UpdateWithQuery<T> extends UpdateWithUpdate<T> {
/**
* Filter documents by given {@literal query}.
*
* @param query must not be {@literal null}.
* @return new instance of {@link UpdateWithQuery}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
UpdateWithUpdate<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}.
*/
TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options);
}
/**
* Trigger findAndModify execution by calling one of the terminating methods.
*/
interface TerminatingFindAndModify<T> {
/**
* Find, modify and return the first matching document.
*
* @return {@link Optional#empty()} if nothing found.
*/
default Optional<T> findAndModify() {
return Optional.ofNullable(findAndModifyValue());
}
/**
* Find, modify and return the first matching document.
*
* @return {@literal null} if nothing found.
*/
T findAndModifyValue();
}
/**
* Trigger update execution by calling one of the terminating methods.
*
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingUpdate<T> extends TerminatingFindAndModify<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();
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface ExecutableUpdate<T> extends UpdateWithCollection<T>, UpdateWithQuery<T>, UpdateWithUpdate<T> {}
}

View File

@@ -0,0 +1,175 @@
/*
* 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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
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
* @author Mark Paluch
* @since 2.0
*/
class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
private static final Query ALL_QUERY = new Query();
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;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation#update(java.lang.Class)
*/
@Override
public <T> ExecutableUpdate<T> update(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null);
}
/**
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
static class ExecutableUpdateSupport<T>
implements ExecutableUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T> {
@NonNull MongoTemplate template;
@NonNull Class<T> domainType;
Query query;
Update update;
String collection;
FindAndModifyOptions options;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateWithUpdate#apply(Update)
*/
@Override
public TerminatingUpdate<T> apply(Update update) {
Assert.notNull(update, "Update must not be null!");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateWithCollection#inCollection(java.lang.String)
*/
@Override
public UpdateWithQuery<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.FindAndModifyWithOptions#withOptions(org.springframework.data.mongodb.core.FindAndModifyOptions)
*/
@Override
public TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options) {
Assert.notNull(options, "Options must not be null!");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithQuery#matching(org.springframework.data.mongodb.core.query.Query)
*/
@Override
public UpdateWithUpdate<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.TerminatingUpdate#all()
*/
@Override
public UpdateResult all() {
return doUpdate(true, false);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.TerminatingUpdate#first()
*/
@Override
public UpdateResult first() {
return doUpdate(false, false);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.TerminatingUpdate#upsert()
*/
@Override
public UpdateResult upsert() {
return doUpdate(true, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.TerminatingFindAndModify#findAndModifyValue()
*/
@Override
public T findAndModifyValue() {
return template.findAndModify(query, update, options, domainType, getCollectionName());
}
private UpdateResult doUpdate(boolean multi, boolean upsert) {
return template.doUpdate(getCollectionName(), query, update, domainType, upsert, multi);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
}
}

View File

@@ -17,6 +17,8 @@ package org.springframework.data.mongodb.core;
import java.util.Optional;
import org.springframework.data.mongodb.core.query.Collation;
/**
* @author Mark Pollak
* @author Oliver Gierke

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.
@@ -15,7 +15,6 @@
*/
package org.springframework.data.mongodb.core;
import com.mongodb.DBCursor;
import com.mongodb.reactivestreams.client.FindPublisher;
/**
@@ -28,7 +27,7 @@ interface FindPublisherPreparer {
/**
* Prepare the given cursor (apply limits, skips and so on). Returns the prepared cursor.
*
* @param cursor
* @param findPublisher must not be {@literal null}.
*/
<T> FindPublisher<T> prepare(FindPublisher<T> findPublisher);
}

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

@@ -125,7 +125,7 @@ abstract class IndexConverters {
return null;
}
return org.springframework.data.mongodb.core.Collation.from(source).toMongoCollation();
return org.springframework.data.mongodb.core.query.Collation.from(source).toMongoCollation();
}
private static Converter<Document, IndexInfo> getDocumentIndexInfoConverter() {

View File

@@ -27,6 +27,7 @@ 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;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
@@ -58,7 +59,7 @@ import com.mongodb.client.result.UpdateResult;
* @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.
@@ -282,7 +283,10 @@ public interface MongoOperations {
ScriptOperations scriptOps();
/**
* Returns a new {@link BulkOperations} for the given collection.
* Returns a new {@link BulkOperations} for the given collection. <br />
* <strong>NOTE:</strong> Any additional support for field mapping, etc. is not available for {@literal update} or
* {@literal remove} operations in bulk mode due to the lack of domain type information. Use
* {@link #bulkOps(BulkMode, Class, String)} to get full type specific support.
*
* @param mode the {@link BulkMode} to use for bulk operations, must not be {@literal null}.
* @param collectionName the name of the collection to work on, must not be {@literal null} or empty.
@@ -318,7 +322,7 @@ public interface MongoOperations {
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way
* to map objects since the test for class type is done in the client and not on the server.
*
* @param entityClass the parameterized type of the returned list
* @param entityClass the parametrized type of the returned list
* @return the converted collection
*/
<T> List<T> findAll(Class<T> entityClass);
@@ -332,7 +336,7 @@ public interface MongoOperations {
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way
* to map objects since the test for class type is done in the client and not on the server.
*
* @param entityClass the parameterized type of the returned list.
* @param entityClass the parametrized type of the returned list.
* @param collectionName name of the collection to retrieve the objects from
* @return the converted collection
*/
@@ -347,7 +351,7 @@ public interface MongoOperations {
* @param inputCollectionName the collection where the group operation will read from
* @param groupBy the conditions under which the group operation will be performed, e.g. keys, initial document,
* reduce function.
* @param entityClass The parameterized type of the returned list
* @param entityClass The parametrized type of the returned list
* @return The results of the group operation
*/
<T> GroupByResults<T> group(String inputCollectionName, GroupBy groupBy, Class<T> entityClass);
@@ -362,7 +366,7 @@ public interface MongoOperations {
* @param inputCollectionName the collection where the group operation will read from
* @param groupBy the conditions under which the group operation will be performed, e.g. keys, initial document,
* reduce function.
* @param entityClass The parameterized type of the returned list
* @param entityClass The parametrized type of the returned list
* @return The results of the group operation
*/
<T> GroupByResults<T> group(Criteria criteria, String inputCollectionName, GroupBy groupBy, Class<T> entityClass);
@@ -374,7 +378,7 @@ public interface MongoOperations {
* @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}.
* @param outputType The parametrized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 1.3
*/
@@ -386,7 +390,7 @@ public interface MongoOperations {
*
* @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}.
* @param outputType The parametrized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 1.3
*/
@@ -399,7 +403,7 @@ public interface MongoOperations {
* {@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}.
* @param outputType The parametrized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 1.3
*/
@@ -412,7 +416,7 @@ public interface MongoOperations {
* {@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}.
* @param outputType The parametrized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 1.3
*/
@@ -431,7 +435,7 @@ public interface MongoOperations {
* @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}.
* @param outputType The parametrized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 2.0
*/
@@ -449,7 +453,7 @@ public interface MongoOperations {
*
* @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}.
* @param outputType The parametrized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 2.0
*/
@@ -468,7 +472,7 @@ public interface MongoOperations {
* {@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}.
* @param outputType The parametrized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 2.0
*/
@@ -487,7 +491,7 @@ public interface MongoOperations {
* {@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}.
* @param outputType The parametrized type of the returned list, must not be {@literal null}.
* @return The results of the aggregation operation.
* @since 2.0
*/
@@ -500,7 +504,7 @@ public interface MongoOperations {
* @param mapFunction The JavaScript map function
* @param reduceFunction The JavaScript reduce function
* @param mapReduceOptions Options that specify detailed map-reduce behavior
* @param entityClass The parameterized type of the returned list
* @param entityClass The parametrized type of the returned list
* @return The results of the map reduce operation
*/
<T> MapReduceResults<T> mapReduce(String inputCollectionName, String mapFunction, String reduceFunction,
@@ -513,7 +517,7 @@ public interface MongoOperations {
* @param mapFunction The JavaScript map function
* @param reduceFunction The JavaScript reduce function
* @param mapReduceOptions Options that specify detailed map-reduce behavior
* @param entityClass The parameterized type of the returned list
* @param entityClass The parametrized type of the returned list
* @return The results of the map reduce operation
*/
<T> MapReduceResults<T> mapReduce(String inputCollectionName, String mapFunction, String reduceFunction,
@@ -528,7 +532,7 @@ public interface MongoOperations {
* @param mapFunction The JavaScript map function
* @param reduceFunction The JavaScript reduce function
* @param mapReduceOptions Options that specify detailed map-reduce behavior
* @param entityClass The parameterized type of the returned list
* @param entityClass The parametrized type of the returned list
* @return The results of the map reduce operation
*/
<T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName, String mapFunction, String reduceFunction,
@@ -542,7 +546,7 @@ public interface MongoOperations {
* @param mapFunction The JavaScript map function
* @param reduceFunction The JavaScript reduce function
* @param mapReduceOptions Options that specify detailed map-reduce behavior
* @param entityClass The parameterized type of the returned list
* @param entityClass The parametrized type of the returned list
* @return The results of the map reduce operation
*/
<T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName, String mapFunction, String reduceFunction,
@@ -585,7 +589,7 @@ public interface MongoOperations {
*
* @param query the query class that specifies the criteria used to find a record and also an optional fields
* specification
* @param entityClass the parameterized type of the returned list.
* @param entityClass the parametrized type of the returned list.
* @return the converted object
*/
<T> T findOne(Query query, Class<T> entityClass);
@@ -602,14 +606,16 @@ public interface MongoOperations {
*
* @param query the query class that specifies the criteria used to find a record and also an optional fields
* specification
* @param entityClass the parameterized type of the returned list.
* @param entityClass the parametrized type of the returned list.
* @param collectionName name of the collection to retrieve the objects from
* @return the converted object
*/
<T> T findOne(Query query, Class<T> entityClass, String collectionName);
/**
* Determine result of given {@link Query} contains at least one element.
* Determine result of given {@link Query} contains at least one element. <br />
* <strong>NOTE:</strong> Any additional support for query/field mapping, etc. is not available due to the lack of
* domain type information. Use {@link #exists(Query, Class, String)} to get full type specific support.
*
* @param query the {@link Query} class that specifies the criteria used to find a record.
* @param collectionName name of the collection to check for objects.
@@ -621,7 +627,7 @@ public interface MongoOperations {
* Determine result of given {@link Query} contains at least one element.
*
* @param query the {@link Query} class that specifies the criteria used to find a record.
* @param entityClass the parameterized type.
* @param entityClass the parametrized type.
* @return
*/
boolean exists(Query query, Class<?> entityClass);
@@ -630,7 +636,7 @@ public interface MongoOperations {
* Determine result of given {@link Query} contains at least one element.
*
* @param query the {@link Query} class that specifies the criteria used to find a record.
* @param entityClass the parameterized type.
* @param entityClass the parametrized type.
* @param collectionName name of the collection to check for objects.
* @return
*/
@@ -647,7 +653,7 @@ public interface MongoOperations {
*
* @param query the query class that specifies the criteria used to find a record and also an optional fields
* specification
* @param entityClass the parameterized type of the returned list.
* @param entityClass the parametrized type of the returned list.
* @return the List of converted objects
*/
<T> List<T> find(Query query, Class<T> entityClass);
@@ -663,7 +669,7 @@ public interface MongoOperations {
*
* @param query the query class that specifies the criteria used to find a record and also an optional fields
* specification
* @param entityClass the parameterized type of the returned list.
* @param entityClass the parametrized type of the returned list.
* @param collectionName name of the collection to retrieve the objects from
* @return the List of converted objects
*/
@@ -698,7 +704,7 @@ public interface MongoOperations {
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param entityClass the parameterized type.
* @param entityClass the parametrized type.
* @return
*/
<T> T findAndModify(Query query, Update update, Class<T> entityClass);
@@ -710,7 +716,7 @@ public interface MongoOperations {
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param entityClass the parameterized type.
* @param entityClass the parametrized type.
* @param collectionName the collection to query.
* @return
*/
@@ -725,7 +731,7 @@ public interface MongoOperations {
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param options the {@link FindAndModifyOptions} holding additional information.
* @param entityClass the parameterized type.
* @param entityClass the parametrized type.
* @return
*/
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass);
@@ -739,7 +745,7 @@ public interface MongoOperations {
* fields specification.
* @param update the {@link Update} to apply on matching documents.
* @param options the {@link FindAndModifyOptions} holding additional information.
* @param entityClass the parameterized type.
* @param entityClass the parametrized type.
* @param collectionName the collection to query.
* @return
*/
@@ -758,7 +764,7 @@ public interface MongoOperations {
*
* @param query the query class that specifies the criteria used to find a record and also an optional fields
* specification
* @param entityClass the parameterized type of the returned list.
* @param entityClass the parametrized type of the returned list.
* @return the converted object
*/
<T> T findAndRemove(Query query, Class<T> entityClass);
@@ -775,7 +781,7 @@ public interface MongoOperations {
*
* @param query the query class that specifies the criteria used to find a record and also an optional fields
* specification
* @param entityClass the parameterized type of the returned list.
* @param entityClass the parametrized type of the returned list.
* @param collectionName name of the collection to retrieve the objects from
* @return the converted object
*/
@@ -793,7 +799,7 @@ public interface MongoOperations {
/**
* Returns the number of documents for the given {@link Query} querying the given collection. The given {@link Query}
* must solely consist of document field references as we lack type information to map potential property references
* onto document fields. TO make sure the query gets mapped, use {@link #count(Query, Class, String)}.
* onto document fields. Use {@link #count(Query, Class, String)} to get full type specific support.
*
* @param query
* @param collectionName must not be {@literal null} or empty.
@@ -916,7 +922,9 @@ public interface MongoOperations {
/**
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
* combining the query document and the update document.
* combining the query document and the update document. <br />
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
* domain type information. Use {@link #upsert(Query, Update, Class, String)} to get full type specific support.
*
* @param query the query document that specifies the criteria used to select a record to be updated
* @param update the update document that contains the updated object or $ operators to manipulate the existing
@@ -952,7 +960,9 @@ public interface MongoOperations {
/**
* Updates the first object that is found in the specified collection that matches the query document criteria with
* the provided updated document.
* the provided updated document. <br />
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
* domain type information. Use {@link #updateFirst(Query, Update, Class, String)} to get full type specific support.
*
* @param query the query document that specifies the criteria used to select a record to be updated
* @param update the update document that contains the updated object or $ operators to manipulate the existing
@@ -989,7 +999,9 @@ public interface MongoOperations {
/**
* Updates all objects that are found in the specified collection that matches the query document criteria with the
* provided updated document.
* provided updated document. <br />
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
* domain type information. Use {@link #updateMulti(Query, Update, Class, String)} to get full type specific support.
*
* @param query the query document that specifies the criteria used to select a record to be updated
* @param update the update document that contains the updated object or $ operators to manipulate the existing
@@ -1048,7 +1060,9 @@ public interface MongoOperations {
/**
* Remove all documents from the specified collection that match the provided query document criteria. There is no
* conversion/mapping done for any criteria using the id field.
* conversion/mapping done for any criteria using the id field. <br />
* <strong>NOTE:</strong> Any additional support for field mapping is not available due to the lack of domain type
* information. Use {@link #remove(Query, Class, String)} to get full type specific support.
*
* @param query the query document that specifies the criteria used to remove a record
* @param collectionName name of the collection where the objects will removed
@@ -1056,10 +1070,12 @@ public interface MongoOperations {
DeleteResult remove(Query query, String collectionName);
/**
* Returns and removes all documents form the specified collection that match the provided query.
* Returns and removes all documents form the specified collection that match the provided query. <br />
* <strong>NOTE:</strong> Any additional support for field mapping is not available due to the lack of domain type
* information. Use {@link #findAllAndRemove(Query, Class, String)} to get full type specific support.
*
* @param query
* @param collectionName
* @param query must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @return
* @since 1.5
*/

View File

@@ -17,7 +17,11 @@ 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 static org.springframework.data.util.Optionals.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.util.*;
@@ -26,7 +30,6 @@ import java.util.concurrent.TimeUnit;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@@ -49,13 +52,13 @@ import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
@@ -67,9 +70,12 @@ import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.convert.MongoWriter;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@@ -88,17 +94,22 @@ import org.springframework.data.mongodb.core.mapreduce.GroupBy;
import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
import org.springframework.data.mongodb.core.mapreduce.MapReduceResults;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Meta;
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.projection.ProjectionInformation;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.util.CloseableIterator;
import org.springframework.data.util.Optionals;
import org.springframework.data.util.Pair;
import org.springframework.data.util.StreamUtils;
import org.springframework.jca.cci.core.ConnectionCallback;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
@@ -118,6 +129,7 @@ import com.mongodb.client.MapReduceIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.Filters;
@@ -148,6 +160,7 @@ import com.mongodb.util.JSONParseException;
* @author Mark Paluch
* @author Laszlo Csontos
* @author Maninder Singh
* @author Borislav Rangelov
*/
@SuppressWarnings("deprecation")
public class MongoTemplate implements MongoOperations, ApplicationContextAware, IndexOperationsProvider {
@@ -173,6 +186,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private final PersistenceExceptionTranslator exceptionTranslator;
private final QueryMapper queryMapper;
private final UpdateMapper updateMapper;
private final SpelAwareProxyProjectionFactory projectionFactory;
private WriteConcern writeConcern;
private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE;
@@ -216,6 +230,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter;
this.queryMapper = new QueryMapper(this.mongoConverter);
this.updateMapper = new UpdateMapper(this.mongoConverter);
this.projectionFactory = new SpelAwareProxyProjectionFactory();
// We always have a mapping context in the converter, whether it's a simple one or not
mappingContext = this.mongoConverter.getMappingContext();
@@ -278,10 +293,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
prepareIndexCreator(applicationContext);
eventPublisher = applicationContext;
if (mappingContext instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher);
}
resourceLoader = applicationContext;
projectionFactory.setBeanFactory(applicationContext);
projectionFactory.setBeanClassLoader(applicationContext.getClassLoader());
}
/**
@@ -333,10 +353,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
@Override
public <T> CloseableIterator<T> stream(final Query query, final Class<T> entityType, final String collectionName) {
return doStream(query, entityType, collectionName, entityType);
}
protected <T> CloseableIterator<T> doStream(final Query query, final Class<?> entityType, final String collectionName,
Class<T> returnType) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(entityType, "Entity type must not be null!");
Assert.hasText(collectionName, "Collection name must not be null or empty!");
Assert.notNull(returnType, "ReturnType must not be null!");
return execute(collectionName, new CollectionCallback<CloseableIterator<T>>() {
@@ -346,14 +372,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
MongoPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entityType);
Document mappedFields = queryMapper.getMappedFields(query.getFieldsObject(), persistentEntity);
Document mappedFields = getMappedFieldsObject(query.getFieldsObject(), persistentEntity, returnType);
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity);
FindIterable<Document> cursor = new QueryCursorPreparer(query, entityType)
.prepare(collection.find(mappedQuery).projection(mappedFields));
return new CloseableIterableCursorAdapter<T>(cursor, exceptionTranslator,
new ReadDocumentCallback<T>(mongoConverter, entityType, collectionName));
new ProjectingReadCallback<>(mongoConverter, entityType, returnType, collectionName));
}
});
}
@@ -523,7 +549,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
public IndexOperations indexOps(Class<?> entityClass) {
return new DefaultIndexOperations(getMongoDbFactory(), determineCollectionName(entityClass), queryMapper);
return new DefaultIndexOperations(getMongoDbFactory(), determineCollectionName(entityClass), queryMapper,
entityClass);
}
public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) {
@@ -539,10 +566,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.notNull(mode, "BulkMode must not be null!");
Assert.hasText(collectionName, "Collection name must not be null or empty!");
DefaultBulkOperations operations = new DefaultBulkOperations(this, mode, collectionName, entityType);
DefaultBulkOperations operations = new DefaultBulkOperations(this, collectionName, new BulkOperationContext(mode,
Optional.ofNullable(getPersistentEntity(entityType)), queryMapper, updateMapper));
operations.setExceptionTranslator(exceptionTranslator);
operations.setWriteConcernResolver(writeConcernResolver);
operations.setDefaultWriteConcern(writeConcern);
return operations;
@@ -565,7 +592,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public <T> T findOne(Query query, Class<T> entityClass, String collectionName) {
if (query.getSortObject() == null) {
if (ObjectUtils.isEmpty(query.getSortObject()) && !query.getCollation().isPresent()) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass);
} else {
query.limit(1);
@@ -589,14 +616,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass));
FindIterable<Document> iterable = execute(collectionName, new FindCallback(mappedQuery));
if (query.getCollation().isPresent()) {
iterable = iterable
.collation(query.getCollation().map(org.springframework.data.mongodb.core.Collation::toMongoCollation).get());
}
return iterable.iterator().hasNext();
return execute(collectionName,
new ExistsCallback(mappedQuery, query.getCollation().map(Collation::toMongoCollation).orElse(null)));
}
// Find methods that take a Query to express the query and that return a List of objects.
@@ -621,9 +643,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public <T> T findById(Object id, Class<T> entityClass, String collectionName) {
String idKey = mappingContext.getPersistentEntity(entityClass)//
.flatMap(it -> it.getIdProperty())//
.map(it -> it.getName()).orElse(ID_FIELD);
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityClass);
String idKey = ID_FIELD;
if (persistentEntity != null) {
if (persistentEntity.getIdProperty() != null) {
idKey = persistentEntity.getIdProperty().getName();
}
}
return doFindOne(collectionName, new Document(idKey, id), null, entityClass);
}
@@ -633,17 +659,21 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
@SuppressWarnings("unchecked")
public <T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass, String collectionName) {
public <T> GeoResults<T> geoNear(NearQuery near, Class<T> domainType, String collectionName) {
return geoNear(near, domainType, collectionName, domainType);
}
public <T> GeoResults<T> geoNear(NearQuery near, Class<?> domainType, String collectionName, Class<T> returnType) {
if (near == null) {
throw new InvalidDataAccessApiUsageException("NearQuery must not be null!");
}
if (entityClass == null) {
if (domainType == null) {
throw new InvalidDataAccessApiUsageException("Entity class must not be null!");
}
String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(entityClass);
String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(domainType);
Document nearDocument = near.toDocument();
Document command = new Document("geoNear", collection);
@@ -651,12 +681,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (nearDocument.containsKey("query")) {
Document query = (Document) nearDocument.get("query");
command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(entityClass)));
command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(domainType)));
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Executing geoNear using: {} for class: {} in collection: {}", serializeToJsonSafely(command),
entityClass, collectionName);
domainType, collectionName);
}
Document commandResult = executeCommand(command, this.readPreference);
@@ -664,7 +694,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
results = results == null ? Collections.emptyList() : results;
DocumentCallback<GeoResult<T>> callback = new GeoNearResultDocumentCallback<T>(
new ReadDocumentCallback<T>(mongoConverter, entityClass, collectionName), near.getMetric());
new ProjectingReadCallback<>(mongoConverter, domainType, returnType, collectionName), near.getMetric());
List<GeoResult<T>> result = new ArrayList<GeoResult<T>>(results.size());
int index = 0;
@@ -752,11 +782,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.hasText(collectionName, "Collection name must not be null or empty!");
final Document document = query == null ? null
Document document = query == null ? null
: queryMapper.getMappedObject(query.getQueryObject(),
Optional.ofNullable(entityClass).flatMap(it -> mappingContext.getPersistentEntity(entityClass)));
Optional.ofNullable(entityClass).map(it -> mappingContext.getPersistentEntity(entityClass)));
return execute(collectionName, (CollectionCallback<Long>) collection -> collection.count(document));
return execute(collectionName, collection -> collection.count(document));
}
/*
@@ -873,13 +903,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private void initializeVersionProperty(Object entity) {
Optional<? extends MongoPersistentEntity<?>> persistentEntity = getPersistentEntity(entity.getClass());
MongoPersistentEntity<?> persistentEntity = getPersistentEntity(entity.getClass());
ifAllPresent(persistentEntity, persistentEntity.flatMap(PersistentEntity::getVersionProperty), (l, r) -> {
ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(l.getPropertyAccessor(entity),
if (persistentEntity != null && persistentEntity.hasVersionProperty()) {
MongoPersistentProperty versionProperty = persistentEntity.getRequiredVersionProperty();
ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(persistentEntity.getPropertyAccessor(entity),
mongoConverter.getConversionService());
accessor.setProperty(r, Optional.of(0));
});
accessor.setProperty(versionProperty, 0);
}
}
public void insert(Collection<? extends Object> batchToSave, Class<?> entityClass) {
@@ -938,7 +971,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
documentList.add(document);
}
List<Object> ids = consolidateIdentifiers(insertDocumentList(collectionName, documentList), documentList);
List<Object> ids = insertDocumentList(collectionName, documentList);
int i = 0;
for (T obj : batchToSave) {
@@ -961,12 +994,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.notNull(objectToSave, "Object to save must not be null!");
Assert.hasText(collectionName, "Collection name must not be null or empty!");
Optional<? extends MongoPersistentEntity<?>> entity = getPersistentEntity(objectToSave.getClass());
Optional<MongoPersistentProperty> versionProperty = entity.flatMap(PersistentEntity::getVersionProperty);
MongoPersistentEntity<?> entity = getPersistentEntity(objectToSave.getClass());
mapIfAllPresent(entity, versionProperty, //
(l, r) -> doSaveVersioned(objectToSave, l, collectionName))//
.orElseGet(() -> doSave(collectionName, objectToSave, this.mongoConverter));
if (entity != null && entity.hasVersionProperty()) {
doSaveVersioned(objectToSave, entity, collectionName);
return;
}
doSave(collectionName, objectToSave, this.mongoConverter);
}
private <T> T doSaveVersioned(T objectToSave, MongoPersistentEntity<?> entity, String collectionName) {
@@ -974,13 +1009,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor(
entity.getPropertyAccessor(objectToSave), mongoConverter.getConversionService());
Optional<MongoPersistentProperty> versionProperty = entity.getVersionProperty();
Optional<Number> versionNumber = versionProperty.flatMap(it -> convertingAccessor.getProperty(it, Number.class));
MongoPersistentProperty property = entity.getRequiredVersionProperty();
Number number = convertingAccessor.getProperty(property, Number.class);
return mapIfAllPresent(versionProperty, versionNumber, (property, number) -> {
if (number != null) {
// Bump version number
convertingAccessor.setProperty(property, Optional.of(number.longValue() + 1));
convertingAccessor.setProperty(property, number.longValue() + 1);
maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave, collectionName));
assertUpdateableIdIfNotSet(objectToSave);
@@ -1002,16 +1037,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (result.getModifiedCount() == 0) {
throw new OptimisticLockingFailureException(
String.format("Cannot save entity %s with version %s to collection %s. Has it been modified meanwhile?", id,
versionNumber, collectionName));
number, collectionName));
}
maybeEmitEvent(new AfterSaveEvent<T>(objectToSave, document, collectionName));
return objectToSave;
}).orElseGet(() -> {
doInsert(collectionName, objectToSave, this.mongoConverter);
return objectToSave;
});
}
doInsert(collectionName, objectToSave, this.mongoConverter);
return objectToSave;
}
protected <T> T doSave(String collectionName, T objectToSave, MongoWriter<T> writer) {
@@ -1051,9 +1086,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
});
}
// TODO: 2.0 - Change method signature to return List<Object> and return all identifiers (DATAMONGO-1513,
// DATAMONGO-1519)
protected List<ObjectId> insertDocumentList(final String collectionName, final List<Document> documents) {
protected List<Object> insertDocumentList(final String collectionName, final List<Document> documents) {
if (documents.isEmpty()) {
return Collections.emptyList();
}
@@ -1062,33 +1096,24 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
LOGGER.debug("Inserting list of Documents containing {} items", documents.size());
}
execute(collectionName, new CollectionCallback<Void>() {
public Void doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null,
null, null);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
execute(collectionName, collection -> {
if (writeConcernToUse == null) {
collection.insertMany(documents);
} else {
collection.withWriteConcern(writeConcernToUse).insertMany(documents);
}
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null,
null, null);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
return null;
if (writeConcernToUse == null) {
collection.insertMany(documents);
} else {
collection.withWriteConcern(writeConcernToUse).insertMany(documents);
}
return null;
});
List<ObjectId> ids = new ArrayList<ObjectId>();
for (Document dbo : documents) {
Object id = dbo.get(ID_FIELD);
if (id instanceof ObjectId) {
ids.add((ObjectId) id);
} else {
// no id was generated
ids.add(null);
}
}
return ids;
return documents.stream()//
.map(it -> it.get(ID_FIELD))//
.collect(StreamUtils.toUnmodifiableList());
}
protected Object saveDocument(final String collectionName, final Document dbDoc, final Class<?> entityClass) {
@@ -1165,8 +1190,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public UpdateResult doInCollection(MongoCollection<Document> collection)
throws MongoException, DataAccessException {
Optional<? extends MongoPersistentEntity<?>> entity = entityClass == null ? Optional.empty()
: getPersistentEntity(entityClass);
MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);
increaseVersionForUpdateIfNecessary(entity, update);
@@ -1212,24 +1236,26 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
});
}
private void increaseVersionForUpdateIfNecessary(Optional<? extends MongoPersistentEntity<?>> persistentEntity,
Update update) {
private void increaseVersionForUpdateIfNecessary(MongoPersistentEntity<?> persistentEntity, Update update) {
ifAllPresent(persistentEntity, persistentEntity.flatMap(PersistentEntity::getVersionProperty),
(entity, property) -> {
String versionFieldName = property.getFieldName();
if (!update.modifies(versionFieldName)) {
update.inc(versionFieldName, 1L);
}
});
if (persistentEntity != null && persistentEntity.hasVersionProperty()) {
String versionFieldName = persistentEntity.getRequiredVersionProperty().getFieldName();
if (!update.modifies(versionFieldName)) {
update.inc(versionFieldName, 1L);
}
}
}
private boolean documentContainsVersionProperty(Document document,
Optional<? extends MongoPersistentEntity<?>> persistentEntity) {
private boolean documentContainsVersionProperty(Document document, MongoPersistentEntity<?> persistentEntity) {
return mapIfAllPresent(persistentEntity, persistentEntity.flatMap(PersistentEntity::getVersionProperty), //
(entity, property) -> document.containsKey(property.getFieldName()))//
.orElse(false);
if (persistentEntity != null && persistentEntity.hasVersionProperty()) {
MongoPersistentProperty property = persistentEntity.getRequiredVersionProperty();
return document.containsKey(property.getFieldName());
}
return false;
}
public DeleteResult remove(Object object) {
@@ -1259,21 +1285,25 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @param object
* @return
*/
private Pair<String, Optional<Object>> extractIdPropertyAndValue(Object object) {
private Pair<String, Object> extractIdPropertyAndValue(Object object) {
Assert.notNull(object, "Id cannot be extracted from 'null'.");
Class<?> objectType = object.getClass();
if (object instanceof Document) {
return Pair.of(ID_FIELD, Optional.ofNullable(((Document) object).get(ID_FIELD)));
return Pair.of(ID_FIELD, ((Document) object).get(ID_FIELD));
}
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(objectType);
return mapIfAllPresent(entity, entity.flatMap(it -> it.getIdProperty()), //
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(objectType);
(l, r) -> Pair.of(r.getFieldName(), l.getPropertyAccessor(object).getProperty(r)))//
.orElseThrow(() -> new MappingException("No id property found for object of type " + objectType));
if (entity != null && entity.hasIdProperty()) {
MongoPersistentProperty idProperty = entity.getIdProperty();
return Pair.of(idProperty.getFieldName(), entity.getPropertyAccessor(object).getProperty(idProperty));
}
throw new MappingException("No id property found for object of type " + objectType);
}
/**
@@ -1284,8 +1314,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
private Query getIdQueryFor(Object object) {
Pair<String, Optional<Object>> id = extractIdPropertyAndValue(object);
return new Query(where(id.getFirst()).is(id.getSecond().orElse(null)));
Pair<String, Object> id = extractIdPropertyAndValue(object);
return new Query(where(id.getFirst()).is(id.getSecond()));
}
/**
@@ -1299,13 +1329,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.notEmpty(objects, "Cannot create Query for empty collection.");
Iterator<?> it = objects.iterator();
Pair<String, Optional<Object>> pair = extractIdPropertyAndValue(it.next());
Pair<String, Object> pair = extractIdPropertyAndValue(it.next());
ArrayList<Object> ids = new ArrayList<Object>(objects.size());
ids.add(pair.getSecond().orElse(null));
ids.add(pair.getSecond());
while (it.hasNext()) {
ids.add(extractIdPropertyAndValue(it.next()).getSecond().orElse(null));
ids.add(extractIdPropertyAndValue(it.next()).getSecond());
}
return new Query(where(pair.getFirst()).in(ids));
@@ -1313,15 +1343,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private void assertUpdateableIdIfNotSet(Object value) {
Optional<? extends MongoPersistentEntity<?>> persistentEntity = mappingContext
.getPersistentEntity(value.getClass());
Optional<MongoPersistentProperty> idProperty = persistentEntity.flatMap(it -> it.getIdProperty());
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(value.getClass());
Optionals.ifAllPresent(persistentEntity, idProperty, (entity, property) -> {
if (entity != null && entity.hasIdProperty()) {
Optional<Object> propertyValue = entity.getPropertyAccessor(value).getProperty(property);
MongoPersistentProperty property = entity.getRequiredIdProperty();
Object propertyValue = entity.getPropertyAccessor(value).getProperty(property);
if (propertyValue.isPresent()) {
if (propertyValue != null) {
return;
}
@@ -1330,7 +1359,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
String.format("Cannot autogenerate id of type %s for entity of type %s!", property.getType().getName(),
value.getClass().getName()));
}
});
}
}
public DeleteResult remove(Query query, String collectionName) {
@@ -1354,7 +1383,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.hasText(collectionName, "Collection name must not be null or empty!");
final Document queryObject = query.getQueryObject();
final Optional<? extends MongoPersistentEntity<?>> entity = getPersistentEntity(entityClass);
final MongoPersistentEntity<?> entity = getPersistentEntity(entityClass);
return execute(collectionName, new CollectionCallback<DeleteResult>() {
@@ -1436,9 +1465,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (query.getMeta() != null && query.getMeta().getMaxTimeMsec() != null) {
result = result.maxTime(query.getMeta().getMaxTimeMsec(), TimeUnit.MILLISECONDS);
}
if (query.getSortObject() != null) {
result = result.sort(query.getSortObject());
}
result = result.sort(query.getSortObject());
result = result.filter(queryMapper.getMappedObject(query.getQueryObject(), Optional.empty()));
}
@@ -1625,17 +1652,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return aggregateStream(aggregation, collectionName, outputType, null);
}
/*
* (non-Javadoc)
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String)
*/
@Override
public <T> List<T> findAllAndRemove(Query query, String collectionName) {
return findAndRemove(query, null, collectionName);
return (List<T>) findAllAndRemove(query, Object.class, collectionName);
}
/*
* (non-Javadoc)
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class)
*/
@Override
@@ -1643,8 +1668,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return findAllAndRemove(query, entityClass, determineCollectionName(entityClass));
}
/*
* (non-Javadoc)
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
*/
@Override
@@ -1765,6 +1789,51 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation#query(java.lang.Class)
*/
@Override
public <T> ExecutableFind<T> query(Class<T> domainType) {
return new ExecutableFindOperationSupport(this).query(domainType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation#update(java.lang.Class)
*/
@Override
public <T> ExecutableUpdate<T> update(Class<T> domainType) {
return new ExecutableUpdateOperationSupport(this).update(domainType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableRemoveOperation#remove(java.lang.Class)
*/
@Override
public <T> ExecutableRemove<T> remove(Class<T> domainType) {
return new ExecutableRemoveOperationSupport(this).remove(domainType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableAggregationOperation#aggregateAndReturn(java.lang.Class)
*/
@Override
public <T> ExecutableAggregation<T> aggregateAndReturn(Class<T> domainType) {
return new ExecutableAggregationOperationSupport(this).aggregateAndReturn(domainType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#insert(java.lang.Class)
*/
@Override
public <T> ExecutableInsert<T> insert(Class<T> domainType) {
return new ExecutableInsertOperationSupport(this).insert(domainType);
}
/**
* Assert that the {@link Document} does not enable Aggregation explain mode.
*
@@ -1883,7 +1952,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
protected <T> T doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass) {
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
Document mappedQuery = queryMapper.getMappedObject(query, entity);
Document mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity);
@@ -1933,7 +2002,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
protected <S, T> List<T> doFind(String collectionName, Document query, Document fields, Class<S> entityClass,
CursorPreparer preparer, DocumentCallback<T> objectCallback) {
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
Document mappedFields = queryMapper.getMappedFields(fields, entity);
Document mappedQuery = queryMapper.getMappedObject(query, entity);
@@ -1947,20 +2016,37 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
collectionName);
}
/**
* Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified targetClass while
* using sourceClass for mapping the query.
*
* @since 2.0
*/
<S, T> List<T> doFind(String collectionName, Document query, Document fields, Class<S> sourceClass,
Class<T> targetClass, CursorPreparer preparer) {
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(sourceClass);
Document mappedFields = getMappedFieldsObject(fields, entity, targetClass);
Document mappedQuery = queryMapper.getMappedObject(query, entity);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}",
serializeToJsonSafely(mappedQuery), mappedFields, sourceClass, collectionName);
}
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer,
new ProjectingReadCallback<>(mongoConverter, sourceClass, targetClass, collectionName), collectionName);
}
protected Document convertToDocument(CollectionOptions collectionOptions) {
Document document = new Document();
if (collectionOptions != null) {
if (collectionOptions.getCapped() != null) {
document.put("capped", collectionOptions.getCapped().booleanValue());
}
if (collectionOptions.getSize() != null) {
document.put("size", collectionOptions.getSize().intValue());
}
if (collectionOptions.getMaxDocuments() != null) {
document.put("max", collectionOptions.getMaxDocuments().intValue());
}
collectionOptions.getCapped().ifPresent(val -> document.put("capped", val));
collectionOptions.getSize().ifPresent(val -> document.put("size", val));
collectionOptions.getMaxDocuments().ifPresent(val -> document.put("max", val));
collectionOptions.getCollation().ifPresent(val -> document.append("collation", val.toDocument()));
}
return document;
@@ -1987,7 +2073,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(query), fields, sort, entityClass, collectionName);
}
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
return executeFindOneInternal(
new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort, collation),
@@ -2003,7 +2089,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
options = new FindAndModifyOptions();
}
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
increaseVersionForUpdateIfNecessary(entity, update);
@@ -2039,17 +2125,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return;
}
getIdPropertyFor(savedObject.getClass()).ifPresent(idProp -> {
MongoPersistentProperty idProperty = getIdPropertyFor(savedObject.getClass());
if (idProperty != null) {
ConversionService conversionService = mongoConverter.getConversionService();
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(savedObject.getClass());
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(savedObject);
Optional<Object> value = accessor.getProperty(idProp);
if (!value.isPresent()) {
new ConvertingPropertyAccessor(accessor, conversionService).setProperty(idProp, Optional.of(id));
Object value = accessor.getProperty(idProperty);
if (value == null) {
new ConvertingPropertyAccessor(accessor, conversionService).setProperty(idProperty, id);
}
});
}
}
private MongoCollection<Document> getAndPrepareCollection(MongoDatabase db, String collectionName) {
@@ -2180,12 +2268,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return exceptionTranslator;
}
private Optional<? extends MongoPersistentEntity<?>> getPersistentEntity(Class<?> type) {
return Optional.ofNullable(type).flatMap(it -> mappingContext.getPersistentEntity(it));
private MongoPersistentEntity<?> getPersistentEntity(Class<?> type) {
return type != null ? mappingContext.getPersistentEntity(type) : null;
}
private Optional<MongoPersistentProperty> getIdPropertyFor(Class<?> type) {
return mappingContext.getPersistentEntity(type).flatMap(it -> it.getIdProperty());
private MongoPersistentProperty getIdPropertyFor(Class<?> type) {
MongoPersistentEntity<?> persistentEntity = getPersistentEntity(type);
return persistentEntity != null ? persistentEntity.getIdProperty() : null;
}
private <T> String determineEntityCollectionName(T obj) {
@@ -2209,20 +2299,57 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private static final MongoConverter getDefaultMongoConverter(MongoDbFactory factory) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
MongoCustomConversions conversions = new MongoCustomConversions(Collections.emptyList());
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
mappingContext.afterPropertiesSet();
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
converter.setCustomConversions(conversions);
converter.afterPropertiesSet();
return converter;
}
private Document getMappedSortObject(Query query, Class<?> type) {
if (query == null || query.getSortObject() == null) {
if (query == null || ObjectUtils.isEmpty(query.getSortObject())) {
return null;
}
return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type));
}
private Document getMappedFieldsObject(Document fields, MongoPersistentEntity<?> entity, Class<?> targetType) {
return queryMapper.getMappedFields(addFieldsForProjection(fields, entity.getType(), targetType), entity);
}
/**
* For cases where {@code fields} is {@literal null} or {@literal empty} add fields required for creating the
* projection (target) type if the {@code targetType} is a {@literal closed interface projection}.
*
* @param fields can be {@literal null}.
* @param domainType must not be {@literal null}.
* @param targetType must not be {@literal null}.
* @return {@link Document} with fields to be included.
*/
private Document addFieldsForProjection(Document fields, Class<?> domainType, Class<?> targetType) {
if ((fields != null && !fields.isEmpty()) || !targetType.isInterface()
|| ClassUtils.isAssignable(domainType, targetType)) {
return fields;
}
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(targetType);
if (projectionInformation.isClosed()) {
projectionInformation.getInputProperties().forEach(it -> fields.append(it.getName(), 1));
}
return fields;
}
/**
* Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original
* exception if the conversation failed. Thus allows safe re-throwing of the return value.
@@ -2237,28 +2364,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return resolved == null ? ex : resolved;
}
/**
* Returns all identifiers for the given documents. Will augment the given identifiers and fill in only the ones that
* are {@literal null} currently. This would've been better solved in {@link #insertDBObjectList(String, List)}
* directly but would require a signature change of that method.
*
* @param ids
* @param documents
* @return TODO: Remove for 2.0 and change method signature of {@link #insertDBObjectList(String, List)}.
*/
private static List<Object> consolidateIdentifiers(List<ObjectId> ids, List<Document> documents) {
List<Object> result = new ArrayList<Object>(ids.size());
for (int i = 0; i < ids.size(); i++) {
ObjectId objectId = ids.get(i);
result.add(objectId == null ? documents.get(i).get(ID_FIELD) : objectId);
}
return result;
}
// Callback implementations
/**
@@ -2276,7 +2381,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public FindOneCallback(Document query, Document fields) {
this.query = query;
this.fields = Optional.ofNullable(fields);
this.fields = Optional.ofNullable(fields).filter(it -> !ObjectUtils.isEmpty(fields));
}
public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
@@ -2317,7 +2422,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public FindCallback(Document query, Document fields) {
this.query = query != null ? query : new Document();
this.fields = Optional.ofNullable(fields);
this.fields = Optional.ofNullable(fields).filter(it -> !ObjectUtils.isEmpty(fields));
}
public FindIterable<Document> doInCollection(MongoCollection<Document> collection)
@@ -2329,6 +2434,25 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
}
/**
* Optimized {@link CollectionCallback} that takes an already mappend query and a nullable
* {@link com.mongodb.client.model.Collation} to execute a count query limited to one element.
*
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
private static class ExistsCallback implements CollectionCallback<Boolean> {
private final Document mappedQuery;
private final com.mongodb.client.model.Collation collation;
@Override
public Boolean doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
return collection.count(mappedQuery, new CountOptions().limit(1).collation(collation)) > 0;
}
}
/**
* Simple {@link CollectionCallback} that takes a query {@link Document} plus an optional fields specification
* {@link Document} and executes that against the {@link DBCollection}.
@@ -2441,6 +2565,46 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
}
/**
* {@link DocumentCallback} transforming {@link Document} into the given {@code targetType} or decorating the
* {@code sourceType} with a {@literal projection} in case the {@code targetType} is an {@litera interface}.
*
* @param <S>
* @param <T>
* @since 2.0
*/
@RequiredArgsConstructor
private class ProjectingReadCallback<S, T> implements DocumentCallback<T> {
private final @NonNull EntityReader<Object, Bson> reader;
private final @NonNull Class<S> entityType;
private final @NonNull Class<T> targetType;
private final @NonNull String collectionName;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.MongoTemplate.DocumentCallback#doWith(org.bson.Document)
*/
@SuppressWarnings("unchecked")
public T doWith(Document object) {
if (object == null) {
return null;
}
Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType
: targetType;
Object source = reader.read(typeToRead, object);
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;
if (result == null) {
maybeEmitEvent(new AfterConvertEvent<>(object, result, collectionName));
}
return (T) result;
}
}
class UnwrapAndReadDocumentCallback<T> extends ReadDocumentCallback<T> {
public UnwrapAndReadDocumentCallback(EntityReader<? super T, Bson> reader, Class<T> type, String collectionName) {
@@ -2491,9 +2655,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return cursor;
}
if (query.getSkip() <= 0 && query.getLimit() <= 0
&& (query.getSortObject() == null || query.getSortObject().isEmpty()) && !StringUtils.hasText(query.getHint())
&& !query.getMeta().hasValues() && !query.getCollation().isPresent()) {
if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject())
&& !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()
&& !query.getCollation().isPresent()) {
return cursor;
}
@@ -2508,7 +2672,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (query.getLimit() > 0) {
cursorToUse = cursorToUse.limit(query.getLimit());
}
if (query.getSortObject() != null && !query.getSortObject().isEmpty()) {
if (!ObjectUtils.isEmpty(query.getSortObject())) {
Document sort = type != null ? getMappedSortObject(query, type) : query.getSortObject();
cursorToUse = cursorToUse.sort(sort);
}
@@ -2592,19 +2756,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @since 1.7
* @author Thomas Darimont
*/
@AllArgsConstructor(access = AccessLevel.PACKAGE)
static class CloseableIterableCursorAdapter<T> implements CloseableIterator<T> {
private volatile MongoCursor<Document> cursor;
private PersistenceExceptionTranslator exceptionTranslator;
private DocumentCallback<T> objectReadCallback;
CloseableIterableCursorAdapter(MongoCursor<Document> cursor, PersistenceExceptionTranslator exceptionTranslator,
DocumentCallback<T> objectReadCallback) {
this.cursor = cursor;
this.exceptionTranslator = exceptionTranslator;
this.objectReadCallback = objectReadCallback;
}
/**
* Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link Cursor}.
*
@@ -2654,8 +2812,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public void close() {
MongoCursor<Document> c = cursor;
try {
c.close();
if (c != null) {
c.close();
}
} catch (RuntimeException ex) {
throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
} finally {

View File

@@ -0,0 +1,101 @@
/*
* 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 reactor.core.publisher.Flux;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
/**
* {@link ReactiveAggregationOperation} allows creation and execution of reactive 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")))
* .all();
* </code>
* </pre>
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveAggregationOperation {
/**
* 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 ReactiveAggregation}. Never {@literal null}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> ReactiveAggregation<T> aggregateAndReturn(Class<T> domainType);
/**
* Collection override (optional).
*/
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}. Never {@literal null}.
* @throws IllegalArgumentException if collection is {@literal null} or empty.
*/
AggregationOperationWithAggregation<T> inCollection(String collection);
}
/**
* Trigger execution by calling one of the terminating methods.
*/
interface TerminatingAggregationOperation<T> {
/**
* Apply pipeline operations as specified and stream all matching elements. <br />
*
* @return a {@link Flux} streaming all matching elements. Never {@literal null}.
*/
Flux<T> all();
}
/**
* Define the aggregation with pipeline stages.
*/
interface AggregationOperationWithAggregation<T> {
/**
* Set the aggregation to be used.
*
* @param aggregation must not be {@literal null}.
* @return new instance of {@link TerminatingAggregationOperation}. Never {@literal null}.
* @throws IllegalArgumentException if aggregation is {@literal null}.
*/
TerminatingAggregationOperation<T> by(Aggregation aggregation);
}
interface ReactiveAggregation<T>
extends AggregationOperationWithCollection<T>, AggregationOperationWithAggregation<T> {}
}

View File

@@ -0,0 +1,126 @@
/*
* 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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import reactor.core.publisher.Flux;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Implementation of {@link ReactiveAggregationOperation} operating directly on {@link ReactiveMongoTemplate}.
*
* @author Mark Paluch
* @autor Christoph Strobl
* @since 2.0
*/
class ReactiveAggregationOperationSupport implements ReactiveAggregationOperation {
private final ReactiveMongoTemplate template;
/**
* Create new instance of {@link ReactiveAggregationOperationSupport}.
*
* @param template must not be {@literal null}.
* @throws IllegalArgumentException if template is {@literal null}.
*/
ReactiveAggregationOperationSupport(ReactiveMongoTemplate template) {
Assert.notNull(template, "Template must not be null!");
this.template = template;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveAggregationOperation#aggregateAndReturn(java.lang.Class)
*/
@Override
public <T> ReactiveAggregation<T> aggregateAndReturn(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveAggregationSupport<>(template, domainType, null, null);
}
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
static class ReactiveAggregationSupport<T>
implements AggregationOperationWithAggregation<T>, ReactiveAggregation<T>, TerminatingAggregationOperation<T> {
@NonNull ReactiveMongoTemplate template;
@NonNull Class<T> domainType;
Aggregation aggregation;
String collection;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveAggregationOperation.AggregationOperationWithCollection#inCollection(java.lang.String)
*/
@Override
public AggregationOperationWithAggregation<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new ReactiveAggregationSupport<>(template, domainType, aggregation, collection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveAggregationOperation.AggregationOperationWithAggregation#by(org.springframework.data.mongodb.core.Aggregation)
*/
@Override
public TerminatingAggregationOperation<T> by(Aggregation aggregation) {
Assert.notNull(aggregation, "Aggregation must not be null!");
return new ReactiveAggregationSupport<>(template, domainType, aggregation, collection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveAggregationOperation.TerminatingAggregationOperation#all()
*/
@Override
public Flux<T> all() {
return template.aggregate(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,177 @@
/*
* 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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
/**
* {@link ReactiveFindOperation} allows creation and execution of reactive 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 Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveFindOperation {
/**
* Start creating a find operation for the given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link ReactiveFind}. Never {@literal null}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> ReactiveFind<T> query(Class<T> domainType);
/**
* Compose find execution by calling one of the terminating methods.
*/
interface TerminatingFind<T> {
/**
* Get exactly zero or one result.
*
* @return {@link Mono#empty()} if no match found. Never {@literal null}.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
Mono<T> one();
/**
* Get the first or no result.
*
* @return {@link Mono#empty()} if no match found. Never {@literal null}.
*/
Mono<T> first();
/**
* Get all matching elements.
*
* @return never {@literal null}.
*/
Flux<T> all();
/**
* Get the number of matching elements.
*
* @return {@link Mono} emitting total number of matching elements. Never {@literal null}.
*/
Mono<Long> count();
/**
* Check for the presence of matching elements.
*
* @return {@link Mono} emitting {@literal true} if at least one matching element exists. Never {@literal null}.
*/
Mono<Boolean> exists();
}
/**
* Compose geonear execution by calling one of the terminating methods.
*/
interface TerminatingFindNear<T> {
/**
* Find all matching elements and return them as {@link org.springframework.data.geo.GeoResult}.
*
* @return never {@literal null}.
*/
Flux<GeoResult<T>> all();
}
/**
* Provide a {@link Query} override (optional).
*/
interface FindWithQuery<T> extends TerminatingFind<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFind}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFind<T> matching(Query query);
/**
* Set the filter query for the geoNear execution.
*
* @param nearQuery must not be {@literal null}.
* @return new instance of {@link TerminatingFindNear}.
* @throws IllegalArgumentException if nearQuery is {@literal null}.
*/
TerminatingFindNear<T> near(NearQuery nearQuery);
}
/**
* Collection override (optional).
*/
interface FindWithCollection<T> extends FindWithQuery<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 FindWithProjection}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
FindWithProjection<T> inCollection(String collection);
}
/**
* Result type override (optional).
*/
interface FindWithProjection<T> extends FindWithQuery<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 FindWithProjection}.
* @throws IllegalArgumentException if resultType is {@literal null}.
*/
<R> FindWithQuery<R> as(Class<R> resultType);
}
/**
* {@link ReactiveFind} provides methods for constructing lookup operations in a fluent way.
*/
interface ReactiveFind<T> extends FindWithCollection<T>, FindWithProjection<T> {}
}

View File

@@ -0,0 +1,219 @@
/*
* 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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.bson.Document;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
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.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.reactivestreams.client.FindPublisher;
/**
* Implementation of {@link ReactiveFindOperation}.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
class ReactiveFindOperationSupport implements ReactiveFindOperation {
private static final Query ALL_QUERY = new Query();
private final @NonNull ReactiveMongoTemplate template;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveFindOperation#query(java.lang.Class)
*/
@Override
public <T> ReactiveFind<T> query(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveFindSupport<>(template, domainType, domainType, null, ALL_QUERY);
}
/**
* @param <T>
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
static class ReactiveFindSupport<T>
implements ReactiveFind<T>, FindWithCollection<T>, FindWithProjection<T>, FindWithQuery<T> {
@NonNull ReactiveMongoTemplate template;
@NonNull Class<?> domainType;
Class<T> returnType;
String collection;
Query query;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithCollection#inCollection(java.lang.String)
*/
@Override
public FindWithProjection<T> inCollection(String collection) {
Assert.hasText(collection, "Collection name must not be null nor empty!");
return new ReactiveFindSupport<>(template, domainType, returnType, collection, query);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection#as(java.lang.Class)
*/
@Override
public <T1> FindWithQuery<T1> as(Class<T1> returnType) {
Assert.notNull(returnType, "ReturnType must not be null!");
return new ReactiveFindSupport<>(template, domainType, returnType, collection, query);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery#matching(org.springframework.data.mongodb.core.query.Query)
*/
@Override
public TerminatingFind<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new ReactiveFindSupport<>(template, domainType, returnType, collection, query);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.TerminatingFind#first()
*/
@Override
public Mono<T> first() {
FindPublisherPreparer preparer = getCursorPreparer(query);
Flux<T> result = doFind(new FindPublisherPreparer() {
@Override
public <D> FindPublisher<D> prepare(FindPublisher<D> publisher) {
return preparer.prepare(publisher).limit(1);
}
});
return result.next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.TerminatingFind#one()
*/
@Override
public Mono<T> one() {
FindPublisherPreparer preparer = getCursorPreparer(query);
Flux<T> result = doFind(new FindPublisherPreparer() {
@Override
public <D> FindPublisher<D> prepare(FindPublisher<D> publisher) {
return preparer.prepare(publisher).limit(2);
}
});
return result.collectList().flatMap(it -> {
if (it.isEmpty()) {
return Mono.empty();
}
if (it.size() > 1) {
return Mono.error(
new IncorrectResultSizeDataAccessException("Query " + asString() + " returned non unique result.", 1));
}
return Mono.just(it.get(0));
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.TerminatingFind#all()
*/
@Override
public Flux<T> all() {
return doFind(null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery#near(org.springframework.data.mongodb.core.query.NearQuery)
*/
@Override
public TerminatingFindNear<T> near(NearQuery nearQuery) {
return () -> template.geoNear(nearQuery, domainType, getCollectionName(), returnType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.TerminatingFind#count()
*/
@Override
public Mono<Long> count() {
return template.count(query, domainType, getCollectionName());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.TerminatingFind#exists()
*/
@Override
public Mono<Boolean> exists() {
return template.exists(query, domainType, getCollectionName());
}
private Flux<T> doFind(FindPublisherPreparer preparer) {
Document queryObject = query.getQueryObject();
Document fieldsObject = query.getFieldsObject();
return template.doFind(getCollectionName(), queryObject, fieldsObject, domainType, returnType,
preparer != null ? preparer : getCursorPreparer(query));
}
private FindPublisherPreparer getCursorPreparer(Query query) {
return template.new QueryFindPublisherPreparer(query, domainType);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
private String asString() {
return SerializationUtils.serializeToJsonSafely(query);
}
}
}

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 reactive MongoDB operations.
*
* @author Mark Paluch
* @since 2.0
*/
public interface ReactiveFluentMongoOperations extends ReactiveFindOperation, ReactiveInsertOperation,
ReactiveUpdateOperation, ReactiveRemoveOperation, ReactiveAggregationOperation {}

View File

@@ -0,0 +1,94 @@
/*
* 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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
/**
* {@link ReactiveInsertOperation} allows creation and execution of reactive 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 Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveInsertOperation {
/**
* Start creating an insert operation for given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link ReactiveInsert}. Never {@literal null}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> ReactiveInsert<T> insert(Class<T> domainType);
/**
* Compose insert execution by calling one of the terminating methods.
*/
interface TerminatingInsert<T> {
/**
* Insert exactly one object.
*
* @param object must not be {@literal null}.
* @return {@link Mono} emitting the inserted {@code object} when operation has completed. Never {@literal null}.
* @throws IllegalArgumentException if object is {@literal null}.
*/
Mono<T> one(T object);
/**
* Insert a collection of objects.
*
* @param objects must not be {@literal null}.
* @return {@literal Flux} emitting the inserted {@code objects} ony by one. Never {@literal null}.
* @throws IllegalArgumentException if objects is {@literal null}.
*/
Flux<T> all(Collection<? extends T> objects);
}
/**
* Collection override (optional).
*/
interface InsertWithCollection<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 TerminatingInsert}. Never {@literal null}.
* @throws IllegalArgumentException if collection is {@literal null}.
*/
TerminatingInsert<T> inCollection(String collection);
}
interface ReactiveInsert<T> extends TerminatingInsert<T>, InsertWithCollection<T> {}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Implementation of {@link ReactiveInsertOperation}.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
class ReactiveInsertOperationSupport implements ReactiveInsertOperation {
private final @NonNull ReactiveMongoTemplate template;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveInsertOperation#insert(java.lang.Class)
*/
@Override
public <T> ReactiveInsert<T> insert(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveInsertSupport<>(template, domainType, null);
}
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
static class ReactiveInsertSupport<T> implements ReactiveInsert<T> {
@NonNull ReactiveMongoTemplate template;
@NonNull Class<T> domainType;
String collection;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveInsertOperation.TerminatingInsert#one(java.lang.Object)
*/
@Override
public Mono<T> one(T object) {
Assert.notNull(object, "Object must not be null!");
return template.insert(object, getCollectionName());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveInsertOperation.TerminatingInsert#all(java.util.Collection)
*/
@Override
public Flux<T> all(Collection<? extends T> objects) {
Assert.notNull(objects, "Objects must not be null!");
return template.insert(objects, getCollectionName());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveInsertOperation.InsertWithCollection#inCollection(java.lang.String)
*/
@Override
public ReactiveInsert<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveInsertSupport<>(template, domainType, collection);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
}
}

View File

@@ -1,51 +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;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoDatabase;
/**
* Helper class featuring helper methods for internal MongoDb classes. Mainly intended for internal use within the
* framework.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
public abstract class ReactiveMongoDbUtils {
/**
* Private constructor to prevent instantiation.
*/
private ReactiveMongoDbUtils() {}
/**
* Obtains a {@link MongoDatabase} connection for the given {@link MongoClient} instance and database name
*
* @param mongo the {@link MongoClient} instance, must not be {@literal null}.
* @param databaseName the database name, must not be {@literal null} or empty.
* @return the {@link MongoDatabase} connection
*/
public static MongoDatabase getMongoDatabase(MongoClient mongo, String databaseName) {
return doGetMongoDatabase(mongo, databaseName, true);
}
private static MongoDatabase doGetMongoDatabase(MongoClient mongo, String databaseName, boolean allowCreate) {
return mongo.getDatabase(databaseName);
}
}

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.
@@ -15,14 +15,21 @@
*/
package org.springframework.data.mongodb.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.bson.Document;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.index.ReactiveIndexOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
@@ -34,9 +41,6 @@ import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import com.mongodb.reactivestreams.client.MongoCollection;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Interface that specifies a basic set of MongoDB operations executed in a reactive way.
* <p>
@@ -51,7 +55,7 @@ import reactor.core.publisher.Mono;
* @see Mono
* @see <a href="http://projectreactor.io/docs/">Project Reactor</a>
*/
public interface ReactiveMongoOperations {
public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
/**
* Returns the reactive operations that can be performed on indexes
@@ -228,8 +232,8 @@ public interface ReactiveMongoOperations {
* <p/>
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way
* to map objects since the test for class type is done in the client and not on the server.
* @param entityClass the parametrized type of the returned {@link Flux}.
*
* @param entityClass the parametrized type of the returned {@link Flux}.
* @return the converted collection
*/
<T> Flux<T> findAll(Class<T> entityClass);
@@ -285,7 +289,9 @@ public interface ReactiveMongoOperations {
<T> Mono<T> findOne(Query query, Class<T> entityClass, String collectionName);
/**
* Determine result of given {@link Query} contains at least one element.
* Determine result of given {@link Query} contains at least one element. <br />
* <strong>NOTE:</strong> Any additional support for query/field mapping, etc. is not available due to the lack of
* domain type information. Use {@link #exists(Query, Class, String)} to get full type specific support.
*
* @param query the {@link Query} class that specifies the criteria used to find a record.
* @param collectionName name of the collection to check for objects.
@@ -368,10 +374,87 @@ public interface ReactiveMongoOperations {
<T> Mono<T> findById(Object id, Class<T> entityClass, String collectionName);
/**
* Returns {@link Flux} of {@link GeoResult} for all entities matching the given {@link NearQuery}. Will consider entity mapping
* information to determine the collection the query is ran against. Note, that MongoDB limits the number of results
* by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect a particular number of
* results.
* Execute an aggregation operation.
* <p>
* The raw results will be mapped to the given entity class.
* <p>
* Aggregation streaming cannot be used with {@link AggregationOptions#isExplain() aggregation explain} nor with
* {@link AggregationOptions#getCursorBatchSize()}. Enabling explanation mode or setting batch size cause
* {@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 aggregation. Must not be {@literal null}.
* @param outputType The parametrized type of the returned {@link Flux}. Must not be {@literal null}.
* @return The results of the aggregation operation.
* @throws IllegalArgumentException if {@code aggregation}, {@code collectionName} or {@code outputType} is
* {@literal null}.
*/
<O> Flux<O> aggregate(TypedAggregation<?> aggregation, String collectionName, Class<O> outputType);
/**
* Execute an aggregation operation.
* <p/>
* 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 {@link TypedAggregation#getInputType() aggregation input type}.
* <p/>
* Aggregation streaming cannot be used with {@link AggregationOptions#isExplain() aggregation explain} nor with
* {@link AggregationOptions#getCursorBatchSize()}. Enabling explanation mode or setting batch size cause
* {@link IllegalArgumentException}.
*
* @param aggregation The {@link TypedAggregation} specification holding the aggregation operations. Must not be
* {@literal null}.
* @param outputType The parametrized type of the returned {@link Flux}. Must not be {@literal null}.
* @return The results of the aggregation operation.
* @throws IllegalArgumentException if {@code aggregation} or {@code outputType} is {@literal null}.
*/
<O> Flux<O> aggregate(TypedAggregation<?> aggregation, Class<O> outputType);
/**
* Execute an aggregation operation.
* <p/>
* The raw results will be mapped to the given {@code ouputType}. The name of the inputCollection is derived from the
* {@code inputType}.
* <p/>
* Aggregation streaming cannot be used with {@link AggregationOptions#isExplain() aggregation explain} nor with
* {@link AggregationOptions#getCursorBatchSize()}. Enabling explanation mode or setting batch size cause
* {@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}.
* @param outputType The parametrized type of the returned {@link Flux}. Must not be {@literal null}.
* @return The results of the aggregation operation.
* @throws IllegalArgumentException if {@code aggregation}, {@code inputType} or {@code outputType} is
* {@literal null}.
*/
<O> Flux<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType);
/**
* Execute an aggregation operation.
* <p/>
* The raw results will be mapped to the given entity class.
* <p/>
* Aggregation streaming cannot be used with {@link AggregationOptions#isExplain() aggregation explain} nor with
* {@link AggregationOptions#getCursorBatchSize()}. Enabling explanation mode or setting batch size cause
* {@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 parametrized type of the returned {@link Flux}. Must not be {@literal null}.
* @return The results of the aggregation operation.
* @throws IllegalArgumentException if {@code aggregation}, {@code collectionName} or {@code outputType} is
* {@literal null}.
*/
<O> Flux<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType);
/**
* Returns {@link Flux} of {@link GeoResult} for all entities matching the given {@link NearQuery}. Will consider
* entity mapping information to determine the collection the query is ran against. Note, that MongoDB limits the
* number of results by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect a
* particular number of results.
*
* @param near must not be {@literal null}.
* @param entityClass must not be {@literal null}.
@@ -380,9 +463,9 @@ public interface ReactiveMongoOperations {
<T> Flux<GeoResult<T>> geoNear(NearQuery near, Class<T> entityClass);
/**
* Returns {@link Flux} of {@link GeoResult} for all entities matching the given {@link NearQuery}. Note, that MongoDB limits the
* number of results by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect a
* particular number of results.
* Returns {@link Flux} of {@link GeoResult} for all entities matching the given {@link NearQuery}. Note, that MongoDB
* limits the number of results by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect
* a particular number of results.
*
* @param near must not be {@literal null}.
* @param entityClass must not be {@literal null}.
@@ -494,7 +577,7 @@ public interface ReactiveMongoOperations {
/**
* Returns the number of documents for the given {@link Query} querying the given collection. The given {@link Query}
* must solely consist of document field references as we lack type information to map potential property references
* onto document fields. TO make sure the query gets mapped, use {@link #count(Query, Class, String)}.
* onto document fields. Use {@link #count(Query, Class, String)} to get full type specific support.
*
* @param query
* @param collectionName must not be {@literal null} or empty.
@@ -707,7 +790,9 @@ public interface ReactiveMongoOperations {
/**
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
* combining the query document and the update document.
* combining the query document and the update document. <br />
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
* domain type information. Use {@link #upsert(Query, Update, Class, String)} to get full type specific support.
*
* @param query the query document that specifies the criteria used to select a record to be updated
* @param update the update document that contains the updated object or $ operators to manipulate the existing
@@ -743,7 +828,9 @@ public interface ReactiveMongoOperations {
/**
* Updates the first object that is found in the specified collection that matches the query document criteria with
* the provided updated document.
* the provided updated document. <br />
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
* domain type information. Use {@link #updateFirst(Query, Update, Class, String)} to get full type specific support.
*
* @param query the query document that specifies the criteria used to select a record to be updated
* @param update the update document that contains the updated object or $ operators to manipulate the existing
@@ -755,7 +842,9 @@ public interface ReactiveMongoOperations {
/**
* Updates the first object that is found in the specified collection that matches the query document criteria with
* the provided updated document.
* the provided updated document. <br />
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
* domain type information. Use {@link #updateFirst(Query, Update, Class, String)} to get full type specific support.
*
* @param query the query document that specifies the criteria used to select a record to be updated
* @param update the update document that contains the updated object or $ operators to manipulate the existing
@@ -780,7 +869,9 @@ public interface ReactiveMongoOperations {
/**
* Updates all objects that are found in the specified collection that matches the query document criteria with the
* provided updated document.
* provided updated document. <br />
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
* domain type information. Use {@link #updateMulti(Query, Update, Class, String)} to get full type specific support.
*
* @param query the query document that specifies the criteria used to select a record to be updated
* @param update the update document that contains the updated object or $ operators to manipulate the existing
@@ -859,7 +950,9 @@ public interface ReactiveMongoOperations {
/**
* Remove all documents from the specified collection that match the provided query document criteria. There is no
* conversion/mapping done for any criteria using the id field.
* conversion/mapping done for any criteria using the id field. <br />
* <strong>NOTE:</strong> Any additional support for field mapping is not available due to the lack of domain type
* information. Use {@link #remove(Query, Class, String)} to get full type specific support.
*
* @param query the query document that specifies the criteria used to remove a record
* @param collectionName name of the collection where the objects will removed
@@ -867,7 +960,9 @@ public interface ReactiveMongoOperations {
Mono<DeleteResult> remove(Query query, String collectionName);
/**
* Returns and removes all documents form the specified collection that match the provided query.
* Returns and removes all documents form the specified collection that match the provided query. <br />
* <strong>NOTE:</strong> Any additional support for field mapping is not available due to the lack of domain type
* information. Use {@link #findAllAndRemove(Query, Class, String)} to get full type specific support.
*
* @param query
* @param collectionName

View File

@@ -0,0 +1,112 @@
/*
* 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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.client.result.DeleteResult;
/**
* {@link ReactiveRemoveOperation} allows creation and execution of reactive 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 Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveRemoveOperation {
/**
* Start creating a remove operation for the given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link ReactiveRemove}. Never {@literal null}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> ReactiveRemove<T> remove(Class<T> domainType);
/**
* Compose remove execution by calling one of the terminating methods.
*/
interface TerminatingRemove<T> {
/**
* Remove all documents matching.
*
* @return {@link Mono} emitting the {@link DeleteResult}. Never {@literal null}.
*/
Mono<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 Flux} if no match found. Never {@literal null}.
*/
Flux<T> findAndRemove();
}
/**
* Collection override (optional).
*/
interface RemoveWithCollection<T> extends RemoveWithQuery<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 RemoveWithCollection}. Never {@literal null}.
* @throws IllegalArgumentException if collection is {@literal null} or empty.
*/
RemoveWithQuery<T> inCollection(String collection);
}
/**
* Provide a {@link Query} override (optional).
*/
interface RemoveWithQuery<T> extends TerminatingRemove<T> {
/**
* Define the query filtering elements.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingRemove}. Never {@literal null}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingRemove<T> matching(Query query);
}
interface ReactiveRemove<T> extends RemoveWithCollection<T> {}
}

View File

@@ -0,0 +1,119 @@
/*
* 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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
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 ReactiveRemoveOperation}.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
class ReactiveRemoveOperationSupport implements ReactiveRemoveOperation {
private static final Query ALL_QUERY = new Query();
private final @NonNull ReactiveMongoTemplate tempate;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveRemoveOperation#remove(java.lang.Class)
*/
@Override
public <T> ReactiveRemove<T> remove(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveRemoveSupport<>(tempate, domainType, ALL_QUERY, null);
}
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
static class ReactiveRemoveSupport<T> implements ReactiveRemove<T>, RemoveWithCollection<T> {
@NonNull ReactiveMongoTemplate template;
@NonNull Class<T> domainType;
Query query;
String collection;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveRemoveOperation.RemoveWithCollection#inCollection(String)
*/
@Override
public RemoveWithQuery<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new ReactiveRemoveSupport<>(template, domainType, query, collection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveRemoveOperation.RemoveWithQuery#matching(org.springframework.data.mongodb.core.Query)
*/
@Override
public TerminatingRemove<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new ReactiveRemoveSupport<>(template, domainType, query, collection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveRemoveOperation.TerminatingRemove#all()
*/
@Override
public Mono<DeleteResult> all() {
String collectionName = getCollectionName();
return template.doRemove(collectionName, query, domainType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveRemoveOperation.TerminatingRemove#findAndRemove()
*/
@Override
public Flux<T> findAndRemove() {
String collectionName = getCollectionName();
return template.doFindAndDelete(collectionName, query, domainType);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
}
}

View File

@@ -0,0 +1,161 @@
/*
* 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 reactor.core.publisher.Mono;
import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.client.result.UpdateResult;
/**
* {@link ReactiveUpdateOperation} allows creation and execution of reactive 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 org.springframework.data.mongodb.core.query.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 Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveUpdateOperation {
/**
* Start creating an update operation for the given {@literal domainType}.
*
* @param domainType must not be {@literal null}.
* @return new instance of {@link ReactiveUpdate}. Never {@literal null}.
* @throws IllegalArgumentException if domainType is {@literal null}.
*/
<T> ReactiveUpdate<T> update(Class<T> domainType);
/**
* Compose findAndModify execution by calling one of the terminating methods.
*/
interface TerminatingFindAndModify<T> {
/**
* Find, modify and return the first matching document.
*
* @return {@link Mono#empty()} if nothing found. Never {@literal null}.
*/
Mono<T> findAndModify();
}
/**
* Compose update execution by calling one of the terminating methods.
*/
interface TerminatingUpdate<T> extends TerminatingFindAndModify<T>, FindAndModifyWithOptions<T> {
/**
* Update all matching documents in the collection.
*
* @return never {@literal null}.
*/
Mono<UpdateResult> all();
/**
* Update the first document in the collection.
*
* @return never {@literal null}.
*/
Mono<UpdateResult> first();
/**
* Creates a new document if no documents match the filter query or updates the matching ones.
*
* @return never {@literal null}.
*/
Mono<UpdateResult> upsert();
}
/**
* Declare the {@link org.springframework.data.mongodb.core.query.Update} to apply.
*/
interface UpdateWithUpdate<T> {
/**
* Set the {@link org.springframework.data.mongodb.core.query.Update} to be applied.
*
* @param update must not be {@literal null}.
* @return new instance of {@link TerminatingUpdate}. Never {@literal null}.
* @throws IllegalArgumentException if update is {@literal null}.
*/
TerminatingUpdate<T> apply(org.springframework.data.mongodb.core.query.Update update);
}
/**
* Explicitly define the name of the collection to perform operation in (optional).
*/
interface UpdateWithCollection<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 UpdateWithCollection}. Never {@literal null}.
* @throws IllegalArgumentException if collection is {@literal null} or empty.
*/
UpdateWithQuery<T> inCollection(String collection);
}
/**
* Define a filter query for the {@link org.springframework.data.mongodb.core.query.Update} (optional).
*/
interface UpdateWithQuery<T> extends UpdateWithUpdate<T> {
/**
* Filter documents by given {@literal query}.
*
* @param query must not be {@literal null}.
* @return new instance of {@link UpdateWithQuery}. Never {@literal null}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
UpdateWithUpdate<T> matching(Query query);
}
/**
* Define {@link FindAndModifyOptions} (optional).
*/
interface FindAndModifyWithOptions<T> {
/**
* Explicitly define {@link FindAndModifyOptions} for the
* {@link org.springframework.data.mongodb.core.query.Update}.
*
* @param options must not be {@literal null}.
* @return new instance of {@link TerminatingFindAndModify}. Never {@literal null}.
* @throws IllegalArgumentException if options is {@literal null}.
*/
TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options);
}
interface ReactiveUpdate<T> extends UpdateWithCollection<T>, UpdateWithQuery<T>, UpdateWithUpdate<T> {}
}

View File

@@ -0,0 +1,163 @@
/*
* 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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import reactor.core.publisher.Mono;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.client.result.UpdateResult;
/**
* Implementation of {@link ReactiveUpdateOperation}.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
@RequiredArgsConstructor
class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
private static final Query ALL_QUERY = new Query();
private final @NonNull ReactiveMongoTemplate template;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation#update(java.lang.Class)
*/
@Override
public <T> ReactiveUpdate<T> update(Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null);
}
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
static class ReactiveUpdateSupport<T>
implements ReactiveUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T> {
@NonNull ReactiveMongoTemplate template;
@NonNull Class<T> domainType;
Query query;
org.springframework.data.mongodb.core.query.Update update;
String collection;
FindAndModifyOptions options;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithUpdate#apply(org.springframework.data.mongodb.core.query.Update)
*/
@Override
public TerminatingUpdate<T> apply(org.springframework.data.mongodb.core.query.Update update) {
Assert.notNull(update, "Update must not be null!");
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithCollection#inCollection(java.lang.String)
*/
@Override
public UpdateWithQuery<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty!");
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.TerminatingUpdate#first()
*/
@Override
public Mono<UpdateResult> first() {
return doUpdate(false, false);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.TerminatingUpdate#upsert()
*/
@Override
public Mono<UpdateResult> upsert() {
return doUpdate(true, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.TerminatingFindAndModify#findAndModify()
*/
@Override
public Mono<T> findAndModify() {
String collectionName = getCollectionName();
return template.findAndModify(query, update, options, domainType, collectionName);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithQuery#matching(org.springframework.data.mongodb.core.Query)
*/
@Override
public UpdateWithUpdate<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.TerminatingUpdate#all()
*/
@Override
public Mono<UpdateResult> all() {
return doUpdate(true, false);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.FindAndModifyWithOptions#withOptions(org.springframework.data.mongodb.core.FindAndModifyOptions)
*/
@Override
public TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options) {
Assert.notNull(options, "Options must not be null!");
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
}
private Mono<UpdateResult> doUpdate(boolean multi, boolean upsert) {
return template.doUpdate(getCollectionName(), query, update, domainType, upsert, multi);
}
private String getCollectionName() {
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
}
}
}

View File

@@ -33,6 +33,7 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
* Factory to create {@link MongoDatabase} instances from a {@link MongoClient} instance.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/
public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, ReactiveMongoDatabaseFactory {
@@ -103,14 +104,8 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
Assert.hasText(dbName, "Database name must not be empty.");
MongoDatabase db = ReactiveMongoDbUtils.getMongoDatabase(mongo, dbName);
if (writeConcern != null) {
db = db.withWriteConcern(writeConcern);
}
return db;
MongoDatabase db = mongo.getDatabase(dbName);
return writeConcern != null ? db.withWriteConcern(writeConcern) : db;
}
/**
@@ -119,6 +114,7 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
* @see DisposableBean#destroy()
*/
public void destroy() throws Exception {
if (mongoInstanceCreated) {
mongo.close();
}

View File

@@ -18,7 +18,7 @@ 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.data.mongodb.core.query.Collation;
import org.springframework.util.Assert;
import com.mongodb.DBObject;
@@ -110,6 +110,16 @@ public class AggregationOptions {
return new AggregationOptions(allowDiskUse, explain, cursor, collation);
}
/**
* Obtain a new {@link Builder} for constructing {@link AggregationOptions}.
*
* @return never {@literal null}.
* @since 2.0
*/
public static Builder builder() {
return new Builder();
}
/**
* Enables writing to temporary files. When set to true, aggregation stages can write data to the _tmp subdirectory in
* the dbPath directory.

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.
@@ -49,9 +49,10 @@ import org.springframework.util.ObjectUtils;
/**
* Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression.
*
*
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
class SpelExpressionTransformer implements AggregationExpressionTransformer {
@@ -84,7 +85,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
* {@link AggregationOperationContext} {@code context}.
* <p>
* Exposes the given @{code params} as <code>[0] ... [n]</code>.
*
*
* @param expression must not be {@literal null}
* @param context must not be {@literal null}
* @param params must not be {@literal null}
@@ -114,7 +115,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* Returns an appropriate {@link ExpressionNodeConversion} for the given {@code node}. Throws an
* {@link IllegalArgumentException} if no conversion could be found.
*
*
* @param node
* @return the appropriate {@link ExpressionNodeConversion} for the given {@link ExpressionNode}.
*/
@@ -133,7 +134,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* Abstract base class for {@link SpelNode} to (Db)-object conversions.
*
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
@@ -145,7 +146,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* Creates a new {@link ExpressionNodeConversion}.
*
*
* @param transformer must not be {@literal null}.
*/
@SuppressWarnings("unchecked")
@@ -161,7 +162,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* Returns whether the current conversion supports the given {@link ExpressionNode}. By default we will match the
* node type against the genric type the subclass types the type parameter to.
*
*
* @param node will never be {@literal null}.
* @return true if {@literal this} conversion can be applied to the given {@code node}.
*/
@@ -171,7 +172,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* Triggers the transformation for the given {@link ExpressionNode} and the given current context.
*
*
* @param node must not be {@literal null}.
* @param context must not be {@literal null}.
* @return
@@ -187,7 +188,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* Triggers the transformation with the given new {@link ExpressionNode}, new parent node, the current operation and
* the previous context.
*
*
* @param node must not be {@literal null}.
* @param parent
* @param operation
@@ -204,7 +205,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
context.getAggregationContext()));
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#transform(org.springframework.data.mongodb.core.aggregation.AggregationExpressionTransformer.AggregationExpressionTransformationContext)
*/
@@ -215,7 +216,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* Performs the actual conversion from {@link SpelNode} to the corresponding representation for MongoDB.
*
*
* @param context
* @return
*/
@@ -224,7 +225,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* A {@link ExpressionNodeConversion} that converts arithmetic operations.
*
*
* @author Thomas Darimont
*/
private static class OperatorNodeConversion extends ExpressionNodeConversion<OperatorNode> {
@@ -233,7 +234,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
super(transformer);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@@ -258,8 +259,10 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
return convertUnaryMinusOp(context, leftResult);
}
// we deliberately ignore the RHS result
transform(currentNode.getRight(), currentNode, operationObject, context);
if (!currentNode.isUnaryOperator()) {
// we deliberately ignore the RHS result
transform(currentNode.getRight(), currentNode, operationObject, context);
}
return operationObject;
}
@@ -299,7 +302,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
return result;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#supports(java.lang.Class)
*/
@@ -311,7 +314,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* A {@link ExpressionNodeConversion} that converts indexed expressions.
*
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
@@ -321,7 +324,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
super(transformer);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@@ -330,7 +333,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
return context.addToPreviousOrReturn(context.getCurrentNode().getValue());
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
*/
@@ -342,7 +345,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* A {@link ExpressionNodeConversion} that converts in-line list expressions.
*
*
* @author Thomas Darimont
*/
private static class InlineListNodeConversion extends ExpressionNodeConversion<ExpressionNode> {
@@ -351,7 +354,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
super(transformer);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@@ -368,7 +371,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
return transform(currentNode.getChild(0), currentNode, null, context);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
*/
@@ -380,7 +383,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* A {@link ExpressionNodeConversion} that converts property or field reference expressions.
*
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
@@ -401,7 +404,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
return context.addToPreviousOrReturn(fieldReference);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
*/
@@ -413,7 +416,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* A {@link ExpressionNodeConversion} that converts literal expressions.
*
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
@@ -423,7 +426,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
super(transformer);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@@ -448,7 +451,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
return value;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#supports(org.springframework.expression.spel.SpelNode)
*/
@@ -460,7 +463,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* A {@link ExpressionNodeConversion} that converts method reference expressions.
*
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
@@ -470,7 +473,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
super(transformer);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@@ -489,7 +492,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
Document dbo = new Document();
int i = 0;
for(ExpressionNode child : node) {
for (ExpressionNode child : node) {
dbo.put(methodReference.getArgumentMap()[i++], transform(child, context));
}
args = dbo;
@@ -510,7 +513,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
/**
* A {@link ExpressionNodeConversion} that converts method compound expressions.
*
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
@@ -520,7 +523,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
super(transformer);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
*/
@@ -537,7 +540,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
return context.addToPreviousOrReturn(currentNode.getValue());
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
*/

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.
@@ -15,10 +15,9 @@
*/
package org.springframework.data.mongodb.core.convert;
import org.bson.Document;
import java.util.List;
import java.util.Optional;
import org.bson.Document;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
@@ -27,7 +26,7 @@ import com.mongodb.DBRef;
/**
* Used to resolve associations annotated with {@link org.springframework.data.mongodb.core.mapping.DBRef}.
*
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
@@ -40,19 +39,19 @@ public interface DbRefResolver {
* Resolves the given {@link DBRef} into an object of the given {@link MongoPersistentProperty}'s type. The method
* might return a proxy object for the {@link DBRef} or resolve it immediately. In both cases the
* {@link DbRefResolverCallback} will be used to obtain the actual backing object.
*
*
* @param property will never be {@literal null}.
* @param dbref the {@link DBRef} to resolve.
* @param callback will never be {@literal null}.
* @return
*/
Optional<Object> resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
DbRefProxyHandler proxyHandler);
/**
* Creates a {@link DBRef} instance for the given {@link org.springframework.data.mongodb.core.mapping.DBRef}
* annotation, {@link MongoPersistentEntity} and id.
*
*
* @param annotation will never be {@literal null}.
* @param entity will never be {@literal null}.
* @param id will never be {@literal null}.
@@ -63,7 +62,7 @@ public interface DbRefResolver {
/**
* Actually loads the {@link DBRef} from the datasource.
*
*
* @param dbRef must not be {@literal null}.
* @return
* @since 1.7

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.
@@ -29,6 +29,7 @@ import com.mongodb.DBRef;
/**
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
*/
class DefaultDbRefProxyHandler implements DbRefProxyHandler {
@@ -50,7 +51,7 @@ class DefaultDbRefProxyHandler implements DbRefProxyHandler {
this.resolver = resolver;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.DbRefProxyHandler#populateId(com.mongodb.DBRef, java.lang.Object)
*/
@@ -62,8 +63,7 @@ class DefaultDbRefProxyHandler implements DbRefProxyHandler {
}
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(property);
MongoPersistentProperty idProperty = entity.getIdProperty()
.orElseThrow(() -> new IllegalStateException("Couldn't find identifier property!"));
MongoPersistentProperty idProperty = entity.getRequiredIdProperty();
if (idProperty.usePropertyAccess()) {
return proxy;

View File

@@ -26,7 +26,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@@ -55,7 +54,7 @@ import com.mongodb.client.model.Filters;
/**
* A {@link DbRefResolver} that resolves {@link org.springframework.data.mongodb.core.mapping.DBRef}s by delegating to a
* {@link DbRefResolverCallback} than is able to generate lazy loading proxies.
*
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
@@ -70,7 +69,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Creates a new {@link DefaultDbRefResolver} with the given {@link MongoDbFactory}.
*
*
* @param mongoDbFactory must not be {@literal null}.
*/
public DefaultDbRefResolver(MongoDbFactory mongoDbFactory) {
@@ -87,20 +86,20 @@ public class DefaultDbRefResolver implements DbRefResolver {
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#resolveDbRef(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.convert.DbRefResolverCallback)
*/
@Override
public Optional<Object> resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
DbRefProxyHandler handler) {
Assert.notNull(property, "Property must not be null!");
Assert.notNull(callback, "Callback must not be null!");
if (isLazyDbRef(property)) {
return Optional.of(createLazyLoadingProxy(property, dbref, callback, handler));
return createLazyLoadingProxy(property, dbref, callback, handler);
}
return Optional.ofNullable(callback.resolve(property));
return callback.resolve(property);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#created(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.Object)
*/
@@ -165,7 +164,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Creates a proxy for the given {@link MongoPersistentProperty} using the given {@link DbRefResolverCallback} to
* eventually resolve the value of the property.
*
*
* @param property must not be {@literal null}.
* @param dbref can be {@literal null}.
* @param callback must not be {@literal null}.
@@ -200,7 +199,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Returns the CGLib enhanced type for the given source type.
*
*
* @param type
* @return
*/
@@ -216,7 +215,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Returns whether the property shall be resolved lazily.
*
*
* @param property must not be {@literal null}.
* @return
*/
@@ -228,7 +227,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
* A {@link MethodInterceptor} that is used within a lazy loading proxy. The property resolving is delegated to a
* {@link DbRefResolverCallback}. The resolving process is triggered by a method invocation on the proxy and is
* guaranteed to be performed only once.
*
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
@@ -259,7 +258,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Creates a new {@link LazyLoadingInterceptor} for the given {@link MongoPersistentProperty},
* {@link PersistenceExceptionTranslator} and {@link DbRefResolverCallback}.
*
*
* @param property must not be {@literal null}.
* @param dbref can be {@literal null}.
* @param callback must not be {@literal null}.
@@ -286,7 +285,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], org.springframework.cglib.proxy.MethodProxy)
*/
@@ -327,12 +326,14 @@ public class DefaultDbRefResolver implements DbRefResolver {
return null;
}
ReflectionUtils.makeAccessible(method);
return method.invoke(target, args);
}
/**
* Returns a to string representation for the given {@code proxy}.
*
*
* @param proxy
* @return
*/
@@ -353,7 +354,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Returns the hashcode for the given {@code proxy}.
*
*
* @param proxy
* @return
*/
@@ -363,7 +364,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Performs an equality check for the given {@code proxy}.
*
*
* @param proxy
* @param that
* @return
@@ -383,7 +384,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Will trigger the resolution if the proxy is not resolved already or return a previously resolved result.
*
*
* @return
*/
private Object ensureResolved() {
@@ -398,7 +399,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Callback method for serialization.
*
*
* @param out
* @throws IOException
*/
@@ -410,7 +411,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Callback method for deserialization.
*
*
* @param in
* @throws IOException
*/
@@ -426,7 +427,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
/**
* Resolves the proxy into its backing object.
*
*
* @return
*/
private synchronized Object resolve() {

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.
@@ -22,9 +22,10 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
/**
* Default implementation of {@link DbRefResolverCallback}.
*
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
*/
class DefaultDbRefResolverCallback implements DbRefResolverCallback {
@@ -36,7 +37,7 @@ class DefaultDbRefResolverCallback implements DbRefResolverCallback {
/**
* Creates a new {@link DefaultDbRefResolverCallback} using the given {@link Document}, {@link ObjectPath},
* {@link ValueResolver} and {@link SpELExpressionEvaluator}.
*
*
* @param surroundingObject must not be {@literal null}.
* @param path must not be {@literal null}.
* @param evaluator must not be {@literal null}.
@@ -51,12 +52,12 @@ class DefaultDbRefResolverCallback implements DbRefResolverCallback {
this.evaluator = evaluator;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.DbRefResolverCallback#resolve(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty)
*/
@Override
public Object resolve(MongoPersistentProperty property) {
return resolver.getValueInternal(property, surroundingObject, evaluator, path).orElse(null);
return resolver.getValueInternal(property, surroundingObject, evaluator, path);
}
}

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.
@@ -32,17 +32,17 @@ import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.ObjectUtils;
import com.mongodb.BasicDBList;
import com.mongodb.DBObject;
import org.springframework.util.ObjectUtils;
/**
* Default implementation of {@link MongoTypeMapper} allowing configuration of the key to lookup and store type
* information in {@link Document}. The key defaults to {@link #DEFAULT_TYPE_KEY}. Actual type-to-{@link String}
* conversion and back is done in {@link #getTypeString(TypeInformation)} or {@link #getTypeInformation(String)}
* respectively.
*
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
@@ -94,7 +94,7 @@ public class DefaultMongoTypeMapper extends DefaultTypeMapper<Bson> implements M
return typeKey == null ? false : typeKey.equals(key);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.MongoTypeMapper#writeTypeRestrictions(java.util.Set)
*/
@@ -111,26 +111,26 @@ public class DefaultMongoTypeMapper extends DefaultTypeMapper<Bson> implements M
Alias typeAlias = getAliasFor(ClassTypeInformation.from(restrictedType));
if (typeAlias != null && !ObjectUtils.nullSafeEquals(Alias.NONE, typeAlias) && typeAlias.getValue().isPresent()) {
restrictedMappedTypes.add(typeAlias.getValue().get());
if (typeAlias != null && !ObjectUtils.nullSafeEquals(Alias.NONE, typeAlias) && typeAlias.isPresent()) {
restrictedMappedTypes.add(typeAlias.getValue());
}
}
accessor.writeTypeTo(result, new Document("$in", restrictedMappedTypes));
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.convert.DefaultTypeMapper#getFallbackTypeFor(java.lang.Object)
*/
@Override
protected Optional<TypeInformation<?>> getFallbackTypeFor(Bson source) {
return Optional.of(source instanceof BasicDBList ? LIST_TYPE_INFO : MAP_TYPE_INFO);
protected TypeInformation<?> getFallbackTypeFor(Bson source) {
return source instanceof BasicDBList ? LIST_TYPE_INFO : MAP_TYPE_INFO;
}
/**
* {@link TypeAliasAccessor} to store aliases in a {@link Document}.
*
*
* @author Oliver Gierke
*/
public static final class DocumentTypeAliasAccessor implements TypeAliasAccessor<Bson> {
@@ -152,9 +152,9 @@ public class DefaultMongoTypeMapper extends DefaultTypeMapper<Bson> implements M
}
if (source instanceof Document) {
return Alias.ofOptional(Optional.ofNullable(((Document) source).get(typeKey)));
return Alias.ofNullable(((Document) source).get(typeKey));
} else if (source instanceof DBObject) {
return Alias.ofOptional(Optional.ofNullable(((DBObject) source).get(typeKey)));
return Alias.ofNullable(((DBObject) source).get(typeKey));
}
throw new IllegalArgumentException("Cannot read alias from " + source.getClass());

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.
@@ -18,8 +18,6 @@ package org.springframework.data.mongodb.core.convert;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import org.bson.Document;
import org.bson.conversions.Bson;
@@ -34,9 +32,10 @@ import com.mongodb.DBObject;
* Wrapper value object for a {@link Document} to be able to access raw values by {@link MongoPersistentProperty}
* references. The accessors will transparently resolve nested document values that a {@link MongoPersistentProperty}
* might refer to through a path expression in field names.
*
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
*/
class DocumentAccessor {
@@ -44,7 +43,7 @@ class DocumentAccessor {
/**
* Creates a new {@link DocumentAccessor} for the given {@link Document}.
*
*
* @param document must be a {@link Document} effectively, must not be {@literal null}.
*/
public DocumentAccessor(Bson document) {
@@ -62,7 +61,7 @@ class DocumentAccessor {
* Puts the given value into the backing {@link Document} based on the coordinates defined through the given
* {@link MongoPersistentProperty}. By default this will be the plain field name. But field names might also consist
* of path traversals so we might need to create intermediate {@link BasicDocument}s.
*
*
* @param prop must not be {@literal null}.
* @param value
*/
@@ -91,20 +90,11 @@ class DocumentAccessor {
}
}
public void computeIfAbsent(MongoPersistentProperty prop, Supplier<Optional<Object>> supplier) {
if (hasValue(prop)) {
return;
}
supplier.get().ifPresent(it -> put(prop, it));
}
/**
* Returns the value the given {@link MongoPersistentProperty} refers to. By default this will be a direct field but
* the method will also transparently resolve nested values the {@link MongoPersistentProperty} might refer to through
* a path expression in the field name metadata.
*
*
* @param property must not be {@literal null}.
* @return
*/
@@ -161,7 +151,7 @@ class DocumentAccessor {
if (this.document instanceof Document) {
source = ((Document) this.document);
}else {
} else {
source = ((DBObject) this.document).toMap();
}
@@ -182,7 +172,7 @@ class DocumentAccessor {
/**
* Returns the given source object as map, i.e. {@link Document}s and maps as is or {@literal null} otherwise.
*
*
* @param source can be {@literal null}.
* @return
*/
@@ -207,7 +197,7 @@ class DocumentAccessor {
/**
* Returns the {@link Document} which either already exists in the given source under the given key, or creates a new
* nested one, registers it with the source and returns it.
*
*
* @param key must not be {@literal null} or empty.
* @param source must not be {@literal null}.
* @return

View File

@@ -15,8 +15,17 @@
*/
package org.springframework.data.mongodb.core.convert;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import org.bson.Document;
import org.bson.conversions.Bson;
@@ -30,14 +39,14 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.convert.TypeMapper;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
@@ -52,7 +61,6 @@ import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -82,9 +90,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
protected static final Logger LOGGER = LoggerFactory.getLogger(MappingMongoConverter.class);
protected final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
protected final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
protected final QueryMapper idMapper;
protected final DbRefResolver dbRefResolver;
protected final DefaultDbRefProxyHandler dbRefProxyHandler;
protected ApplicationContext applicationContext;
protected MongoTypeMapper typeMapper;
@@ -112,6 +120,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
this.idMapper = new QueryMapper(this);
this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE);
this.dbRefProxyHandler = new DefaultDbRefProxyHandler(spELContext, mappingContext, MappingMongoConverter.this);
}
/**
@@ -248,66 +257,89 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final Document bson, final ObjectPath path) {
final DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
ParameterValueProvider<MongoPersistentProperty> provider = getParameterProvider(entity, bson, evaluator, path);
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
S instance = instantiator.createInstance(entity, provider);
final PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance),
PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance),
conversionService);
final Optional<MongoPersistentProperty> idProperty = entity.getIdProperty();
final S result = instance;
MongoPersistentProperty idProperty = entity.getIdProperty();
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
// make sure id property is set before all other properties
Optional<Object> idValue = idProperty.filter(documentAccessor::hasValue).map(it -> {
Object idValue = null;
Optional<Object> value = getValueInternal(it, bson, evaluator, path);
accessor.setProperty(it, value);
if (idProperty != null && documentAccessor.hasValue(idProperty)) {
return value;
});
idValue = readIdValue(path, evaluator, idProperty, documentAccessor);
accessor.setProperty(idProperty, idValue);
}
final ObjectPath currentPath = path.push(result, entity,
idValue.isPresent() ? idProperty.map(it -> bson.get(it.getFieldName())).orElse(null) : null);
ObjectPath currentPath = path.push(instance, entity, idValue != null ? bson.get(idProperty.getFieldName()) : null);
// Set properties not already set in the constructor
entity.doWithProperties((PropertyHandler<MongoPersistentProperty>) prop -> {
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(documentAccessor, evaluator,
currentPath);
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(bson, currentPath, evaluator,
MappingMongoConverter.this);
readProperties(entity, accessor, idProperty, documentAccessor, valueProvider, callback);
return instance;
}
private Object readIdValue(ObjectPath path, DefaultSpELExpressionEvaluator evaluator,
MongoPersistentProperty idProperty, DocumentAccessor documentAccessor) {
String expression = idProperty.getSpelExpression();
Object resolvedValue = expression != null ? evaluator.evaluate(expression) : documentAccessor.get(idProperty);
return resolvedValue != null ? readValue(resolvedValue, idProperty.getTypeInformation(), path) : null;
}
private void readProperties(MongoPersistentEntity<?> entity, PersistentPropertyAccessor accessor,
MongoPersistentProperty idProperty, DocumentAccessor documentAccessor,
MongoDbPropertyValueProvider valueProvider, DbRefResolverCallback callback) {
for (MongoPersistentProperty prop : entity) {
if(prop.isAssociation() && !entity.isConstructorArgument(prop)) {
readAssociation(prop.getAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback );
continue;
}
// we skip the id property since it was already set
if (idProperty != null && idProperty.equals(prop)) {
return;
continue;
}
if (entity.isConstructorArgument(prop) || !documentAccessor.hasValue(prop)) {
return;
continue;
}
accessor.setProperty(prop, getValueInternal(prop, bson, evaluator, currentPath));
});
if(prop.isAssociation()) {
readAssociation(prop.getAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback );
continue;
}
// Handle associations
entity.doWithAssociations((AssociationHandler<MongoPersistentProperty>) association -> {
accessor.setProperty(prop, valueProvider.getPropertyValue(prop));
}
}
final MongoPersistentProperty property = association.getInverse();
private void readAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor accessor,
DocumentAccessor documentAccessor, DbRefProxyHandler handler, DbRefResolverCallback callback) {
MongoPersistentProperty property = association.getInverse();
Object value = documentAccessor.get(property);
if (value == null || entity.isConstructorArgument(property)) {
if (value == null) {
return;
}
DBRef dbref = value instanceof DBRef ? (DBRef) value : null;
DbRefProxyHandler handler = new DefaultDbRefProxyHandler(spELContext, mappingContext, MappingMongoConverter.this);
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(bson, currentPath, evaluator,
MappingMongoConverter.this);
accessor.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, callback, handler));
});
return result;
}
/*
@@ -348,8 +380,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Object target = obj instanceof LazyLoadingProxy ? ((LazyLoadingProxy) obj).getTarget() : obj;
writeInternal(target, bson, Optional.of(type));
if (asMap(bson).containsKey("_is") && asMap(bson).get("_id") == null) {
writeInternal(target, bson, type);
if (asMap(bson).containsKey("_id") && asMap(bson).get("_id") == null) {
removeFromMap(bson, "_id");
}
@@ -366,7 +398,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param bson
*/
@SuppressWarnings("unchecked")
protected void writeInternal(final Object obj, final Bson bson, final Optional<TypeInformation<?>> typeHint) {
protected void writeInternal(final Object obj, final Bson bson, final TypeInformation<?> typeHint) {
if (null == obj) {
return;
@@ -387,7 +419,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
if (Collection.class.isAssignableFrom(entityType)) {
writeCollectionInternal((Collection<?>) obj, Optional.of(ClassTypeInformation.LIST), (BasicDBList) bson);
writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (BasicDBList) bson);
return;
}
@@ -406,46 +438,65 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
throw new MappingException("No mapping metadata found for entity of type " + obj.getClass().getName());
}
final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(obj);
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(obj);
DocumentAccessor dbObjectAccessor = new DocumentAccessor(bson);
Optional<MongoPersistentProperty> idProperty = entity.getIdProperty();
idProperty.ifPresent(
prop -> dbObjectAccessor.computeIfAbsent(prop, () -> idMapper.convertId(accessor.getProperty(prop))));
MongoPersistentProperty idProperty = entity.getIdProperty();
if (idProperty != null && !dbObjectAccessor.hasValue(idProperty)) {
Object value = idMapper.convertId(accessor.getProperty(idProperty));
if (value != null) {
dbObjectAccessor.put(idProperty, value);
}
}
writeProperties(bson, entity, accessor, dbObjectAccessor, idProperty);
}
private void writeProperties(Bson bson, MongoPersistentEntity<?> entity, PersistentPropertyAccessor accessor,
DocumentAccessor dbObjectAccessor, MongoPersistentProperty idProperty) {
// Write the properties
entity.doWithProperties((PropertyHandler<MongoPersistentProperty>) prop -> {
for (MongoPersistentProperty prop : entity) {
if (idProperty.map(it -> it.equals(prop)).orElse(false) || !prop.isWritable()) {
return;
if (prop.equals(idProperty) || !prop.isWritable()) {
continue;
}
if(prop.isAssociation()) {
writeAssociation(prop.getAssociation(), accessor, dbObjectAccessor);
continue;
}
accessor.getProperty(prop).ifPresent(it -> {
if (!conversions.isSimpleType(it.getClass())) {
Object value = accessor.getProperty(prop);
writePropertyInternal(it, bson, prop);
} else {
writeSimpleInternal(it, bson, prop);
}
});
});
if (value == null) {
continue;
}
entity.doWithAssociations((AssociationHandler<MongoPersistentProperty>) association -> {
if (!conversions.isSimpleType(value.getClass())) {
writePropertyInternal(value, dbObjectAccessor, prop);
} else {
writeSimpleInternal(value, bson, prop);
}
}
}
private void writeAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor accessor,
DocumentAccessor dbObjectAccessor) {
MongoPersistentProperty inverseProp = association.getInverse();
accessor.getProperty(inverseProp).ifPresent(it -> writePropertyInternal(it, bson, inverseProp));
});
writePropertyInternal(accessor.getProperty(inverseProp), dbObjectAccessor, inverseProp);
}
@SuppressWarnings({ "unchecked" })
protected void writePropertyInternal(Object obj, Bson bson, MongoPersistentProperty prop) {
protected void writePropertyInternal(Object obj, DocumentAccessor accessor, MongoPersistentProperty prop) {
if (obj == null) {
return;
}
DocumentAccessor accessor = new DocumentAccessor(bson);
TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
TypeInformation<?> type = prop.getTypeInformation();
@@ -497,14 +548,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return;
}
Object existingValue = accessor.get(prop);
Document document = existingValue instanceof Document ? (Document) existingValue : new Document();
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass())
? mappingContext.getRequiredPersistentEntity(obj.getClass()) : mappingContext.getRequiredPersistentEntity(type);
Object existingValue = accessor.get(prop);
Document document = existingValue instanceof Document ? (Document) existingValue : new Document();
writeInternal(obj, document, entity);
addCustomTypeKeyIfNecessary(Optional.of(ClassTypeInformation.from(prop.getRawType())), obj, document);
addCustomTypeKeyIfNecessary(ClassTypeInformation.from(prop.getRawType()), obj, document);
accessor.put(prop, document);
}
@@ -539,7 +590,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
protected List<Object> createCollection(Collection<?> collection, MongoPersistentProperty property) {
if (!property.isDbReference()) {
return writeCollectionInternal(collection, Optional.of(property.getTypeInformation()), new BasicDBList());
return writeCollectionInternal(collection, property.getTypeInformation(), new BasicDBList());
}
List<Object> dbList = new ArrayList<>(collection.size());
@@ -601,10 +652,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param sink the {@link BasicDBList} to write to.
* @return
*/
private BasicDBList writeCollectionInternal(Collection<?> source, Optional<TypeInformation<?>> type,
BasicDBList sink) {
private BasicDBList writeCollectionInternal(Collection<?> source, TypeInformation<?> type, BasicDBList sink) {
Optional<TypeInformation<?>> componentType = type.flatMap(TypeInformation::getComponentType);
TypeInformation<?> componentType = null;
if (type != null) {
componentType = type.getComponentType();
}
for (Object element : source) {
@@ -649,8 +703,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
writeCollectionInternal(asCollection(val), propertyType.getMapValueType(), new BasicDBList()));
} else {
Document document = new Document();
Optional<TypeInformation<?>> valueTypeInfo = propertyType.isMap() ? propertyType.getMapValueType()
: Optional.of(ClassTypeInformation.OBJECT);
TypeInformation<?> valueTypeInfo = propertyType.isMap() ? propertyType.getMapValueType()
: ClassTypeInformation.OBJECT;
writeInternal(val, document, valueTypeInfo);
addToMap(bson, simpleKey, document);
}
@@ -736,10 +790,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param value must not be {@literal null}.
* @param bson must not be {@literal null}.
*/
protected void addCustomTypeKeyIfNecessary(Optional<TypeInformation<?>> type, Object value, Bson bson) {
protected void addCustomTypeKeyIfNecessary(TypeInformation<?> type, Object value, Bson bson) {
Optional<Class<?>> actualType = type.map(TypeInformation::getActualType).map(TypeInformation::getType);
Class<?> reference = actualType.orElse(Object.class);
Class<?> reference = type != null ? type.getActualType().getType() : Object.class;
Class<?> valueType = ClassUtils.getUserClass(value.getClass());
boolean notTheSameClass = !valueType.equals(reference);
@@ -779,18 +832,19 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(value.getClass());
return customTarget.map(it -> (Object) conversionService.convert(value, it)).orElseGet(() -> {
if (customTarget.isPresent()) {
return conversionService.convert(value, customTarget.get());
}
if (ObjectUtils.isArray(value)) {
if (ObjectUtils.isArray(value)) {
if (value instanceof byte[]) {
return value;
}
return asCollection(value);
if (value instanceof byte[]) {
return value;
}
return asCollection(value);
}
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
});
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
}
/**
@@ -827,30 +881,30 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return (DBRef) target;
}
Optional<? extends MongoPersistentEntity<?>> targetEntity = mappingContext.getPersistentEntity(target.getClass());
targetEntity = targetEntity.isPresent() ? targetEntity : mappingContext.getPersistentEntity(property);
MongoPersistentEntity<?> targetEntity = mappingContext.getPersistentEntity(target.getClass());
targetEntity = targetEntity != null ? targetEntity : mappingContext.getPersistentEntity(property);
if (null == targetEntity) {
throw new MappingException("No mapping metadata found for " + target.getClass());
}
MongoPersistentEntity<?> entity = targetEntity
.orElseThrow(() -> new MappingException("No mapping metadata found for " + target.getClass()));
MongoPersistentEntity<?> entity = targetEntity;
Optional<MongoPersistentProperty> idProperty = entity.getIdProperty();
MongoPersistentProperty idProperty = entity.getIdProperty();
return idProperty.map(it -> {
if (idProperty != null) {
Object id = target.getClass().equals(it.getType()) ? target : entity.getPropertyAccessor(target).getProperty(it);
Object id = target.getClass().equals(idProperty.getType()) ? target
: entity.getPropertyAccessor(target).getProperty(idProperty);
if (null == id) {
throw new MappingException("Cannot create a reference to an object with a NULL id.");
}
return dbRefResolver.createDbRef(property == null ? null : property.getDBRef(), entity,
idMapper.convertId(id instanceof Optional ? (Optional) id : Optional.ofNullable(id)).orElse(null));
return dbRefResolver.createDbRef(property == null ? null : property.getDBRef(), entity, idMapper.convertId(id));
}
}).orElseThrow(() -> new MappingException("No id property found on class " + entity.getType()));
throw new MappingException("No id property found on class " + entity.getType());
}
/*
@@ -858,7 +912,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @see org.springframework.data.mongodb.core.convert.ValueResolver#getValueInternal(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, com.mongodb.Document, org.springframework.data.mapping.model.SpELExpressionEvaluator, java.lang.Object)
*/
@Override
public Optional<Object> getValueInternal(MongoPersistentProperty prop, Bson bson, SpELExpressionEvaluator evaluator,
public Object getValueInternal(MongoPersistentProperty prop, Bson bson, SpELExpressionEvaluator evaluator,
ObjectPath path) {
return new MongoDbPropertyValueProvider(bson, evaluator, path).getPropertyValue(prop);
}
@@ -879,7 +933,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Class<?> collectionType = targetType.getType();
TypeInformation<?> componentType = targetType.getComponentType().orElse(ClassTypeInformation.OBJECT);
TypeInformation<?> componentType = targetType.getComponentType() != null ? targetType.getComponentType()
: ClassTypeInformation.OBJECT;
Class<?> rawComponentType = componentType.getType();
collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class;
@@ -941,15 +996,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Class<?> mapType = typeMapper.readType(bson, type).getType();
Optional<TypeInformation<?>> valueType = type.getMapValueType();
Class<?> rawKeyType = type.getComponentType().map(TypeInformation::getType).orElse(null);
Class<?> rawValueType = type.getMapValueType().map(TypeInformation::getType).orElse(null);
TypeInformation<?> keyType = type.getComponentType();
TypeInformation<?> valueType = type.getMapValueType();
Class<?> rawKeyType = keyType != null ? keyType.getType() : null;
Class<?> rawValueType = valueType != null ? valueType.getType() : null;
Map<String, Object> sourceMap = asMap(bson);
Map<Object, Object> map = CollectionFactory.createMap(mapType, rawKeyType, sourceMap.keySet().size());
if (!DBRef.class.equals(rawValueType) && isCollectionOfDbRefWhereBulkFetchIsPossible(sourceMap.values())) {
bulkReadAndConvertDBRefMapIntoTarget(valueType.orElse(null), rawValueType, sourceMap, map);
bulkReadAndConvertDBRefMapIntoTarget(valueType, rawValueType, sourceMap, map);
return map;
}
@@ -961,12 +1018,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Object key = potentiallyUnescapeMapKey(entry.getKey());
if (rawKeyType != null) {
if (rawKeyType != null && !rawKeyType.isAssignableFrom(key.getClass())) {
key = conversionService.convert(key, rawKeyType);
}
Object value = entry.getValue();
TypeInformation<?> defaultedValueType = valueType.orElse(ClassTypeInformation.OBJECT);
TypeInformation<?> defaultedValueType = valueType != null ? valueType : ClassTypeInformation.OBJECT;
if (value instanceof Document) {
map.put(key, read(defaultedValueType, (Document) value, path));
@@ -976,7 +1033,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
map.put(key, DBRef.class.equals(rawValueType) ? value
: readAndConvertDBRef((DBRef) value, defaultedValueType, ObjectPath.ROOT, rawValueType));
} else if (value instanceof List) {
map.put(key, readCollectionOrArray(valueType.orElse(ClassTypeInformation.LIST), (List) value, path));
map.put(key,
readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST, (List) value, path));
} else {
map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType));
}
@@ -1098,7 +1156,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (obj instanceof Map) {
Map<Object, Object> converted = new LinkedHashMap<>(((Map) obj).size(), 1);
Document result = new Document();
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) obj).entrySet()) {
@@ -1199,7 +1256,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
*
* @author Oliver Gierke
*/
private class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {
class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {
private final DocumentAccessor source;
private final SpELExpressionEvaluator evaluator;
@@ -1224,17 +1281,39 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
this.path = path;
}
/**
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
* {@link ObjectPath}.
*
* @param accessor must not be {@literal null}.
* @param evaluator must not be {@literal null}.
* @param path can be {@literal null}.
*/
public MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEvaluator evaluator, ObjectPath path) {
Assert.notNull(accessor, "DocumentAccessor must no be null!");
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null!");
Assert.notNull(path, "ObjectPath must not be null!");
this.source = accessor;
this.evaluator = evaluator;
this.path = path;
}
/*
* (non-Javadoc)
* @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
*/
public <T> Optional<T> getPropertyValue(MongoPersistentProperty property) {
return Optional
public <T> T getPropertyValue(MongoPersistentProperty property) {
.ofNullable(property.getSpelExpression()//
.map(evaluator::evaluate)//
.orElseGet(() -> source.get(property)))//
.map(it -> readValue(it, property.getTypeInformation(), path));
String expression = property.getSpelExpression();
Object value = expression != null ? evaluator.evaluate(expression) : source.get(property);
if (value == null) {
return null;
}
return readValue(value, property.getTypeInformation(), path);
}
}
@@ -1275,7 +1354,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
@SuppressWarnings("unchecked")
private <T> T readValue(Object value, TypeInformation<?> type, ObjectPath path) {
<T> T readValue(Object value, TypeInformation<?> type, ObjectPath path) {
Class<?> rawType = type.getType();
@@ -1301,8 +1380,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return (T) dbref;
}
Object object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getCollectionName());
return (T) (object != null ? object : readAndConvertDBRef(dbref, type, path, rawType));
T object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getCollectionName(), (Class<T>) rawType);
return object != null ? object : readAndConvertDBRef(dbref, type, path, rawType);
}
private <T> T readAndConvertDBRef(DBRef dbref, TypeInformation<?> type, ObjectPath path, final Class<?> rawType) {

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.
@@ -17,7 +17,6 @@ package org.springframework.data.mongodb.core.convert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -38,10 +37,9 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.MongoRegexCreator;
import org.springframework.data.mongodb.core.query.MongoRegexCreator.MatchMode;
import org.springframework.data.mongodb.core.query.SerializationUtils;
import org.springframework.data.repository.core.support.ExampleMatcherAccessor;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.data.util.Optionals;
import org.springframework.data.support.ExampleMatcherAccessor;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -50,24 +48,18 @@ import org.springframework.util.StringUtils;
/**
* @author Christoph Strobl
* @author Mark Paluch
* @author Jens Schauder
* @since 1.8
*/
public class MongoExampleMapper {
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final MongoConverter converter;
private final Map<StringMatcher, Type> stringMatcherPartMapping = new HashMap<StringMatcher, Type>();
public MongoExampleMapper(MongoConverter converter) {
this.converter = converter;
this.mappingContext = converter.getMappingContext();
stringMatcherPartMapping.put(StringMatcher.EXACT, Type.SIMPLE_PROPERTY);
stringMatcherPartMapping.put(StringMatcher.CONTAINING, Type.CONTAINING);
stringMatcherPartMapping.put(StringMatcher.STARTING, Type.STARTING_WITH);
stringMatcherPartMapping.put(StringMatcher.ENDING, Type.ENDING_WITH);
stringMatcherPartMapping.put(StringMatcher.REGEX, Type.REGEX);
}
/**
@@ -99,8 +91,12 @@ public class MongoExampleMapper {
Document reference = (Document) converter.convertToMongoType(example.getProbe());
if(entity.getIdProperty().isPresent() && !entity.getIdentifierAccessor(example.getProbe()).getIdentifier().isPresent()) {
reference.remove(entity.getIdProperty().get().getFieldName());
if (entity.getIdProperty() != null) {
Object identifier = entity.getIdentifierAccessor(example.getProbe()).getIdentifier();
if (identifier == null) {
reference.remove(entity.getIdProperty().getFieldName());
}
}
ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher());
@@ -153,7 +149,7 @@ public class MongoExampleMapper {
while (parts.hasNext()) {
String part = parts.next();
MongoPersistentProperty prop = entity.getPersistentProperty(part).orElse(null);
MongoPersistentProperty prop = entity.getPersistentProperty(part);
if (prop == null) {
@@ -249,7 +245,7 @@ public class MongoExampleMapper {
Document document = new Document();
if (ObjectUtils.nullSafeEquals(StringMatcher.DEFAULT, stringMatcher)) {
if (StringMatcher.DEFAULT == stringMatcher) {
if (ignoreCase) {
document.put("$regex", Pattern.quote((String) entry.getValue()));
@@ -257,8 +253,8 @@ public class MongoExampleMapper {
}
} else {
Type type = stringMatcherPartMapping.get(stringMatcher);
String expression = MongoRegexCreator.INSTANCE.toRegularExpression((String) entry.getValue(), type);
String expression = MongoRegexCreator.INSTANCE.toRegularExpression((String) entry.getValue(),
toMatchMode(stringMatcher));
document.put("$regex", expression);
entry.setValue(document);
}
@@ -267,4 +263,29 @@ public class MongoExampleMapper {
document.put("$options", "i");
}
}
/**
* Return the {@link MatchMode} for the given {@link StringMatcher}.
*
* @param matcher must not be {@literal null}.
* @return
*/
private static MatchMode toMatchMode(StringMatcher matcher) {
switch (matcher) {
case CONTAINING:
return MatchMode.CONTAINING;
case STARTING:
return MatchMode.STARTING_WITH;
case ENDING:
return MatchMode.ENDING_WITH;
case EXACT:
return MatchMode.EXACT;
case REGEX:
return MatchMode.REGEX;
case DEFAULT:
default:
return MatchMode.DEFAULT;
}
}
}

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,13 +15,15 @@
*/
package org.springframework.data.mongodb.core.convert;
import lombok.Value;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -29,36 +31,39 @@ import org.springframework.util.StringUtils;
* when resolving more nested objects. This allows to avoid re-resolving object instances that are logically equivalent
* to already resolved ones.
* <p>
* An immutable ordered set of target objects for {@link Document} to {@link Object} conversions. Object paths can be
* constructed by the {@link #toObjectPath(Object)} method and extended via {@link #push(Object)}.
*
* An immutable ordered set of target objects for {@link org.bson.Document} to {@link Object} conversions. Object paths
* can be extended via {@link #push(Object, MongoPersistentEntity, Object)}.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.6
*/
class ObjectPath {
public static final ObjectPath ROOT = new ObjectPath();
static final ObjectPath ROOT = new ObjectPath();
private final List<ObjectPathItem> items;
private final ObjectPathItem[] items;
private ObjectPath() {
this.items = Collections.emptyList();
this.items = new ObjectPathItem[0];
}
/**
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} by adding the provided
* {@link ObjectPathItem} to it.
*
*
* @param parent can be {@literal null}.
* @param item
*/
private ObjectPath(ObjectPath parent, ObjectPath.ObjectPathItem item) {
List<ObjectPath.ObjectPathItem> items = new ArrayList<ObjectPath.ObjectPathItem>(parent.items);
items.add(item);
ObjectPathItem[] items = new ObjectPathItem[parent.items.length + 1];
System.arraycopy(parent.items, 0, items, 0, parent.items.length);
items[parent.items.length] = item;
this.items = Collections.unmodifiableList(items);
this.items = items;
}
/**
@@ -67,9 +72,9 @@ class ObjectPath {
* @param object must not be {@literal null}.
* @param entity must not be {@literal null}.
* @param id must not be {@literal null}.
* @return
* @return new instance of {@link ObjectPath}.
*/
public ObjectPath push(Object object, MongoPersistentEntity<?> entity, Object id) {
ObjectPath push(Object object, MongoPersistentEntity<?> entity, Object id) {
Assert.notNull(object, "Object must not be null!");
Assert.notNull(entity, "MongoPersistentEntity must not be null!");
@@ -79,14 +84,16 @@ class ObjectPath {
}
/**
* Returns the object with the given id and stored in the given collection if it's contained in the {@link ObjectPath}
* .
*
* Returns the object with the given id and stored in the given collection if it's contained in the
* {@link ObjectPath}.
*
* @param id must not be {@literal null}.
* @param collection must not be {@literal null} or empty.
* @return
* @deprecated use {@link #getPathItem(Object, String, Class)}.
*/
public Object getPathItem(Object id, String collection) {
@Deprecated
Object getPathItem(Object id, String collection) {
Assert.notNull(id, "Id must not be null!");
Assert.hasText(collection, "Collection name must not be null!");
@@ -95,11 +102,7 @@ class ObjectPath {
Object object = item.getObject();
if (object == null) {
continue;
}
if (item.getIdValue() == null) {
if (object == null || item.getIdValue() == null) {
continue;
}
@@ -112,29 +115,62 @@ class ObjectPath {
}
/**
* Returns the current object of the {@link ObjectPath} or {@literal null} if the path is empty.
*
* @return
* Get the object with given {@literal id}, stored in the {@literal collection} that is assignable to the given
* {@literal type} or {@literal null} if no match found.
*
* @param id must not be {@literal null}.
* @param collection must not be {@literal null} or empty.
* @param type must not be {@literal null}.
* @return {@literal null} when no match found.
* @since 2.0
*/
public Optional<Object> getCurrentObject() {
return items.isEmpty() ? Optional.empty() : Optional.of(items.get(items.size() - 1).getObject());
<T> T getPathItem(Object id, String collection, Class<T> type) {
Assert.notNull(id, "Id must not be null!");
Assert.hasText(collection, "Collection name must not be null!");
Assert.notNull(type, "Type must not be null!");
for (ObjectPathItem item : items) {
Object object = item.getObject();
if (object == null || item.getIdValue() == null) {
continue;
}
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())
&& ClassUtils.isAssignable(type, object.getClass())) {
return type.cast(object);
}
}
return null;
}
/*
/**
* Returns the current object of the {@link ObjectPath} or {@literal null} if the path is empty.
*
* @return
*/
Object getCurrentObject() {
return items.length == 0 ? null : items[items.length - 1].getObject();
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
if (items.isEmpty()) {
if (items.length == 0) {
return "[empty]";
}
List<String> strings = new ArrayList<String>(items.size());
List<String> strings = new ArrayList<>(items.length);
for (ObjectPathItem item : items) {
strings.add(item.object.toString());
strings.add(ObjectUtils.nullSafeToString(item.object));
}
return StringUtils.collectionToDelimitedString(strings, " -> ");
@@ -142,40 +178,16 @@ class ObjectPath {
/**
* An item in an {@link ObjectPath}.
*
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Mark Paluch
*/
@Value
private static class ObjectPathItem {
private final Object object;
private final Object idValue;
private final String collection;
/**
* Creates a new {@link ObjectPathItem}.
*
* @param object
* @param idValue
* @param collection
*/
ObjectPathItem(Object object, Object idValue, String collection) {
this.object = object;
this.idValue = idValue;
this.collection = collection;
}
public Object getObject() {
return object;
}
public Object getIdValue() {
return idValue;
}
public String getCollection() {
return collection;
}
Object object;
Object idValue;
String collection;
}
}

View File

@@ -39,7 +39,7 @@ import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter.NestedDocument;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
@@ -122,7 +122,7 @@ public class QueryMapper {
if (Query.isRestrictedTypeKey(key)) {
@SuppressWarnings("unchecked")
Set<Class<?>> restrictedTypes = (Set<Class<?>>) BsonUtils.get(query, key);
Set<Class<?>> restrictedTypes = BsonUtils.get(query, key);
this.converter.getTypeMapper().writeTypeRestrictions(result, restrictedTypes);
continue;
@@ -318,11 +318,11 @@ public class QueryMapper {
String inKey = valueDbo.containsField("$in") ? "$in" : "$nin";
List<Object> ids = new ArrayList<Object>();
for (Object id : (Iterable<?>) valueDbo.get(inKey)) {
ids.add(convertId(id).get());
ids.add(convertId(id));
}
resultDbo.put(inKey, ids);
} else if (valueDbo.containsField("$ne")) {
resultDbo.put("$ne", convertId(valueDbo.get("$ne")).get());
resultDbo.put("$ne", convertId(valueDbo.get("$ne")));
} else {
return getMappedObject(resultDbo, Optional.empty());
}
@@ -337,18 +337,18 @@ public class QueryMapper {
String inKey = valueDbo.containsKey("$in") ? "$in" : "$nin";
List<Object> ids = new ArrayList<Object>();
for (Object id : (Iterable<?>) valueDbo.get(inKey)) {
ids.add(convertId(id).orElse(null));
ids.add(convertId(id));
}
resultDbo.put(inKey, ids);
} else if (valueDbo.containsKey("$ne")) {
resultDbo.put("$ne", convertId(valueDbo.get("$ne")).orElse(null));
resultDbo.put("$ne", convertId(valueDbo.get("$ne")));
} else {
return getMappedObject(resultDbo, Optional.empty());
}
return resultDbo;
} else {
return convertId(value).orElse(null);
return convertId(value);
}
}
@@ -394,7 +394,7 @@ public class QueryMapper {
MongoPersistentEntity<?> entity = documentField.getPropertyEntity();
return entity.hasIdProperty() && (type.equals(DBRef.class)
|| entity.getIdProperty().map(it -> it.getActualType().isAssignableFrom(type)).orElse(false));
|| entity.getRequiredIdProperty().getActualType().isAssignableFrom(type));
}
/**
@@ -461,7 +461,7 @@ public class QueryMapper {
if (source instanceof DBRef) {
DBRef ref = (DBRef) source;
return new DBRef(ref.getCollectionName(), convertId(ref.getId()).get());
return new DBRef(ref.getCollectionName(), convertId(ref.getId()));
}
if (source instanceof Iterable) {
@@ -537,31 +537,28 @@ public class QueryMapper {
return converter.toDBRef(source, property);
}
private Optional<Object> convertId(Object id) {
return convertId(Optional.ofNullable(id));
}
/**
* Converts the given raw id value into either {@link ObjectId} or {@link String}.
*
* @param id
* @return
*/
public Optional<Object> convertId(Optional<Object> id) {
public Object convertId(Object id) {
return id.map(it -> {
if (id == null) {
return null;
}
if (it instanceof String) {
return ObjectId.isValid(it.toString()) ? conversionService.convert(it, ObjectId.class) : it;
}
if (id instanceof String) {
return ObjectId.isValid(id.toString()) ? conversionService.convert(id, ObjectId.class) : id;
}
try {
return conversionService.canConvert(it.getClass(), ObjectId.class)
? conversionService.convert(it, ObjectId.class) : delegateConvertToMongoType(it, null);
} catch (ConversionException o_O) {
return delegateConvertToMongoType(it, null);
}
});
try {
return conversionService.canConvert(id.getClass(), ObjectId.class) ? conversionService.convert(id, ObjectId.class)
: delegateConvertToMongoType(id, null);
} catch (ConversionException o_O) {
return delegateConvertToMongoType(id, null);
}
}
/**
@@ -836,9 +833,13 @@ public class QueryMapper {
@Override
public boolean isIdField() {
return entity.getIdProperty()//
.map(it -> it.getName().equals(name) || it.getFieldName().equals(name))//
.orElseGet(() -> DEFAULT_ID_NAMES.contains(name));
MongoPersistentProperty idProperty = entity.getIdProperty();
if (idProperty != null) {
return idProperty.getName().equals(name) || idProperty.getFieldName().equals(name);
}
return DEFAULT_ID_NAMES.contains(name);
}
/*
@@ -857,7 +858,7 @@ public class QueryMapper {
@Override
public MongoPersistentEntity<?> getPropertyEntity() {
MongoPersistentProperty property = getProperty();
return property == null ? null : mappingContext.getPersistentEntity(property).orElse(null);
return property == null ? null : mappingContext.getPersistentEntity(property);
}
/*
@@ -883,15 +884,15 @@ public class QueryMapper {
*
* @return
*/
private final Association<MongoPersistentProperty> findAssociation() {
private Association<MongoPersistentProperty> findAssociation() {
if (this.path != null) {
for (MongoPersistentProperty p : this.path) {
Optional<Association<MongoPersistentProperty>> association = p.getAssociation();
Association<MongoPersistentProperty> association = p.getAssociation();
if (association.isPresent()) {
return association.get();
if (association != null) {
return association;
}
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.data.mongodb.core.convert;
import java.util.Optional;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
@@ -24,7 +22,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
/**
* Internal API to trigger the resolution of properties.
*
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
@@ -33,13 +31,13 @@ interface ValueResolver {
/**
* Resolves the value for the given {@link MongoPersistentProperty} within the given {@link Document} using the given
* {@link SpELExpressionEvaluator} and {@link ObjectPath}.
*
*
* @param prop
* @param bson
* @param evaluator
* @param parent
* @return
*/
Optional<Object> getValueInternal(MongoPersistentProperty prop, Bson bson, SpELExpressionEvaluator evaluator,
Object getValueInternal(MongoPersistentProperty prop, Bson bson, SpELExpressionEvaluator evaluator,
ObjectPath path);
}

View File

@@ -18,7 +18,7 @@ package org.springframework.data.mongodb.core.index;
import java.util.Optional;
import org.bson.Document;
import org.springframework.data.mongodb.core.Collation;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

View File

@@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit;
import org.bson.Document;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.Collation;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

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.
@@ -13,33 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
package org.springframework.data.mongodb.core.index;
import java.util.List;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
/**
* Index operations on a collection.
*
*
* @author Mark Pollack
* @author Oliver Gierke
* @author Christoph Strobl
* @author Jens Schauder
*/
public interface IndexOperations {
/**
* Ensure that an index for the provided {@link IndexDefinition} exists for the collection indicated by the entity
* class. If not it will be created.
*
*
* @param indexDefinition must not be {@literal null}.
*/
String ensureIndex(IndexDefinition indexDefinition);
/**
* Drops an index from this collection.
*
*
* @param name name of index to drop
*/
void dropIndex(String name);
@@ -51,7 +49,7 @@ public interface IndexOperations {
/**
* Returns the index information on the collection.
*
*
* @return index information on the collection
*/
List<IndexInfo> getIndexInfo();

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.
@@ -14,12 +14,10 @@
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
package org.springframework.data.mongodb.core.index;
import java.util.List;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.util.Assert;
/**

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.
@@ -14,13 +14,13 @@
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import org.springframework.data.mongodb.core.convert.QueryMapper;
package org.springframework.data.mongodb.core.index;
/**
* TODO: Revisit for a better pattern.
*
* @author Mark Paluch
* @author Jens Schauder
* @since 2.0
*/
public interface IndexOperationsProvider {

View File

@@ -28,8 +28,6 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.MappingContextEvent;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.mongodb.core.IndexOperations;
import org.springframework.data.mongodb.core.IndexOperationsProvider;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@@ -131,7 +129,7 @@ public class MongoPersistentEntityIndexCreator implements ApplicationListener<Ma
private void checkForAndCreateIndexes(MongoPersistentEntity<?> entity) {
if (entity.findAnnotation(Document.class).isPresent()) {
if (entity.isAnnotationPresent(Document.class)) {
for (IndexDefinitionHolder indexToCreate : indexResolver.resolveIndexFor(entity.getTypeInformation())) {
createIndex(indexToCreate);
}

View File

@@ -23,7 +23,6 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
@@ -33,7 +32,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.TextIndexIncludeOptions.IncludeStrategy;
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder;
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexedFieldSpec;
@@ -94,7 +93,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
public List<IndexDefinitionHolder> resolveIndexForEntity(final MongoPersistentEntity<?> root) {
Assert.notNull(root, "Index cannot be resolved for given 'null' entity.");
Document document = root.findAnnotation(Document.class).orElseThrow(() -> new IllegalArgumentException("Given entity is not collection root."));
Document document = root.findAnnotation(Document.class);
Assert.notNull(document, "Given entity is not collection root.");
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
@@ -255,20 +254,20 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
indexDefinitionBuilder.withLanguageOverride(persistentProperty.getFieldName());
}
Optional<TextIndexed> indexed = persistentProperty.findAnnotation(TextIndexed.class);
TextIndexed indexed = persistentProperty.findAnnotation(TextIndexed.class);
if (includeOptions.isForce() || indexed.isPresent()|| persistentProperty.isEntity()) {
if (includeOptions.isForce() || indexed != null || persistentProperty.isEntity()) {
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "")
+ persistentProperty.getFieldName();
Float weight = indexed.isPresent() ? indexed.get().weight()
Float weight = indexed != null ? indexed.weight()
: (includeOptions.getParentFieldSpec() != null ? includeOptions.getParentFieldSpec().getWeight() : 1.0F);
if (persistentProperty.isEntity()) {
TextIndexIncludeOptions optionsForNestedType = includeOptions;
if (!IncludeStrategy.FORCE.equals(includeOptions.getStrategy()) && indexed.isPresent()) {
if (!IncludeStrategy.FORCE.equals(includeOptions.getStrategy()) && indexed != null) {
optionsForNestedType = new TextIndexIncludeOptions(IncludeStrategy.FORCE,
new TextIndexedFieldSpec(propertyDotPath, weight));
}
@@ -282,7 +281,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
LOGGER.info(String.format("Potentially invalid index structure discovered. Breaking operation for %s.",
entity.getName()), e);
}
} else if (includeOptions.isForce() || indexed.isPresent()) {
} else if (includeOptions.isForce() || indexed != null) {
indexDefinitionBuilder.onField(propertyDotPath, weight);
}
}
@@ -304,18 +303,18 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
MongoPersistentEntity<?> entity) {
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
Optional<CompoundIndexes> indexes = entity.findAnnotation(CompoundIndexes.class);
CompoundIndexes indexes = entity.findAnnotation(CompoundIndexes.class);
if (indexes.isPresent()) {
for (CompoundIndex index : indexes.get().value()) {
if (indexes != null) {
for (CompoundIndex index : indexes.value()) {
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index, entity));
}
}
Optional<CompoundIndex> index = entity.findAnnotation(CompoundIndex.class);
CompoundIndex index = entity.findAnnotation(CompoundIndex.class);
if (index.isPresent()) {
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index.get(), entity));
if (index != null) {
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index, entity));
}
return indexDefinitions;
@@ -382,33 +381,33 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
protected IndexDefinitionHolder createIndexDefinition(String dotPath, String collection,
MongoPersistentProperty persitentProperty) {
Optional<Indexed> index = persitentProperty.findAnnotation(Indexed.class);
Indexed index = persitentProperty.findAnnotation(Indexed.class);
if(!index.isPresent()){
if (index == null) {
return null;
}
Index indexDefinition = new Index().on(dotPath,
IndexDirection.ASCENDING.equals(index.get().direction()) ? Sort.Direction.ASC : Sort.Direction.DESC);
IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC);
if (!index.get().useGeneratedName()) {
indexDefinition.named(pathAwareIndexName(index.get().name(), dotPath, persitentProperty));
if (!index.useGeneratedName()) {
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persitentProperty));
}
if (index.get().unique()) {
if (index.unique()) {
indexDefinition.unique();
}
if (index.get().sparse()) {
if (index.sparse()) {
indexDefinition.sparse();
}
if (index.get().background()) {
if (index.background()) {
indexDefinition.background();
}
if (index.get().expireAfterSeconds() >= 0) {
indexDefinition.expire(index.get().expireAfterSeconds(), TimeUnit.SECONDS);
if (index.expireAfterSeconds() >= 0) {
indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS);
}
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
@@ -426,21 +425,21 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
protected IndexDefinitionHolder createGeoSpatialIndexDefinition(String dotPath, String collection,
MongoPersistentProperty persistentProperty) {
Optional<GeoSpatialIndexed> index = persistentProperty.findAnnotation(GeoSpatialIndexed.class);
GeoSpatialIndexed index = persistentProperty.findAnnotation(GeoSpatialIndexed.class);
if(!index.isPresent()) {
if (index == null) {
return null;
}
GeospatialIndex indexDefinition = new GeospatialIndex(dotPath);
indexDefinition.withBits(index.get().bits());
indexDefinition.withMin(index.get().min()).withMax(index.get().max());
indexDefinition.withBits(index.bits());
indexDefinition.withMin(index.min()).withMax(index.max());
if (!index.get().useGeneratedName()) {
indexDefinition.named(pathAwareIndexName(index.get().name(), dotPath, persistentProperty));
if (!index.useGeneratedName()) {
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persistentProperty));
}
indexDefinition.typed(index.get().type()).withBucketSize(index.get().bucketSize()).withAdditionalField(index.get().additionalField());
indexDefinition.typed(index.type()).withBucketSize(index.bucketSize()).withAdditionalField(index.additionalField());
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}

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,12 +13,7 @@
* 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.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
package org.springframework.data.mongodb.core.index;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -58,5 +53,4 @@ public interface ReactiveIndexOperations {
* @return index information on the collection
*/
Flux<IndexInfo> getIndexInfo();
}

View File

@@ -20,7 +20,6 @@ import java.lang.reflect.Modifier;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
@@ -32,7 +31,7 @@ import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mongodb.MongoCollectionUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.Expression;
@@ -47,7 +46,7 @@ import org.springframework.util.StringUtils;
/**
* MongoDB specific {@link MongoPersistentEntity} implementation that adds Mongo specific meta-data such as the
* collection name and the like.
*
*
* @author Jon Brisbin
* @author Oliver Gierke
* @author Thomas Darimont
@@ -69,23 +68,30 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
/**
* Creates a new {@link BasicMongoPersistentEntity} with the given {@link TypeInformation}. Will default the
* collection name to the entities simple type name.
*
*
* @param typeInformation must not be {@literal null}.
*/
public BasicMongoPersistentEntity(TypeInformation<T> typeInformation) {
super(typeInformation, Optional.of(MongoPersistentPropertyComparator.INSTANCE));
super(typeInformation, MongoPersistentPropertyComparator.INSTANCE);
Class<?> rawType = typeInformation.getType();
String fallback = MongoCollectionUtils.getPreferredCollectionName(rawType);
Optional<Document> document = this.findAnnotation(Document.class);
this.expression = document.map(it -> detectExpression(it)).orElse(null);
this.context = new StandardEvaluationContext();
this.collection = document.filter(it -> StringUtils.hasText(it.collection())).map(it -> it.collection())
.orElse(fallback);
this.language = document.filter(it -> StringUtils.hasText(it.language())).map(it -> it.language()).orElse("");
if (this.isAnnotationPresent(Document.class)) {
Document document = this.findAnnotation(Document.class);
this.collection = StringUtils.hasText(document.collection()) ? document.collection() : fallback;
this.language = StringUtils.hasText(document.language()) ? document.language() : "";
this.expression = document != null ? detectExpression(document) : null;
} else {
this.collection = fallback;
this.language = "";
this.expression = null;
}
}
/*
@@ -122,7 +128,7 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
*/
@Override
public MongoPersistentProperty getTextScoreProperty() {
return getPersistentProperty(TextScore.class).orElse(null);
return getPersistentProperty(TextScore.class);
}
/*
@@ -159,7 +165,7 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
/**
* {@link Comparator} implementation inspecting the {@link MongoPersistentProperty}'s order.
*
*
* @author Oliver Gierke
*/
static enum MongoPersistentPropertyComparator implements Comparator<MongoPersistentProperty> {
@@ -189,7 +195,7 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
* that is annotated with @see {@link Id}. The property id is updated according to the following rules: 1) An id
* property which is defined explicitly takes precedence over an implicitly defined id property. 2) In case of any
* ambiguity a @see {@link MappingException} is thrown.
*
*
* @param property - the new id property candidate
* @return
*/
@@ -202,45 +208,47 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
return null;
}
Optional<MongoPersistentProperty> currentIdProperty = getIdProperty();
MongoPersistentProperty currentIdProperty = getIdProperty();
return currentIdProperty.map(it -> {
boolean currentIdPropertyIsSet = currentIdProperty != null;
@SuppressWarnings("null")
boolean currentIdPropertyIsExplicit = currentIdPropertyIsSet ? currentIdProperty.isExplicitIdProperty() : false;
boolean newIdPropertyIsExplicit = property.isExplicitIdProperty();
boolean currentIdPropertyIsExplicit = it.isExplicitIdProperty();
boolean newIdPropertyIsExplicit = property.isExplicitIdProperty();
Optional<Field> currentIdPropertyField = it.getField();
if (!currentIdPropertyIsSet) {
return property;
if (newIdPropertyIsExplicit && currentIdPropertyIsExplicit) {
throw new MappingException(
String.format(
"Attempt to add explicit id property %s but already have an property %s registered "
+ "as explicit id. Check your mapping configuration!",
property.getField(), currentIdPropertyField));
}
} else if (newIdPropertyIsExplicit && !currentIdPropertyIsExplicit) {
// explicit id property takes precedence over implicit id property
return property;
@SuppressWarnings("null")
Field currentIdPropertyField = currentIdProperty.getField();
} else if (!newIdPropertyIsExplicit && currentIdPropertyIsExplicit) {
// no id property override - current property is explicitly defined
if (newIdPropertyIsExplicit && currentIdPropertyIsExplicit) {
throw new MappingException(
String.format("Attempt to add explicit id property %s but already have an property %s registered "
+ "as explicit id. Check your mapping configuration!", property.getField(), currentIdPropertyField));
} else {
throw new MappingException(
String.format("Attempt to add id property %s but already have an property %s registered "
+ "as id. Check your mapping configuration!", property.getField(), currentIdPropertyField));
}
} else if (newIdPropertyIsExplicit && !currentIdPropertyIsExplicit) {
// explicit id property takes precedence over implicit id property
return property;
return null;
} else if (!newIdPropertyIsExplicit && currentIdPropertyIsExplicit) {
// no id property override - current property is explicitly defined
}).orElse(property);
} else {
throw new MappingException(
String.format("Attempt to add id property %s but already have an property %s registered "
+ "as id. Check your mapping configuration!", property.getField(), currentIdPropertyField));
}
return null;
}
/**
* Returns a SpEL {@link Expression} frór the collection String expressed in the given {@link Document} annotation if
* present or {@literal null} otherwise. Will also return {@literal null} it the collection {@link String} evaluates
* to a {@link LiteralExpression} (indicating that no subsequent evaluation is necessary).
*
*
* @param document can be {@literal null}
* @return
*/
@@ -264,7 +272,7 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
/**
* Handler to collect {@link MongoPersistentProperty} instances and check that each of them is mapped to a distinct
* field name.
*
*
* @author Oliver Gierke
*/
private static class AssertFieldNameUniquenessHandler

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.core.mapping;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import org.bson.types.ObjectId;
@@ -27,19 +26,20 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.util.StringUtils;
/**
* MongoDB specific {@link org.springframework.data.mapping.MongoPersistentProperty} implementation.
*
* MongoDB specific {@link org.springframework.data.mapping.PersistentProperty} implementation.
*
* @author Oliver Gierke
* @author Patryk Wasik
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
public class BasicMongoPersistentProperty extends AnnotationBasedPersistentProperty<MongoPersistentProperty>
implements MongoPersistentProperty {
@@ -65,7 +65,7 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
/**
* Creates a new {@link BasicMongoPersistentProperty}.
*
*
* @param field
* @param propertyDescriptor
* @param owner
@@ -86,7 +86,7 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
/**
* Also considers fields as id that are of supported id type and name.
*
*
* @see #SUPPORTED_ID_PROPERTY_NAMES
* @see #SUPPORTED_ID_TYPES
*/
@@ -113,14 +113,14 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
/**
* Returns the key to be used to store the value of the property inside a Mongo {@link org.bson.Document}.
*
*
* @return
*/
public String getFieldName() {
if (isIdProperty()) {
if (!getOwner().getIdProperty().isPresent()) {
if (getOwner().getIdProperty() == null) {
return ID_FIELD_NAME;
}
@@ -154,13 +154,10 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
private String getAnnotatedFieldName() {
Optional<org.springframework.data.mongodb.core.mapping.Field> annotation = findAnnotation(
org.springframework.data.mongodb.core.mapping.Field annotation = findAnnotation(
org.springframework.data.mongodb.core.mapping.Field.class);
return annotation//
.filter(it -> StringUtils.hasText(it.value()))//
.map(it -> it.value())//
.orElse(null);
return annotation != null ? annotation.value() : null;
}
/*
@@ -169,10 +166,10 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
*/
public int getFieldOrder() {
Optional<org.springframework.data.mongodb.core.mapping.Field> annotation = findAnnotation(
org.springframework.data.mongodb.core.mapping.Field annotation = findAnnotation(
org.springframework.data.mongodb.core.mapping.Field.class);
return annotation.map(it -> it.order()).orElse(Integer.MAX_VALUE);
return annotation != null ? annotation.order() : Integer.MAX_VALUE;
}
/*
@@ -197,7 +194,7 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#getDBRef()
*/
public DBRef getDBRef() {
return findAnnotation(DBRef.class).orElse(null);
return findAnnotation(DBRef.class);
}
/*

View File

@@ -18,7 +18,7 @@ package org.springframework.data.mongodb.core.mapreduce;
import java.util.Optional;
import org.bson.Document;
import org.springframework.data.mongodb.core.Collation;
import org.springframework.data.mongodb.core.query.Collation;
/**
* Collects the parameters required to perform a group operation on a collection. The query condition and the input

View File

@@ -20,7 +20,7 @@ import java.util.Map;
import java.util.Optional;
import org.bson.Document;
import org.springframework.data.mongodb.core.Collation;
import org.springframework.data.mongodb.core.query.Collation;
import com.mongodb.MapReduceCommand;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 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.
@@ -18,13 +18,11 @@ package org.springframework.data.mongodb.core.query;
import static org.springframework.util.ObjectUtils.*;
import org.bson.Document;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
import org.springframework.util.Assert;
/**
* Custom {@link Query} implementation to setup a basic query from some arbitrary JSON query string.
*
*
* @author Thomas Risberg
* @author Oliver Gierke
* @author Christoph Strobl
@@ -35,6 +33,7 @@ import com.mongodb.util.JSON;
public class BasicQuery extends Query {
private final Document queryObject;
private Document fieldsObject;
private Document sortObject;
@@ -53,7 +52,7 @@ public class BasicQuery extends Query {
* @param queryObject may be {@literal null}.
*/
public BasicQuery(Document queryObject) {
this(queryObject, null);
this(queryObject, new Document());
}
/**
@@ -64,17 +63,22 @@ public class BasicQuery extends Query {
*/
public BasicQuery(String query, String fields) {
this.queryObject = query != null ? Document.parse(query) : null;
this.fieldsObject = fields != null ? Document.parse(fields) : null;
this.queryObject = query != null ? Document.parse(query) : new Document();
this.fieldsObject = fields != null ? Document.parse(fields) : new Document();
}
/**
* Create a new {@link BasicQuery} given a query {@link Document} and field specification {@link Document}.
*
* @param queryObject may be {@literal null}.
* @param fieldsObject may be {@literal null}.
* @param queryObject must not be {@literal null}.
* @param fieldsObject must not be {@literal null}.
* @throws IllegalArgumentException when {@code sortObject} or {@code fieldsObject} is {@literal null}.
*/
public BasicQuery(Document queryObject, Document fieldsObject) {
Assert.notNull(queryObject, "Query document must not be null");
Assert.notNull(fieldsObject, "Field document must not be null");
this.queryObject = queryObject;
this.fieldsObject = fieldsObject;
}
@@ -85,15 +89,25 @@ public class BasicQuery extends Query {
*/
@Override
public Query addCriteria(CriteriaDefinition criteria) {
this.queryObject.putAll(criteria.getCriteriaObject());
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.query.Query#getQueryObject()
*/
@Override
public Document getQueryObject() {
return this.queryObject;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.query.Query#getFieldsObject()
*/
@Override
public Document getFieldsObject() {
@@ -112,31 +126,49 @@ public class BasicQuery extends Query {
return fieldsObject;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.query.Query#getSortObject()
*/
@Override
public Document getSortObject() {
Document result = new Document();
if (sortObject != null) {
result.putAll(sortObject);
}
Document overrides = super.getSortObject();
if (overrides != null) {
result.putAll(overrides);
}
result.putAll(overrides);
return result;
}
/**
* Set the sort {@link Document}.
*
* @param sortObject must not be {@literal null}.
* @throws IllegalArgumentException when {@code sortObject} is {@literal null}.
*/
public void setSortObject(Document sortObject) {
Assert.notNull(sortObject, "Sort document must not be null");
this.sortObject = sortObject;
}
/**
* Set the fields (projection) {@link Document}.
*
* @param fieldsObject must not be {@literal null}.
* @throws IllegalArgumentException when {@code fieldsObject} is {@literal null}.
* @since 1.6
* @param fieldsObject
*/
protected void setFieldsObject(Document fieldsObject) {
Assert.notNull(sortObject, "Field document must not be null");
this.fieldsObject = fieldsObject;
}

View File

@@ -13,12 +13,7 @@
* 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;
package org.springframework.data.mongodb.core.query;
import java.util.Locale;
import java.util.Optional;
@@ -34,18 +29,24 @@ import com.mongodb.client.model.CollationCaseFirst;
import com.mongodb.client.model.CollationMaxVariable;
import com.mongodb.client.model.CollationStrength;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* 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 />
* <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
* @author Jens Schauder
* @see <a href="https://docs.mongodb.com/manual/reference/collation/">MongoDB Reference - Collation</a>
* @since 2.0
*/
public class Collation {
@@ -720,8 +721,8 @@ public class Collation {
/**
* 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>
* @since 2.0
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class CollationLocale {

View File

@@ -17,62 +17,95 @@ package org.springframework.data.mongodb.core.query;
import java.util.regex.Pattern;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
* @author Mark Paluch
* @author Jens Schauder
* @since 1.8
*/
public enum MongoRegexCreator {
INSTANCE;
/**
* Match modes for treatment of {@link String} values.
*
* @author Christoph Strobl
* @author Jens Schauder
*/
public enum MatchMode {
/**
* Store specific default.
*/
DEFAULT,
/**
* Matches the exact string
*/
EXACT,
/**
* Matches string starting with pattern
*/
STARTING_WITH,
/**
* Matches string ending with pattern
*/
ENDING_WITH,
/**
* Matches string containing pattern
*/
CONTAINING,
/**
* Treats strings as regular expression patterns
*/
REGEX,
LIKE;
}
private static final Pattern PUNCTATION_PATTERN = Pattern.compile("\\p{Punct}");
/**
* Creates a regular expression String to be used with {@code $regex}.
*
*
* @param source the plain String
* @param type
* @return {@literal source} when {@literal source} or {@literal type} is {@literal null}.
* @param matcherType the type of matching to perform
* @return {@literal source} when {@literal source} or {@literal matcherType} is {@literal null}.
*/
public String toRegularExpression(String source, Type type) {
public String toRegularExpression(String source, MatchMode matcherType) {
if (type == null || source == null) {
if (matcherType == null || source == null) {
return source;
}
String regex = prepareAndEscapeStringBeforeApplyingLikeRegex(source, type);
String regex = prepareAndEscapeStringBeforeApplyingLikeRegex(source, matcherType);
switch (type) {
switch (matcherType) {
case STARTING_WITH:
regex = "^" + regex;
break;
return String.format("^%s", regex);
case ENDING_WITH:
regex = regex + "$";
break;
return String.format("%s$", regex);
case CONTAINING:
case NOT_CONTAINING:
regex = ".*" + regex + ".*";
break;
case SIMPLE_PROPERTY:
case NEGATING_SIMPLE_PROPERTY:
regex = "^" + regex + "$";
return String.format(".*%s.*", regex);
case EXACT:
return String.format("^%s$", regex);
default:
return regex;
}
return regex;
}
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, Type type) {
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, MatchMode matcherType) {
if (ObjectUtils.nullSafeEquals(Type.REGEX, type)) {
if (MatchMode.REGEX == matcherType) {
return source;
}
if (!ObjectUtils.nullSafeEquals(Type.LIKE, type) && !ObjectUtils.nullSafeEquals(Type.NOT_LIKE, type)) {
if (MatchMode.LIKE != matcherType) {
return PUNCTATION_PATTERN.matcher(source).find() ? Pattern.quote(source) : source;
}
@@ -95,12 +128,13 @@ public enum MongoRegexCreator {
if (leadingWildcard) {
sb.append(".*");
}
sb.append(valueToUse);
if (trailingWildcard) {
sb.append(".*");
}
return sb.toString();
}
}

View File

@@ -19,6 +19,7 @@ import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import static org.springframework.util.ObjectUtils.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -33,10 +34,11 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
import org.springframework.data.mongodb.core.Collation;
import org.springframework.util.Assert;
/**
* MongoDB Query object representing criteria, projection, sorting and query hints.
*
* @author Thomas Risberg
* @author Oliver Gierke
* @author Thomas Darimont
@@ -212,13 +214,14 @@ public class Query {
Assert.notNull(additionalTypes, "AdditionalTypes must not be null");
restrictedTypes.add(type);
for (Class<?> additionalType : additionalTypes) {
restrictedTypes.add(additionalType);
}
restrictedTypes.addAll(Arrays.asList(additionalTypes));
return this;
}
/**
* @return the query {@link Document}.
*/
public Document getQueryObject() {
Document document = new Document();
@@ -234,14 +237,20 @@ public class Query {
return document;
}
/**
* @return the field {@link Document}.
*/
public Document getFieldsObject() {
return this.fieldSpec == null ? null : fieldSpec.getFieldsObject();
return this.fieldSpec == null ? new Document() : fieldSpec.getFieldsObject();
}
/**
* @return the sort {@link Document}.
*/
public Document getSortObject() {
if (this.sort.isUnsorted()) {
return null;
return new Document();
}
Document document = new Document();

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.
@@ -21,8 +21,9 @@ import org.bson.Document;
/**
* {@link Query} implementation to be used to for performing full text searches.
*
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.6
*/
public class TextQuery extends Query {
@@ -36,7 +37,7 @@ public class TextQuery extends Query {
/**
* Creates new {@link TextQuery} using the the given {@code wordsAndPhrases} with {@link TextCriteria}
*
*
* @param wordsAndPhrases
* @see TextCriteria#matching(String)
*/
@@ -48,7 +49,7 @@ public class TextQuery extends Query {
* Creates new {@link TextQuery} in {@code language}. <br />
* For a full list of supported languages see the mongdodb reference manual for
* <a href="https://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages</a>.
*
*
* @param wordsAndPhrases
* @param language
* @see TextCriteria#forLanguage(String)
@@ -62,7 +63,7 @@ public class TextQuery extends Query {
* Creates new {@link TextQuery} using the {@code locale}s language.<br />
* For a full list of supported languages see the mongdodb reference manual for
* <a href="https://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages</a>.
*
*
* @param wordsAndPhrases
* @param locale
*/
@@ -72,7 +73,7 @@ public class TextQuery extends Query {
/**
* Creates new {@link TextQuery} for given {@link TextCriteria}.
*
*
* @param criteria.
*/
public TextQuery(TextCriteria criteria) {
@@ -81,7 +82,7 @@ public class TextQuery extends Query {
/**
* Creates new {@link TextQuery} searching for given {@link TextCriteria}.
*
*
* @param criteria
* @return
*/
@@ -91,7 +92,7 @@ public class TextQuery extends Query {
/**
* Add sorting by text score. Will also add text score to returned fields.
*
*
* @see TextQuery#includeScore()
* @return
*/
@@ -104,7 +105,7 @@ public class TextQuery extends Query {
/**
* Add field {@literal score} holding the documents textScore to the returned fields.
*
*
* @return
*/
public TextQuery includeScore() {
@@ -115,7 +116,7 @@ public class TextQuery extends Query {
/**
* Include text search document score in returned fields using the given fieldname.
*
*
* @param fieldname
* @return
*/
@@ -128,7 +129,7 @@ public class TextQuery extends Query {
/**
* Set the fieldname used for scoring.
*
*
* @param fieldName
*/
public void setScoreFieldName(String fieldName) {
@@ -137,7 +138,7 @@ public class TextQuery extends Query {
/**
* Get the fieldname used for scoring
*
*
* @return
*/
public String getScoreFieldName() {
@@ -178,9 +179,7 @@ public class TextQuery extends Query {
sort.put(getScoreFieldName(), META_TEXT_SCORE);
}
if (super.getSortObject() != null) {
sort.putAll(super.getSortObject());
}
sort.putAll(super.getSortObject());
return sort;
}

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.
@@ -34,6 +34,7 @@ import org.springframework.expression.spel.ast.StringLiteral;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
*/
public class LiteralNode extends ExpressionNode {
@@ -56,7 +57,7 @@ public class LiteralNode extends ExpressionNode {
/**
* Creates a new {@link LiteralNode} from the given {@link Literal} and {@link ExpressionState}.
*
*
* @param node must not be {@literal null}.
* @param state must not be {@literal null}.
*/
@@ -67,7 +68,7 @@ public class LiteralNode extends ExpressionNode {
/**
* Returns whether the given {@link ExpressionNode} is a unary minus.
*
*
* @param parent
* @return
*/
@@ -78,7 +79,7 @@ public class LiteralNode extends ExpressionNode {
}
OperatorNode operator = (OperatorNode) parent;
return operator.isUnaryMinus() && operator.getRight() == null;
return operator.isUnaryMinus();
}
/*

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.
@@ -22,21 +22,7 @@ import java.util.Map;
import java.util.Set;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.ast.OpAnd;
import org.springframework.expression.spel.ast.OpDivide;
import org.springframework.expression.spel.ast.OpEQ;
import org.springframework.expression.spel.ast.OpGE;
import org.springframework.expression.spel.ast.OpGT;
import org.springframework.expression.spel.ast.OpLE;
import org.springframework.expression.spel.ast.OpLT;
import org.springframework.expression.spel.ast.OpMinus;
import org.springframework.expression.spel.ast.OpModulus;
import org.springframework.expression.spel.ast.OpMultiply;
import org.springframework.expression.spel.ast.OpNE;
import org.springframework.expression.spel.ast.OpOr;
import org.springframework.expression.spel.ast.OpPlus;
import org.springframework.expression.spel.ast.Operator;
import org.springframework.expression.spel.ast.OperatorPower;
import org.springframework.expression.spel.ast.*;
/**
* An {@link ExpressionNode} representing an operator.
@@ -44,6 +30,7 @@ import org.springframework.expression.spel.ast.OperatorPower;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
public class OperatorNode extends ExpressionNode {
@@ -102,7 +89,7 @@ public class OperatorNode extends ExpressionNode {
this.operator = node;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.spel.ExpressionNode#isMathematicalOperation()
*/
@@ -122,16 +109,16 @@ public class OperatorNode extends ExpressionNode {
/**
* Returns whether the operator is unary.
*
*
* @return
*/
public boolean isUnaryOperator() {
return operator.getRightOperand() == null;
return operator.getChildCount() == 1;
}
/**
* Returns the Mongo expression of the operator.
*
*
* @return
*/
public String getMongoOperator() {
@@ -147,7 +134,7 @@ public class OperatorNode extends ExpressionNode {
/**
* Returns whether the operator is a unary minus, e.g. -1.
*
*
* @return
*/
public boolean isUnaryMinus() {
@@ -156,7 +143,7 @@ public class OperatorNode extends ExpressionNode {
/**
* Returns the left operand as {@link ExpressionNode}.
*
*
* @return
*/
public ExpressionNode getLeft() {
@@ -165,7 +152,7 @@ public class OperatorNode extends ExpressionNode {
/**
* Returns the right operand as {@link ExpressionNode}.
*
*
* @return
*/
public ExpressionNode getRight() {

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