Browse Source

DATAMONGO-1444 - Polishing.

- Update Javadoc comments and reference documentation.
- Introduce Adapter for blocking IndexOperations.
- Remove transaction synchronization.
- Remove unused types.
- Remove dropDupos options from indexes.
- Prevent usage of Querydsl along with reactive repository.
- Use ReactiveQueryMethod in ReactiveMongoQuery.
pull/411/merge
Christoph Strobl 9 years ago committed by Oliver Gierke
parent
commit
e0f371f648
  1. 8
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java
  2. 16
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java
  3. 8
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java
  4. 21
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java
  5. 27
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java
  6. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java
  7. 26
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java
  8. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexOperations.java
  9. 66
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexOperationsAdapter.java
  10. 3
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexOperationsProvider.java
  11. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveCollectionCallback.java
  12. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveDatabaseCallback.java
  13. 6
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveIndexOperations.java
  14. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientFactoryBean.java
  15. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientSettingsFactoryBean.java
  16. 88
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoDatabaseHolder.java
  17. 113
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoDbUtils.java
  18. 17
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
  19. 160
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  20. 21
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java
  21. 15
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java
  22. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/InfiniteStream.java
  23. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/config/EnableReactiveMongoRepositories.java
  24. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/config/MongoRepositoryConfigurationExtension.java
  25. 7
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/config/ReactiveMongoRepositoriesRegistrar.java
  26. 11
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/config/ReactiveMongoRepositoryConfigurationExtension.java
  27. 18
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/config/RepositoryType.java
  28. 29
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
  29. 24
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoParameterAccessor.java
  30. 20
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java
  31. 16
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java
  32. 6
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java
  33. 6
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java
  34. 16
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java
  35. 39
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveChunk.java
  36. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java
  37. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactoryBean.java
  38. 1
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactivePageImpl.java
  39. 13
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.java
  40. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java
  41. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfigurationIntegrationTests.java
  42. 3
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfigurationUnitTests.java
  43. 1
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java
  44. 9
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateExecuteTests.java
  45. 29
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java
  46. 63
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java
  47. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexInfoUnitTests.java
  48. 13
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java
  49. 169
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/ReactivePerformanceTests.java
  50. 3
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java
  51. 4
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactivePersonRepository.java
  52. 3
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SimpleReactiveMongoRepositoryTests.java
  53. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryExecutionUnitTests.java
  54. 5
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java
  55. 8
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactivePageImplUnitTests.java
  56. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactiveSliceImplUnitTests.java
  57. 8
      src/main/asciidoc/reference/reactive-mongo-repositories.adoc
  58. 96
      src/main/asciidoc/reference/reactive-mongodb.adoc

8
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java

@ -24,7 +24,7 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
/** /**
* Interface for factories creating reactive {@link MongoDatabase} instances. * Interface for factories creating reactive {@link MongoDatabase} instances.
* *
* @author Mark Paluch * @author Mark Paluch
* @since 2.0 * @since 2.0
*/ */
@ -32,7 +32,7 @@ public interface ReactiveMongoDatabaseFactory {
/** /**
* Creates a default {@link MongoDatabase} instance. * Creates a default {@link MongoDatabase} instance.
* *
* @return * @return
* @throws DataAccessException * @throws DataAccessException
*/ */
@ -40,7 +40,7 @@ public interface ReactiveMongoDatabaseFactory {
/** /**
* Creates a {@link MongoDatabase} instance to access the database with the given name. * Creates a {@link MongoDatabase} instance to access the database with the given name.
* *
* @param dbName must not be {@literal null} or empty. * @param dbName must not be {@literal null} or empty.
* @return * @return
* @throws DataAccessException * @throws DataAccessException
@ -49,7 +49,7 @@ public interface ReactiveMongoDatabaseFactory {
/** /**
* Exposes a shared {@link MongoExceptionTranslator}. * Exposes a shared {@link MongoExceptionTranslator}.
* *
* @return will never be {@literal null}. * @return will never be {@literal null}.
*/ */
PersistenceExceptionTranslator getExceptionTranslator(); PersistenceExceptionTranslator getExceptionTranslator();

16
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java

@ -18,7 +18,9 @@ package org.springframework.data.mongodb.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory; import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory; import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
@ -28,7 +30,7 @@ import com.mongodb.reactivestreams.client.MongoClient;
/** /**
* Base class for reactive Spring Data MongoDB configuration using JavaConfig. * Base class for reactive Spring Data MongoDB configuration using JavaConfig.
* *
* @author Mark Paluch * @author Mark Paluch
* @since 2.0 * @since 2.0
* @see MongoConfigurationSupport * @see MongoConfigurationSupport
@ -39,39 +41,39 @@ public abstract class AbstractReactiveMongoConfiguration extends MongoConfigurat
/** /**
* Return the {@link MongoClient} instance to connect to. Annotate with {@link Bean} in case you want to expose a * Return the {@link MongoClient} instance to connect to. Annotate with {@link Bean} in case you want to expose a
* {@link MongoClient} instance to the {@link org.springframework.context.ApplicationContext}. * {@link MongoClient} instance to the {@link org.springframework.context.ApplicationContext}.
* *
* @return * @return
*/ */
public abstract MongoClient mongoClient(); public abstract MongoClient mongoClient();
/** /**
* Creates a {@link ReactiveMongoTemplate}. * Creates a {@link ReactiveMongoTemplate}.
* *
* @return * @return
*/ */
@Bean @Bean
public ReactiveMongoTemplate reactiveMongoTemplate() throws Exception { public ReactiveMongoOperations reactiveMongoTemplate() throws Exception {
return new ReactiveMongoTemplate(mongoDbFactory(), mappingMongoConverter()); return new ReactiveMongoTemplate(mongoDbFactory(), mappingMongoConverter());
} }
/** /**
* Creates a {@link SimpleMongoDbFactory} to be used by the {@link MongoTemplate}. Will use the {@link Mongo} instance * Creates a {@link SimpleMongoDbFactory} to be used by the {@link MongoTemplate}. Will use the {@link Mongo} instance
* configured in {@link #mongoClient()}. * configured in {@link #mongoClient()}.
* *
* @see #mongoClient() * @see #mongoClient()
* @see #reactiveMongoTemplate() * @see #reactiveMongoTemplate()
* @return * @return
* @throws Exception * @throws Exception
*/ */
@Bean @Bean
public SimpleReactiveMongoDatabaseFactory mongoDbFactory() { public ReactiveMongoDatabaseFactory mongoDbFactory() {
return new SimpleReactiveMongoDatabaseFactory(mongoClient(), getDatabaseName()); return new SimpleReactiveMongoDatabaseFactory(mongoClient(), getDatabaseName());
} }
/** /**
* Creates a {@link MappingMongoConverter} using the configured {@link #mongoDbFactory()} and * Creates a {@link MappingMongoConverter} using the configured {@link #mongoDbFactory()} and
* {@link #mongoMappingContext()}. Will get {@link #customConversions()} applied. * {@link #mongoMappingContext()}. Will get {@link #customConversions()} applied.
* *
* @see #customConversions() * @see #customConversions()
* @see #mongoMappingContext() * @see #mongoMappingContext()
* @see #mongoDbFactory() * @see #mongoDbFactory()

8
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2011-2014 the original author or authors. * Copyright 2011-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -73,6 +73,7 @@ import org.w3c.dom.Element;
* @author Maciej Walkowiak * @author Maciej Walkowiak
* @author Thomas Darimont * @author Thomas Darimont
* @author Christoph Strobl * @author Christoph Strobl
* @author Mark Paluch
*/ */
public class MappingMongoConverterParser implements BeanDefinitionParser { public class MappingMongoConverterParser implements BeanDefinitionParser {
@ -121,6 +122,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
} }
if(!registry.containsBeanDefinition("indexOperationsProvider")){ if(!registry.containsBeanDefinition("indexOperationsProvider")){
BeanDefinitionBuilder indexOperationsProviderBuilder = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.data.mongodb.core.DefaultIndexOperationsProvider"); BeanDefinitionBuilder indexOperationsProviderBuilder = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.data.mongodb.core.DefaultIndexOperationsProvider");
indexOperationsProviderBuilder.addConstructorArgReference(dbFactoryRef); indexOperationsProviderBuilder.addConstructorArgReference(dbFactoryRef);
parserContext.registerBeanComponent(new BeanComponentDefinition(indexOperationsProviderBuilder.getBeanDefinition(), "indexOperationsProvider")); parserContext.registerBeanComponent(new BeanComponentDefinition(indexOperationsProviderBuilder.getBeanDefinition(), "indexOperationsProvider"));
@ -129,9 +131,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
try { try {
registry.getBeanDefinition(INDEX_HELPER_BEAN_NAME); registry.getBeanDefinition(INDEX_HELPER_BEAN_NAME);
} catch (NoSuchBeanDefinitionException ignored) { } catch (NoSuchBeanDefinitionException ignored) {
if (!StringUtils.hasText(dbFactoryRef)) {
dbFactoryRef = DB_FACTORY_BEAN_NAME;
}
BeanDefinitionBuilder indexHelperBuilder = BeanDefinitionBuilder BeanDefinitionBuilder indexHelperBuilder = BeanDefinitionBuilder
.genericBeanDefinition(MongoPersistentEntityIndexCreator.class); .genericBeanDefinition(MongoPersistentEntityIndexCreator.class);
indexHelperBuilder.addConstructorArgReference(ctxRef); indexHelperBuilder.addConstructorArgReference(ctxRef);

21
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java

@ -16,6 +16,7 @@
package org.springframework.data.mongodb.config; package org.springframework.data.mongodb.config;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@ -28,7 +29,9 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent; import org.springframework.data.annotation.Persistent;
import org.springframework.data.authentication.UserCredentials; import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory; import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy; import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
@ -51,7 +54,7 @@ import com.mongodb.MongoClient;
/** /**
* Base class for Spring Data MongoDB to be extended for JavaConfiguration usage. * Base class for Spring Data MongoDB to be extended for JavaConfiguration usage.
* *
* @author Mark Paluch * @author Mark Paluch
* @since 2.0 * @since 2.0
*/ */
@ -100,20 +103,22 @@ public abstract class MongoConfigurationSupport {
/** /**
* Returns a {@link MappingContextIsNewStrategyFactory} wrapped into a {@link CachingIsNewStrategyFactory}. * Returns a {@link MappingContextIsNewStrategyFactory} wrapped into a {@link CachingIsNewStrategyFactory}.
* *
* @return * @return
* @throws ClassNotFoundException * @throws ClassNotFoundException
*/ */
@Bean @Bean
public IsNewStrategyFactory isNewStrategyFactory() throws ClassNotFoundException { public IsNewStrategyFactory isNewStrategyFactory() throws ClassNotFoundException {
return new CachingIsNewStrategyFactory(new MappingContextIsNewStrategyFactory(mongoMappingContext()));
return new CachingIsNewStrategyFactory(new MappingContextIsNewStrategyFactory(
new PersistentEntities(Arrays.<MappingContext<?, ?>> asList(new MappingContext[] { mongoMappingContext() }))));
} }
/** /**
* Register custom {@link Converter}s in a {@link CustomConversions} object if required. These * Register custom {@link Converter}s in a {@link CustomConversions} object if required. These
* {@link CustomConversions} will be registered with the {@link #mappingMongoConverter()} and * {@link CustomConversions} will be registered with the {@link #mappingMongoConverter()} and
* {@link #mongoMappingContext()}. Returns an empty {@link CustomConversions} instance by default. * {@link #mongoMappingContext()}. Returns an empty {@link CustomConversions} instance by default.
* *
* @return must not be {@literal null}. * @return must not be {@literal null}.
*/ */
@Bean @Bean
@ -124,7 +129,7 @@ public abstract class MongoConfigurationSupport {
/** /**
* Scans the mapping base package for classes annotated with {@link Document}. By default, it scans for entities in * Scans the mapping base package for classes annotated with {@link Document}. By default, it scans for entities in
* all packages returned by {@link #getMappingBasePackages()}. * all packages returned by {@link #getMappingBasePackages()}.
* *
* @see #getMappingBasePackages() * @see #getMappingBasePackages()
* @return * @return
* @throws ClassNotFoundException * @throws ClassNotFoundException
@ -143,7 +148,7 @@ public abstract class MongoConfigurationSupport {
/** /**
* Scans the given base package for entities, i.e. MongoDB specific types annotated with {@link Document} and * Scans the given base package for entities, i.e. MongoDB specific types annotated with {@link Document} and
* {@link Persistent}. * {@link Persistent}.
* *
* @param basePackage must not be {@literal null}. * @param basePackage must not be {@literal null}.
* @return * @return
* @throws ClassNotFoundException * @throws ClassNotFoundException
@ -178,7 +183,7 @@ public abstract class MongoConfigurationSupport {
* Configures whether to abbreviate field names for domain objects by configuring a * Configures whether to abbreviate field names for domain objects by configuring a
* {@link CamelCaseAbbreviatingFieldNamingStrategy} on the {@link MongoMappingContext} instance created. For advanced * {@link CamelCaseAbbreviatingFieldNamingStrategy} on the {@link MongoMappingContext} instance created. For advanced
* customization needs, consider overriding {@link #mappingMongoConverter()}. * customization needs, consider overriding {@link #mappingMongoConverter()}.
* *
* @return * @return
*/ */
protected boolean abbreviateFieldNames() { protected boolean abbreviateFieldNames() {
@ -187,7 +192,7 @@ public abstract class MongoConfigurationSupport {
/** /**
* Configures a {@link FieldNamingStrategy} on the {@link MongoMappingContext} instance created. * Configures a {@link FieldNamingStrategy} on the {@link MongoMappingContext} instance created.
* *
* @return * @return
* @since 1.5 * @since 1.5
*/ */

27
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java

@ -15,7 +15,7 @@
*/ */
package org.springframework.data.mongodb.core; package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException; import static org.springframework.data.mongodb.core.MongoTemplate.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -65,19 +65,18 @@ public class DefaultIndexOperations implements IndexOperations {
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.mongodb.core.IndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition) * @see org.springframework.data.mongodb.core.IndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition)
*/ */
public void ensureIndex(final IndexDefinition indexDefinition) { public String ensureIndex(final IndexDefinition indexDefinition) {
execute(collection -> {
return execute(collection -> {
Document indexOptions = indexDefinition.getIndexOptions(); Document indexOptions = indexDefinition.getIndexOptions();
if (indexOptions != null) { if (indexOptions != null) {
IndexOptions ops = IndexConverters.DEFINITION_TO_MONGO_INDEX_OPTIONS.convert(indexDefinition); IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
collection.createIndex(indexDefinition.getIndexKeys(), ops); return collection.createIndex(indexDefinition.getIndexKeys(), ops);
} else {
collection.createIndex(indexDefinition.getIndexKeys());
} }
return null; return collection.createIndex(indexDefinition.getIndexKeys());
}); });
} }
@ -86,11 +85,10 @@ public class DefaultIndexOperations implements IndexOperations {
* @see org.springframework.data.mongodb.core.IndexOperations#dropIndex(java.lang.String) * @see org.springframework.data.mongodb.core.IndexOperations#dropIndex(java.lang.String)
*/ */
public void dropIndex(final String name) { public void dropIndex(final String name) {
execute(new CollectionCallback<Void>() {
public Void doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException { execute(collection -> {
collection.dropIndex(name); collection.dropIndex(name);
return null; return null;
}
}); });
} }
@ -110,6 +108,7 @@ public class DefaultIndexOperations implements IndexOperations {
public List<IndexInfo> getIndexInfo() { public List<IndexInfo> getIndexInfo() {
return execute(new CollectionCallback<List<IndexInfo>>() { return execute(new CollectionCallback<List<IndexInfo>>() {
public List<IndexInfo> doInCollection(MongoCollection<Document> collection) public List<IndexInfo> doInCollection(MongoCollection<Document> collection)
throws MongoException, DataAccessException { throws MongoException, DataAccessException {
@ -124,7 +123,7 @@ public class DefaultIndexOperations implements IndexOperations {
while (cursor.hasNext()) { while (cursor.hasNext()) {
Document ix = cursor.next(); Document ix = cursor.next();
IndexInfo indexInfo = IndexConverters.DOCUMENT_INDEX_INFO.convert(ix); IndexInfo indexInfo = IndexConverters.documentToIndexInfoConverter().convert(ix);
indexInfoList.add(indexInfo); indexInfoList.add(indexInfo);
} }

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java

@ -62,7 +62,7 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
if (indexOptions != null) { if (indexOptions != null) {
return collection.createIndex(indexDefinition.getIndexKeys(), return collection.createIndex(indexDefinition.getIndexKeys(),
IndexConverters.DEFINITION_TO_MONGO_INDEX_OPTIONS.convert(indexDefinition)); IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition));
} }
return collection.createIndex(indexDefinition.getIndexKeys()); return collection.createIndex(indexDefinition.getIndexKeys());
@ -96,7 +96,7 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
ListIndexesPublisher<Document> indexesPublisher = collection.listIndexes(Document.class); ListIndexesPublisher<Document> indexesPublisher = collection.listIndexes(Document.class);
return Flux.from(indexesPublisher).map(t -> IndexConverters.DOCUMENT_INDEX_INFO.convert(t)); return Flux.from(indexesPublisher).map(IndexConverters.documentToIndexInfoConverter()::convert);
}); });
} }
} }

26
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java

@ -35,14 +35,15 @@ import com.mongodb.client.model.IndexOptions;
/** /**
* {@link Converter Converters} for index-related MongoDB documents/types. * {@link Converter Converters} for index-related MongoDB documents/types.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0 * @since 2.0
*/ */
abstract class IndexConverters { abstract class IndexConverters {
public final static Converter<IndexDefinition, IndexOptions> DEFINITION_TO_MONGO_INDEX_OPTIONS; private static final Converter<IndexDefinition, IndexOptions> DEFINITION_TO_MONGO_INDEX_OPTIONS;
public final static Converter<Document, IndexInfo> DOCUMENT_INDEX_INFO; private static final Converter<Document, IndexInfo> DOCUMENT_INDEX_INFO;
private static final Double ONE = Double.valueOf(1); private static final Double ONE = Double.valueOf(1);
private static final Double MINUS_ONE = Double.valueOf(-1); private static final Double MINUS_ONE = Double.valueOf(-1);
@ -58,6 +59,14 @@ abstract class IndexConverters {
} }
static Converter<IndexDefinition, IndexOptions> indexDefinitionToIndexOptionsConverter() {
return DEFINITION_TO_MONGO_INDEX_OPTIONS;
}
static Converter<Document, IndexInfo> documentToIndexInfoConverter() {
return DOCUMENT_INDEX_INFO;
}
private static Converter<IndexDefinition, IndexOptions> getIndexDefinitionIndexOptionsConverter() { private static Converter<IndexDefinition, IndexOptions> getIndexDefinitionIndexOptionsConverter() {
return indexDefinition -> { return indexDefinition -> {
@ -115,14 +124,14 @@ abstract class IndexConverters {
private static Converter<Document, IndexInfo> getDocumentIndexInfoConverter() { private static Converter<Document, IndexInfo> getDocumentIndexInfoConverter() {
return ix -> { return ix -> {
Document keyDbObject = (Document) ix.get("key"); Document keyDocument = (Document) ix.get("key");
int numberOfElements = keyDbObject.keySet().size(); int numberOfElements = keyDocument.keySet().size();
List<IndexField> indexFields = new ArrayList<IndexField>(numberOfElements); List<IndexField> indexFields = new ArrayList<IndexField>(numberOfElements);
for (String key : keyDbObject.keySet()) { for (String key : keyDocument.keySet()) {
Object value = keyDbObject.get(key); Object value = keyDocument.get(key);
if (TWO_D_IDENTIFIERS.contains(value)) { if (TWO_D_IDENTIFIERS.contains(value)) {
indexFields.add(IndexField.geo(key)); indexFields.add(IndexField.geo(key));
@ -148,11 +157,10 @@ abstract class IndexConverters {
String name = ix.get("name").toString(); String name = ix.get("name").toString();
boolean unique = ix.containsKey("unique") ? (Boolean) ix.get("unique") : false; boolean unique = ix.containsKey("unique") ? (Boolean) ix.get("unique") : false;
boolean dropDuplicates = ix.containsKey("dropDups") ? (Boolean) ix.get("dropDups") : false;
boolean sparse = ix.containsKey("sparse") ? (Boolean) ix.get("sparse") : false; boolean sparse = ix.containsKey("sparse") ? (Boolean) ix.get("sparse") : false;
String language = ix.containsKey("default_language") ? (String) ix.get("default_language") : ""; String language = ix.containsKey("default_language") ? (String) ix.get("default_language") : "";
return new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language); return new IndexInfo(indexFields, name, unique, sparse, language);
}; };
} }
} }

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexOperations.java

@ -35,7 +35,7 @@ public interface IndexOperations {
* *
* @param indexDefinition must not be {@literal null}. * @param indexDefinition must not be {@literal null}.
*/ */
void ensureIndex(IndexDefinition indexDefinition); String ensureIndex(IndexDefinition indexDefinition);
/** /**
* Drops an index from this collection. * Drops an index from this collection.

66
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexOperationsAdapter.java

@ -0,0 +1,66 @@
/*
* 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 java.util.List;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.util.Assert;
/**
* Adapter for creating synchronous {@link IndexOperations}.
*
* @author Christoph Strobl
* @since 2.0
*/
public interface IndexOperationsAdapter extends IndexOperations {
/**
* Obtain a blocking variant of {@link IndexOperations} wrapping {@link ReactiveIndexOperations}.
*
* @param reactiveIndexOperations must not be {@literal null}.
* @return never {@literal null}
*/
static IndexOperationsAdapter blocking(ReactiveIndexOperations reactiveIndexOperations) {
Assert.notNull(reactiveIndexOperations, "ReactiveIndexOperations must not be null!");
return new IndexOperationsAdapter() {
@Override
public String ensureIndex(IndexDefinition indexDefinition) {
return reactiveIndexOperations.ensureIndex(indexDefinition).block();
}
@Override
public void dropIndex(String name) {
reactiveIndexOperations.dropIndex(name).block();
}
@Override
public void dropAllIndexes() {
reactiveIndexOperations.dropAllIndexes().block();
}
@Override
public List<IndexInfo> getIndexInfo() {
return reactiveIndexOperations.getIndexInfo().collectList().block();
}
};
}
}

3
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexOperationsProvider.java

@ -16,11 +16,10 @@
package org.springframework.data.mongodb.core; package org.springframework.data.mongodb.core;
import org.springframework.dao.support.PersistenceExceptionTranslator;
/** /**
* TODO: Revisit for a better pattern. * TODO: Revisit for a better pattern.
* @author Mark Paluch * @author Mark Paluch
* @since 2.0
*/ */
public interface IndexOperationsProvider { public interface IndexOperationsProvider {

5
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveCollectionCallback.java

@ -22,6 +22,11 @@ import com.mongodb.reactivestreams.client.MongoCollection;
import org.bson.Document; import org.bson.Document;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
/**
* @author Mark Paluch
* @param <T>
* @since 2.0
*/
public interface ReactiveCollectionCallback<T> { public interface ReactiveCollectionCallback<T> {
Publisher<T> doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException; Publisher<T> doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException;

5
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveDatabaseCallback.java

@ -21,6 +21,11 @@ import com.mongodb.MongoException;
import com.mongodb.reactivestreams.client.MongoDatabase; import com.mongodb.reactivestreams.client.MongoDatabase;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
/**
* @author Mark Paluch
* @param <T>
* @since 2.0
*/
public interface ReactiveDatabaseCallback<T> { public interface ReactiveDatabaseCallback<T> {
Publisher<T> doInDB(MongoDatabase db) throws MongoException, DataAccessException; Publisher<T> doInDB(MongoDatabase db) throws MongoException, DataAccessException;

6
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveIndexOperations.java

@ -15,6 +15,8 @@
*/ */
package org.springframework.data.mongodb.core; 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.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo; import org.springframework.data.mongodb.core.index.IndexInfo;
@ -25,7 +27,8 @@ import reactor.core.publisher.Mono;
* Index operations on a collection. * Index operations on a collection.
* *
* @author Mark Paluch * @author Mark Paluch
* @since 1.11 * @author Christoph Strobl
* @since 2.0
*/ */
public interface ReactiveIndexOperations { public interface ReactiveIndexOperations {
@ -55,4 +58,5 @@ public interface ReactiveIndexOperations {
* @return index information on the collection * @return index information on the collection
*/ */
Flux<IndexInfo> getIndexInfo(); Flux<IndexInfo> getIndexInfo();
} }

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientFactoryBean.java

@ -29,6 +29,8 @@ import com.mongodb.reactivestreams.client.MongoClients;
* Convenient factory for configuring a reactive streams {@link MongoClient}. * Convenient factory for configuring a reactive streams {@link MongoClient}.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/ */
public class ReactiveMongoClientFactoryBean extends AbstractFactoryBean<MongoClient> public class ReactiveMongoClientFactoryBean extends AbstractFactoryBean<MongoClient>
implements PersistenceExceptionTranslator { implements PersistenceExceptionTranslator {
@ -122,6 +124,6 @@ public class ReactiveMongoClientFactoryBean extends AbstractFactoryBean<MongoCli
@Override @Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) { public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return DEFAULT_EXCEPTION_TRANSLATOR.translateExceptionIfPossible(ex); return exceptionTranslator.translateExceptionIfPossible(ex);
} }
} }

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientSettingsFactoryBean.java

@ -38,7 +38,7 @@ import com.mongodb.connection.StreamFactoryFactory;
* A factory bean for construction of a {@link MongoClientSettings} instance to be used with the async MongoDB driver. * A factory bean for construction of a {@link MongoClientSettings} instance to be used with the async MongoDB driver.
* *
* @author Mark Paluch * @author Mark Paluch
* @since 1.7 * @since 2.0
*/ */
public class ReactiveMongoClientSettingsFactoryBean extends AbstractFactoryBean<MongoClientSettings> { public class ReactiveMongoClientSettingsFactoryBean extends AbstractFactoryBean<MongoClientSettings> {

88
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoDatabaseHolder.java

@ -1,88 +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.MongoDatabase;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.transaction.support.ResourceHolderSupport;
import org.springframework.util.Assert;
/**
* @author Mark Paluch
*/
class ReactiveMongoDatabaseHolder extends ResourceHolderSupport {
private static final Object DEFAULT_KEY = new Object();
private final Map<Object, MongoDatabase> dbMap = new ConcurrentHashMap<Object, MongoDatabase>();
public ReactiveMongoDatabaseHolder(MongoDatabase db) {
addMongoDatabase(db);
}
public ReactiveMongoDatabaseHolder(Object key, MongoDatabase db) {
addMongoDatabase(key, db);
}
public MongoDatabase getMongoDatabase() {
return getMongoDatabase(DEFAULT_KEY);
}
public MongoDatabase getMongoDatabase(Object key) {
return this.dbMap.get(key);
}
public MongoDatabase getAnyMongoDatabase() {
if (!this.dbMap.isEmpty()) {
return this.dbMap.values().iterator().next();
}
return null;
}
public void addMongoDatabase(MongoDatabase session) {
addMongoDatabase(DEFAULT_KEY, session);
}
public void addMongoDatabase(Object key, MongoDatabase session) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(session, "DB must not be null");
this.dbMap.put(key, session);
}
public MongoDatabase removeMongoDatabase(Object key) {
return this.dbMap.remove(key);
}
public boolean containsMongoDatabase(MongoDatabase session) {
return this.dbMap.containsValue(session);
}
public boolean isEmpty() {
return this.dbMap.isEmpty();
}
public boolean doesNotHoldNonDefaultMongoDatabase() {
synchronized (this.dbMap) {
return this.dbMap.isEmpty() || (this.dbMap.size() == 1 && this.dbMap.containsKey(DEFAULT_KEY));
}
}
}

113
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoDbUtils.java

@ -17,24 +17,17 @@ package org.springframework.data.mongodb.core;
import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoDatabase; import com.mongodb.reactivestreams.client.MongoDatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.util.MongoClientVersion;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/** /**
* Helper class featuring helper methods for internal MongoDb classes. Mainly intended for internal use within the * Helper class featuring helper methods for internal MongoDb classes. Mainly intended for internal use within the
* framework. * framework.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/ */
public abstract class ReactiveMongoDbUtils { public abstract class ReactiveMongoDbUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveMongoDbUtils.class);
/** /**
* Private constructor to prevent instantiation. * Private constructor to prevent instantiation.
*/ */
@ -42,109 +35,17 @@ public abstract class ReactiveMongoDbUtils {
/** /**
* Obtains a {@link MongoDatabase} connection for the given {@link MongoClient} instance and database name * 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 mongo the {@link MongoClient} instance, must not be {@literal null}.
* @param databaseName the database name, must not be {@literal null} or empty. * @param databaseName the database name, must not be {@literal null} or empty.
* @return the {@link MongoDatabase} connection * @return the {@link MongoDatabase} connection
*/ */
public static MongoDatabase getMongoDatabase(MongoClient mongo, String databaseName) { public static MongoDatabase getMongoDatabase(MongoClient mongo, String databaseName) {
return doGetMongoDatabase(mongo, databaseName, UserCredentials.NO_CREDENTIALS, true, databaseName); return doGetMongoDatabase(mongo, databaseName, true);
} }
private static MongoDatabase doGetMongoDatabase(MongoClient mongo, String databaseName, UserCredentials credentials, private static MongoDatabase doGetMongoDatabase(MongoClient mongo, String databaseName, boolean allowCreate) {
boolean allowCreate, String authenticationDatabaseName) { return mongo.getDatabase(databaseName);
ReactiveMongoDatabaseHolder dbHolder = (ReactiveMongoDatabaseHolder) TransactionSynchronizationManager
.getResource(mongo);
// Do we have a populated holder and TX sync active?
if (dbHolder != null && !dbHolder.isEmpty() && TransactionSynchronizationManager.isSynchronizationActive()) {
MongoDatabase db = dbHolder.getMongoDatabase(databaseName);
// DB found but not yet synchronized
if (db != null && !dbHolder.isSynchronizedWithTransaction()) {
LOGGER.debug("Registering Spring transaction synchronization for existing MongoDB {}.", databaseName);
TransactionSynchronizationManager.registerSynchronization(new MongoSynchronization(dbHolder, mongo));
dbHolder.setSynchronizedWithTransaction(true);
}
if (db != null) {
return db;
}
}
// Lookup fresh database instance
LOGGER.debug("Getting Mongo Database name=[{}]", databaseName);
MongoDatabase db = mongo.getDatabase(databaseName);
// TX sync active, bind new database to thread
if (TransactionSynchronizationManager.isSynchronizationActive()) {
LOGGER.debug("Registering Spring transaction synchronization for MongoDB instance {}.", databaseName);
ReactiveMongoDatabaseHolder holderToUse = dbHolder;
if (holderToUse == null) {
holderToUse = new ReactiveMongoDatabaseHolder(databaseName, db);
} else {
holderToUse.addMongoDatabase(databaseName, db);
}
// synchronize holder only if not yet synchronized
if (!holderToUse.isSynchronizedWithTransaction()) {
TransactionSynchronizationManager.registerSynchronization(new MongoSynchronization(holderToUse, mongo));
holderToUse.setSynchronizedWithTransaction(true);
}
if (holderToUse != dbHolder) {
TransactionSynchronizationManager.bindResource(mongo, holderToUse);
}
}
// Check whether we are allowed to return the DB.
if (!allowCreate && !isDBTransactional(db, mongo)) {
throw new IllegalStateException(
"No Mongo DB bound to thread, " + "and configuration does not allow creation of non-transactional one here");
}
return db;
} }
/**
* Return whether the given DB instance is transactional, that is, bound to the current thread by Spring's transaction
* facilities.
*
* @param db the DB to check
* @param mongoClient the Mongo instance that the DB was created with (may be <code>null</code>)
* @return whether the DB is transactional
*/
public static boolean isDBTransactional(MongoDatabase db, MongoClient mongoClient) {
if (mongoClient == null) {
return false;
}
ReactiveMongoDatabaseHolder dbHolder = (ReactiveMongoDatabaseHolder) TransactionSynchronizationManager
.getResource(mongoClient);
return dbHolder != null && dbHolder.containsMongoDatabase(db);
}
/**
* Check if credentials present. In case we're using a mongo-java-driver version 3 or above we do not have the need
* for authentication as the auth data has to be provided within the MongoClient
*
* @param credentials
* @return
*/
private static boolean requiresAuthDbAuthentication(UserCredentials credentials) {
if (credentials == null || !credentials.hasUsername()) {
return false;
}
return !MongoClientVersion.isMongo3Driver();
}
} }

17
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java

@ -21,7 +21,6 @@ import org.bson.Document;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription; import org.reactivestreams.Subscription;
import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.BasicQuery;
@ -46,6 +45,7 @@ import reactor.core.publisher.Mono;
* {@link ReactiveMongoOperations} is deferred until subscriber subscribes to the {@link Publisher}. * {@link ReactiveMongoOperations} is deferred until subscriber subscribes to the {@link Publisher}.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @see Flux * @see Flux
* @see Mono * @see Mono
* @see http://projectreactor.io/docs/ * @see http://projectreactor.io/docs/
@ -67,20 +67,6 @@ public interface ReactiveMongoOperations {
*/ */
ReactiveIndexOperations reactiveIndexOps(Class<?> entityClass); ReactiveIndexOperations reactiveIndexOps(Class<?> entityClass);
/**
* Returns the operations that can be performed on indexes
*
* @return index operations on the named collection
*/
IndexOperations indexOps(String collectionName);
/**
* Returns the operations that can be performed on indexes
*
* @return index operations on the named collection associated with the given entity class
*/
IndexOperations indexOps(Class<?> entityClass);
/** /**
* Execute the a MongoDB command expressed as a JSON string. This will call the method JSON.parse that is part of the * Execute the a MongoDB command expressed as a JSON string. This will call the method JSON.parse that is part of the
* MongoDB driver to convert the JSON string to a DBObject. Any errors that result from executing this command will be * MongoDB driver to convert the JSON string to a DBObject. Any errors that result from executing this command will be
@ -955,4 +941,5 @@ public interface ReactiveMongoOperations {
* @return * @return
*/ */
MongoConverter getConverter(); MongoConverter getConverter();
} }

160
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

@ -60,7 +60,6 @@ import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate.DocumentCallback;
import org.springframework.data.mongodb.core.convert.DbRefProxyHandler; import org.springframework.data.mongodb.core.convert.DbRefProxyHandler;
import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DbRefResolverCallback; import org.springframework.data.mongodb.core.convert.DbRefResolverCallback;
@ -69,8 +68,6 @@ import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.MongoWriter; import org.springframework.data.mongodb.core.convert.MongoWriter;
import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.convert.UpdateMapper; import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher; import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@ -128,11 +125,12 @@ import reactor.util.function.Tuple2;
* extract results. This class executes BSON queries or updates, initiating iteration over {@link FindPublisher} and * extract results. This class executes BSON queries or updates, initiating iteration over {@link FindPublisher} and
* catching MongoDB exceptions and translating them to the generic, more informative exception hierarchy defined in the * catching MongoDB exceptions and translating them to the generic, more informative exception hierarchy defined in the
* org.springframework.dao package. Can be used within a service implementation via direct instantiation with a * org.springframework.dao package. Can be used within a service implementation via direct instantiation with a
* {@link SimpleReactiveMongoDatabaseFactory} reference, or get prepared in an application context and given to services as * {@link SimpleReactiveMongoDatabaseFactory} reference, or get prepared in an application context and given to services
* bean reference. Note: The {@link SimpleReactiveMongoDatabaseFactory} should always be configured as a bean in the * as bean reference. Note: The {@link SimpleReactiveMongoDatabaseFactory} should always be configured as a bean in the
* application context, in the first case given to the service directly, in the second case to the prepared template. * application context, in the first case given to the service directly, in the second case to the prepared template.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0 * @since 2.0
*/ */
public class ReactiveMongoTemplate implements ReactiveMongoOperations, ApplicationContextAware { public class ReactiveMongoTemplate implements ReactiveMongoOperations, ApplicationContextAware {
@ -211,7 +209,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
if (null != mappingContext && mappingContext instanceof MongoMappingContext) { if (null != mappingContext && mappingContext instanceof MongoMappingContext) {
indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext, indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext,
new BlockingIndexOptionsProvider(this)); (collectionName) -> IndexOperationsAdapter.blocking(reactiveIndexOps(collectionName)));
eventPublisher = new MongoMappingEventPublisher(indexCreator); eventPublisher = new MongoMappingEventPublisher(indexCreator);
if (mappingContext instanceof ApplicationEventPublisherAware) { if (mappingContext instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher);
@ -329,20 +327,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return new DefaultReactiveIndexOperations(this, determineCollectionName(entityClass)); return new DefaultReactiveIndexOperations(this, determineCollectionName(entityClass));
} }
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#reactiveIndexOps(java.lang.String)
*/
public IndexOperations indexOps(String collectionName) {
return new BlockingIndexOperations(new DefaultReactiveIndexOperations(this, collectionName));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#reactiveIndexOps(java.lang.Class)
*/
public IndexOperations indexOps(Class<?> entityClass) {
return new BlockingIndexOperations(new DefaultReactiveIndexOperations(this, determineCollectionName(entityClass)));
}
public String getCollectionName(Class<?> entityClass) { public String getCollectionName(Class<?> entityClass) {
return this.determineCollectionName(entityClass); return this.determineCollectionName(entityClass);
} }
@ -480,7 +464,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.Class, org.springframework.data.mongodb.core.CollectionOptions) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.Class, org.springframework.data.mongodb.core.CollectionOptions)
*/ */
public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass, public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass,
CollectionOptions collectionOptions) { CollectionOptions collectionOptions) {
return createCollection(determineCollectionName(entityClass), collectionOptions); return createCollection(determineCollectionName(entityClass), collectionOptions);
} }
@ -495,7 +479,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.String, org.springframework.data.mongodb.core.CollectionOptions) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.String, org.springframework.data.mongodb.core.CollectionOptions)
*/ */
public Mono<MongoCollection<Document>> createCollection(final String collectionName, public Mono<MongoCollection<Document>> createCollection(final String collectionName,
final CollectionOptions collectionOptions) { final CollectionOptions collectionOptions) {
return doCreateCollection(collectionName, convertToCreateCollectionOptions(collectionOptions)); return doCreateCollection(collectionName, convertToCreateCollectionOptions(collectionOptions));
} }
@ -730,7 +714,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class, java.lang.String) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class, java.lang.String)
*/ */
public <T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass, public <T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
String collectionName) { String collectionName) {
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(), return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), entityClass, update, options); getMappedSortObject(query, entityClass), entityClass, update, options);
} }
@ -777,7 +761,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
final Document Document = query == null ? null final Document Document = query == null ? null
: queryMapper.getMappedObject(query.getQueryObject(), : queryMapper.getMappedObject(query.getQueryObject(),
entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); entityClass == null ? null : mappingContext.getPersistentEntity(entityClass));
return collection.count(Document); return collection.count(Document);
}); });
@ -904,7 +888,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
} }
protected <T> Flux<T> doInsertBatch(final String collectionName, final Collection<? extends T> batchToSave, protected <T> Flux<T> doInsertBatch(final String collectionName, final Collection<? extends T> batchToSave,
final MongoWriter<Object> writer) { final MongoWriter<Object> writer) {
Assert.notNull(writer); Assert.notNull(writer);
@ -1094,7 +1078,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
} }
private MongoCollection<Document> prepareCollection(MongoCollection<Document> collection, private MongoCollection<Document> prepareCollection(MongoCollection<Document> collection,
WriteConcern writeConcernToUse) { WriteConcern writeConcernToUse) {
MongoCollection<Document> collectionToUse = collection; MongoCollection<Document> collectionToUse = collection;
if (writeConcernToUse != null) { if (writeConcernToUse != null) {
@ -1194,12 +1178,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class, java.lang.String) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class, java.lang.String)
*/ */
public Mono<UpdateResult> updateMulti(final Query query, final Update update, Class<?> entityClass, public Mono<UpdateResult> updateMulti(final Query query, final Update update, Class<?> entityClass,
String collectionName) { String collectionName) {
return doUpdate(collectionName, query, update, entityClass, false, true); return doUpdate(collectionName, query, update, entityClass, false, true);
} }
protected Mono<UpdateResult> doUpdate(final String collectionName, final Query query, final Update update, protected Mono<UpdateResult> doUpdate(final String collectionName, final Query query, final Update update,
final Class<?> entityClass, final boolean upsert, final boolean multi) { final Class<?> entityClass, final boolean upsert, final boolean multi) {
MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass); MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);
@ -1413,7 +1397,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
} }
protected <T> Mono<DeleteResult> doRemove(final String collectionName, final Query query, protected <T> Mono<DeleteResult> doRemove(final String collectionName, final Query query,
final Class<T> entityClass) { final Class<T> entityClass) {
if (query == null) { if (query == null) {
throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null!"); throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null!");
@ -1437,7 +1421,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Remove using query: {} in collection: {}.", LOGGER.debug("Remove using query: {} in collection: {}.",
new Object[]{serializeToJsonSafely(dboq), collectionName}); new Object[] { serializeToJsonSafely(dboq), collectionName });
} }
return collectionToUse.deleteMany(dboq); return collectionToUse.deleteMany(dboq);
@ -1534,7 +1518,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @return the collection that was created * @return the collection that was created
*/ */
protected Mono<MongoCollection<Document>> doCreateCollection(final String collectionName, protected Mono<MongoCollection<Document>> doCreateCollection(final String collectionName,
final CreateCollectionOptions collectionOptions) { final CreateCollectionOptions collectionOptions) {
return createMono(db -> db.createCollection(collectionName, collectionOptions)).map(success -> { return createMono(db -> db.createCollection(collectionName, collectionOptions)).map(success -> {
@ -1596,17 +1580,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @param fields the document that specifies the fields to be returned. * @param fields the document that specifies the fields to be returned.
* @param entityClass the parameterized type of the returned list. * @param entityClass the parameterized type of the returned list.
* @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply
* limits, skips and so on). * limits, skips and so on).
* @return the {@link List} of converted objects. * @return the {@link List} of converted objects.
*/ */
protected <T> Flux<T> doFind(String collectionName, Document query, Document fields, Class<T> entityClass, protected <T> Flux<T> doFind(String collectionName, Document query, Document fields, Class<T> entityClass,
FindPublisherPreparer preparer) { FindPublisherPreparer preparer) {
return doFind(collectionName, query, fields, entityClass, preparer, return doFind(collectionName, query, fields, entityClass, preparer,
new ReadDocumentCallback<T>(mongoConverter, entityClass, collectionName)); new ReadDocumentCallback<T>(mongoConverter, entityClass, collectionName));
} }
protected <S, T> Flux<T> doFind(String collectionName, Document query, Document fields, Class<S> entityClass, protected <S, T> Flux<T> doFind(String collectionName, Document query, Document fields, Class<S> entityClass,
FindPublisherPreparer preparer, DocumentCallback<T> objectCallback) { FindPublisherPreparer preparer, DocumentCallback<T> objectCallback) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass); MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
@ -1654,7 +1638,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @return the List of converted objects. * @return the List of converted objects.
*/ */
protected <T> Mono<T> doFindAndRemove(String collectionName, Document query, Document fields, Document sort, protected <T> Mono<T> doFindAndRemove(String collectionName, Document query, Document fields, Document sort,
Class<T> entityClass) { Class<T> entityClass) {
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("findAndRemove using query: %s fields: %s sort: %s for class: %s in collection: %s", LOGGER.debug(String.format("findAndRemove using query: %s fields: %s sort: %s for class: %s in collection: %s",
@ -1668,7 +1652,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
} }
protected <T> Mono<T> doFindAndModify(String collectionName, Document query, Document fields, Document sort, protected <T> Mono<T> doFindAndModify(String collectionName, Document query, Document fields, Document sort,
Class<T> entityClass, Update update, FindAndModifyOptions options) { Class<T> entityClass, Update update, FindAndModifyOptions options) {
FindAndModifyOptions optionsToUse; FindAndModifyOptions optionsToUse;
if (options == null) { if (options == null) {
@ -1687,13 +1671,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
Document mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity); Document mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER LOGGER.debug(String.format(
.debug( "findAndModify using query: %s fields: %s sort: %s for class: %s and update: %s " + "in collection: %s",
String.format( serializeToJsonSafely(mappedQuery), fields, sort, entityClass, serializeToJsonSafely(mappedUpdate),
"findAndModify using query: %s fields: %s sort: %s for class: %s and update: %s " collectionName));
+ "in collection: %s",
serializeToJsonSafely(mappedQuery), fields, sort, entityClass, serializeToJsonSafely(mappedUpdate),
collectionName));
} }
return executeFindOneInternal(new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate, optionsToUse), return executeFindOneInternal(new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate, optionsToUse),
@ -1828,7 +1809,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @return * @return
*/ */
private <T> Mono<T> executeFindOneInternal(ReactiveCollectionCallback<Document> collectionCallback, private <T> Mono<T> executeFindOneInternal(ReactiveCollectionCallback<Document> collectionCallback,
DocumentCallback<T> objectCallback, String collectionName) { DocumentCallback<T> objectCallback, String collectionName) {
return createMono(collectionName, return createMono(collectionName,
collection -> Mono.from(collectionCallback.doInCollection(collection)).map(objectCallback::doWith)); collection -> Mono.from(collectionCallback.doInCollection(collection)).map(objectCallback::doWith));
@ -1846,14 +1827,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* *
* @param collectionCallback the callback to retrieve the {@link FindPublisher} with, must not be {@literal null}. * @param collectionCallback the callback to retrieve the {@link FindPublisher} with, must not be {@literal null}.
* @param preparer the {@link FindPublisherPreparer} to potentially modify the {@link FindPublisher} before iterating * @param preparer the {@link FindPublisherPreparer} to potentially modify the {@link FindPublisher} before iterating
* over it, may be {@literal null} * over it, may be {@literal null}
* @param objectCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type, must * @param objectCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type, must
* not be {@literal null}. * not be {@literal null}.
* @param collectionName the collection to be queried, must not be {@literal null}. * @param collectionName the collection to be queried, must not be {@literal null}.
* @return * @return
*/ */
private <T> Flux<T> executeFindMultiInternal(ReactiveCollectionQueryCallback<Document> collectionCallback, private <T> Flux<T> executeFindMultiInternal(ReactiveCollectionQueryCallback<Document> collectionCallback,
FindPublisherPreparer preparer, DocumentCallback<T> objectCallback, String collectionName) { FindPublisherPreparer preparer, DocumentCallback<T> objectCallback, String collectionName) {
return createFlux(collectionName, collection -> { return createFlux(collectionName, collection -> {
@ -1921,7 +1902,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @return * @return
*/ */
private static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex, private static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex,
PersistenceExceptionTranslator exceptionTranslator) { PersistenceExceptionTranslator exceptionTranslator) {
RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex); RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex);
return resolved == null ? ex : resolved; return resolved == null ? ex : resolved;
} }
@ -2137,7 +2118,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
private final FindAndModifyOptions options; private final FindAndModifyOptions options;
FindAndModifyCallback(Document query, Document fields, Document sort, Document update, FindAndModifyCallback(Document query, Document fields, Document sort, Document update,
FindAndModifyOptions options) { FindAndModifyOptions options) {
this.query = query; this.query = query;
this.fields = fields; this.fields = fields;
@ -2160,7 +2141,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
} }
private FindOneAndUpdateOptions convertToFindOneAndUpdateOptions(FindAndModifyOptions options, Document fields, private FindOneAndUpdateOptions convertToFindOneAndUpdateOptions(FindAndModifyOptions options, Document fields,
Document sort) { Document sort) {
FindOneAndUpdateOptions result = new FindOneAndUpdateOptions(); FindOneAndUpdateOptions result = new FindOneAndUpdateOptions();
@ -2299,29 +2280,29 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
this.type = type; this.type = type;
} }
public <T> FindPublisher<T> prepare(FindPublisher<T> cursor) { public <T> FindPublisher<T> prepare(FindPublisher<T> findPublisher) {
if (query == null) { if (query == null) {
return cursor; return findPublisher;
} }
if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null
&& !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) { && !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) {
return cursor; return findPublisher;
} }
FindPublisher<T> cursorToUse = cursor; FindPublisher<T> findPublisherToUse = findPublisher;
try { try {
if (query.getSkip() > 0) { if (query.getSkip() > 0) {
cursorToUse = cursorToUse.skip(query.getSkip()); findPublisherToUse = findPublisherToUse.skip(query.getSkip());
} }
if (query.getLimit() > 0) { if (query.getLimit() > 0) {
cursorToUse = cursorToUse.limit(query.getLimit()); findPublisherToUse = findPublisherToUse.limit(query.getLimit());
} }
if (query.getSortObject() != null) { if (query.getSortObject() != null) {
Document sortDbo = type != null ? getMappedSortObject(query, type) : query.getSortObject(); Document sort = type != null ? getMappedSortObject(query, type) : query.getSortObject();
cursorToUse = cursorToUse.sort(sortDbo); findPublisherToUse = findPublisherToUse.sort(sort);
} }
BasicDBObject modifiers = new BasicDBObject(); BasicDBObject modifiers = new BasicDBObject();
@ -2336,13 +2317,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
} }
if (!modifiers.isEmpty()) { if (!modifiers.isEmpty()) {
cursorToUse = cursorToUse.modifiers(modifiers); findPublisherToUse = findPublisherToUse.modifiers(modifiers);
} }
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw potentiallyConvertRuntimeException(e, exceptionTranslator); throw potentiallyConvertRuntimeException(e, exceptionTranslator);
} }
return cursorToUse; return findPublisherToUse;
} }
} }
@ -2353,8 +2334,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
} }
@Override @Override
public <T> FindPublisher<T> prepare(FindPublisher<T> cursor) { public <T> FindPublisher<T> prepare(FindPublisher<T> findPublisher) {
return super.prepare(cursor.cursorType(CursorType.TailableAwait)); return super.prepare(findPublisher.cursorType(CursorType.TailableAwait));
} }
} }
@ -2371,13 +2352,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
@Override @Override
public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback, public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
DbRefProxyHandler proxyHandler) { DbRefProxyHandler proxyHandler) {
return null; return null;
} }
@Override @Override
public DBRef createDbRef(org.springframework.data.mongodb.core.mapping.DBRef annotation, public DBRef createDbRef(org.springframework.data.mongodb.core.mapping.DBRef annotation,
MongoPersistentEntity<?> entity, Object id) { MongoPersistentEntity<?> entity, Object id) {
return null; return null;
} }
@ -2391,55 +2372,4 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return null; return null;
} }
} }
/**
* @author Mark Paluch
*/
private static class BlockingIndexOptionsProvider implements IndexOperationsProvider {
private final ReactiveMongoOperations mongoOperations;
public BlockingIndexOptionsProvider(ReactiveMongoOperations mongoOperations) {
this.mongoOperations = mongoOperations;
}
@Override
public IndexOperations indexOps(String collectionName) {
return new BlockingIndexOperations(mongoOperations.reactiveIndexOps(collectionName));
}
}
/**
* Blocking {@link IndexOperations} overlay to synchronize calls.
*
* @author Mark Paluch
*/
private static class BlockingIndexOperations implements IndexOperations {
private final ReactiveIndexOperations reactiveIndexOperations;
public BlockingIndexOperations(ReactiveIndexOperations reactiveIndexOperations) {
this.reactiveIndexOperations = reactiveIndexOperations;
}
@Override
public void ensureIndex(IndexDefinition indexDefinition) {
reactiveIndexOperations.ensureIndex(indexDefinition).block();
}
@Override
public void dropIndex(String name) {
reactiveIndexOperations.dropIndex(name).block();
}
@Override
public void dropAllIndexes() {
reactiveIndexOperations.dropAllIndexes().block();
}
@Override
public List<IndexInfo> getIndexInfo() {
return reactiveIndexOperations.getIndexInfo().collectList().block();
}
}
} }

21
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java

@ -82,7 +82,6 @@ abstract class MongoConverters {
converters.add(DocumentToNamedMongoScriptConverter.INSTANCE); converters.add(DocumentToNamedMongoScriptConverter.INSTANCE);
converters.add(CurrencyToStringConverter.INSTANCE); converters.add(CurrencyToStringConverter.INSTANCE);
converters.add(StringToCurrencyConverter.INSTANCE); converters.add(StringToCurrencyConverter.INSTANCE);
converters.add(NumberToNumberConverterFactory.INSTANCE);
converters.add(AtomicIntegerToIntegerConverter.INSTANCE); converters.add(AtomicIntegerToIntegerConverter.INSTANCE);
converters.add(AtomicLongToLongConverter.INSTANCE); converters.add(AtomicLongToLongConverter.INSTANCE);
converters.add(LongToAtomicLongConverter.INSTANCE); converters.add(LongToAtomicLongConverter.INSTANCE);
@ -297,26 +296,6 @@ abstract class MongoConverters {
} }
} }
@ReadingConverter
public static enum PublisherToFluxConverter implements Converter<Publisher<?>, Flux<?>> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Flux<?> convert(Publisher<?> source) {
if(source instanceof Flux){
return (Flux<?>) source;
}
return Flux.from((Publisher<?>) source);
}
}
/** /**
* {@link Converter} implementation converting ISO 4217 {@link String} into {@link Currency}. * {@link Converter} implementation converting ISO 4217 {@link String} into {@link Currency}.
* *

15
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java

@ -34,17 +34,15 @@ public class IndexInfo {
private final String name; private final String name;
private final boolean unique; private final boolean unique;
private final boolean dropDuplicates;
private final boolean sparse; private final boolean sparse;
private final String language; private final String language;
public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse, public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean sparse,
String language) { String language) {
this.indexFields = Collections.unmodifiableList(indexFields); this.indexFields = Collections.unmodifiableList(indexFields);
this.name = name; this.name = name;
this.unique = unique; this.unique = unique;
this.dropDuplicates = dropDuplicates;
this.sparse = sparse; this.sparse = sparse;
this.language = language; this.language = language;
} }
@ -84,10 +82,6 @@ public class IndexInfo {
return unique; return unique;
} }
public boolean isDropDuplicates() {
return dropDuplicates;
}
public boolean isSparse() { public boolean isSparse() {
return sparse; return sparse;
} }
@ -102,8 +96,7 @@ public class IndexInfo {
@Override @Override
public String toString() { public String toString() {
return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", dropDuplicates=" return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", sparse=" + sparse + ", language=" + language + "]";
+ dropDuplicates + ", sparse=" + sparse + ", language=" + language + "]";
} }
@Override @Override
@ -111,7 +104,6 @@ public class IndexInfo {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + (dropDuplicates ? 1231 : 1237);
result = prime * result + ObjectUtils.nullSafeHashCode(indexFields); result = prime * result + ObjectUtils.nullSafeHashCode(indexFields);
result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + (sparse ? 1231 : 1237); result = prime * result + (sparse ? 1231 : 1237);
@ -132,9 +124,6 @@ public class IndexInfo {
return false; return false;
} }
IndexInfo other = (IndexInfo) obj; IndexInfo other = (IndexInfo) obj;
if (dropDuplicates != other.dropDuplicates) {
return false;
}
if (indexFields == null) { if (indexFields == null) {
if (other.indexFields != null) { if (other.indexFields != null) {
return false; return false;

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/InfiniteStream.java

@ -37,7 +37,7 @@ import reactor.core.Cancellation;
* <p> * <p>
* A stream that is no longer in use must be {@link Cancellation#dispose()} disposed} otherwise the streams will linger * A stream that is no longer in use must be {@link Cancellation#dispose()} disposed} otherwise the streams will linger
* and exhaust resources. * and exhaust resources.
* *
* @author Mark Paluch * @author Mark Paluch
* @see <a href="https://docs.mongodb.com/manual/core/tailable-cursors/">Tailable Cursors</a> * @see <a href="https://docs.mongodb.com/manual/core/tailable-cursors/">Tailable Cursors</a>
*/ */

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/config/EnableReactiveMongoRepositories.java

@ -37,7 +37,7 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key;
* Annotation to activate reactive MongoDB repositories. If no base package is configured through either * Annotation to activate reactive MongoDB repositories. If no base package is configured through either
* {@link #value()}, {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of * {@link #value()}, {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of
* annotated class. * annotated class.
* *
* @author Mark Paluch * @author Mark Paluch
* @since 2.0 * @since 2.0
*/ */

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/config/MongoRepositoryConfigurationExtension.java

@ -61,7 +61,7 @@ public class MongoRepositoryConfigurationExtension extends RepositoryConfigurati
return "MongoDB"; return "MongoDB";
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModulePrefix() * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModulePrefix()
*/ */
@ -152,7 +152,7 @@ public class MongoRepositoryConfigurationExtension extends RepositoryConfigurati
return repositoryConfigurations.stream().filter(configuration -> { return repositoryConfigurations.stream().filter(configuration -> {
Class<?> repositoryInterface = super.loadRepositoryInterface(configuration, loader); Class<?> repositoryInterface = loadRepositoryInterface(configuration, loader);
return !RepositoryType.isReactiveRepository(repositoryInterface); return !RepositoryType.isReactiveRepository(repositoryInterface);
}).collect(Collectors.toList()); }).collect(Collectors.toList());

7
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/config/ReactiveMongoRepositoriesRegistrar.java

@ -24,12 +24,13 @@ import org.springframework.data.repository.config.RepositoryConfigurationExtensi
/** /**
* Mongo-specific {@link ImportBeanDefinitionRegistrar}. * Mongo-specific {@link ImportBeanDefinitionRegistrar}.
* *
* @author Mark Paluch * @author Mark Paluch
* @since 2.0
*/ */
class ReactiveMongoRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { class ReactiveMongoRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation() * @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation()
*/ */
@ -38,7 +39,7 @@ class ReactiveMongoRepositoriesRegistrar extends RepositoryBeanDefinitionRegistr
return EnableReactiveMongoRepositories.class; return EnableReactiveMongoRepositories.class;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension() * @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension()
*/ */

11
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/config/ReactiveMongoRepositoryConfigurationExtension.java

@ -45,6 +45,8 @@ import org.w3c.dom.Element;
* Reactive {@link RepositoryConfigurationExtension} for MongoDB. * Reactive {@link RepositoryConfigurationExtension} for MongoDB.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/ */
public class ReactiveMongoRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport { public class ReactiveMongoRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport {
@ -161,11 +163,8 @@ public class ReactiveMongoRepositoryConfigurationExtension extends RepositoryCon
Collection<RepositoryConfiguration<T>> repositoryConfigurations = super.getRepositoryConfigurations(configSource, Collection<RepositoryConfiguration<T>> repositoryConfigurations = super.getRepositoryConfigurations(configSource,
loader, strictMatchesOnly); loader, strictMatchesOnly);
return repositoryConfigurations.stream().filter(configuration -> { return repositoryConfigurations.stream()
.filter(configuration -> RepositoryType.isReactiveRepository(loadRepositoryInterface(configuration, loader)))
Class<?> repositoryInterface = super.loadRepositoryInterface(configuration, loader); .collect(Collectors.toList());
return RepositoryType.isReactiveRepository(repositoryInterface);
}).collect(Collectors.toList());
} }
} }

18
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/config/RepositoryType.java

@ -16,8 +16,11 @@
package org.springframework.data.mongodb.repository.config; package org.springframework.data.mongodb.repository.config;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.repository.util.ReactiveWrappers; import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.util.ReflectionUtils;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
@ -25,6 +28,8 @@ import lombok.experimental.UtilityClass;
* Utility class to discover whether a repository interface uses reactive wrapper types. * Utility class to discover whether a repository interface uses reactive wrapper types.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/ */
@UtilityClass @UtilityClass
class RepositoryType { class RepositoryType {
@ -41,16 +46,9 @@ class RepositoryType {
return false; return false;
} }
Method[] methods = repositoryInterface.getMethods(); List<Method> reactiveMethods = new ArrayList<>();
ReflectionUtils.doWithMethods(repositoryInterface, reactiveMethods::add, RepositoryType::usesReactiveWrappers);
for (Method method : methods) { return !reactiveMethods.isEmpty();
if (usesReactiveWrappers(method)) {
return true;
}
}
return false;
} }
private static boolean usesReactiveWrappers(Method method) { private static boolean usesReactiveWrappers(Method method) {

29
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java

@ -44,22 +44,24 @@ import reactor.core.publisher.Mono;
* Base class for reactive {@link RepositoryQuery} implementations for MongoDB. * Base class for reactive {@link RepositoryQuery} implementations for MongoDB.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/ */
public abstract class AbstractReactiveMongoQuery implements RepositoryQuery { public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
private final MongoQueryMethod method; private final ReactiveMongoQueryMethod method;
private final ReactiveMongoOperations operations; private final ReactiveMongoOperations operations;
private final EntityInstantiators instantiators; private final EntityInstantiators instantiators;
/** /**
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and * Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
* {@link MongoOperations}. * {@link MongoOperations}.
* *
* @param method must not be {@literal null}. * @param method must not be {@literal null}.
* @param operations must not be {@literal null}. * @param operations must not be {@literal null}.
* @param conversionService must not be {@literal null}. * @param conversionService must not be {@literal null}.
*/ */
public AbstractReactiveMongoQuery(MongoQueryMethod method, ReactiveMongoOperations operations, public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations,
ConversionService conversionService) { ConversionService conversionService) {
Assert.notNull(method, "MongoQueryMethod must not be null!"); Assert.notNull(method, "MongoQueryMethod must not be null!");
@ -84,13 +86,8 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
*/ */
public Object execute(Object[] parameters) { public Object execute(Object[] parameters) {
boolean hasReactiveParameters = hasReactiveWrapperParameter(); return method.hasReactiveWrapperParameter() ? executeDeferred(parameters)
: execute(new MongoParametersParameterAccessor(method, parameters));
if (hasReactiveParameters) {
return executeDeferred(parameters);
}
return execute(new MongoParametersParameterAccessor(method, parameters));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -120,16 +117,6 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
return execution.execute(query, processor.getReturnedType().getDomainType(), collection); return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
} }
private boolean hasReactiveWrapperParameter() {
for (MongoParameters.MongoParameter mongoParameter : method.getParameters()) {
if (ReactiveWrapperConverters.supports(mongoParameter.getType())) {
return true;
}
}
return false;
}
/** /**
* Returns the execution instance to use. * Returns the execution instance to use.
* *
@ -153,7 +140,7 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
return new SlicedExecution(operations, accessor.getPageable()); return new SlicedExecution(operations, accessor.getPageable());
} else if (isInfiniteStream(method)) { } else if (isInfiniteStream(method)) {
return new TailExecution(operations, accessor.getPageable()); return new TailExecution(operations, accessor.getPageable());
} else if (method.isCollectionQuery()) { } else if (method.isCollectionQuery()) {
return new CollectionExecution(operations, accessor.getPageable()); return new CollectionExecution(operations, accessor.getPageable());
} else if (method.isPageQuery()) { } else if (method.isPageQuery()) {
return new PagedExecution(operations, accessor.getPageable()); return new PagedExecution(operations, accessor.getPageable());

24
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoParameterAccessor.java

@ -15,6 +15,9 @@
*/ */
package org.springframework.data.mongodb.repository.query; package org.springframework.data.mongodb.repository.query;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.repository.util.ReactiveWrappers; import org.springframework.data.repository.util.ReactiveWrappers;
@ -27,35 +30,34 @@ import reactor.core.publisher.MonoProcessor;
* to reactive parameter wrapper types upon creation. This class performs synchronization when acessing parameters. * to reactive parameter wrapper types upon creation. This class performs synchronization when acessing parameters.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/ */
class ReactiveMongoParameterAccessor extends MongoParametersParameterAccessor { class ReactiveMongoParameterAccessor extends MongoParametersParameterAccessor {
private final Object[] values; private final Object[] values;
private final MonoProcessor<?>[] subscriptions; private final List<MonoProcessor<?>> subscriptions;
public ReactiveMongoParameterAccessor(MongoQueryMethod method, Object[] values) { public ReactiveMongoParameterAccessor(MongoQueryMethod method, Object[] values) {
super(method, values); super(method, values);
this.values = values; this.values = values;
this.subscriptions = new MonoProcessor<?>[values.length]; this.subscriptions = new ArrayList<>(values.length);
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
Object value = values[i]; Object value = values[i];
if (value == null) { if (value == null || !ReactiveWrappers.supports(value.getClass())) {
continue; subscriptions.add(null);
}
if (!ReactiveWrappers.supports(value.getClass())) {
continue; continue;
} }
if (ReactiveWrappers.isSingleValueType(value.getClass())) { if (ReactiveWrappers.isSingleValueType(value.getClass())) {
subscriptions[i] = ReactiveWrapperConverters.toWrapper(value, Mono.class).subscribe(); subscriptions.add(ReactiveWrapperConverters.toWrapper(value, Mono.class).subscribe());
} else { } else {
subscriptions[i] = ReactiveWrapperConverters.toWrapper(value, Flux.class).collectList().subscribe(); subscriptions.add(ReactiveWrapperConverters.toWrapper(value, Flux.class).collectList().subscribe());
} }
} }
} }
@ -67,8 +69,8 @@ class ReactiveMongoParameterAccessor extends MongoParametersParameterAccessor {
@Override @Override
protected <T> T getValue(int index) { protected <T> T getValue(int index) {
if (subscriptions[index] != null) { if (subscriptions.get(index) != null) {
return (T) subscriptions[index].block(); return (T) subscriptions.get(index).block();
} }
return super.getValue(index); return super.getValue(index);

20
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java

@ -27,8 +27,10 @@ import org.springframework.data.geo.GeoResult;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.repository.query.MongoParameters.MongoParameter;
import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.repository.util.ReactiveWrappers; import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
@ -37,6 +39,7 @@ import org.springframework.data.util.TypeInformation;
* Reactive specific implementation of {@link MongoQueryMethod}. * Reactive specific implementation of {@link MongoQueryMethod}.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0 * @since 2.0
*/ */
public class ReactiveMongoQueryMethod extends MongoQueryMethod { public class ReactiveMongoQueryMethod extends MongoQueryMethod {
@ -147,4 +150,21 @@ public class ReactiveMongoQueryMethod extends MongoQueryMethod {
public boolean isStreamQuery() { public boolean isStreamQuery() {
return true; return true;
} }
/**
* Check if the given {@link org.springframework.data.repository.query.QueryMethod} receives a reactive parameter
* wrapper as one of its parameters.
*
* @return
*/
public boolean hasReactiveWrapperParameter() {
for (MongoParameter mongoParameter : getParameters()) {
if (ReactiveWrapperConverters.supports(mongoParameter.getType())) {
return true;
}
}
return false;
}
} }

16
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java

@ -15,6 +15,7 @@
*/ */
package org.springframework.data.mongodb.repository.query; package org.springframework.data.mongodb.repository.query;
import org.bson.Document;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.MongoTemplate;
@ -37,6 +38,8 @@ import com.mongodb.util.JSONParseException;
* Reactive PartTree {@link RepositoryQuery} implementation for Mongo. * Reactive PartTree {@link RepositoryQuery} implementation for Mongo.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/ */
public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery { public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
@ -52,7 +55,7 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
* @param mongoOperations must not be {@literal null}. * @param mongoOperations must not be {@literal null}.
* @param conversionService must not be {@literal null}. * @param conversionService must not be {@literal null}.
*/ */
public ReactivePartTreeMongoQuery(MongoQueryMethod method, ReactiveMongoOperations mongoOperations, ConversionService conversionService) { public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ConversionService conversionService) {
super(method, mongoOperations, conversionService); super(method, mongoOperations, conversionService);
@ -90,19 +93,14 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
query.addCriteria(textCriteria); query.addCriteria(textCriteria);
} }
String fieldSpec = this.getQueryMethod().getFieldSpecification(); String fieldSpec = getQueryMethod().getFieldSpecification();
if (!StringUtils.hasText(fieldSpec)) { if (!StringUtils.hasText(fieldSpec)) {
ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType(); ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType();
if (returnedType.isProjecting()) { if (returnedType.isProjecting()) {
returnedType.getInputProperties().forEach(query.fields()::include);
Field fields = query.fields();
for (String field : returnedType.getInputProperties()) {
fields.include(field);
}
} }
return query; return query;
@ -110,7 +108,7 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
try { try {
BasicQuery result = new BasicQuery(query.getQueryObject().toJson(), fieldSpec); BasicQuery result = new BasicQuery(query.getQueryObject(), Document.parse(fieldSpec));
result.setSortObject(query.getSortObject()); result.setSortObject(query.getSortObject());
return result; return result;

6
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java

@ -36,6 +36,8 @@ import org.springframework.util.Assert;
* Query to use a plain JSON String to create the {@link Query} to actually execute. * Query to use a plain JSON String to create the {@link Query} to actually execute.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/ */
public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery { public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
@ -60,7 +62,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
* @param evaluationContextProvider must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}.
* @param conversionService must not be {@literal null}. * @param conversionService must not be {@literal null}.
*/ */
public ReactiveStringBasedMongoQuery(MongoQueryMethod method, ReactiveMongoOperations mongoOperations, public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations,
SpelExpressionParser expressionParser, EvaluationContextProvider evaluationContextProvider, ConversionService conversionService) { SpelExpressionParser expressionParser, EvaluationContextProvider evaluationContextProvider, ConversionService conversionService) {
this(method.getAnnotatedQuery(), method, mongoOperations, expressionParser, evaluationContextProvider, conversionService); this(method.getAnnotatedQuery(), method, mongoOperations, expressionParser, evaluationContextProvider, conversionService);
} }
@ -75,7 +77,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
* @param expressionParser must not be {@literal null}. * @param expressionParser must not be {@literal null}.
* @param conversionService must not be {@literal null}. * @param conversionService must not be {@literal null}.
*/ */
public ReactiveStringBasedMongoQuery(String query, MongoQueryMethod method, ReactiveMongoOperations mongoOperations, public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations,
SpelExpressionParser expressionParser, EvaluationContextProvider evaluationContextProvider, ConversionService conversionService) { SpelExpressionParser expressionParser, EvaluationContextProvider evaluationContextProvider, ConversionService conversionService) {
super(method, mongoOperations, conversionService); super(method, mongoOperations, conversionService);

6
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java

@ -22,6 +22,7 @@ import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.IndexOperationsProvider; import org.springframework.data.mongodb.core.IndexOperationsProvider;
import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoOperations;
@ -40,6 +41,7 @@ import org.springframework.util.Assert;
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
*/ */
class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTreeMongoQuery> { class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTreeMongoQuery> {
@ -81,7 +83,7 @@ class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTr
// Add fixed sorting criteria to index // Add fixed sorting criteria to index
if (sort != null) { if (sort != null) {
for (Sort.Order order : sort) { for (Order order : sort) {
index.on(order.getProperty(), order.getDirection()); index.on(order.getProperty(), order.getDirection());
} }
} }
@ -97,7 +99,7 @@ class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTr
return Direction.DESC; return Direction.DESC;
} }
org.springframework.data.domain.Sort.Order order = sort.getOrderFor(property); Order order = sort.getOrderFor(property);
return order == null ? Direction.DESC : order.isAscending() ? Direction.ASC : Direction.DESC; return order == null ? Direction.DESC : order.isAscending() ? Direction.ASC : Direction.DESC;
} }
} }

16
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java

@ -20,6 +20,7 @@ import static org.springframework.data.querydsl.QueryDslUtils.*;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoOperations;
@ -43,6 +44,7 @@ import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.reactive.ReactiveCrudRepository; import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.data.repository.reactive.RxJavaCrudRepository; import org.springframework.data.repository.reactive.RxJavaCrudRepository;
import org.springframework.data.repository.util.QueryExecutionConverters; import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -56,6 +58,7 @@ import org.springframework.util.ClassUtils;
*/ */
public class MongoRepositoryFactory extends RepositoryFactorySupport { public class MongoRepositoryFactory extends RepositoryFactorySupport {
// TODO: to we need this here and in ReactiveWrappers?
private static final boolean PROJECT_REACTOR_PRESENT = ClassUtils.isPresent("reactor.core.publisher.Flux", private static final boolean PROJECT_REACTOR_PRESENT = ClassUtils.isPresent("reactor.core.publisher.Flux",
QueryExecutionConverters.class.getClassLoader()); QueryExecutionConverters.class.getClassLoader());
private static final boolean RXJAVA_OBSERVABLE_PRESENT = ClassUtils.isPresent("rx.Observable", private static final boolean RXJAVA_OBSERVABLE_PRESENT = ClassUtils.isPresent("rx.Observable",
@ -86,17 +89,22 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
@Override @Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
boolean isReactiveRepository = (PROJECT_REACTOR_PRESENT && ReactiveCrudRepository.class.isAssignableFrom(metadata.getRepositoryInterface())) || ( boolean isReactiveRepository = (PROJECT_REACTOR_PRESENT && ReactiveCrudRepository.class.isAssignableFrom(metadata.getRepositoryInterface())) || (
RXJAVA_OBSERVABLE_PRESENT && RxJavaCrudRepository.class.isAssignableFrom(metadata.getRepositoryInterface())); RXJAVA_OBSERVABLE_PRESENT && RxJavaCrudRepository.class.isAssignableFrom(metadata.getRepositoryInterface()));
boolean isQueryDslRepository = QUERY_DSL_PRESENT
&& QueryDslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface());
if (isReactiveRepository) { if (isReactiveRepository) {
if(isQueryDslRepository) {
throw new InvalidDataAccessApiUsageException("Cannot combine Querydsl and reactive repository in one interface");
}
return SimpleReactiveMongoRepository.class; return SimpleReactiveMongoRepository.class;
} }
boolean isQueryDslRepository = QUERY_DSL_PRESENT
&& QueryDslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface());
return isQueryDslRepository ? QueryDslMongoRepository.class : SimpleMongoRepository.class; return isQueryDslRepository ? QueryDslMongoRepository.class : SimpleMongoRepository.class;
} }

39
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveChunk.java

@ -35,7 +35,10 @@ import reactor.core.publisher.MonoProcessor;
* A reactive chunk of data restricted by the configured {@link Pageable}. * A reactive chunk of data restricted by the configured {@link Pageable}.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0
*/ */
//TODO: should that one move to SD Commons
abstract class ReactiveChunk<T> implements Slice<T>, Serializable { abstract class ReactiveChunk<T> implements Slice<T>, Serializable {
private static final long serialVersionUID = 867755909294344406L; private static final long serialVersionUID = 867755909294344406L;
@ -88,7 +91,7 @@ abstract class ReactiveChunk<T> implements Slice<T>, Serializable {
* @see org.springframework.data.domain.Slice#getNumberOfElements() * @see org.springframework.data.domain.Slice#getNumberOfElements()
*/ */
public int getNumberOfElements() { public int getNumberOfElements() {
return getContent0().size(); return getContent().size();
} }
/* /*
@ -141,7 +144,7 @@ abstract class ReactiveChunk<T> implements Slice<T>, Serializable {
* @see org.springframework.data.domain.Slice#hasContent() * @see org.springframework.data.domain.Slice#hasContent()
*/ */
public boolean hasContent() { public boolean hasContent() {
return !getContent0().isEmpty(); return !getContent().isEmpty();
} }
/* /*
@ -149,7 +152,18 @@ abstract class ReactiveChunk<T> implements Slice<T>, Serializable {
* @see org.springframework.data.domain.Slice#getContent() * @see org.springframework.data.domain.Slice#getContent()
*/ */
public List<T> getContent() { public List<T> getContent() {
return Collections.unmodifiableList(getContent0());
if (contentCache != null) {
return Collections.unmodifiableList(contentCache);
}
List<T> list = processor.block();
if (list.size() > pageable.getPageSize()) {
return list.subList(0, pageable.getPageSize());
}
return Collections.unmodifiableList(list);
} }
/* /*
@ -165,7 +179,7 @@ abstract class ReactiveChunk<T> implements Slice<T>, Serializable {
* @see java.lang.Iterable#iterator() * @see java.lang.Iterable#iterator()
*/ */
public Iterator<T> iterator() { public Iterator<T> iterator() {
return getContent0().iterator(); return getContent().iterator();
} }
/** /**
@ -178,7 +192,7 @@ abstract class ReactiveChunk<T> implements Slice<T>, Serializable {
Assert.notNull(converter, "Converter must not be null!"); Assert.notNull(converter, "Converter must not be null!");
List<S> result = new ArrayList<S>(getContent0().size()); List<S> result = new ArrayList<S>(getContent().size());
for (T element : this) { for (T element : this) {
result.add(converter.convert(element)); result.add(converter.convert(element));
@ -187,21 +201,6 @@ abstract class ReactiveChunk<T> implements Slice<T>, Serializable {
return result; return result;
} }
protected List<T> getContent0() {
if (contentCache != null) {
return contentCache;
}
List<T> list = processor.block();
if (list.size() > pageable.getPageSize()) {
return list.subList(0, pageable.getPageSize());
}
return list;
}
/** /**
* Returns whether the returned list contains more elements than specified by {@link Pageable#getPageSize()}. * Returns whether the returned list contains more elements than specified by {@link Pageable#getPageSize()}.
* *

5
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java

@ -54,6 +54,7 @@ import org.springframework.util.ClassUtils;
* Factory to create {@link org.springframework.data.mongodb.repository.ReactiveMongoRepository} instances. * Factory to create {@link org.springframework.data.mongodb.repository.ReactiveMongoRepository} instances.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0 * @since 2.0
*/ */
public class ReactiveMongoRepositoryFactory extends RepositoryFactorySupport { public class ReactiveMongoRepositoryFactory extends RepositoryFactorySupport {
@ -140,7 +141,7 @@ public class ReactiveMongoRepositoryFactory extends RepositoryFactorySupport {
/** /**
* Reactive MongoDB support requires reactive wrapper support. If return type/parameters are reactive wrapper types, * Reactive MongoDB support requires reactive wrapper support. If return type/parameters are reactive wrapper types,
* then it's required to be able to convert these into Publisher. * then it's required to be able to convert these into Publisher.
* *
* @param method the method to validate. * @param method the method to validate.
*/ */
private static void validate(Method method) { private static void validate(Method method) {
@ -212,7 +213,7 @@ public class ReactiveMongoRepositoryFactory extends RepositoryFactorySupport {
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
NamedQueries namedQueries) { NamedQueries namedQueries) {
MongoQueryMethod queryMethod = new ReactiveMongoQueryMethod(method, metadata, factory, mappingContext); ReactiveMongoQueryMethod queryMethod = new ReactiveMongoQueryMethod(method, metadata, factory, mappingContext);
String namedQueryName = queryMethod.getNamedQueryName(); String namedQueryName = queryMethod.getNamedQueryName();
if (namedQueries.hasQuery(namedQueryName)) { if (namedQueries.hasQuery(namedQueryName)) {

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactoryBean.java

@ -19,6 +19,7 @@ package org.springframework.data.mongodb.repository.support;
import java.io.Serializable; import java.io.Serializable;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.IndexOperationsAdapter;
import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
@ -30,6 +31,7 @@ import org.springframework.util.Assert;
* {@link org.springframework.data.mongodb.repository.ReactiveMongoRepository} instances. * {@link org.springframework.data.mongodb.repository.ReactiveMongoRepository} instances.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 2.0 * @since 2.0
* @see org.springframework.data.repository.reactive.ReactivePagingAndSortingRepository * @see org.springframework.data.repository.reactive.ReactivePagingAndSortingRepository
* @see org.springframework.data.repository.reactive.RxJavaPagingAndSortingRepository * @see org.springframework.data.repository.reactive.RxJavaPagingAndSortingRepository
@ -84,7 +86,7 @@ public class ReactiveMongoRepositoryFactoryBean<T extends Repository<S, ID>, S,
if (createIndexesForQueryMethods) { if (createIndexesForQueryMethods) {
factory.addQueryCreationListener( factory.addQueryCreationListener(
new IndexEnsuringQueryCreationListener(collectionName -> operations.indexOps(collectionName))); new IndexEnsuringQueryCreationListener(collectionName -> IndexOperationsAdapter.blocking(operations.reactiveIndexOps(collectionName))));
} }
return factory; return factory;

1
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactivePageImpl.java

@ -32,6 +32,7 @@ import reactor.core.publisher.MonoProcessor;
* @author Mark Paluch * @author Mark Paluch
* @since 2.0 * @since 2.0
*/ */
//TODO: should that one move to SD-Commons?
public class ReactivePageImpl<T> extends ReactiveChunk<T> implements Page<T> { public class ReactivePageImpl<T> extends ReactiveChunk<T> implements Page<T> {
private static final long serialVersionUID = 867755909294344406L; private static final long serialVersionUID = 867755909294344406L;

13
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.java

@ -216,9 +216,7 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
Assert.notNull(entities, "The given Publisher of entities must not be null!"); Assert.notNull(entities, "The given Publisher of entities must not be null!");
return Flux.from(entities).flatMap(entity -> { return Flux.from(entities).flatMap(entity -> mongoOperations.insert(entity, entityInformation.getCollectionName()));
return mongoOperations.insert(entity, entityInformation.getCollectionName());
});
} }
public <S extends T> Mono<S> save(S entity) { public <S extends T> Mono<S> save(S entity) {
@ -272,15 +270,17 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
}); });
} }
// TODO: should this one really be void?
public Mono<Void> delete(ID id) { public Mono<Void> delete(ID id) {
Assert.notNull(id, "The given id must not be null!"); Assert.notNull(id, "The given id must not be null!");
return mongoOperations return mongoOperations
.remove(getIdQuery(id), entityInformation.getJavaType(), entityInformation.getCollectionName()) .remove(getIdQuery(id), entityInformation.getJavaType(), entityInformation.getCollectionName()).then();
.then();
} }
// TODO: should this one really be void?
public Mono<Void> delete(T entity) { public Mono<Void> delete(T entity) {
Assert.notNull(entity, "The given entity must not be null!"); Assert.notNull(entity, "The given entity must not be null!");
@ -288,6 +288,7 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
return delete(entityInformation.getId(entity)); return delete(entityInformation.getId(entity));
} }
// TODO: should this one really be void?
public Mono<Void> delete(Iterable<? extends T> entities) { public Mono<Void> delete(Iterable<? extends T> entities) {
Assert.notNull(entities, "The given Iterable of entities must not be null!"); Assert.notNull(entities, "The given Iterable of entities must not be null!");
@ -295,6 +296,7 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
return Flux.fromIterable(entities).flatMap(entity -> delete(entityInformation.getId(entity))).then(); return Flux.fromIterable(entities).flatMap(entity -> delete(entityInformation.getId(entity))).then();
} }
// TODO: should this one really be void?
@Override @Override
public Mono<Void> delete(Publisher<? extends T> entityStream) { public Mono<Void> delete(Publisher<? extends T> entityStream) {
@ -303,6 +305,7 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
return Flux.from(entityStream).flatMap(entity -> delete(entityInformation.getId(entity))).then(); return Flux.from(entityStream).flatMap(entity -> delete(entityInformation.getId(entity))).then();
} }
// TODO: should this one really be void?
public Mono<Void> deleteAll() { public Mono<Void> deleteAll() {
return mongoOperations.remove(new Query(), entityInformation.getCollectionName()) return mongoOperations.remove(new Query(), entityInformation.getCollectionName())
.then(deleteResult -> Mono.empty()); .then(deleteResult -> Mono.empty());

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java

@ -48,7 +48,7 @@ import example.second.Second;
/** /**
* Unit tests for {@link AbstractMongoConfiguration}. * Unit tests for {@link AbstractMongoConfiguration}.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont * @author Thomas Darimont
* @author Mark Paluch * @author Mark Paluch

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfigurationIntegrationTests.java

@ -33,7 +33,7 @@ import com.mongodb.reactivestreams.client.MongoClients;
/** /**
* Integration tests for {@link AbstractReactiveMongoConfiguration}. * Integration tests for {@link AbstractReactiveMongoConfiguration}.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)

3
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfigurationUnitTests.java

@ -50,7 +50,7 @@ import example.second.Second;
/** /**
* Unit tests for {@link AbstractReactiveMongoConfiguration}. * Unit tests for {@link AbstractReactiveMongoConfiguration}.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
public class AbstractReactiveMongoConfigurationUnitTests { public class AbstractReactiveMongoConfigurationUnitTests {
@ -74,7 +74,6 @@ public class AbstractReactiveMongoConfigurationUnitTests {
*/ */
@Test @Test
public void doesNotScanPackageIfMappingPackageIsNull() throws ClassNotFoundException { public void doesNotScanPackageIfMappingPackageIsNull() throws ClassNotFoundException {
assertScanningDisabled(null); assertScanningDisabled(null);
} }

1
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java

@ -376,7 +376,6 @@ public class MongoTemplateTests {
assertThat(indexInfoList.size(), is(2)); assertThat(indexInfoList.size(), is(2));
IndexInfo ii = indexInfoList.get(1); IndexInfo ii = indexInfoList.get(1);
assertThat(ii.isUnique(), is(true)); assertThat(ii.isUnique(), is(true));
assertThat(ii.isDropDuplicates(), is(false));
assertThat(ii.isSparse(), is(false)); assertThat(ii.isSparse(), is(false));
List<IndexField> indexFields = ii.getIndexFields(); List<IndexField> indexFields = ii.getIndexFields();

9
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateExecuteTests.java

@ -33,6 +33,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mongodb.UncategorizedMongoDbException; import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.util.Version;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ -52,22 +53,22 @@ import reactor.test.TestSubscriber;
@ContextConfiguration("classpath:reactive-infrastructure.xml") @ContextConfiguration("classpath:reactive-infrastructure.xml")
public class ReactiveMongoTemplateExecuteTests { public class ReactiveMongoTemplateExecuteTests {
private static final org.springframework.data.util.Version THREE = org.springframework.data.util.Version.parse("3.0"); private static final Version THREE = Version.parse("3.0");
@Autowired SimpleReactiveMongoDatabaseFactory factory; @Autowired SimpleReactiveMongoDatabaseFactory factory;
@Autowired ReactiveMongoOperations operations; @Autowired ReactiveMongoOperations operations;
@Rule public ExpectedException thrown = ExpectedException.none(); @Rule public ExpectedException thrown = ExpectedException.none();
org.springframework.data.util.Version mongoVersion; Version mongoVersion;
@Before @Before
public void setUp() { public void setUp() {
cleanUp(); cleanUp();
if (mongoVersion == null) { if (mongoVersion == null) {
org.bson.Document result = operations.executeCommand("{ buildInfo: 1 }").block(); Document result = operations.executeCommand("{ buildInfo: 1 }").block();
mongoVersion = org.springframework.data.util.Version.parse(result.get("version").toString()); mongoVersion = Version.parse(result.get("version").toString());
} }
} }

29
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java

@ -31,7 +31,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.index.Index; import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.Index.Duplicates;
import org.springframework.data.mongodb.core.index.IndexField; import org.springframework.data.mongodb.core.index.IndexField;
import org.springframework.data.mongodb.core.index.IndexInfo; import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.util.Version; import org.springframework.data.util.Version;
@ -50,14 +49,12 @@ import reactor.test.TestSubscriber;
* Integration test for {@link MongoTemplate}. * Integration test for {@link MongoTemplate}.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:reactive-infrastructure.xml") @ContextConfiguration("classpath:reactive-infrastructure.xml")
public class ReactiveMongoTemplateIndexTests { public class ReactiveMongoTemplateIndexTests {
private static final org.springframework.data.util.Version TWO_DOT_EIGHT = org.springframework.data.util.Version
.parse("2.8");
@Autowired SimpleReactiveMongoDatabaseFactory factory; @Autowired SimpleReactiveMongoDatabaseFactory factory;
@Autowired ReactiveMongoTemplate template; @Autowired ReactiveMongoTemplate template;
@ -68,20 +65,11 @@ public class ReactiveMongoTemplateIndexTests {
@Before @Before
public void setUp() { public void setUp() {
cleanDb(); cleanDb();
queryMongoVersionIfNecessary();
} }
@After @After
public void cleanUp() {} public void cleanUp() {}
private void queryMongoVersionIfNecessary() {
if (mongoVersion == null) {
org.bson.Document result = template.executeCommand("{ buildInfo: 1 }").block();
mongoVersion = Version.parse(result.get("version").toString());
}
}
private void cleanDb() { private void cleanDb() {
template.dropCollection(Person.class).block(); template.dropCollection(Person.class).block();
} }
@ -90,7 +78,6 @@ public class ReactiveMongoTemplateIndexTests {
* @see DATAMONGO-1444 * @see DATAMONGO-1444
*/ */
@Test @Test
@SuppressWarnings("deprecation")
public void testEnsureIndexShouldCreateIndex() { public void testEnsureIndexShouldCreateIndex() {
Person p1 = new Person("Oliver"); Person p1 = new Person("Oliver");
@ -100,8 +87,7 @@ public class ReactiveMongoTemplateIndexTests {
p2.setAge(40); p2.setAge(40);
template.insert(p2); template.insert(p2);
template.reactiveIndexOps(Person.class).ensureIndex(new Index().on("age", Direction.DESC).unique()) template.reactiveIndexOps(Person.class).ensureIndex(new Index().on("age", Direction.DESC).unique()).block();
.block();
MongoCollection<Document> coll = template.getCollection(template.getCollectionName(Person.class)); MongoCollection<Document> coll = template.getCollection(template.getCollectionName(Person.class));
List<Document> indexInfo = Flux.from(coll.listIndexes()).collectList().block(); List<Document> indexInfo = Flux.from(coll.listIndexes()).collectList().block();
@ -109,14 +95,14 @@ public class ReactiveMongoTemplateIndexTests {
assertThat(indexInfo.size(), is(2)); assertThat(indexInfo.size(), is(2));
Object indexKey = null; Object indexKey = null;
boolean unique = false; boolean unique = false;
for (org.bson.Document ix : indexInfo) { for (Document ix : indexInfo) {
if ("age_-1".equals(ix.get("name"))) { if ("age_-1".equals(ix.get("name"))) {
indexKey = ix.get("key"); indexKey = ix.get("key");
unique = (Boolean) ix.get("unique"); unique = (Boolean) ix.get("unique");
} }
} }
assertThat(((org.bson.Document) indexKey), hasEntry("age", -1)); assertThat(((Document) indexKey), hasEntry("age", -1));
assertThat(unique, is(true)); assertThat(unique, is(true));
} }
@ -124,15 +110,13 @@ public class ReactiveMongoTemplateIndexTests {
* @see DATAMONGO-1444 * @see DATAMONGO-1444
*/ */
@Test @Test
@SuppressWarnings("deprecation")
public void getIndexInfoShouldReturnCorrectIndex() { public void getIndexInfoShouldReturnCorrectIndex() {
Person p1 = new Person("Oliver"); Person p1 = new Person("Oliver");
p1.setAge(25); p1.setAge(25);
template.insert(p1).block(); template.insert(p1).block();
template.reactiveIndexOps(Person.class).ensureIndex(new Index().on("age", Direction.DESC).unique()) template.reactiveIndexOps(Person.class).ensureIndex(new Index().on("age", Direction.DESC).unique()).block();
.block();
List<IndexInfo> indexInfoList = Flux.from(template.reactiveIndexOps(Person.class).getIndexInfo()).collectList() List<IndexInfo> indexInfoList = Flux.from(template.reactiveIndexOps(Person.class).getIndexInfo()).collectList()
.block(); .block();
@ -140,7 +124,6 @@ public class ReactiveMongoTemplateIndexTests {
IndexInfo ii = indexInfoList.get(1); IndexInfo ii = indexInfoList.get(1);
assertThat(ii.isUnique(), is(true)); assertThat(ii.isUnique(), is(true));
assertThat(ii.isDropDuplicates(), is(false));
assertThat(ii.isSparse(), is(false)); assertThat(ii.isSparse(), is(false));
List<IndexField> indexFields = ii.getIndexFields(); List<IndexField> indexFields = ii.getIndexFields();
@ -168,7 +151,7 @@ public class ReactiveMongoTemplateIndexTests {
ListIndexesPublisher<Document> listIndexesPublisher = template ListIndexesPublisher<Document> listIndexesPublisher = template
.getCollection(template.getCollectionName(Person.class)).listIndexes(); .getCollection(template.getCollectionName(Person.class)).listIndexes();
List<Document> indexInfo = Flux.from(listIndexesPublisher).collectList().block(); List<Document> indexInfo = Flux.from(listIndexesPublisher).collectList().block();
org.bson.Document indexKey = null; Document indexKey = null;
boolean unique = false; boolean unique = false;
for (Document document : indexInfo) { for (Document document : indexInfo) {

63
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java

@ -74,6 +74,7 @@ import reactor.test.TestSubscriber;
* Integration test for {@link MongoTemplate}. * Integration test for {@link MongoTemplate}.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:reactive-infrastructure.xml") @ContextConfiguration("classpath:reactive-infrastructure.xml")
@ -89,20 +90,11 @@ public class ReactiveMongoTemplateTests {
@Before @Before
public void setUp() { public void setUp() {
cleanDb(); cleanDb();
queryMongoVersionIfNecessary();
} }
@After @After
public void cleanUp() {} public void cleanUp() {}
private void queryMongoVersionIfNecessary() {
if (mongoVersion == null) {
org.bson.Document result = template.executeCommand("{ buildInfo: 1 }").block();
mongoVersion = org.springframework.data.util.Version.parse(result.get("version").toString());
}
}
private void cleanDb() { private void cleanDb() {
template.dropCollection("people") // template.dropCollection("people") //
.and(template.dropCollection("collection")) // .and(template.dropCollection("collection")) //
@ -181,7 +173,7 @@ public class ReactiveMongoTemplateTests {
template.insert(person).block(); template.insert(person).block();
TestSubscriber<Person> testSubscriber = TestSubscriber.create(); TestSubscriber<Person> testSubscriber = TestSubscriber.create();
Flux<Person> flux = template.find(new Query(Criteria.where("_id").is(person.getId())), Person.class); Flux<Person> flux = template.find(new Query(where("_id").is(person.getId())), Person.class);
flux.subscribe(testSubscriber); flux.subscribe(testSubscriber);
testSubscriber.awaitAndAssertNextValueCount(1); testSubscriber.awaitAndAssertNextValueCount(1);
@ -225,7 +217,7 @@ public class ReactiveMongoTemplateTests {
template.insert(person, "people").block(); template.insert(person, "people").block();
TestSubscriber<Person> testSubscriber = TestSubscriber.create(); TestSubscriber<Person> testSubscriber = TestSubscriber.create();
Flux<Person> flux = template.find(new Query(Criteria.where("_id").is(person.getId())), Person.class, "people"); Flux<Person> flux = template.find(new Query(where("_id").is(person.getId())), Person.class, "people");
flux.subscribe(testSubscriber); flux.subscribe(testSubscriber);
testSubscriber.awaitAndAssertNextValueCount(1); testSubscriber.awaitAndAssertNextValueCount(1);
@ -297,7 +289,7 @@ public class ReactiveMongoTemplateTests {
p.setAge(22); p.setAge(22);
template.insert(p).block(); template.insert(p).block();
Query q1 = new Query(Criteria.where("id").is(p.getId())); Query q1 = new Query(where("id").is(p.getId()));
PersonWithAList p2 = template.findOne(q1, PersonWithAList.class).block(); PersonWithAList p2 = template.findOne(q1, PersonWithAList.class).block();
assertThat(p2, notNullValue()); assertThat(p2, notNullValue());
assertThat(p2.getWishList().size(), is(0)); assertThat(p2.getWishList().size(), is(0));
@ -345,7 +337,7 @@ public class ReactiveMongoTemplateTests {
template.insert(p3).block(); template.insert(p3).block();
// test query with a sort // test query with a sort
Query q2 = new Query(Criteria.where("age").gt(10)); Query q2 = new Query(where("age").gt(10));
q2.with(new Sort(Direction.DESC, "age")); q2.with(new Sort(Direction.DESC, "age"));
PersonWithAList p5 = template.findOne(q2, PersonWithAList.class).block(); PersonWithAList p5 = template.findOne(q2, PersonWithAList.class).block();
assertThat(p5.getFirstName(), is("Mark")); assertThat(p5.getFirstName(), is("Mark"));
@ -364,7 +356,7 @@ public class ReactiveMongoTemplateTests {
person.setAge(25); person.setAge(25);
mongoTemplate.insert(person).block(); mongoTemplate.insert(person).block();
Query q = new Query(Criteria.where("BOGUS").gt(22)); Query q = new Query(where("BOGUS").gt(22));
Update u = new Update().set("firstName", "Sven"); Update u = new Update().set("firstName", "Sven");
mongoTemplate.updateFirst(q, u, Person.class).block(); mongoTemplate.updateFirst(q, u, Person.class).block();
} }
@ -377,9 +369,8 @@ public class ReactiveMongoTemplateTests {
Person person = new Person("Oliver2", 25); Person person = new Person("Oliver2", 25);
template.insert(person) // template.insert(person) //
.then(template.updateFirst(new Query(Criteria.where("age").is(25)), new Update().set("firstName", "Sven"), .then(template.updateFirst(new Query(where("age").is(25)), new Update().set("firstName", "Sven"), Person.class)) //
Person.class)) // .flatMap(p -> template.find(new Query(where("age").is(25)), Person.class))
.flatMap(p -> template.find(new Query(Criteria.where("age").is(25)), Person.class))
.subscribeWith(TestSubscriber.create()) // .subscribeWith(TestSubscriber.create()) //
.await() // .await() //
.assertValuesWith(result -> { .assertValuesWith(result -> {
@ -395,9 +386,8 @@ public class ReactiveMongoTemplateTests {
Person person = new Person("Oliver2", 25); Person person = new Person("Oliver2", 25);
template.insert(person, "people") // template.insert(person, "people") //
.then(template.updateFirst(new Query(Criteria.where("age").is(25)), new Update().set("firstName", "Sven"), .then(template.updateFirst(new Query(where("age").is(25)), new Update().set("firstName", "Sven"), "people")) //
"people")) // .flatMap(p -> template.find(new Query(where("age").is(25)), Person.class, "people"))
.flatMap(p -> template.find(new Query(Criteria.where("age").is(25)), Person.class, "people"))
.subscribeWith(TestSubscriber.create()) // .subscribeWith(TestSubscriber.create()) //
.await() // .await() //
.assertValuesWith(result -> { .assertValuesWith(result -> {
@ -411,13 +401,13 @@ public class ReactiveMongoTemplateTests {
@Test @Test
public void updateMultiByEntityTypeShouldUpdateObjects() throws Exception { public void updateMultiByEntityTypeShouldUpdateObjects() throws Exception {
Query query = new Query(new Criteria().orOperator(Criteria.where("firstName").is("Walter Jr"), Query query = new Query(
Criteria.where("firstName").is("Walter"))); new Criteria().orOperator(where("firstName").is("Walter Jr"), Criteria.where("firstName").is("Walter")));
template.insertAll(Flux.just(new Person("Walter", 50), new Person("Skyler", 43), new Person("Walter Jr", 16))) // template.insertAll(Flux.just(new Person("Walter", 50), new Person("Skyler", 43), new Person("Walter Jr", 16))) //
.collectList() // .collectList() //
.flatMap(a -> template.updateMulti(query, new Update().set("firstName", "Walt"), Person.class)) // .flatMap(a -> template.updateMulti(query, new Update().set("firstName", "Walt"), Person.class)) //
.flatMap(p -> template.find(new Query(Criteria.where("firstName").is("Walt")), Person.class)) // .flatMap(p -> template.find(new Query(where("firstName").is("Walt")), Person.class)) //
.subscribeWith(TestSubscriber.create()) // .subscribeWith(TestSubscriber.create()) //
.awaitAndAssertNextValueCount(2); .awaitAndAssertNextValueCount(2);
} }
@ -428,14 +418,14 @@ public class ReactiveMongoTemplateTests {
@Test @Test
public void updateMultiByCollectionNameShouldUpdateObject() throws Exception { public void updateMultiByCollectionNameShouldUpdateObject() throws Exception {
Query query = new Query(new Criteria().orOperator(Criteria.where("firstName").is("Walter Jr"), Query query = new Query(
Criteria.where("firstName").is("Walter"))); new Criteria().orOperator(where("firstName").is("Walter Jr"), Criteria.where("firstName").is("Walter")));
template template
.insert(Flux.just(new Person("Walter", 50), new Person("Skyler", 43), new Person("Walter Jr", 16)), "people") // .insert(Flux.just(new Person("Walter", 50), new Person("Skyler", 43), new Person("Walter Jr", 16)), "people") //
.collectList() // .collectList() //
.flatMap(a -> template.updateMulti(query, new Update().set("firstName", "Walt"), Person.class, "people")) // .flatMap(a -> template.updateMulti(query, new Update().set("firstName", "Walt"), Person.class, "people")) //
.flatMap(p -> template.find(new Query(Criteria.where("firstName").is("Walt")), Person.class, "people")) // .flatMap(p -> template.find(new Query(where("firstName").is("Walt")), Person.class, "people")) //
.subscribeWith(TestSubscriber.create()) // .subscribeWith(TestSubscriber.create()) //
.awaitAndAssertNextValueCount(2); .awaitAndAssertNextValueCount(2);
} }
@ -482,7 +472,7 @@ public class ReactiveMongoTemplateTests {
thrown.expectMessage("age"); thrown.expectMessage("age");
// thrown.expectMessage("failed"); // thrown.expectMessage("failed");
Query query = new Query(Criteria.where("firstName").is("Amol")); Query query = new Query(where("firstName").is("Amol"));
Update upd = new Update().push("age", 29); Update upd = new Update().push("age", 29);
template.updateFirst(query, upd, Person.class).block(); template.updateFirst(query, upd, Person.class).block();
} }
@ -627,7 +617,7 @@ public class ReactiveMongoTemplateTests {
@Test @Test
public void doesNotFailOnVersionInitForUnversionedEntity() { public void doesNotFailOnVersionInitForUnversionedEntity() {
org.bson.Document dbObject = new org.bson.Document(); Document dbObject = new Document();
dbObject.put("firstName", "Oliver"); dbObject.put("firstName", "Oliver");
template.insert(dbObject, template.determineCollectionName(PersonWithVersionPropertyOfTypeInteger.class)); template.insert(dbObject, template.determineCollectionName(PersonWithVersionPropertyOfTypeInteger.class));
@ -685,7 +675,7 @@ public class ReactiveMongoTemplateTests {
@Test @Test
public void savesPlainDbObjectCorrectly() { public void savesPlainDbObjectCorrectly() {
org.bson.Document dbObject = new org.bson.Document("foo", "bar"); Document dbObject = new Document("foo", "bar");
template.save(dbObject, "collection").block(); template.save(dbObject, "collection").block();
assertThat(dbObject.containsKey("_id"), is(true)); assertThat(dbObject.containsKey("_id"), is(true));
@ -697,10 +687,10 @@ public class ReactiveMongoTemplateTests {
@Test(expected = InvalidDataAccessApiUsageException.class) @Test(expected = InvalidDataAccessApiUsageException.class)
public void rejectsPlainObjectWithOutExplicitCollection() { public void rejectsPlainObjectWithOutExplicitCollection() {
org.bson.Document dbObject = new org.bson.Document("foo", "bar"); Document dbObject = new Document("foo", "bar");
template.save(dbObject, "collection").block(); template.save(dbObject, "collection").block();
template.findById(dbObject.get("_id"), org.bson.Document.class).block(); template.findById(dbObject.get("_id"), Document.class).block();
} }
/** /**
@ -709,10 +699,10 @@ public class ReactiveMongoTemplateTests {
@Test @Test
public void readsPlainDbObjectById() { public void readsPlainDbObjectById() {
org.bson.Document dbObject = new org.bson.Document("foo", "bar"); Document dbObject = new Document("foo", "bar");
template.save(dbObject, "collection").block(); template.save(dbObject, "collection").block();
org.bson.Document result = template.findById(dbObject.get("_id"), org.bson.Document.class, "collection").block(); Document result = template.findById(dbObject.get("_id"), Document.class, "collection").block();
assertThat(result.get("foo"), is(dbObject.get("foo"))); assertThat(result.get("foo"), is(dbObject.get("foo")));
assertThat(result.get("_id"), is(dbObject.get("_id"))); assertThat(result.get("_id"), is(dbObject.get("_id")));
} }
@ -729,7 +719,8 @@ public class ReactiveMongoTemplateTests {
new Venue("Maplewood, NJ", -74.2713, 40.73137)); new Venue("Maplewood, NJ", -74.2713, 40.73137));
template.insertAll(venues).blockLast(); template.insertAll(venues).blockLast();
template.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2D)); IndexOperationsAdapter.blocking(template.reactiveIndexOps(Venue.class))
.ensureIndex(new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2D));
NearQuery geoFar = NearQuery.near(-73, 40, Metrics.KILOMETERS).num(10).maxDistance(150, Metrics.KILOMETERS); NearQuery geoFar = NearQuery.near(-73, 40, Metrics.KILOMETERS).num(10).maxDistance(150, Metrics.KILOMETERS);
@ -931,11 +922,11 @@ public class ReactiveMongoTemplateTests {
@Test @Test
public void savesJsonStringCorrectly() { public void savesJsonStringCorrectly() {
org.bson.Document dbObject = new org.bson.Document().append("first", "first").append("second", "second"); Document dbObject = new Document().append("first", "first").append("second", "second");
template.save(dbObject, "collection").block(); template.save(dbObject, "collection").block();
org.bson.Document result = template.findAll(org.bson.Document.class, "collection").next().block(); Document result = template.findAll(Document.class, "collection").next().block();
assertThat(result.containsKey("first"), is(true)); assertThat(result.containsKey("first"), is(true));
} }

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexInfoUnitTests.java

@ -36,7 +36,7 @@ public class IndexInfoUnitTests {
IndexField fooField = IndexField.create("foo", Direction.ASC); IndexField fooField = IndexField.create("foo", Direction.ASC);
IndexField barField = IndexField.create("bar", Direction.DESC); IndexField barField = IndexField.create("bar", Direction.DESC);
IndexInfo info = new IndexInfo(Arrays.asList(fooField, barField), "myIndex", false, false, false, ""); IndexInfo info = new IndexInfo(Arrays.asList(fooField, barField), "myIndex", false, false, "");
assertThat(info.isIndexForFields(Arrays.asList("foo", "bar")), is(true)); assertThat(info.isIndexForFields(Arrays.asList("foo", "bar")), is(true));
} }
} }

13
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java

@ -37,7 +37,6 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@ -64,8 +63,8 @@ public class MongoPersistentEntityIndexCreatorIntegrationTests {
static final String SAMPLE_TYPE_COLLECTION_NAME = "sampleEntity"; static final String SAMPLE_TYPE_COLLECTION_NAME = "sampleEntity";
static final String RECURSIVE_TYPE_COLLECTION_NAME = "recursiveGenericTypes"; static final String RECURSIVE_TYPE_COLLECTION_NAME = "recursiveGenericTypes";
public static @ClassRule RuleChain rules = RuleChain.outerRule(MongoVersionRule.atLeast(new Version(2, 6))).around( public static @ClassRule RuleChain rules = RuleChain.outerRule(MongoVersionRule.atLeast(new Version(2, 6)))
CleanMongoDB.indexes(Arrays.asList(SAMPLE_TYPE_COLLECTION_NAME, RECURSIVE_TYPE_COLLECTION_NAME))); .around(CleanMongoDB.indexes(Arrays.asList(SAMPLE_TYPE_COLLECTION_NAME, RECURSIVE_TYPE_COLLECTION_NAME)));
public @Rule ExpectedException expectedException = ExpectedException.none(); public @Rule ExpectedException expectedException = ExpectedException.none();
@ -113,11 +112,11 @@ public class MongoPersistentEntityIndexCreatorIntegrationTests {
MongoPersistentEntityIndexCreator indexCreator = new MongoPersistentEntityIndexCreator(new MongoMappingContext(), MongoPersistentEntityIndexCreator indexCreator = new MongoPersistentEntityIndexCreator(new MongoMappingContext(),
mongoTemplate); mongoTemplate);
indexCreator.createIndex(new IndexDefinitionHolder("dalinar.kohlin", new Index().named("stormlight") indexCreator.createIndex(new IndexDefinitionHolder("dalinar.kohlin",
.on("lastname", Direction.ASC).unique(), "datamongo-1125")); new Index().named("stormlight").on("lastname", Direction.ASC).unique(), "datamongo-1125"));
indexCreator.createIndex(new IndexDefinitionHolder("dalinar.kohlin", new Index().named("stormlight") indexCreator.createIndex(new IndexDefinitionHolder("dalinar.kohlin",
.on("lastname", Direction.ASC).sparse(), "datamongo-1125")); new Index().named("stormlight").on("lastname", Direction.ASC).sparse(), "datamongo-1125"));
} }
@Document(collection = RECURSIVE_TYPE_COLLECTION_NAME) @Document(collection = RECURSIVE_TYPE_COLLECTION_NAME)

169
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/ReactivePerformanceTests.java

@ -18,8 +18,6 @@ package org.springframework.data.mongodb.performance;
import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*; import static org.springframework.data.mongodb.core.query.Query.*;
import static org.springframework.util.Assert.*; import static org.springframework.util.Assert.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
@ -43,9 +41,9 @@ import org.junit.Test;
import org.springframework.core.Constants; import org.springframework.core.Constants;
import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.convert.DbRefProxyHandler; import org.springframework.data.mongodb.core.convert.DbRefProxyHandler;
import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DbRefResolverCallback; import org.springframework.data.mongodb.core.convert.DbRefResolverCallback;
@ -70,11 +68,13 @@ import com.mongodb.reactivestreams.client.MongoClients;
import com.mongodb.reactivestreams.client.MongoCollection; import com.mongodb.reactivestreams.client.MongoCollection;
import com.mongodb.reactivestreams.client.MongoDatabase; import com.mongodb.reactivestreams.client.MongoDatabase;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/** /**
* Test class to execute performance tests for plain Reactive Streams MongoDB driver usage, {@link ReactiveMongoOperations} and the repositories * Test class to execute performance tests for plain Reactive Streams MongoDB driver usage,
* abstraction. * {@link ReactiveMongoOperations} and the repositories abstraction.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
public class ReactivePerformanceTests { public class ReactivePerformanceTests {
@ -96,22 +96,25 @@ public class ReactivePerformanceTests {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
this.mongo = MongoClients.create(); mongo = MongoClients.create();
SimpleReactiveMongoDatabaseFactory mongoDbFactory = new SimpleReactiveMongoDatabaseFactory(this.mongo, DATABASE_NAME); SimpleReactiveMongoDatabaseFactory mongoDbFactory = new SimpleReactiveMongoDatabaseFactory(this.mongo,
DATABASE_NAME);
MongoMappingContext context = new MongoMappingContext(); MongoMappingContext context = new MongoMappingContext();
context.setInitialEntitySet(Collections.singleton(Person.class)); context.setInitialEntitySet(Collections.singleton(Person.class));
context.afterPropertiesSet(); context.afterPropertiesSet();
this.converter = new MappingMongoConverter(new DbRefResolver() { converter = new MappingMongoConverter(new DbRefResolver() {
@Override @Override
public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback, DbRefProxyHandler proxyHandler) { public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
DbRefProxyHandler proxyHandler) {
return null; return null;
} }
@Override @Override
public DBRef createDbRef(org.springframework.data.mongodb.core.mapping.DBRef annotation, MongoPersistentEntity<?> entity, Object id) { public DBRef createDbRef(org.springframework.data.mongodb.core.mapping.DBRef annotation,
MongoPersistentEntity<?> entity, Object id) {
return null; return null;
} }
@ -125,12 +128,12 @@ public class ReactivePerformanceTests {
return null; return null;
} }
}, context); }, context);
this.operations = new ReactiveMongoTemplate(mongoDbFactory, converter); operations = new ReactiveMongoTemplate(mongoDbFactory, converter);
ReactiveMongoRepositoryFactory factory = new ReactiveMongoRepositoryFactory(operations); ReactiveMongoRepositoryFactory factory = new ReactiveMongoRepositoryFactory(operations);
factory.setConversionService(new GenericConversionService()); factory.setConversionService(new GenericConversionService());
this.repository = factory.getRepository(ReactivePersonRepository.class); repository = factory.getRepository(ReactivePersonRepository.class);
} }
@ -139,24 +142,23 @@ public class ReactivePerformanceTests {
*/ */
@Test @Test
public void writeWithWriteConcerns() { public void writeWithWriteConcerns() {
executeWithWriteConcerns(new WriteConcernCallback() { executeWithWriteConcerns((constantName, concern) -> {
public void doWithWriteConcern(String constantName, WriteConcern concern) {
writeHeadline("WriteConcern: " + constantName); writeHeadline("WriteConcern: " + constantName);
System.out.println(String.format("Writing %s objects using plain driver took %sms", NUMBER_OF_PERSONS, System.out.println(String.format("Writing %s objects using plain driver took %sms", NUMBER_OF_PERSONS,
writingObjectsUsingPlainDriver(NUMBER_OF_PERSONS, concern))); writingObjectsUsingPlainDriver(NUMBER_OF_PERSONS, concern)));
System.out.println(String.format("Writing %s objects using template took %sms", NUMBER_OF_PERSONS, System.out.println(String.format("Writing %s objects using template took %sms", NUMBER_OF_PERSONS,
writingObjectsUsingMongoTemplate(NUMBER_OF_PERSONS, concern))); writingObjectsUsingMongoTemplate(NUMBER_OF_PERSONS, concern)));
System.out.println(String.format("Writing %s objects using repository took %sms", NUMBER_OF_PERSONS, System.out.println(String.format("Writing %s objects using repository took %sms", NUMBER_OF_PERSONS,
writingObjectsUsingRepositories(NUMBER_OF_PERSONS, concern))); writingObjectsUsingRepositories(NUMBER_OF_PERSONS, concern)));
System.out.println(String.format("Writing %s objects async using plain driver took %sms", NUMBER_OF_PERSONS, System.out.println(String.format("Writing %s objects async using plain driver took %sms", NUMBER_OF_PERSONS,
writingAsyncObjectsUsingPlainDriver(NUMBER_OF_PERSONS, concern))); writingAsyncObjectsUsingPlainDriver(NUMBER_OF_PERSONS, concern)));
System.out.println(String.format("Writing %s objects async using template took %sms", NUMBER_OF_PERSONS, System.out.println(String.format("Writing %s objects async using template took %sms", NUMBER_OF_PERSONS,
writingAsyncObjectsUsingMongoTemplate(NUMBER_OF_PERSONS, concern))); writingAsyncObjectsUsingMongoTemplate(NUMBER_OF_PERSONS, concern)));
System.out.println(String.format("Writing %s objects async using repository took %sms", NUMBER_OF_PERSONS, System.out.println(String.format("Writing %s objects async using repository took %sms", NUMBER_OF_PERSONS,
writingAsyncObjectsUsingRepositories(NUMBER_OF_PERSONS, concern))); writingAsyncObjectsUsingRepositories(NUMBER_OF_PERSONS, concern)));
writeFooter(); writeFooter();
}
}); });
} }
@ -178,19 +180,15 @@ public class ReactivePerformanceTests {
private long convertDirectly(final List<Document> dbObjects) { private long convertDirectly(final List<Document> dbObjects) {
executeWatched(new WatchCallback<List<Person>>() { executeWatched(() -> {
@Override List<Person> persons = new ArrayList<Person>();
public List<Person> doInWatch() {
List<Person> persons = new ArrayList<ReactivePerformanceTests.Person>();
for (Document dbObject : dbObjects) {
persons.add(Person.from(new Document(dbObject)));
}
return persons; for (Document dbObject : dbObjects) {
persons.add(Person.from(new Document(dbObject)));
} }
return persons;
}); });
return watch.getLastTaskTimeMillis(); return watch.getLastTaskTimeMillis();
@ -198,19 +196,15 @@ public class ReactivePerformanceTests {
private long convertUsingConverter(final List<Document> dbObjects) { private long convertUsingConverter(final List<Document> dbObjects) {
executeWatched(new WatchCallback<List<Person>>() { executeWatched(() -> {
@Override
public List<Person> doInWatch() {
List<Person> persons = new ArrayList<ReactivePerformanceTests.Person>();
for (Document dbObject : dbObjects) { List<Person> persons = new ArrayList<Person>();
persons.add(converter.read(Person.class, dbObject));
}
return persons; for (Document dbObject : dbObjects) {
persons.add(converter.read(Person.class, dbObject));
} }
return persons;
}); });
return watch.getLastTaskTimeMillis(); return watch.getLastTaskTimeMillis();
@ -237,9 +231,12 @@ public class ReactivePerformanceTests {
statistics.registerTime(Api.TEMPLATE, Mode.WRITE, writingObjectsUsingMongoTemplate(numberOfPersons, concern)); statistics.registerTime(Api.TEMPLATE, Mode.WRITE, writingObjectsUsingMongoTemplate(numberOfPersons, concern));
statistics.registerTime(Api.REPOSITORY, Mode.WRITE, writingObjectsUsingRepositories(numberOfPersons, concern)); statistics.registerTime(Api.REPOSITORY, Mode.WRITE, writingObjectsUsingRepositories(numberOfPersons, concern));
statistics.registerTime(Api.DRIVER, Mode.WRITE_ASYNC, writingAsyncObjectsUsingPlainDriver(numberOfPersons, concern)); statistics.registerTime(Api.DRIVER, Mode.WRITE_ASYNC,
statistics.registerTime(Api.TEMPLATE, Mode.WRITE_ASYNC, writingAsyncObjectsUsingMongoTemplate(numberOfPersons, concern)); writingAsyncObjectsUsingPlainDriver(numberOfPersons, concern));
statistics.registerTime(Api.REPOSITORY, Mode.WRITE_ASYNC, writingAsyncObjectsUsingRepositories(numberOfPersons, concern)); statistics.registerTime(Api.TEMPLATE, Mode.WRITE_ASYNC,
writingAsyncObjectsUsingMongoTemplate(numberOfPersons, concern));
statistics.registerTime(Api.REPOSITORY, Mode.WRITE_ASYNC,
writingAsyncObjectsUsingRepositories(numberOfPersons, concern));
statistics.registerTime(Api.DRIVER, Mode.READ, readingUsingPlainDriver()); statistics.registerTime(Api.DRIVER, Mode.READ, readingUsingPlainDriver());
statistics.registerTime(Api.TEMPLATE, Mode.READ, readingUsingTemplate()); statistics.registerTime(Api.TEMPLATE, Mode.READ, readingUsingTemplate());
@ -267,22 +264,16 @@ public class ReactivePerformanceTests {
} }
private long queryUsingTemplate() { private long queryUsingTemplate() {
executeWatched(new WatchCallback<List<Person>>() { executeWatched(() -> {
public List<Person> doInWatch() { Query query = query(where("addresses.zipCode").regex(".*1.*"));
Query query = query(where("addresses.zipCode").regex(".*1.*")); return operations.find(query, Person.class, "template").collectList().block();
return operations.find(query, Person.class, "template").collectList().block();
}
}); });
return watch.getLastTaskTimeMillis(); return watch.getLastTaskTimeMillis();
} }
private long queryUsingRepository() { private long queryUsingRepository() {
executeWatched(new WatchCallback<List<Person>>() { executeWatched(() -> repository.findByAddressesZipCodeContaining("1").collectList().block());
public List<Person> doInWatch() {
return repository.findByAddressesZipCodeContaining("1").collectList().block();
}
});
return watch.getLastTaskTimeMillis(); return watch.getLastTaskTimeMillis();
} }
@ -325,7 +316,8 @@ public class ReactivePerformanceTests {
private long writingObjectsUsingPlainDriver(int numberOfPersons, WriteConcern concern) { private long writingObjectsUsingPlainDriver(int numberOfPersons, WriteConcern concern) {
final MongoCollection<Document> collection = mongo.getDatabase(DATABASE_NAME).getCollection("driver").withWriteConcern(concern); final MongoCollection<Document> collection = mongo.getDatabase(DATABASE_NAME).getCollection("driver")
.withWriteConcern(concern);
final List<Person> persons = getPersonObjects(numberOfPersons); final List<Person> persons = getPersonObjects(numberOfPersons);
executeWatched(new WatchCallback<Void>() { executeWatched(new WatchCallback<Void>() {
@ -372,17 +364,19 @@ public class ReactivePerformanceTests {
return watch.getLastTaskTimeMillis(); return watch.getLastTaskTimeMillis();
} }
private long writingAsyncObjectsUsingPlainDriver(int numberOfPersons, WriteConcern concern) { private long writingAsyncObjectsUsingPlainDriver(int numberOfPersons, WriteConcern concern) {
final MongoCollection<Document> collection = mongo.getDatabase(DATABASE_NAME).getCollection("driver").withWriteConcern(concern); final MongoCollection<Document> collection = mongo.getDatabase(DATABASE_NAME).getCollection("driver")
.withWriteConcern(concern);
final List<Person> persons = getPersonObjects(numberOfPersons); final List<Person> persons = getPersonObjects(numberOfPersons);
executeWatched(new WatchCallback<Void>() { executeWatched(new WatchCallback<Void>() {
public Void doInWatch() { public Void doInWatch() {
Flux.from(collection.insertMany(persons.stream().map(person -> new Document(person.toDocument())).collect(Collectors.toList()))).then().block(); Flux.from(collection
.insertMany(persons.stream().map(person -> new Document(person.toDocument())).collect(Collectors.toList())))
.then().block();
return null; return null;
} }
}); });
@ -408,7 +402,6 @@ public class ReactivePerformanceTests {
final List<Person> persons = getPersonObjects(numberOfPersons); final List<Person> persons = getPersonObjects(numberOfPersons);
executeWatched(new WatchCallback<Void>() { executeWatched(new WatchCallback<Void>() {
public Void doInWatch() { public Void doInWatch() {
operations.setWriteConcern(concern); operations.setWriteConcern(concern);
@ -417,53 +410,38 @@ public class ReactivePerformanceTests {
} }
}); });
return watch.getLastTaskTimeMillis(); return watch.getLastTaskTimeMillis();
} }
private long readingUsingPlainDriver() { private long readingUsingPlainDriver() {
executeWatched(new WatchCallback<List<Person>>() { executeWatched(() -> Flux.from(mongo.getDatabase(DATABASE_NAME).getCollection("driver").find()).map(Person::from)
public List<Person> doInWatch() { .collectList().block());
return Flux.from(mongo.getDatabase(DATABASE_NAME).getCollection("driver").find()).map(Person::from).collectList().block();
}
});
return watch.getLastTaskTimeMillis(); return watch.getLastTaskTimeMillis();
} }
private long readingUsingTemplate() { private long readingUsingTemplate() {
executeWatched(new WatchCallback<List<Person>>() { executeWatched(() -> operations.findAll(Person.class, "template").collectList().block());
public List<Person> doInWatch() {
return operations.findAll(Person.class, "template").collectList().block();
}
});
return watch.getLastTaskTimeMillis(); return watch.getLastTaskTimeMillis();
} }
private long readingUsingRepository() { private long readingUsingRepository() {
executeWatched(new WatchCallback<List<Person>>() { executeWatched(() -> repository.findAll().collectList().block());
public List<Person> doInWatch() {
return repository.findAll().collectList().block();
}
});
return watch.getLastTaskTimeMillis(); return watch.getLastTaskTimeMillis();
} }
private long queryUsingPlainDriver() { private long queryUsingPlainDriver() {
executeWatched(new WatchCallback<List<Person>>() { executeWatched(() -> {
public List<Person> doInWatch() {
MongoCollection<Document> collection = mongo.getDatabase(DATABASE_NAME).getCollection("driver"); MongoCollection<Document> collection = mongo.getDatabase(DATABASE_NAME).getCollection("driver");
Document regex = new Document("$regex", Pattern.compile(".*1.*")); Document regex = new Document("$regex", Pattern.compile(".*1.*"));
Document query = new Document("addresses.zipCode", regex); Document query = new Document("addresses.zipCode", regex);
return Flux.from(collection.find(query)).map(Person::from).collectList().block(); return Flux.from(collection.find(query)).map(Person::from).collectList().block();
}
}); });
return watch.getLastTaskTimeMillis(); return watch.getLastTaskTimeMillis();
@ -755,8 +733,7 @@ public class ReactivePerformanceTests {
} }
enum Mode { enum Mode {
WRITE, READ, QUERY, WRITE, READ, QUERY, WRITE_ASYNC
WRITE_ASYNC
} }
private static class Statistics { private static class Statistics {

3
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java

@ -57,6 +57,7 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.util.ClassUtils;
import reactor.core.Cancellation; import reactor.core.Cancellation;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -83,7 +84,7 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF
@Override @Override
public void setBeanClassLoader(ClassLoader classLoader) { public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader == null ? org.springframework.util.ClassUtils.getDefaultClassLoader() : classLoader; this.classLoader = classLoader == null ? ClassUtils.getDefaultClassLoader() : classLoader;
} }
@Override @Override

4
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactivePersonRepository.java

@ -20,14 +20,14 @@ import reactor.core.publisher.Flux;
/** /**
* Sample reactive repository managing {@link Person} entities. * Sample reactive repository managing {@link Person} entities.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
public interface ReactivePersonRepository extends ReactiveMongoRepository<Person, String> { public interface ReactivePersonRepository extends ReactiveMongoRepository<Person, String> {
/** /**
* Returns all {@link Person}s with the given lastname. * Returns all {@link Person}s with the given lastname.
* *
* @param lastname * @param lastname
* @return * @return
*/ */

3
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SimpleReactiveMongoRepositoryTests.java

@ -43,6 +43,7 @@ import org.springframework.data.mongodb.repository.support.SimpleReactiveMongoRe
import org.springframework.data.repository.query.DefaultEvaluationContextProvider; import org.springframework.data.repository.query.DefaultEvaluationContextProvider;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ClassUtils;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -70,7 +71,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
@Override @Override
public void setBeanClassLoader(ClassLoader classLoader) { public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader == null ? org.springframework.util.ClassUtils.getDefaultClassLoader() : classLoader; this.classLoader = classLoader == null ? ClassUtils.getDefaultClassLoader() : classLoader;
} }
@Override @Override

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryExecutionUnitTests.java

@ -46,7 +46,7 @@ import reactor.core.publisher.Flux;
/** /**
* Unit tests for {@link ReactiveMongoQueryExecution}. * Unit tests for {@link ReactiveMongoQueryExecution}.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)

5
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java

@ -56,7 +56,7 @@ import reactor.core.publisher.Mono;
/** /**
* Unit tests for {@link ReactiveStringBasedMongoQuery}. * Unit tests for {@link ReactiveStringBasedMongoQuery}.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
@ -138,7 +138,8 @@ public class ReactiveStringBasedMongoQueryUnitTests {
@Test @Test
public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception { public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception {
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Document("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1)); ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter,
new Document("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1));
ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod("findByParameterizedCriteriaAndFields", ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod("findByParameterizedCriteriaAndFields",
Document.class, Map.class); Document.class, Map.class);

8
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactivePageImplUnitTests.java

@ -21,14 +21,13 @@ import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.repository.support.ReactivePageImpl;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
/** /**
* Unit tests for {@link ReactivePageImpl}. * Unit tests for {@link ReactivePageImpl}.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
public class ReactivePageImplUnitTests { public class ReactivePageImplUnitTests {
@ -57,14 +56,15 @@ public class ReactivePageImplUnitTests {
assertThat(page.hasNext(), is(true)); assertThat(page.hasNext(), is(true));
assertThat(page.nextPageable(), is(new PageRequest(1, 1))); assertThat(page.nextPageable(), is(new PageRequest(1, 1)));
} }
/** /**
* @see DATAMONGO-1444 * @see DATAMONGO-1444
*/ */
@Test @Test
public void returnsContentBoundedByPageSize() { public void returnsContentBoundedByPageSize() {
Page<Object> page = new ReactivePageImpl<>(Flux.just(new Object(), new Object()), new PageRequest(0, 1), Mono.just(10L)); Page<Object> page = new ReactivePageImpl<>(Flux.just(new Object(), new Object()), new PageRequest(0, 1),
Mono.just(10L));
assertThat(page.getContent(), hasSize(1)); assertThat(page.getContent(), hasSize(1));
assertThat(page.hasNext(), is(true)); assertThat(page.hasNext(), is(true));

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactiveSliceImplUnitTests.java

@ -26,7 +26,7 @@ import reactor.core.publisher.Flux;
/** /**
* Unit tests for {@link ReactiveSliceImpl}. * Unit tests for {@link ReactiveSliceImpl}.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
public class ReactiveSliceImplUnitTests { public class ReactiveSliceImplUnitTests {

8
src/main/asciidoc/reference/reactive-mongo-repositories.adoc

@ -11,7 +11,7 @@ This chapter will point out the specialties for reactive repository support for
The reactive space offers various reactive composition libraries. The most common libraries are https://github.com/ReactiveX/RxJava[RxJava] and https://projectreactor.io/[Project Reactor]. The reactive space offers various reactive composition libraries. The most common libraries are https://github.com/ReactiveX/RxJava[RxJava] and https://projectreactor.io/[Project Reactor].
Spring Data MongoDB is built on top of the MongoDB Reactive Streams driver to provide maximal interoperability relying on the http://www.reactive-streams.org/[Reactive Streams] initiative. Static APIs such as `ReactiveMongoOperations` are provided by using Project Reactor's `Flux` and `Mono` types. Project Reactor offers various adapters to convert reactive wrapper types (`Flux` to `Observable` and vice versa) but conversion can easily clutter your code. Spring Data MongoDB is built on top of the https://mongodb.github.io/mongo-java-driver-reactivestreams/[MongoDB Reactive Streams] driver to provide maximal interoperability relying on the http://www.reactive-streams.org/[Reactive Streams] initiative. Static APIs such as `ReactiveMongoOperations` are provided by using Project Reactor's `Flux` and `Mono` types. Project Reactor offers various adapters to convert reactive wrapper types (`Flux` to `Observable` and vice versa) but conversion can easily clutter your code.
Spring Data's Repository abstraction is a dynamic API, mostly defined by you and your requirements, as you're declaring query methods. Reactive MongoDB repositories can be either implemented using RxJava or Project Reactor wrapper types by simply extending from one of the library-specific repository interfaces: Spring Data's Repository abstraction is a dynamic API, mostly defined by you and your requirements, as you're declaring query methods. Reactive MongoDB repositories can be either implemented using RxJava or Project Reactor wrapper types by simply extending from one of the library-specific repository interfaces:
@ -99,21 +99,17 @@ As our domain repository extends `ReactivePagingAndSortingRepository` it provide
==== ====
[source,java] [source,java]
---- ----
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PersonRepositoryTests { public class PersonRepositoryTests {
@Autowired ReactivePersonRepository repository; @Autowired ReactivePersonRepository repository;
@Test @Test
public void readsFirstPageCorrectly() { public void readsFirstPageCorrectly() {
Mono<Page<Person>> persons = repository.findAll(new PageRequest(0, 10)); Mono<Page<Person>> persons = repository.findAll(new PageRequest(0, 10));
} }
@Test @Test
public void readsFirstPageAsStream() { public void readsFirstPageAsStream() {
Flux<Person> persons = repository.findAll(new PageRequest(0, 10)); Flux<Person> persons = repository.findAll(new PageRequest(0, 10));
} }
} }
@ -136,7 +132,7 @@ Following features are supported:
* <<mongodb.repositories.queries.full-text>> * <<mongodb.repositories.queries.full-text>>
* <<projections>> * <<projections>>
Reactive Repositories do not support Type-safe Query methods using QueryDSL. WARNING: Reactive Repositories do not support Type-safe Query methods using Querydsl.
[[mongodb.reactive.repositories.queries.geo-spatial]] [[mongodb.reactive.repositories.queries.geo-spatial]]
=== Geo-spatial repository queries === Geo-spatial repository queries

96
src/main/asciidoc/reference/reactive-mongodb.adoc

@ -3,13 +3,13 @@
The reactive MongoDB support contains a basic set of features which are summarized below. The reactive MongoDB support contains a basic set of features which are summarized below.
* Spring configuration support using Java based @Configuration classes a Mongo client instance and replica sets * Spring configuration support using Java based `@Configuration` classes a `MongoClient` instance and replica sets.
* `ReactiveMongoTemplate` helper class that increases productivity using Mongo operations in a reactive manner. Includes integrated object mapping between documents and POJOs. * `ReactiveMongoTemplate` helper class that increases productivity using `MongoOperations in a reactive manner. Includes integrated object mapping between `Documents and POJOs.
* Exception translation into Spring's portable Data Access Exception hierarchy * Exception translation into Spring's portable Data Access Exception hierarchy.
* Feature Rich Object Mapping integrated with Spring's Conversion Service * Feature Rich Object Mapping integrated with Spring's `ConversionService`.
* Annotation based mapping metadata but extensible to support other metadata formats * Annotation based mapping metadata but extensible to support other metadata formats.
* Persistence and mapping lifecycle events * Persistence and mapping lifecycle events.
* Java based Query, Criteria, and Update DSLs * Java based `Query`, `Criteria`, and `Update` DSLs.
* Automatic implementation of reactive Repository interfaces including support for custom finder methods. * Automatic implementation of reactive Repository interfaces including support for custom finder methods.
For most tasks you will find yourself using `ReactiveMongoTemplate` or the Repository support that both leverage the rich mapping functionality. `ReactiveMongoTemplate` is the place to look for accessing functionality such as incrementing counters or ad-hoc CRUD operations. `ReactiveMongoTemplate` also provides callback methods so that it is easy for you to get a hold of the low level API artifacts such as `MongoDatabase` to communicate directly with MongoDB. The goal with naming conventions on various API artifacts is to copy those in the base MongoDB Java driver so you can easily map your existing knowledge onto the Spring APIs. For most tasks you will find yourself using `ReactiveMongoTemplate` or the Repository support that both leverage the rich mapping functionality. `ReactiveMongoTemplate` is the place to look for accessing functionality such as incrementing counters or ad-hoc CRUD operations. `ReactiveMongoTemplate` also provides callback methods so that it is easy for you to get a hold of the low level API artifacts such as `MongoDatabase` to communicate directly with MongoDB. The goal with naming conventions on various API artifacts is to copy those in the base MongoDB Java driver so you can easily map your existing knowledge onto the Spring APIs.
@ -54,12 +54,11 @@ Then add the following to pom.xml dependencies section.
NOTE: MongoDB uses two different drivers for blocking and reactive (non-blocking) data access. While blocking operations are provided by default, you're have to opt-in for reactive usage. NOTE: MongoDB uses two different drivers for blocking and reactive (non-blocking) data access. While blocking operations are provided by default, you're have to opt-in for reactive usage.
Create a simple Person class to persist: Create a simple `Person` class to persist:
[source,java] [source,java]
---- ----
package org.spring.mongodb.example; @Document
public class Person { public class Person {
private String id; private String id;
@ -92,17 +91,6 @@ And a main application to run
[source,java] [source,java]
---- ----
package org.spring.mongodb.example;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.reactivestreams.client.MongoClients;
public class ReactiveMongoApp { public class ReactiveMongoApp {
private static final Logger log = LoggerFactory.getLogger(ReactiveMongoApp.class); private static final Logger log = LoggerFactory.getLogger(ReactiveMongoApp.class);
@ -114,15 +102,10 @@ public class ReactiveMongoApp {
ReactiveMongoTemplate mongoOps = new ReactiveMongoTemplate(MongoClients.create(), "database"); ReactiveMongoTemplate mongoOps = new ReactiveMongoTemplate(MongoClients.create(), "database");
mongoOps.insert(new Person("Joe", 34)) mongoOps.insert(new Person("Joe", 34))
.flatMap(p -> mongoOps.findOne(new Query(where("name").is("Joe")), Person.class)) .flatMap(p -> mongoOps.findOne(new Query(where("name").is("Joe")), Person.class))
.doOnNext(person -> log.info(person.toString())) .doOnNext(person -> log.info(person.toString()))
.flatMap(person -> mongoOps.dropCollection("person")) .flatMap(person -> mongoOps.dropCollection("person"))
.doOnComplete(latch::countDown) .doOnComplete(latch::countDown)
.subscribe(); .subscribe();
latch.await(); latch.await();
@ -181,7 +164,7 @@ public class AppConfig {
This approach allows you to use the standard `com.mongodb.reactivestreams.client.MongoClient` API that you may already be used to using. This approach allows you to use the standard `com.mongodb.reactivestreams.client.MongoClient` API that you may already be used to using.
An alternative is to register an instance of `com.mongodb.reactivestreams.client.MongoClient` instance with the container using Spring's `ReactiveMongoClientFactoryBean`. As compared to instantiating a `com.mongodb.reactivestreams.client.MongoClient` instance directly, the FactoryBean approach has the added advantage of also providing the container with an ExceptionTranslator implementation that translates MongoDB exceptions to exceptions in Spring's portable `DataAccessException` hierarchy for data access classes annotated with the `@Repository` annotation. This hierarchy and use of `@Repository` is described in http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html[Spring's DAO support features]. An alternative is to register an instance of `com.mongodb.reactivestreams.client.MongoClient` instance with the container using Spring's `ReactiveMongoClientFactoryBean`. As compared to instantiating a `com.mongodb.reactivestreams.client.MongoClient` instance directly, the FactoryBean approach has the added advantage of also providing the container with an `ExceptionTranslator` implementation that translates MongoDB exceptions to exceptions in Spring's portable `DataAccessException` hierarchy for data access classes annotated with the `@Repository` annotation. This hierarchy and use of `@Repository` is described in http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html[Spring's DAO support features].
An example of a Java based bean metadata that supports exception translation on `@Repository` annotated classes is shown below: An example of a Java based bean metadata that supports exception translation on `@Repository` annotated classes is shown below:
@ -197,16 +180,16 @@ public class AppConfig {
*/ */
public @Bean ReactiveMongoClientFactoryBean mongoClient() { public @Bean ReactiveMongoClientFactoryBean mongoClient() {
ReactiveMongoClientFactoryBean mongoClient = new ReactiveMongoClientFactoryBean(); ReactiveMongoClientFactoryBean clientFactory = new ReactiveMongoClientFactoryBean();
mongoClient.setHost("localhost"); clientFactory.setHost("localhost");
return mongoClient; return clientFactory;
} }
} }
---- ----
==== ====
To access the `com.mongodb.reactivestreams.client.MongoClient` object created by the `ReactiveMongoClientFactoryBean` in other `@Configuration` or your own classes, use a `private @Autowired MongoClient mongoClient;` field. To access the `com.mongodb.reactivestreams.client.MongoClient` object created by the `ReactiveMongoClientFactoryBean` in other `@Configuration` or your own classes, just obtain the `MongoClient` from the context.
[[mongo.mongo-db-factory]] [[mongo.mongo-db-factory]]
@ -246,7 +229,7 @@ public interface ReactiveMongoDatabaseFactory {
The class `org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory` provides implements the ReactiveMongoDatabaseFactory interface and is created with a standard `com.mongodb.reactivestreams.client.MongoClient` instance and the database name. The class `org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory` provides implements the ReactiveMongoDatabaseFactory interface and is created with a standard `com.mongodb.reactivestreams.client.MongoClient` instance and the database name.
Instead of using the IoC container to create an instance of ReactiveMongoTemplate, you can just use them in standard Java code as shown below. Instead of using the IoC container to create an instance of `ReactiveMongoTemplate`, you can just use them in standard Java code as shown below.
[source,java] [source,java]
---- ----
@ -256,27 +239,23 @@ public class MongoApp {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
ReactiveMongoOperations mongoOps = new ReactiveMongoOperations(*new SimpleReactiveMongoDatabaseFactory(MongoClient.create(), "database")*); ReactiveMongoOperations mongoOps = new ReactiveMongoOperations(new SimpleReactiveMongoDatabaseFactory(MongoClient.create(), "database"));
mongoOps.insert(new Person("Joe", 34)) mongoOps.insert(new Person("Joe", 34))
.flatMap(p -> mongoOps.findOne(new Query(where("name").is("Joe")), Person.class)) .flatMap(p -> mongoOps.findOne(new Query(where("name").is("Joe")), Person.class))
.doOnNext(person -> log.info(person.toString())) .doOnNext(person -> log.info(person.toString()))
.flatMap(person -> mongoOps.dropCollection("person")) .flatMap(person -> mongoOps.dropCollection("person"))
.subscribe(); .subscribe();
} }
} }
---- ----
The code in bold highlights the use of SimpleMongoDbFactory and is the only difference between the listing shown in the <<mongodb-reactive-getting-started,getting started section>>. The use of `SimpleMongoDbFactory` is the only difference between the listing shown in the <<mongodb-reactive-getting-started,getting started section>>.
[[mongo.mongo-db-factory-java]] [[mongo.mongo-db-factory-java]]
=== Registering a ReactiveMongoDatabaseFactory instance using Java based metadata === Registering a ReactiveMongoDatabaseFactory instance using Java based metadata
To register a ReactiveMongoDatabaseFactory instance with the container, you write code much like what was highlighted in the previous code listing. A simple example is shown below To register a `ReactiveMongoDatabaseFactory` instance with the container, you write code much like what was highlighted in the previous code listing. A simple example is shown below
[source,java] [source,java]
---- ----
@ -315,7 +294,7 @@ NOTE: Once configured, `ReactiveMongoTemplate` is thread-safe and can be reused
The mapping between MongoDB documents and domain classes is done by delegating to an implementation of the interface `MongoConverter`. Spring provides a default implementation with `MongoMappingConverter`, but you can also write your own converter. Please refer to the section on MongoConverters for more detailed information. The mapping between MongoDB documents and domain classes is done by delegating to an implementation of the interface `MongoConverter`. Spring provides a default implementation with `MongoMappingConverter`, but you can also write your own converter. Please refer to the section on MongoConverters for more detailed information.
The `ReactiveMongoTemplate` class implements the interface `ReactiveMongoOperations`. In as much as possible, the methods on `ReactiveMongoOperations` are named after methods available on the MongoDB driver `Collection` object as as to make the API familiar to existing MongoDB developers who are used to the driver API. For example, you will find methods such as "find", "findAndModify", "findOne", "insert", "remove", "save", "update" and "updateMulti". The design goal was to make it as easy as possible to transition between the use of the base MongoDB driver and `ReactiveMongoOperations`. A major difference in between the two APIs is that ReactiveMongoOperations can be passed domain objects instead of `Document` and there are fluent APIs for `Query`, `Criteria`, and `Update` operations instead of populating a `Document` to specify the parameters for those operations. The `ReactiveMongoTemplate` class implements the interface `ReactiveMongoOperations`. In as much as possible, the methods on `ReactiveMongoOperations` are named after methods available on the MongoDB driver `Collection` object as as to make the API familiar to existing MongoDB developers who are used to the driver API. For example, you will find methods such as "find", "findAndModify", "findOne", "insert", "remove", "save", "update" and "updateMulti". The design goal was to make it as easy as possible to transition between the use of the base MongoDB driver and `ReactiveMongoOperations`. A major difference in between the two APIs is that `ReactiveMongoOperations` can be passed domain objects instead of `Document` and there are fluent APIs for `Query`, `Criteria`, and `Update` operations instead of populating a `Document` to specify the parameters for those operations.
NOTE: The preferred way to reference the operations on `ReactiveMongoTemplate` instance is via its interface `ReactiveMongoOperations`. NOTE: The preferred way to reference the operations on `ReactiveMongoTemplate` instance is via its interface `ReactiveMongoOperations`.
@ -350,7 +329,7 @@ public class AppConfig {
---- ----
==== ====
There are several overloaded constructors of ReactiveMongoTemplate. These are There are several overloaded constructors of `ReactiveMongoTemplate`. These are
* `ReactiveMongoTemplate(MongoClient mongo, String databaseName)` - takes the `com.mongodb.Mongo` object and the default database name to operate against. * `ReactiveMongoTemplate(MongoClient mongo, String databaseName)` - takes the `com.mongodb.Mongo` object and the default database name to operate against.
* `ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory)` - takes a ReactiveMongoDatabaseFactory object that encapsulated the `com.mongodb.reactivestreams.client.MongoClient` object and database name. * `ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory)` - takes a ReactiveMongoDatabaseFactory object that encapsulated the `com.mongodb.reactivestreams.client.MongoClient` object and database name.
@ -364,13 +343,13 @@ NOTE: The preferred way to reference the operations on `ReactiveMongoTemplate` i
[[mongo.reactive.template.writeresultchecking]] [[mongo.reactive.template.writeresultchecking]]
=== WriteResultChecking Policy === WriteResultChecking Policy
When in development it is very handy to either log or throw an exception if the `com.mongodb.WriteResult` returned from any MongoDB operation contains an error. It is quite common to forget to do this during development and then end up with an application that looks like it runs successfully but in fact the database was not modified according to your expectations. Set MongoTemplate's property to an enum with the following values, `LOG`, `EXCEPTION`, or `NONE` to either log the error, throw and exception or do nothing. The default is to use a `WriteResultChecking` value of `NONE`. When in development it is very handy to either log or throw an `Exception` if the `com.mongodb.WriteResult` returned from any MongoDB operation contains an error. It is quite common to forget to do this during development and then end up with an application that looks like it runs successfully but in fact the database was not modified according to your expectations. Set MongoTemplate's property to an enum with the following values, `LOG`, `EXCEPTION`, or `NONE` to either log the error, throw and exception or do nothing. The default is to use a `WriteResultChecking` value of `NONE`.
[[mongo.reactive.template.writeconcern]] [[mongo.reactive.template.writeconcern]]
=== WriteConcern === WriteConcern
You can set the `com.mongodb.WriteConcern` property that the `ReactiveMongoTemplate` will use for write operations if it has not yet been specified via the driver at a higher level such as `MongoDatabase`. If ReactiveMongoTemplate's `WriteConcern` property is not set it will default to the one set in the MongoDB driver's DB or Collection setting. You can set the `com.mongodb.WriteConcern` property that the `ReactiveMongoTemplate` will use for write operations if it has not yet been specified via the driver at a higher level such as `MongoDatabase`. If ReactiveMongoTemplate's `WriteConcern` property is not set it will default to the one set in the MongoDB driver's `MongoDatabase` or `MongoCollection` setting.
[[mongo.reactive.template.writeconcernresolver]] [[mongo.reactive.template.writeconcernresolver]]
@ -445,17 +424,6 @@ You can save, update and delete the object as shown below.
[source,java] [source,java]
---- ----
package org.spring.mongodb.example;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.reactivestreams.client.MongoClients;
public class ReactiveMongoApp { public class ReactiveMongoApp {
private static final Logger log = LoggerFactory.getLogger(ReactiveMongoApp.class); private static final Logger log = LoggerFactory.getLogger(ReactiveMongoApp.class);
@ -467,15 +435,10 @@ public class ReactiveMongoApp {
ReactiveMongoTemplate mongoOps = new ReactiveMongoTemplate(MongoClients.create(), "database"); ReactiveMongoTemplate mongoOps = new ReactiveMongoTemplate(MongoClients.create(), "database");
mongoOps.insert(new Person("Joe", 34)).doOnNext(person -> log.info("Insert: " + person)) mongoOps.insert(new Person("Joe", 34)).doOnNext(person -> log.info("Insert: " + person))
.flatMap(person -> mongoOps.findById(person.getId(), Person.class)) .flatMap(person -> mongoOps.findById(person.getId(), Person.class))
.doOnNext(person -> log.info("Found: " + person)) .doOnNext(person -> log.info("Found: " + person))
.zipWith(person -> mongoOps.updateFirst(query(where("name").is("Joe")), update("age", 35), Person.class)) .zipWith(person -> mongoOps.updateFirst(query(where("name").is("Joe")), update("age", 35), Person.class))
.flatMap(tuple -> mongoOps.remove(tuple.getT1())).flatMap(deleteResult -> mongoOps.findAll(Person.class)) .flatMap(tuple -> mongoOps.remove(tuple.getT1())).flatMap(deleteResult -> mongoOps.findAll(Person.class))
.count().doOnSuccess(count -> { .count().doOnSuccess(count -> {
log.info("Number of people: " + count); log.info("Number of people: " + count);
latch.countDown(); latch.countDown();
@ -529,14 +492,9 @@ Here is an example that uses the `ReactiveCollectionCallback` to return informat
[source,java] [source,java]
---- ----
Flux<Boolean> hasIndex = template.execute("geolocation", collection -> { Flux<Boolean> hasIndex = operations.execute("geolocation",
collection -> Flux.from(collection.listIndexes(Document.class))
List<IndexInfo> indexes = template.indexOps(collection.getNamespace().getCollectionName()).getIndexInfo(); .filter(document -> document.get("name").equals("fancy-index-name"))
for (IndexInfo dbo : indexes) { .flatMap(document -> Mono.just(true))
if ("location_2d".equals(dbo.getName())) { .defaultIfEmpty(false));
return Mono.just(true);
}
}
return Mono.just(false);
});
---- ----

Loading…
Cancel
Save