diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 4d0c51fa5a55..e97f0f63beb8 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -17,6 +17,7 @@
/sdk/core/ @alzimmermsft @jianghaolu @srnagar @hemanttanwar @anuchandy
/sdk/core/azure-core-tracing-opentelemetry/ @samvaity @alzimmermsft
/sdk/cosmos/ @moderakh @kushagraThapar @David-Noble-at-work @kirankumarkolli @mbhaskar
+/sdk/cosmos/azure-spring-data-cosmosdb/ @kushagraThapar
/sdk/eventhubs/ @conniey @srnagar @mssfang
/sdk/formrecognizer/ @samvaity @mssfang @sima-zhu
/sdk/identity/ @schaabs @g2vinay @jianghaolu
diff --git a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml
index 886abb829987..143fd1dc5216 100755
--- a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml
+++ b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml
@@ -457,6 +457,10 @@
+
+
@@ -464,4 +468,9 @@
+
+
+
+
+
diff --git a/eng/jacoco-test-coverage/pom.xml b/eng/jacoco-test-coverage/pom.xml
index b07e392a1911..13940326009b 100644
--- a/eng/jacoco-test-coverage/pom.xml
+++ b/eng/jacoco-test-coverage/pom.xml
@@ -243,6 +243,11 @@
azure-data-gremlin-spring-boot-starter
2.3.3-beta.1
+
+ com.microsoft.azure
+ spring-data-cosmosdb
+ 2.3.1-beta.1
+
diff --git a/eng/versioning/external_dependencies.txt b/eng/versioning/external_dependencies.txt
index 93839243d0a7..37b8010866ff 100644
--- a/eng/versioning/external_dependencies.txt
+++ b/eng/versioning/external_dependencies.txt
@@ -4,7 +4,9 @@ com.fasterxml.jackson.core:jackson-annotations;2.10.1
com.fasterxml.jackson.core:jackson-core;2.10.1
com.fasterxml.jackson.core:jackson-databind;2.10.1
com.fasterxml.jackson.dataformat:jackson-dataformat-xml;2.10.1
+com.fasterxml.jackson.datatype:jackson-datatype-jdk8;2.10.0
com.fasterxml.jackson.datatype:jackson-datatype-jsr310;2.10.1
+com.fasterxml.jackson.module:jackson-module-parameter-names;2.10.0
com.github.spotbugs:spotbugs;4.0.0-beta3
com.github.spotbugs:spotbugs-maven-plugin;3.1.12.2
com.google.code.gson:gson;2.8.5
@@ -15,15 +17,15 @@ com.microsoft.azure:azure-arm-client-runtime;1.7.3
com.microsoft.azure:azure-client-authentication;1.7.3
com.microsoft.azure:azure-client-runtime;1.7.3
com.microsoft.azure:azure-core;0.9.8
-com.microsoft.azure:azure-cosmos;3.7.1
+com.microsoft.azure:azure-cosmos;3.7.3
com.microsoft.azure:azure-keyvault-cryptography;1.2.2
com.microsoft.azure:azure-media;0.9.8
com.microsoft.azure:azure-servicebus-jms;0.0.2
com.microsoft.azure:qpid-proton-j-extensions;1.2.3
-com.microsoft.azure:spring-data-cosmosdb;2.2.3.FIX1
com.microsoft.rest:client-runtime;1.7.4
com.microsoft.rest.v2:client-runtime;2.1.1
com.microsoft.spring.data.gremlin:spring-data-gremlin;2.2.3
+com.microsoft.azure:spring-data-cosmosdb;2.3.0
com.squareup.okhttp3:okhttp;4.2.2
commons-codec:commons-codec;1.13
io.micrometer:micrometer-core;1.2.0
@@ -45,6 +47,7 @@ javax.annotation:javax.annotation-api;1.3.2
javax.servlet:javax.servlet-api;4.0.1
javax.validation:validation-api;2.0.1.Final
net.minidev:json-smart;2.3
+org.apache.ant:ant;1.9.4
org.apache.avro:avro;1.9.2
org.apache.httpcomponents:httpclient;4.3.6
org.apache.logging.log4j:log4j-api;2.11.1
@@ -55,6 +58,8 @@ org.asynchttpclient:async-http-client;2.10.5
org.codehaus.groovy:groovy-eclipse-batch;2.5.8-01
org.codehaus.groovy:groovy-eclipse-compiler;3.4.0-01
org.hibernate.validator:hibernate-validator;6.0.17.Final
+org.javatuples:javatuples;1.2
+org.json:json;20140107
org.linguafranca.pwdb:KeePassJava2;2.1.4
org.powermock:powermock-api-mockito2;2.0.2
org.powermock:powermock-module-junit4;2.0.2
@@ -82,6 +87,11 @@ org.springframework.security:spring-security-oauth2-core;5.3.2.RELEASE
org.springframework.security:spring-security-oauth2-jose;5.3.2.RELEASE
org.springframework:spring-web;5.2.6.RELEASE
org.springframework:spring-jms;5.2.6.RELEASE
+org.springframework.data:spring-data-commons;2.3.0.RELEASE
+org.springframework:spring-beans;5.2.6.RELEASE
+org.springframework:spring-core;5.2.6.RELEASE
+org.springframework:spring-expression;5.2.6.RELEASE
+org.springframework:spring-tx;5.2.6.RELEASE
pl.pragmatists:JUnitParams;1.1.1
## Test dependency versions
@@ -183,7 +193,9 @@ org.apache.maven.plugins:maven-source-plugin;3.0.1
org.apache.maven.plugins:maven-surefire-plugin;3.0.0-M3
org.apidesign.javadoc:codesnippet-doclet;0.53
org.codehaus.mojo:build-helper-maven-plugin;3.0.0
+org.codehaus.mojo:cobertura-maven-plugin;2.7
org.codehaus.mojo:exec-maven-plugin;1.2.1
+org.codehaus.mojo:findbugs-maven-plugin;3.0.5
org.codehaus.mojo:properties-maven-plugin;1.0.0
org.codehaus.mojo:xml-maven-plugin;1.0
org.eclipse.jetty:jetty-maven-plugin;9.3.22.v20171030
@@ -254,3 +266,4 @@ storage_com.microsoft.azure:azure-storage;8.4.0
spring_io.micrometer:micrometer-core;1.3.0
spring_io.micrometer:micrometer-registry-azure-monitor;1.3.0
spring_com.microsoft.azure:azure;1.34.0
+
diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt
index 39f82e3969b6..98a8f44ed8da 100644
--- a/eng/versioning/version_client.txt
+++ b/eng/versioning/version_client.txt
@@ -55,7 +55,7 @@ com.microsoft.azure:azure-servicebus-jms-spring-boot-starter;2.3.2;2.3.3-beta.1
com.microsoft.azure:azure-spring-boot-metrics-starter;2.3.2;2.3.3-beta.1
com.microsoft.azure:azure-spring-boot-tests;2.3.2;2.3.3-beta.1
com.microsoft.azure:azure-spring-boot-test-core;2.3.2;2.3.3-beta.1
-
+com.microsoft.azure:spring-data-cosmosdb;2.3.0;2.3.1-beta.1
# Unreleased dependencies: Copy the entry from above, prepend "unreleased_" and remove the current
# version. Unreleased dependencies are only valid for dependency versions.
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/CHANGELOG.md b/sdk/cosmos/azure-spring-data-cosmosdb/CHANGELOG.md
new file mode 100644
index 000000000000..d98c3d0f2282
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Release History
+
+## 2.3.1-beta.1 (Unreleased)
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/README.md b/sdk/cosmos/azure-spring-data-cosmosdb/README.md
new file mode 100644
index 000000000000..c181d5b5ff53
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/README.md
@@ -0,0 +1,342 @@
+[](https://travis-ci.org/Microsoft/spring-data-cosmosdb)
+[](https://codecov.io/gh/Microsoft/spring-data-cosmosdb)
+[ ](https://github.com/Microsoft/spring-data-cosmosdb/blob/master/LICENSE)
+
+
+#Azure Cosmos DB client library for Java
+
+## Getting started
+[Azure Cosmos DB](https://docs.microsoft.com/en-us/azure/cosmos-db/introduction) is a globally-distributed database service that allows developers to work with data using a variety of standard APIs, such as SQL, MongoDB, Cassandra, Graph, and Table.
+
+**Spring Data Azure Cosmos DB** provides initial Spring Data support for Azure Cosmos DB using the [SQL API](https://docs.microsoft.com/en-us/azure/cosmos-db/sql-api-introduction), based on Spring Data framework. Currently it only supports SQL API, the other APIs are in the plan.
+
+## TOC
+
+* [Examples](#Examples)
+* [Spring data version support](#spring-data-version-support)
+* [Feature List](#feature-list)
+* [Quick Start](#quick-start)
+* [Query Partitioned Collection](QueryPartitionedCollection.md)
+* [Snapshots](#snapshots)
+* [Troubleshooting](#Troubleshooting)
+* [Contributing](#Contributing)
+* [Code of Conduct](#code-of-conduct)
+* [Key concepts](#Key concepts)
+* [Next steps](#Next steps)
+
+## Examples
+Please refer to [sample project here](./samplecode).
+
+## Spring Data Version Support
+Version mapping between spring boot and spring-data-cosmosdb:
+
+| Spring boot version | spring-data-cosmosdb version |
+| :----------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
+|  | [](https://search.maven.org/search?q=g:com.microsoft.azure%20AND%20a:spring-data-cosmosdb%20AND%20v:2.3.*) |
+|  | [](https://search.maven.org/search?q=g:com.microsoft.azure%20AND%20a:spring-data-cosmosdb%20AND%20v:2.2.*) |
+|  | [](https://search.maven.org/search?q=g:com.microsoft.azure%20AND%20a:spring-data-cosmosdb%20AND%20v:2.1.*) |
+|  | [](https://search.maven.org/search?q=g:com.microsoft.azure%20AND%20a:spring-data-cosmosdb%20AND%20v:2.0.*) |
+
+## Feature List
+- Spring Data ReactiveCrudRepository CrudRepository basic CRUD functionality
+ - save
+ - findAll
+ - findOne by Id
+ - deleteAll
+ - delete by Id
+ - delete entity
+- Spring Data [@Id](https://github.com/spring-projects/spring-data-commons/blob/db62390de90c93a78743c97cc2cc9ccd964994a5/src/main/java/org/springframework/data/annotation/Id.java) annotation.
+ There're 2 ways to map a field in domain class to `id` field of Azure Cosmos DB document.
+ - annotate a field in domain class with `@Id`, this field will be mapped to document `id` in Cosmos DB.
+ - set name of this field to `id`, this field will be mapped to document `id` in Azure Cosmos DB.
+- Custom collection Name.
+ By default, collection name will be class name of user domain class. To customize it, add the `@Document(collection="myCustomCollectionName")` annotation to the domain class. The collection field also supports SpEL expressions (eg. `collection = "${dynamic.collection.name}"` or `collection = "#{@someBean.getContainerName()}"`) in order to provide collection names programmatically/via configuration properties.
+- Custom IndexingPolicy
+ By default, IndexingPolicy will be set by azure service. To customize it add annotation `@DocumentIndexingPolicy` to domain class. This annotation has 4 attributes to customize, see following:
+```java
+ boolean automatic; // Indicate if indexing policy use automatic or not
+ IndexingMode mode; // Indexing policy mode, option Consistent|Lazy|None.
+ String[] includePaths; // Included paths for indexing
+ String[] excludePaths; // Excluded paths for indexing
+```
+- Supports Optimistic Locking for specific collections, which means upserts/deletes by document will fail with an exception in case the document was modified by another process in the meanwhile. To enable Optimistic Locking for a collection, just create a string `_etag` field and mark it with the `@Version` annotation. See the following:
+
+```java
+@Document(collection = "myCollection")
+class MyDocument {
+ String id;
+ String data;
+ @Version
+ String _etag;
+}
+```
+- Supports [Azure Cosmos DB partition](https://docs.microsoft.com/en-us/azure/cosmos-db/partition-data). To specify a field of domain class to be partition key field, just annotate it with `@PartitionKey`. When you do CRUD operation, pls specify your partition value. For more sample on partition CRUD, pls refer to [test here](./src/test/java/com/microsoft/azure/spring/data/cosmosdb/repository/integration/AddressRepositoryIT.java)
+- Supports [Spring Data custom query](https://docs.spring.io/spring-data/commons/docs/current/reference/html/#repositories.query-methods.details) find operation, e.g., `findByAFieldAndBField`
+- Supports [Spring Data pagable and sort](https://docs.spring.io/spring-data/commons/docs/current/reference/html/#repositories.special-parameters).
+ - Based on available RUs on the database account, cosmosDB can return documents less than or equal to the requested size.
+ - Due to this variable number of returned documents in every iteration, user should not rely on the totalPageSize, and instead iterating over pageable should be done in this way.
+```java
+ final CosmosPageRequest pageRequest = new CosmosPageRequest(0, pageSize, null);
+ Page page = tRepository.findAll(pageRequest);
+ List pageContent = page.getContent();
+ while(page.hasNext()) {
+ Pageable nextPageable = page.nextPageable();
+ page = repository.findAll(nextPageable);
+ pageContent = page.getContent();
+ }
+```
+- Supports [spring-boot-starter-data-rest](https://projects.spring.io/spring-data-rest/).
+- Supports List and nested type in domain class.
+- Configurable ObjectMapper bean with unique name `cosmosdbObjectMapper`, only configure customized ObjectMapper if you really need to. e.g.,
+```java
+ @Bean(name = "cosmosdbObjectMapper")
+ public ObjectMapper objectMapper() {
+ return new ObjectMapper(); // Do configuration to the ObjectMapper if required
+ }
+```
+
+## Quick Start
+
+### Add the dependency
+`spring-data-cosmosdb` is published on Maven Central Repository.
+If you are using Maven, add the following dependency.
+
+```xml
+
+ com.microsoft.azure
+ spring-data-cosmosdb
+ 2.2.4
+
+```
+
+### Setup Configuration
+Setup configuration class.
+
+CosmosKeyCredential feature provides capability to rotate keys on the fly. You can switch keys using switchToSecondaryKey().
+For more information on this, see the Sample Application code.
+
+### Sync and Reactive Repository support
+2.2.x supports both sync and reactive repository support.
+
+Use `@EnableCosmosRepositories` to enable sync repository support.
+
+For reactive repository support, use `@EnableReactiveCosmosRepositories`
+
+### Response Diagnostics String and Query Metrics
+2.2.x supports Response Diagnostics String and Query Metrics.
+Set `populateQueryMetrics` flag to true in application.properties to enable query metrics.
+In addition to setting the flag, implement `ResponseDiagnosticsProcessor` to log diagnostics information.
+
+```java
+@Configuration
+@EnableCosmosRepositories
+@Slf4j
+public class AppConfiguration extends AbstractCosmosConfiguration {
+
+ @Value("${azure.cosmosdb.uri}")
+ private String uri;
+
+ @Value("${azure.cosmosdb.key}")
+ private String key;
+
+ @Value("${azure.cosmosdb.secondaryKey}")
+ private String secondaryKey;
+
+ @Value("${azure.cosmosdb.database}")
+ private String dbName;
+
+ @Value("${azure.cosmosdb.populateQueryMetrics}")
+ private boolean populateQueryMetrics;
+
+ private CosmosKeyCredential cosmosKeyCredential;
+
+ public CosmosDBConfig getConfig() {
+ this.cosmosKeyCredential = new CosmosKeyCredential(key);
+ CosmosDbConfig cosmosdbConfig = CosmosDBConfig.builder(uri,
+ this.cosmosKeyCredential, dbName).build();
+ cosmosdbConfig.setPopulateQueryMetrics(populateQueryMetrics);
+ cosmosdbConfig.setResponseDiagnosticsProcessor(new ResponseDiagnosticsProcessorImplementation());
+ return cosmosdbConfig;
+ }
+
+ public void switchToSecondaryKey() {
+ this.cosmosKeyCredential.key(secondaryKey);
+ }
+
+ private static class ResponseDiagnosticsProcessorImplementation implements ResponseDiagnosticsProcessor {
+
+ @Override
+ public void processResponseDiagnostics(@Nullable ResponseDiagnostics responseDiagnostics) {
+ log.info("Response Diagnostics {}", responseDiagnostics);
+ }
+ }
+
+}
+```
+Or if you want to customize your config:
+```java
+public CosmosDBConfig getConfig() {
+ this.cosmosKeyCredential = new CosmosKeyCredential(key);
+ CosmosDBConfig cosmosDbConfig = CosmosDBConfig.builder(uri, this.cosmosKeyCredential, dbName).build();
+ cosmosDbConfig.getConnectionPolicy().setConnectionMode(ConnectionMode.DIRECT);
+ cosmosDbConfig.getConnectionPolicy().setMaxPoolSize(1000);
+ return cosmosDbConfig;
+}
+```
+By default, `@EnableCosmosRepositories` will scan the current package for any interfaces that extend one of Spring Data's repository interfaces. Using it to annotate your Configuration class to scan a different root package by type if your project layout has multiple projects and it's not finding your repositories.
+```java
+@Configuration
+@EnableCosmosRepositories(basePackageClass=UserRepository.class)
+public class AppConfiguration extends AbstractCosmosConfiguration {
+ // configuration code
+}
+```
+
+
+### Define an entity
+Define a simple entity as Document in Azure Cosmos DB.
+
+You can define entities by adding the `@Document` annotation and specifying properties related to the container, such as the container name, request units (RUs), time to live, and auto-create container.
+
+Containers are created automatically unless you don't want them to: Set `autoCreateCollection` to false in `@Document` annotation to disable auto creation of containers.
+
+Note: By default request units assigned to newly created containers is 4000. Specify different ru value to customize request units for container created by the SDK (minimum RU value is 400).
+
+```java
+@Document(collection = "myCollection", ru = "400")
+public class User {
+ private String id;
+ private String firstName;
+
+ @PartitionKey
+ private String lastName;
+
+ ... // setters and getters
+
+ public User() {
+ // If you do not want to create a default constructor,
+ // use annotation @JsonCreator and @JsonProperty in the full args constructor
+ }
+
+ public User(String id, String firstName, String lastName) {
+ this.id = id;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("User: %s %s, %s", firstName, lastName, id);
+ }
+}
+```
+`id` field will be used as document id in Azure Cosmos DB. If you want use another field like `emailAddress` as document `id`, just annotate that field with `@Id` annotation.
+
+Annotation `@Document(collection="mycollection")` is used to specify collection name in Azure Cosmos DB.
+Annotation `@PartitionKey` on `lastName` field is used to specify this field be partition key in Azure Cosmos DB.
+
+```java
+@Document(collection = "mycollection")
+public class User {
+ @Id
+ private String emailAddress;
+
+ ...
+}
+```
+
+### Create repositories
+Extends CosmosRepository interface, which provides Spring Data repository support.
+
+```java
+import CosmosRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserRepository extends CosmosRepository {
+ List findByFirstName(String firstName);
+}
+```
+
+`findByFirstName` method is custom query method, it will find documents per FirstName.
+
+### Create an Application class
+Here create an application class with all the components
+
+```java
+@SpringBootApplication
+public class SampleApplication implements CommandLineRunner {
+
+ @Autowired
+ private UserRepository repository;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ public static void main(String[] args) {
+ SpringApplication.run(SampleApplication.class, args);
+ }
+
+ public void run(String... var1) throws Exception {
+
+ final User testUser = new User("testId", "testFirstName", "testLastName");
+
+ repository.deleteAll();
+ repository.save(testUser);
+
+ // to find by Id, please specify partition key value if collection is partitioned
+ final User result = repository.findOne(testUser.getId(), testUser.getLastName);
+ // if emailAddress is mapped to id, then
+ // final User result = respository.findOne(testUser.getEmailAddress(), testUser.getLastName());
+
+ // Switch to secondary key
+ UserRepositoryConfiguration bean =
+ applicationContext.getBean(UserRepositoryConfiguration.class);
+ bean.switchToSecondaryKey();
+
+ // Now repository will use secondary key
+ repository.save(testUser);
+
+ }
+}
+```
+Autowired UserRepository interface, then can do save, delete and find operations. Spring Data Azure Cosmos DB uses the CosmosTemplate to execute the queries behind *find*, *save* methods. You can use the template yourself for more complex queries.
+
+## Snapshots
+[](https://oss.sonatype.org/content/repositories/snapshots/com/microsoft/azure/spring-data-cosmosdb/)
+
+Snapshots built from `master` branch are available, add [maven repositories](https://maven.apache.org/settings.html#Repositories) configuration to your pom file as below.
+```xml
+
+
+ nexus-snapshots
+ https://oss.sonatype.org/content/repositories/snapshots/
+
+ true
+ always
+
+
+
+```
+
+## Troubleshooting
+
+If you encounter any bug, please file an issue [here](https://github.com/Microsoft/spring-data-cosmosdb/issues/new).
+
+To suggest a new feature or changes that could be made, file an issue the same way you would for a bug.
+
+## Contributing
+
+Contribution is welcome. Please follow [this instruction](https://github.com/Azure/azure-sdk-for-java/blob/master/CONTRIBUTING.md) to contribute code.
+
+## Code of Conduct
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+
+### Data/Telemetry
+
+ This project collects usage data and sends it to Microsoft to help improve our products and services. Read our [privacy](https://privacy.microsoft.com/en-us/privacystatement) statement to learn more.
+
+## Key concepts
+
+## Next steps
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/pom.xml b/sdk/cosmos/azure-spring-data-cosmosdb/pom.xml
new file mode 100644
index 000000000000..c173ad25dd9a
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/pom.xml
@@ -0,0 +1,275 @@
+
+
+ 4.0.0
+
+ com.azure
+ azure-client-sdk-parent
+ 1.7.0
+ ../../parents/azure-client-sdk-parent
+
+
+ com.microsoft.azure
+ spring-data-cosmosdb
+ 2.3.1-beta.1
+
+ Spring Data for Azure Cosmos DB SQL API
+ Spring Data for Azure Cosmos DB SQL API
+ https://github.com/Microsoft/spring-data-cosmosdb
+
+
+ MM-dd-HH-mm-ss
+ 0.17
+ 0.18
+
+ spring-data-cosmosdb-test
+ testdb-${maven.build.timestamp}
+ true
+ false
+ false
+
+
+
+
+ org.springframework
+ spring-core
+ 5.2.6.RELEASE
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ org.springframework
+ spring-web
+ 5.2.6.RELEASE
+
+
+
+ org.springframework
+ spring-beans
+ 5.2.6.RELEASE
+
+
+
+ org.springframework
+ spring-context
+ 5.2.6.RELEASE
+
+
+
+ org.springframework
+ spring-tx
+ 5.2.6.RELEASE
+
+
+
+ org.springframework.data
+ spring-data-commons
+ 2.3.0.RELEASE
+
+
+
+ org.springframework
+ spring-expression
+ 5.2.6.RELEASE
+
+
+ com.microsoft.azure
+ azure-cosmos
+ 3.7.3
+
+
+
+ com.fasterxml.jackson.module
+ jackson-module-parameter-names
+ 2.10.0
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jdk8
+ 2.10.0
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.10.1
+
+
+ org.json
+ json
+ 20140107
+
+
+
+ org.javatuples
+ javatuples
+ 1.2
+
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+
+
+
+
+ org.mockito
+ mockito-core
+ 3.0.0
+ test
+
+
+ org.powermock
+ powermock-module-junit4
+ 2.0.2
+ test
+
+
+ org.powermock
+ powermock-api-mockito2
+ 2.0.2
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 2.3.0.RELEASE
+ test
+
+
+ com.vaadin.external.google
+ android-json
+
+
+
+
+
+ io.projectreactor
+ reactor-test
+ 3.3.5.RELEASE
+ test
+
+
+
+ org.slf4j
+ slf4j-simple
+ 1.7.25
+ test
+
+
+
+ com.google.code.gson
+ gson
+ 2.8.5
+ test
+
+
+
+
+
+
+ src/main/resources
+ true
+
+ META-INF/project.properties
+ telemetry.config
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.0.0-M3
+
+
+
+
+ com.azure:*
+ org.springframework:spring-beans:[5.2.6.RELEASE]
+ org.springframework:spring-web:[5.2.6.RELEASE]
+ org.springframework:spring-tx:[5.2.6.RELEASE]
+ org.springframework:spring-expression:[5.2.6.RELEASE]
+ org.springframework:spring-core:[5.2.6.RELEASE]
+ org.springframework:spring-context:[5.2.6.RELEASE]
+ org.springframework.data:spring-data-commons:[2.3.0.RELEASE]
+ com.microsoft.azure:azure-cosmos:[3.7.3]
+ org.javatuples:javatuples:[1.2]
+ com.fasterxml.jackson.datatype:jackson-datatype-jdk8:[2.10.0]
+ com.fasterxml.jackson.datatype:jackson-datatype-jsr310:[2.10.1]
+ org.json:json:[20140107]
+ com.fasterxml.jackson.module:jackson-module-parameter-names:[2.10.0]
+ javax.annotation:javax.annotation-api:[1.3.2]
+ org.slf4j:slf4j-simple:[1.7.25]
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ cobertura-maven-plugin
+ 2.7
+
+
+ html
+ xml
+
+
+ true
+ 65
+ 65
+
+
+
+ com/microsoft/azure/**/GetHashMac.class
+ com/microsoft/azure/**/Constants.class
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 2.22.0
+
+ src/test/resources/application.properties
+ ${skip.integration.tests}
+
+
+
+ integration-test
+
+ integration-test
+ verify
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.1.1
+
+ private
+
+
+ BasicCosmosPersistentProperty.java
+
+
+ ${basedir}/src/main/java/com/microsoft/azure/spring/data/cosmosdb/core/mapping/
+
+
+
+
+
+
+
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/Constants.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/Constants.java
new file mode 100644
index 000000000000..63001bddc981
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/Constants.java
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.azure.spring.data.cosmosdb;
+
+import com.azure.data.cosmos.IndexingMode;
+
+/**
+ * Constants class of CosmosDB properties
+ */
+public final class Constants {
+
+ public static final String DEFAULT_COLLECTION_NAME = "";
+ public static final String DEFAULT_REQUEST_UNIT = "4000";
+ public static final boolean DEFAULT_INDEXINGPOLICY_AUTOMATIC = true;
+ public static final IndexingMode DEFAULT_INDEXINGPOLICY_MODE = IndexingMode.CONSISTENT;
+ public static final String DEFAULT_REPOSITORY_IMPLEMENT_POSTFIX = "Impl";
+ public static final int DEFAULT_TIME_TO_LIVE = -1; // Indicates never expire
+ public static final boolean DEFAULT_AUTO_CREATE_CONTAINER = true;
+
+ public static final String ID_PROPERTY_NAME = "id";
+
+ public static final String COSMOSDB_MODULE_NAME = "cosmosdb";
+ public static final String COSMOSDB_MODULE_PREFIX = "cosmosdb";
+ public static final String COSMOS_MAPPING_CONTEXT = "cosmosMappingContext";
+
+ public static final String USER_AGENT_SUFFIX = "spring-data/";
+
+ public static final String OBJECTMAPPER_BEAN_NAME = "cosmosdbObjectMapper";
+
+ public static final String ISO_8601_COMPATIBLE_DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss:SSSXXX";
+
+ private Constants() {
+ }
+}
+
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/CosmosDbFactory.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/CosmosDbFactory.java
new file mode 100644
index 000000000000..20ca21753504
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/CosmosDbFactory.java
@@ -0,0 +1,120 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.azure.spring.data.cosmosdb;
+
+import com.azure.data.cosmos.ConnectionPolicy;
+import com.azure.data.cosmos.CosmosClient;
+import com.azure.data.cosmos.sync.CosmosSyncClient;
+import com.microsoft.azure.spring.data.cosmosdb.common.MacAddress;
+import com.microsoft.azure.spring.data.cosmosdb.common.PropertyLoader;
+import com.microsoft.azure.spring.data.cosmosdb.common.TelemetrySender;
+import com.microsoft.azure.spring.data.cosmosdb.config.CosmosDBConfig;
+import org.springframework.lang.NonNull;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * Factory class for cosmosdb to create client
+ */
+public class CosmosDbFactory {
+
+ private final CosmosDBConfig config;
+
+ private static final boolean IS_TELEMETRY_ALLOWED = PropertyLoader.isApplicationTelemetryAllowed();
+
+ private static final String USER_AGENT_SUFFIX = Constants.USER_AGENT_SUFFIX + PropertyLoader.getProjectVersion();
+
+ private String getUserAgentSuffix() {
+ String suffix = ";" + USER_AGENT_SUFFIX;
+
+ if (IS_TELEMETRY_ALLOWED || config.isAllowTelemetry()) {
+ suffix += ";" + MacAddress.getHashMac();
+ }
+
+ return suffix;
+ }
+
+ /**
+ * Validate config and initialization
+ *
+ * @param config cosmosdb config
+ */
+ public CosmosDbFactory(@NonNull CosmosDBConfig config) {
+ validateConfig(config);
+
+ this.config = config;
+ }
+
+ /**
+ * To create a CosmosClient
+ *
+ * @return CosmosClient
+ */
+ public CosmosClient getCosmosClient() {
+ final ConnectionPolicy policy = config.getConnectionPolicy();
+ final String userAgent = getUserAgentSuffix() + ";" + policy.userAgentSuffix();
+
+ policy.userAgentSuffix(userAgent);
+ return CosmosClient.builder()
+ .endpoint(config.getUri())
+ .key(config.getKey())
+ .cosmosKeyCredential(config.getCosmosKeyCredential())
+ .connectionPolicy(policy)
+ .consistencyLevel(config.getConsistencyLevel())
+ .build();
+ }
+
+ /**
+ * To create a CosmosSyncClient
+ *
+ * @return CosmosSyncClient
+ */
+ public CosmosSyncClient getCosmosSyncClient() {
+ final ConnectionPolicy policy = config.getConnectionPolicy();
+ final String userAgent = getUserAgentSuffix() + ";" + policy.userAgentSuffix();
+
+ policy.userAgentSuffix(userAgent);
+ return CosmosClient.builder()
+ .endpoint(config.getUri())
+ .key(config.getKey())
+ .cosmosKeyCredential(config.getCosmosKeyCredential())
+ .connectionPolicy(policy)
+ .consistencyLevel(config.getConsistencyLevel())
+ .buildSyncClient();
+ }
+
+ private void validateConfig(@NonNull CosmosDBConfig config) {
+ Assert.hasText(config.getUri(), "cosmosdb host url should have text!");
+ if (config.getCosmosKeyCredential() == null) {
+ Assert.hasText(config.getKey(), "cosmosdb host key should have text!");
+ } else if (StringUtils.isEmpty(config.getKey())) {
+ Assert.hasText(config.getCosmosKeyCredential().key(),
+ "cosmosdb credential host key should have text!");
+ }
+ Assert.hasText(config.getDatabase(), "cosmosdb database should have text!");
+ Assert.notNull(config.getConnectionPolicy(), "cosmosdb connection policy should not be null!");
+ }
+
+ @PostConstruct
+ private void sendTelemetry() {
+ // If any one of them is enabled, send telemetry data
+ if (IS_TELEMETRY_ALLOWED || config.isAllowTelemetry()) {
+ final TelemetrySender sender = new TelemetrySender();
+
+ sender.send(this.getClass().getSimpleName());
+ }
+ }
+
+ /**
+ * To get config object of cosmosdb
+ *
+ * @return CosmosDBConfig
+ */
+ public CosmosDBConfig getConfig() {
+ return config;
+ }
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/CosmosdbUtils.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/CosmosdbUtils.java
new file mode 100644
index 000000000000..446a67539437
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/CosmosdbUtils.java
@@ -0,0 +1,85 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.azure.spring.data.cosmosdb.common;
+
+import com.azure.data.cosmos.CosmosResponse;
+import com.azure.data.cosmos.CosmosResponseDiagnostics;
+import com.azure.data.cosmos.FeedResponse;
+import com.azure.data.cosmos.FeedResponseDiagnostics;
+import com.azure.data.cosmos.Resource;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.microsoft.azure.spring.data.cosmosdb.core.ResponseDiagnostics;
+import com.microsoft.azure.spring.data.cosmosdb.core.ResponseDiagnosticsProcessor;
+import com.microsoft.azure.spring.data.cosmosdb.core.convert.ObjectMapperFactory;
+import com.microsoft.azure.spring.data.cosmosdb.exception.ConfigurationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.lang.NonNull;
+
+import java.io.IOException;
+
+/**
+ * Util class to fill and process response diagnostics
+ */
+public class CosmosdbUtils {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(CosmosdbUtils.class);
+
+ /**
+ * Get a copy of an existing instance
+ * @param instance the known instance
+ * @param type of instance
+ * @return copy instance
+ * @throws ConfigurationException if the class type is invalid
+ */
+ @SuppressWarnings("unchecked")
+ public static T getCopyFrom(@NonNull T instance) {
+ final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();
+
+ try {
+ final String s = mapper.writeValueAsString(instance);
+ return (T) mapper.readValue(s, instance.getClass());
+ } catch (IOException e) {
+ throw new ConfigurationException("failed to get copy from "
+ + instance.getClass().getName(), e);
+ }
+ }
+
+ /**
+ * Generate ResponseDiagnostics with cosmos and feed response diagnostics
+ *
+ * @param type of cosmosResponse
+ * @param responseDiagnosticsProcessor collect Response Diagnostics from API responses and
+ * then set in {@link ResponseDiagnostics} object.
+ * @param cosmosResponse response from cosmos
+ * @param feedResponse response from feed
+ */
+ public static void fillAndProcessResponseDiagnostics(
+ ResponseDiagnosticsProcessor responseDiagnosticsProcessor,
+ CosmosResponse cosmosResponse, FeedResponse feedResponse) {
+ if (responseDiagnosticsProcessor == null) {
+ return;
+ }
+ CosmosResponseDiagnostics cosmosResponseDiagnostics = null;
+ if (cosmosResponse != null) {
+ cosmosResponseDiagnostics = cosmosResponse.cosmosResponseDiagnosticsString();
+ }
+ FeedResponseDiagnostics feedResponseDiagnostics = null;
+ ResponseDiagnostics.CosmosResponseStatistics cosmosResponseStatistics = null;
+ if (feedResponse != null) {
+ feedResponseDiagnostics = feedResponse.feedResponseDiagnostics();
+ cosmosResponseStatistics = new ResponseDiagnostics.CosmosResponseStatistics(feedResponse);
+ }
+ if (cosmosResponseDiagnostics == null
+ && (feedResponseDiagnostics == null || feedResponseDiagnostics.toString().isEmpty())
+ && cosmosResponseStatistics == null) {
+ LOGGER.debug("Empty response diagnostics");
+ return;
+ }
+ final ResponseDiagnostics responseDiagnostics =
+ new ResponseDiagnostics(cosmosResponseDiagnostics, feedResponseDiagnostics, cosmosResponseStatistics);
+
+ // Process response diagnostics
+ responseDiagnosticsProcessor.processResponseDiagnostics(responseDiagnostics);
+ }
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/ExpressionResolver.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/ExpressionResolver.java
new file mode 100644
index 000000000000..c992ae5eaa4a
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/ExpressionResolver.java
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.azure.spring.data.cosmosdb.common;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.beans.factory.config.EmbeddedValueResolver;
+
+/**
+ *
+ * @author Domenico Sibilio
+ *
+ */
+public class ExpressionResolver {
+
+ private static EmbeddedValueResolver embeddedValueResolver;
+
+ /**
+ * Initialize ExpressionResolver with ConfigurableBeanFactory
+ * @param beanFactory used to initialize the embeddedValueResolver
+ */
+ public ExpressionResolver(BeanFactory beanFactory) {
+ if (beanFactory instanceof ConfigurableBeanFactory) {
+ setEmbeddedValueResolver(new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory));
+ }
+ }
+
+ /**
+ * Resolve the given string value via an {@link EmbeddedValueResolver}
+ * @param expression the expression to be resolved
+ * @return the resolved expression, may be {@literal null}
+ */
+ public static String resolveExpression(String expression) {
+ return embeddedValueResolver != null
+ ? embeddedValueResolver.resolveStringValue(expression)
+ : expression;
+ }
+
+ private static void setEmbeddedValueResolver(EmbeddedValueResolver embeddedValueResolver) {
+ ExpressionResolver.embeddedValueResolver = embeddedValueResolver;
+ }
+
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/MacAddress.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/MacAddress.java
new file mode 100644
index 000000000000..9db4d22a09ef
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/MacAddress.java
@@ -0,0 +1,156 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+/*
+ * Disclaimer:
+ * This class is copied from https://github.com/Microsoft/azure-tools-for-java/ with minor modification (fixing
+ * static analysis error).
+ * Location in the repo: /Utils/azuretools-core/src/com/microsoft/azuretools/azurecommons/util/MacAddress.java
+ */
+
+package com.microsoft.azure.spring.data.cosmosdb.common;
+
+import org.springframework.lang.NonNull;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Mac address class to transfer mac address to hash mac address.
+ */
+public final class MacAddress {
+
+ private static final String UNKNOWN_MAC_ADDRESS = "Unknown-Mac-Address";
+ private static final String MAC_REGEX = "([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}";
+ private static final String MAC_REGEX_ZERO = "([0]{2}[:-]){5}[0]{2}";
+ private static final String HASHED_MAC_REGEX = "[0-9a-f]{64}";
+
+ private MacAddress() {
+ }
+
+ private static boolean isValidHashMacFormat(@NonNull String hashMac) {
+ if (hashMac.isEmpty()) {
+ return false;
+ }
+
+ return Pattern.compile(HASHED_MAC_REGEX).matcher(hashMac).matches();
+ }
+
+ private static String getRawMac() {
+ final List commands;
+ final String os = System.getProperty("os.name");
+ final StringBuilder macBuilder = new StringBuilder();
+
+ if (os != null
+ && !os.isEmpty()
+ && os.toLowerCase(Locale.US).startsWith("win")) {
+ commands = Collections.singletonList("getmac");
+ } else {
+ commands = Arrays.asList("ifconfig", "-a");
+ }
+
+ try {
+ String tmp;
+ final ProcessBuilder builder = new ProcessBuilder(commands);
+ final Process process = builder.start();
+ final InputStreamReader streamReader = new InputStreamReader(process.getInputStream(),
+ StandardCharsets.UTF_8);
+
+ try {
+ final BufferedReader reader = new BufferedReader(streamReader);
+ try {
+ while ((tmp = reader.readLine()) != null) {
+ macBuilder.append(tmp);
+ }
+ } finally {
+ reader.close();
+ }
+ } finally {
+ streamReader.close();
+ }
+ } catch (IOException e) {
+ return "";
+ }
+
+ return macBuilder.toString();
+ }
+
+ private static String getHexDigest(byte digest) {
+ final String hex = Integer.toString((digest & 0xff) + 0x100, 16);
+
+ return hex.substring(1);
+ }
+
+ private static String hash(@NonNull String mac) {
+ if (mac.isEmpty()) {
+ return "";
+ }
+
+ final StringBuilder builder = new StringBuilder();
+
+ try {
+ final MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
+
+ messageDigest.update(mac.getBytes(StandardCharsets.UTF_8));
+
+ final byte[] digestBytes = messageDigest.digest();
+
+ for (final byte digest : digestBytes) {
+ builder.append(getHexDigest(digest));
+ }
+ } catch (NoSuchAlgorithmException ex) {
+ return "";
+ }
+
+ Assert.isTrue(isValidHashMacFormat(builder.toString()), "Invalid format for HashMac");
+
+ return builder.toString();
+ }
+
+ /**
+ * To get a hash Mac address.
+ *
+ * @return String Hash mac address
+ */
+ public static String getHashMac() {
+ final String rawMac = getRawMac();
+
+ if (rawMac.isEmpty()) {
+ return UNKNOWN_MAC_ADDRESS;
+ }
+
+ final Pattern pattern = Pattern.compile(MAC_REGEX);
+ final Pattern patternZero = Pattern.compile(MAC_REGEX_ZERO);
+ final Matcher matcher = pattern.matcher(rawMac);
+
+ String mac = "";
+
+ while (matcher.find()) {
+ mac = matcher.group(0);
+
+ if (!patternZero.matcher(mac).matches()) {
+ break;
+ }
+ }
+
+ final String hashMac = hash(mac);
+
+ if (StringUtils.hasText(hashMac)) {
+ return hashMac;
+ }
+
+ return UNKNOWN_MAC_ADDRESS;
+ }
+}
+
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/Memoizer.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/Memoizer.java
new file mode 100644
index 000000000000..ce3530647e72
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/Memoizer.java
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.azure.spring.data.cosmosdb.common;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+/**
+ * Memoize function computation results
+ */
+public final class Memoizer {
+
+ private final Map cache = new ConcurrentHashMap<>();
+
+ private Memoizer() {
+ }
+
+ /**
+ * Put function computation results into Memoizer
+ *
+ * @param the type of the input to the function
+ * @param the type of the output of the function
+ * @param function represents a function that accepts one argument and produces a result
+ * @return Function
+ */
+ public static Function memoize(Function function) {
+ return new Memoizer().internalMemoize(function);
+ }
+
+ private Function internalMemoize(Function function) {
+ return input -> cache.computeIfAbsent(input, function);
+ }
+
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/PropertyLoader.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/PropertyLoader.java
new file mode 100644
index 000000000000..e01c5bba3221
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/PropertyLoader.java
@@ -0,0 +1,87 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.azure.spring.data.cosmosdb.common;
+
+import org.springframework.lang.NonNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * Load properties from files
+ */
+public final class PropertyLoader {
+
+ private static final String PROJECT_PROPERTY_FILE = "/META-INF/project.properties";
+
+ private static final String APPLICATION_PROPERTY_FILE = "/application.properties";
+
+ private static final String APPLICATION_YML_FILE = "/application.yml";
+
+ private static final String TELEMETRY_CONFIG_FILE = "/telemetry.config";
+
+ private PropertyLoader() {
+ }
+
+ /**
+ * Get project version from /META-INF/project.properties
+ *
+ * @return String project version
+ */
+ public static String getProjectVersion() {
+ return getPropertyByName("project.version", PROJECT_PROPERTY_FILE);
+ }
+
+ /**
+ * Get telemetry instrumentation key from /telemetry.config
+ *
+ * @return String telemetry instrumentation key
+ */
+ public static String getTelemetryInstrumentationKey() {
+ return getPropertyByName("telemetry.instrumentationKey", TELEMETRY_CONFIG_FILE);
+ }
+
+ /**
+ * Check if telemetry is allowed
+ *
+ * @return boolean if telemetry is allowed
+ */
+ public static boolean isApplicationTelemetryAllowed() {
+ String allowed = getPropertyByName("cosmosdb.telemetryAllowed", APPLICATION_PROPERTY_FILE);
+
+ if (allowed == null) {
+ allowed = getPropertyByName("telemetryAllowed", APPLICATION_YML_FILE);
+ }
+
+ // Default, no telemetry
+ if (allowed == null) {
+ return false;
+ } else {
+ return !allowed.equalsIgnoreCase("false");
+ }
+ }
+
+ private static String getPropertyByName(@NonNull String name, @NonNull String filename) {
+ final Properties properties = new Properties();
+ final InputStream inputStream = PropertyLoader.class.getResourceAsStream(filename);
+
+ if (inputStream == null) {
+ return null;
+ }
+
+ try {
+ properties.load(inputStream);
+ } catch (IOException e) {
+ // Omitted
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // Omitted
+ }
+ }
+
+ return properties.getProperty(name);
+ }
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/TelemetryEventData.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/TelemetryEventData.java
new file mode 100644
index 000000000000..56a980dcd3d0
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/TelemetryEventData.java
@@ -0,0 +1,159 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.azure.spring.data.cosmosdb.common;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.springframework.lang.NonNull;
+import org.springframework.util.Assert;
+
+import java.time.Instant;
+import java.util.Map;
+
+/**
+ * Data format class for telemetry event.
+ */
+public class TelemetryEventData {
+
+ private final String name;
+
+ @JsonProperty("iKey")
+ private final String instrumentationKey;
+
+ private final Tags tags = new Tags("Spring-on-azure", "Java-maven-plugin");
+
+ private final EventData data = new EventData("EventData");
+
+ private final String time;
+
+ /**
+ * Initialize data of a telemetry event
+ *
+ * @param eventName specify an event
+ * @param properties properties of event
+ */
+ public TelemetryEventData(String eventName, @NonNull Map properties) {
+ Assert.hasText(eventName, "Event name should contain text.");
+
+ name = "Microsoft.ApplicationInsights.Event";
+ instrumentationKey = PropertyLoader.getTelemetryInstrumentationKey();
+
+ data.getBaseData().setName(eventName);
+ data.getBaseData().setProperties(properties);
+ time = Instant.now().toString();
+ }
+
+ /**
+ * Get name of event
+ *
+ * @return name value
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Get instrumentationKey of event
+ *
+ * @return instrumentationKey value
+ */
+ public String getInstrumentationKey() {
+ return instrumentationKey;
+ }
+
+ /**
+ * Get tags of event
+ *
+ * @return Tags value
+ */
+ public Tags getTags() {
+ return tags;
+ }
+
+ /**
+ * Get data of event
+ *
+ * @return EventData value
+ */
+ public EventData getData() {
+ return data;
+ }
+
+ /**
+ * Get time of event
+ *
+ * @return time value
+ */
+ public String getTime() {
+ return time;
+ }
+
+ private static class Tags {
+
+ @JsonProperty("ai.cloud.roleInstance")
+ private final String aiCloudRoleInstance;
+
+ @JsonProperty("ai.internal.sdkVersion")
+ private final String aiInternalSdkVersion;
+
+ Tags(String instance, String sdkVersion) {
+ aiCloudRoleInstance = instance;
+ aiInternalSdkVersion = sdkVersion;
+ }
+
+ public String getAiCloudRoleInstance() {
+ return aiCloudRoleInstance;
+ }
+
+ public String getAiInternalSdkVersion() {
+ return aiInternalSdkVersion;
+ }
+ }
+
+ private static class EventData {
+
+ private final String baseType;
+
+ private final CustomData baseData = new CustomData();
+
+ EventData(String baseType) {
+ this.baseType = baseType;
+ }
+
+ public String getBaseType() {
+ return baseType;
+ }
+
+ public CustomData getBaseData() {
+ return baseData;
+ }
+
+ private static class CustomData {
+
+ private final Integer ver = 2;
+
+ private String name;
+
+ private Map properties;
+
+ public Integer getVer() {
+ return ver;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ private void setName(String name) {
+ this.name = name;
+ }
+
+ public Map getProperties() {
+ return properties;
+ }
+
+ private void setProperties(Map properties) {
+ this.properties = properties;
+ }
+ }
+ }
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/TelemetrySender.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/TelemetrySender.java
new file mode 100644
index 000000000000..d6ef8baac173
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/TelemetrySender.java
@@ -0,0 +1,101 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.azure.spring.data.cosmosdb.common;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.Assert;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.lang.NonNull;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON;
+
+/**
+ * Class for telemetry sender to send request and event data
+ */
+public class TelemetrySender {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(TelemetrySender.class);
+
+ private static final String PROPERTY_INSTALLATION_ID = "installationId";
+
+ private static final String PROPERTY_VERSION = "version";
+
+ private static final String PROPERTY_SERVICE_NAME = "serviceName";
+
+ private static final String PROJECT_INFO = "spring-data-cosmosdb/"
+ + PropertyLoader.getProjectVersion();
+
+ private static final String TELEMETRY_TARGET_URL = "https://dc.services.visualstudio.com/v2/track";
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private static final int RETRY_LIMIT = 3; // Align the retry times with sdk
+
+ private ResponseEntity executeRequest(final TelemetryEventData eventData) {
+ final HttpHeaders headers = new HttpHeaders();
+
+ headers.add(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON.toString());
+
+ try {
+ final RestTemplate restTemplate = new RestTemplate();
+ final HttpEntity body = new HttpEntity<>(MAPPER.writeValueAsString(eventData), headers);
+
+ return restTemplate.exchange(TELEMETRY_TARGET_URL, HttpMethod.POST, body, String.class);
+ } catch (JsonProcessingException | HttpClientErrorException ignore) {
+ LOGGER.warn("Failed to exchange telemetry request, {}.", ignore.getMessage());
+ }
+
+ return null;
+ }
+
+ private void sendTelemetryData(@NonNull TelemetryEventData eventData) {
+ ResponseEntity response = null;
+
+ for (int i = 0; i < RETRY_LIMIT; i++) {
+ response = executeRequest(eventData);
+
+ if (response != null
+ && response.getStatusCode() == HttpStatus.OK) {
+ return;
+ }
+ }
+
+ if (response != null
+ && response.getStatusCode() != HttpStatus.OK) {
+ LOGGER.warn("Failed to send telemetry data, response status code {}.", response.getStatusCode().toString());
+ }
+ }
+
+ /**
+ * Send telemetry data according to event name
+ *
+ * @param name event name
+ */
+ public void send(String name) {
+ Assert.hasText(name, "Event name should contain text.");
+
+ sendTelemetryData(new TelemetryEventData(name, getProperties()));
+ }
+
+ private Map getProperties() {
+ final Map properties = new HashMap<>();
+
+ properties.put(PROPERTY_VERSION, PROJECT_INFO);
+ properties.put(PROPERTY_SERVICE_NAME, "cosmosdb");
+ properties.put(PROPERTY_INSTALLATION_ID, MacAddress.getHashMac());
+
+ return properties;
+ }
+}
+
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/package-info.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/package-info.java
new file mode 100644
index 000000000000..bbfa061b299a
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/common/package-info.java
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+/**
+ * This package contains the classes of utils for cosmosdb
+ */
+package com.microsoft.azure.spring.data.cosmosdb.common;
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/AbstractCosmosConfiguration.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/AbstractCosmosConfiguration.java
new file mode 100644
index 000000000000..005398857889
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/AbstractCosmosConfiguration.java
@@ -0,0 +1,92 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.azure.spring.data.cosmosdb.config;
+
+import com.azure.data.cosmos.CosmosClient;
+import com.azure.data.cosmos.sync.CosmosSyncClient;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.microsoft.azure.spring.data.cosmosdb.Constants;
+import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
+import com.microsoft.azure.spring.data.cosmosdb.core.CosmosTemplate;
+import com.microsoft.azure.spring.data.cosmosdb.core.ReactiveCosmosTemplate;
+import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingCosmosConverter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * To configure cosmos with client, cosmoscdb factory and template
+ */
+@Configuration
+public abstract class AbstractCosmosConfiguration extends CosmosConfigurationSupport {
+
+ /**
+ * Declare CosmosClient bean.
+ * @param config of cosmosDbFactory
+ * @return CosmosClient bean
+ */
+ @Bean
+ public CosmosClient cosmosClient(CosmosDBConfig config) {
+ return this.cosmosDbFactory(config).getCosmosClient();
+ }
+
+ /**
+ * Declare CosmosSyncClient bean.
+ * @param config of cosmosDbFactory
+ * @return CosmosSyncClient bean
+ */
+ @Bean
+ public CosmosSyncClient cosmosSyncClient(CosmosDBConfig config) {
+ return this.cosmosDbFactory(config).getCosmosSyncClient();
+ }
+
+ @Qualifier(Constants.OBJECTMAPPER_BEAN_NAME)
+ @Autowired(required = false)
+ private ObjectMapper objectMapper;
+
+ /**
+ * Declare CosmosDbFactory bean.
+ * @param config of cosmosDbFactory
+ * @return CosmosDbFactory bean
+ */
+ @Bean
+ public CosmosDbFactory cosmosDbFactory(CosmosDBConfig config) {
+ return new CosmosDbFactory(config);
+ }
+
+ /**
+ * Declare CosmosTemplate bean.
+ * @param config of cosmosDbFactory
+ * @return CosmosTemplate bean
+ * @throws ClassNotFoundException if the class type is invalid
+ */
+ @Bean
+ public CosmosTemplate cosmosTemplate(CosmosDBConfig config) throws ClassNotFoundException {
+ return new CosmosTemplate(this.cosmosDbFactory(config), this.mappingCosmosConverter(),
+ config.getDatabase());
+ }
+
+ /**
+ * Declare ReactiveCosmosTemplate bean.
+ * @param config of cosmosDbFactory
+ * @return ReactiveCosmosTemplate bean
+ * @throws ClassNotFoundException if the class type is invalid
+ */
+ @Bean
+ public ReactiveCosmosTemplate reactiveCosmosTemplate(CosmosDBConfig config) throws ClassNotFoundException {
+ return new ReactiveCosmosTemplate(this.cosmosDbFactory(config), this.mappingCosmosConverter(),
+ config.getDatabase());
+ }
+
+ /**
+ * Declare MappingCosmosConverter bean.
+ * @return MappingCosmosConverter bean
+ * @throws ClassNotFoundException if the class type is invalid
+ */
+ @Bean
+ public MappingCosmosConverter mappingCosmosConverter() throws ClassNotFoundException {
+ return new MappingCosmosConverter(this.cosmosMappingContext(), objectMapper);
+ }
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/CosmosConfigurationSupport.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/CosmosConfigurationSupport.java
new file mode 100644
index 000000000000..458cc27cb253
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/CosmosConfigurationSupport.java
@@ -0,0 +1,101 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.azure.spring.data.cosmosdb.config;
+
+import com.microsoft.azure.spring.data.cosmosdb.common.ExpressionResolver;
+import com.microsoft.azure.spring.data.cosmosdb.core.mapping.CosmosMappingContext;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.data.annotation.Persistent;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A support class for cosmos configuration to scan beans and get initial entities
+ */
+public abstract class CosmosConfigurationSupport {
+
+ /**
+ * Declare ExpressionResolver bean.
+ * @param beanFactory used to initialize the embeddedValueResolver
+ * @return ExpressionResolver bean
+ */
+ @Bean
+ public ExpressionResolver expressionResolver(BeanFactory beanFactory) {
+ return new ExpressionResolver(beanFactory);
+ }
+
+ /**
+ * Declare CosmosMappingContext bean.
+ * @return CosmosMappingContext bean
+ * @throws ClassNotFoundException if the class type is invalid
+ */
+ @Bean
+ public CosmosMappingContext cosmosMappingContext() throws ClassNotFoundException {
+ final CosmosMappingContext mappingContext = new CosmosMappingContext();
+ mappingContext.setInitialEntitySet(getInitialEntitySet());
+
+ return mappingContext;
+ }
+
+ protected Collection getMappingBasePackages() {
+ final Package mappingBasePackage = getClass().getPackage();
+ return Collections.singleton(mappingBasePackage == null ? null : mappingBasePackage.getName());
+ }
+
+ /**
+ * Scan all base packages and get all beans
+ * @return initial entity set
+ * @throws ClassNotFoundException if the class type is invalid
+ */
+ protected Set> getInitialEntitySet() throws ClassNotFoundException {
+ final Set> initialEntitySet = new HashSet<>();
+
+ for (final String basePackage : getMappingBasePackages()) {
+ initialEntitySet.addAll(scanForEntities(basePackage));
+ }
+
+ return initialEntitySet;
+ }
+
+ /**
+ * Scan all beans under the given base package
+ * @param basePackage set the base location of beans
+ * @return initial entity set for found beans
+ * @throws ClassNotFoundException if the class type is invalid
+ */
+ protected Set> scanForEntities(String basePackage) throws ClassNotFoundException {
+ if (!StringUtils.hasText(basePackage)) {
+ return Collections.emptySet();
+ }
+
+ final Set> initialEntitySet = new HashSet<>();
+
+ if (StringUtils.hasText(basePackage)) {
+ final ClassPathScanningCandidateComponentProvider componentProvider =
+ new ClassPathScanningCandidateComponentProvider(false);
+
+ componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
+
+ for (final BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
+ final String className = candidate.getBeanClassName();
+ Assert.notNull(className, "Bean class name is null.");
+
+ initialEntitySet
+ .add(ClassUtils.forName(className, CosmosConfigurationSupport.class.getClassLoader()));
+ }
+ }
+
+ return initialEntitySet;
+ }
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/CosmosDBConfig.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/CosmosDBConfig.java
new file mode 100644
index 000000000000..d8b607986dd7
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/CosmosDBConfig.java
@@ -0,0 +1,397 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.azure.spring.data.cosmosdb.config;
+
+import com.azure.data.cosmos.ConnectionPolicy;
+import com.azure.data.cosmos.ConsistencyLevel;
+import com.azure.data.cosmos.CosmosKeyCredential;
+import com.azure.data.cosmos.internal.RequestOptions;
+import com.microsoft.azure.spring.data.cosmosdb.core.ResponseDiagnosticsProcessor;
+import com.microsoft.azure.spring.data.cosmosdb.exception.CosmosDBAccessException;
+import org.springframework.util.Assert;
+
+import java.beans.ConstructorProperties;
+
+/**
+ * Config properties of CosmosDB
+ */
+public class CosmosDBConfig {
+ private String uri;
+
+ private String key;
+
+ private String database;
+
+ private ConnectionPolicy connectionPolicy;
+
+ private ConsistencyLevel consistencyLevel;
+
+ private boolean allowTelemetry;
+
+ private RequestOptions requestOptions;
+
+ private CosmosKeyCredential cosmosKeyCredential;
+
+ private ResponseDiagnosticsProcessor responseDiagnosticsProcessor;
+
+ private boolean populateQueryMetrics;
+
+ /**
+ * Initialization
+ * @param uri must not be {@literal null}
+ * @param key must not be {@literal null}
+ * @param database must not be {@literal null}
+ * @param connectionPolicy must not be {@literal null}
+ * @param consistencyLevel must not be {@literal null}
+ * @param allowTelemetry must not be {@literal null}
+ * @param requestOptions must not be {@literal null}
+ * @param cosmosKeyCredential must not be {@literal null}
+ * @param responseDiagnosticsProcessor must not be {@literal null}
+ * @param populateQueryMetrics must not be {@literal null}
+ */
+ @ConstructorProperties({"uri", "key", "database", "connectionPolicy", "consistencyLevel", "allowTelemetry",
+ "requestOptions", "cosmosKeyCredential", "responseDiagnosticsProcessor",
+ "populateQueryMetrics"})
+ public CosmosDBConfig(String uri, String key, String database, ConnectionPolicy connectionPolicy,
+ ConsistencyLevel consistencyLevel, boolean allowTelemetry, RequestOptions requestOptions,
+ CosmosKeyCredential cosmosKeyCredential,
+ ResponseDiagnosticsProcessor responseDiagnosticsProcessor, boolean populateQueryMetrics) {
+ this.uri = uri;
+ this.key = key;
+ this.database = database;
+ this.connectionPolicy = connectionPolicy;
+ this.consistencyLevel = consistencyLevel;
+ this.allowTelemetry = allowTelemetry;
+ this.requestOptions = requestOptions;
+ this.cosmosKeyCredential = cosmosKeyCredential;
+ this.responseDiagnosticsProcessor = responseDiagnosticsProcessor;
+ this.populateQueryMetrics = populateQueryMetrics;
+ }
+
+ /**
+ * Gets uri
+ * @return uri
+ */
+ public String getUri() {
+ return uri;
+ }
+
+ /**
+ * Gets key
+ * @return key
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Gets database
+ * @return database
+ */
+ public String getDatabase() {
+ return database;
+ }
+
+ /**
+ * Gets connection policy
+ * @return connectionPolicy
+ */
+ public ConnectionPolicy getConnectionPolicy() {
+ return connectionPolicy;
+ }
+
+ /**
+ * Gets consistency level
+ * @return ConsistencyLevel
+ */
+ public ConsistencyLevel getConsistencyLevel() {
+ return consistencyLevel;
+ }
+
+ /**
+ * Checks if telemetry is allowed
+ * @return boolean
+ */
+ public boolean isAllowTelemetry() {
+ return allowTelemetry;
+ }
+
+ /**
+ * Gets request options
+ * @return RequestOptions
+ */
+ public RequestOptions getRequestOptions() {
+ return requestOptions;
+ }
+
+ /**
+ * Gets Cosmos key credential
+ * @return CosmosKeyCredential
+ */
+ public CosmosKeyCredential getCosmosKeyCredential() {
+ return cosmosKeyCredential;
+ }
+
+ /**
+ * Gets response diagnostics processor
+ * @return ResponseDiagnosticsProcessor
+ */
+ public ResponseDiagnosticsProcessor getResponseDiagnosticsProcessor() {
+ return responseDiagnosticsProcessor;
+ }
+
+ /**
+ * Checks if is populate query metrics
+ * @return boolean
+ */
+ public boolean isPopulateQueryMetrics() {
+ return populateQueryMetrics;
+ }
+
+ /**
+ * Sets response diagnostics processor
+ * @param responseDiagnosticsProcessor must not be {@literal null}
+ */
+ public void setResponseDiagnosticsProcessor(ResponseDiagnosticsProcessor responseDiagnosticsProcessor) {
+ this.responseDiagnosticsProcessor = responseDiagnosticsProcessor;
+ }
+
+ /**
+ * Sets populate query metrics
+ * @param populateQueryMetrics must not be {@literal null}
+ */
+ public void setPopulateQueryMetrics(boolean populateQueryMetrics) {
+ this.populateQueryMetrics = populateQueryMetrics;
+ }
+
+ /**
+ * create a CosmosDBConfigBuilder with cosmos uri, cosmosKeyCredential and database name
+ * @param uri must not be {@literal null}
+ * @param cosmosKeyCredential must not be {@literal null}
+ * @param database must not be {@literal null}
+ * @return CosmosDBConfigBuilder
+ */
+ public static CosmosDBConfigBuilder builder(String uri, CosmosKeyCredential cosmosKeyCredential,
+ String database) {
+ return defaultBuilder()
+ .uri(uri)
+ .cosmosKeyCredential(cosmosKeyCredential)
+ .database(database)
+ .connectionPolicy(ConnectionPolicy.defaultPolicy())
+ .consistencyLevel(ConsistencyLevel.SESSION)
+ .requestOptions(new RequestOptions());
+ }
+
+ /**
+ * create a CosmosDBConfigBuilder with cosmos uri, key and database name
+ * @param uri must not be {@literal null}
+ * @param key must not be {@literal null}
+ * @param database must not be {@literal null}
+ * @return CosmosDBConfigBuilder
+ */
+ public static CosmosDBConfigBuilder builder(String uri, String key, String database) {
+ return defaultBuilder()
+ .uri(uri)
+ .key(key)
+ .database(database)
+ .connectionPolicy(ConnectionPolicy.defaultPolicy())
+ .consistencyLevel(ConsistencyLevel.SESSION)
+ .requestOptions(new RequestOptions());
+ }
+
+ /**
+ * create a CosmosDBConfigBuilder with connection string and database name
+ * @param connectionString must not be {@literal null}
+ * @param database must not be {@literal null}
+ * @return CosmosDBConfigBuilder
+ * @throws CosmosDBAccessException for invalid connection string
+ */
+ public static CosmosDBConfigBuilder builder(String connectionString, String database) {
+ Assert.hasText(connectionString, "connection string should have text!");
+ try {
+ final String uri = connectionString.split(";")[0].split("=")[1];
+ final String key = connectionString.split(";")[1].split("=")[1];
+ return builder(uri, key, database);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new CosmosDBAccessException("could not parse connection string");
+ }
+ }
+
+ /**
+ * create a CosmosDBConfigBuilder instance
+ * @return CosmosDBConfigBuilder
+ */
+ public static CosmosDBConfigBuilder defaultBuilder() {
+ return new CosmosDBConfigBuilder();
+ }
+
+ /**
+ * Builder class for cosmos db config
+ */
+ public static class CosmosDBConfigBuilder {
+ private String uri;
+ private String key;
+ private String database;
+ private ConnectionPolicy connectionPolicy;
+ private ConsistencyLevel consistencyLevel;
+ private boolean allowTelemetry;
+ private RequestOptions requestOptions;
+ private CosmosKeyCredential cosmosKeyCredential;
+ private ResponseDiagnosticsProcessor responseDiagnosticsProcessor;
+ private boolean populateQueryMetrics;
+
+ CosmosDBConfigBuilder() {
+ }
+
+ /**
+ * Set uri
+ *
+ * @param uri value to initialize
+ * @return CosmosDBConfigBuilder
+ */
+ public CosmosDBConfigBuilder uri(String uri) {
+ this.uri = uri;
+ return this;
+ }
+
+ /**
+ * Set key
+ *
+ * @param key value to initialize
+ * @return CosmosDBConfigBuilder
+ */
+ public CosmosDBConfigBuilder key(String key) {
+ this.key = key;
+ return this;
+ }
+
+ /**
+ * Set database
+ *
+ * @param database value to initialize
+ * @return CosmosDBConfigBuilder
+ */
+ public CosmosDBConfigBuilder database(String database) {
+ this.database = database;
+ return this;
+ }
+
+ /**
+ * Set connectionPolicy
+ *
+ * @param connectionPolicy value to initialize
+ * @return CosmosDBConfigBuilder
+ */
+ public CosmosDBConfigBuilder connectionPolicy(ConnectionPolicy connectionPolicy) {
+ this.connectionPolicy = connectionPolicy;
+ return this;
+ }
+
+ /**
+ * Set consistencyLevel
+ *
+ * @param consistencyLevel value to initialize
+ * @return CosmosDBConfigBuilder
+ */
+ public CosmosDBConfigBuilder consistencyLevel(ConsistencyLevel consistencyLevel) {
+ this.consistencyLevel = consistencyLevel;
+ return this;
+ }
+
+ /**
+ * Set allowTelemetry
+ *
+ * @param allowTelemetry value to initialize
+ * @return CosmosDBConfigBuilder
+ */
+ public CosmosDBConfigBuilder allowTelemetry(boolean allowTelemetry) {
+ this.allowTelemetry = allowTelemetry;
+ return this;
+ }
+
+ /**
+ * Set requestOptions
+ *
+ * @param requestOptions value to initialize
+ * @return CosmosDBConfigBuilder
+ */
+ public CosmosDBConfigBuilder requestOptions(RequestOptions requestOptions) {
+ this.requestOptions = requestOptions;
+ return this;
+ }
+
+ /**
+ * Set cosmosKeyCredential
+ *
+ * @param cosmosKeyCredential value to initialize
+ * @return CosmosDBConfigBuilder
+ */
+ public CosmosDBConfigBuilder cosmosKeyCredential(CosmosKeyCredential cosmosKeyCredential) {
+ this.cosmosKeyCredential = cosmosKeyCredential;
+ return this;
+ }
+
+ /**
+ * Set responseDiagnosticsProcessor
+ *
+ * @param responseDiagnosticsProcessor value to initialize
+ * @return CosmosDBConfigBuilder
+ */
+ public CosmosDBConfigBuilder responseDiagnosticsProcessor(ResponseDiagnosticsProcessor
+ responseDiagnosticsProcessor) {
+ this.responseDiagnosticsProcessor = responseDiagnosticsProcessor;
+ return this;
+ }
+
+ /**
+ * Set populateQueryMetrics
+ *
+ * @param populateQueryMetrics value to initialize
+ * @return CosmosDBConfigBuilder
+ */
+ public CosmosDBConfigBuilder populateQueryMetrics(boolean populateQueryMetrics) {
+ this.populateQueryMetrics = populateQueryMetrics;
+ return this;
+ }
+
+ /**
+ * Build a CosmosDBConfig instance
+ *
+ * @return CosmosDBConfig
+ */
+ public CosmosDBConfig build() {
+ return new CosmosDBConfig(this.uri, this.key, this.database, this.connectionPolicy, this.consistencyLevel,
+ this.allowTelemetry, this.requestOptions, this.cosmosKeyCredential, this.responseDiagnosticsProcessor,
+ this.populateQueryMetrics);
+ }
+
+ /**
+ * Generate string info of instance
+ *
+ * @return String
+ */
+ public String toString() {
+ return "CosmosDBConfig.CosmosDBConfigBuilder(uri="
+ + this.uri
+ + ", key="
+ + this.key
+ + ", database="
+ + this.database
+ + ", connectionPolicy="
+ + this.connectionPolicy
+ + ", consistencyLevel="
+ + this.consistencyLevel
+ + ", allowTelemetry="
+ + this.allowTelemetry
+ + ", requestOptions="
+ + this.requestOptions
+ + ", cosmosKeyCredential="
+ + this.cosmosKeyCredential
+ + ", responseDiagnosticsProcessor="
+ + this.responseDiagnosticsProcessor
+ + ", populateQueryMetrics="
+ + this.populateQueryMetrics
+ + ")";
+ }
+ }
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/package-info.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/package-info.java
new file mode 100644
index 000000000000..f2fcbfbf6754
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/config/package-info.java
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+/**
+ * This package contains the classes to configure properties of cosmos db
+ */
+package com.microsoft.azure.spring.data.cosmosdb.config;
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/CosmosOperations.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/CosmosOperations.java
new file mode 100644
index 000000000000..458e29377112
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/CosmosOperations.java
@@ -0,0 +1,281 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.azure.spring.data.cosmosdb.core;
+
+import com.azure.data.cosmos.CosmosContainerProperties;
+import com.azure.data.cosmos.PartitionKey;
+import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingCosmosConverter;
+import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery;
+import com.microsoft.azure.spring.data.cosmosdb.repository.support.CosmosEntityInformation;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+import java.util.List;
+
+/**
+ * Interface for cosmosdb operations
+ */
+public interface CosmosOperations {
+
+ /**
+ * Use getContainerName() instead
+ * @param domainType class type
+ * @return container name
+ * @deprecated Use {@link #getContainerName(Class)} instead
+ */
+ @Deprecated
+ String getCollectionName(Class> domainType);
+
+ /**
+ * To get container name by domaintype
+ * @param domainType class type
+ * @return String
+ */
+ String getContainerName(Class> domainType);
+
+ /**
+ * Use createContainerIfNotExists() instead
+ * @param information cosmos entity information
+ * @return created container properties
+ * @deprecated Use {@link #createContainerIfNotExists(CosmosEntityInformation)} instead
+ */
+ @Deprecated
+ CosmosContainerProperties createCollectionIfNotExists(CosmosEntityInformation, ?> information);
+
+ /**
+ * Creates container if not exists
+ * @param information CosmosEntityInformation
+ * @return CosmosContainerProperties
+ */
+ CosmosContainerProperties createContainerIfNotExists(CosmosEntityInformation, ?> information);
+
+ /**
+ * Find the DocumentQuery, find all the items specified by domain type.
+ *
+ * @param domainType the domain type
+ * @param class type of domain
+ * @return found results in a List
+ */
+ List findAll(Class domainType);
+
+ /**
+ * Find the DocumentQuery, find all the items specified by domain type in the given container.
+ *
+ * @param containerName the container name
+ * @param domainType the domain type
+ * @param class type of domain
+ * @return found results in a List
+ */
+ List findAll(String containerName, Class domainType);
+
+ /**
+ * Find the DocumentQuery, find all the items specified by domain type in the given container.
+ *
+ * @param partitionKey the partition key
+ * @param domainType the domain type
+ * @param class type of domain
+ * @return found results in a List
+ */
+ List findAll(PartitionKey partitionKey, Class domainType);
+
+ /**
+ * Finds item by id
+ * @param id must not be {@literal null}
+ * @param domainType must not be {@literal null}
+ * @param type class of domain type
+ * @return found item
+ */
+ T findById(Object id, Class domainType);
+
+ /**
+ * Finds item by id
+ * @param containerName must not be {@literal null}
+ * @param id must not be {@literal null}
+ * @param domainType must not be {@literal null}
+ * @param type class of domain type
+ * @return found item
+ */
+ T findById(String containerName, Object id, Class domainType);
+
+ /**
+ * Finds item by id
+ * @param id must not be {@literal null}
+ * @param domainType must not be {@literal null}
+ * @param partitionKey must not be {@literal null}
+ * @param type class of domain type
+ * @return found item
+ */
+ T findById(Object id, Class domainType, PartitionKey partitionKey);
+
+ /**
+ * Inserts item
+ *
+ * @param objectToSave must not be {@literal null}
+ * @param partitionKey must not be {@literal null}
+ * @param type class of domain type
+ * @return the inserted item
+ */
+ T insert(T objectToSave, PartitionKey partitionKey);
+
+ /**
+ * Inserts item
+ *
+ * @param containerName must not be {@literal null}
+ * @param objectToSave must not be {@literal null}
+ * @param partitionKey must not be {@literal null}
+ * @param type class of domain type
+ * @return the inserted item
+ */
+ T insert(String containerName, T objectToSave, PartitionKey partitionKey);
+
+ /**
+ * Upserts an item with partition key
+ * @param object upsert object
+ * @param partitionKey the partition key
+ * @param type of upsert object
+ */
+ void upsert(T object, PartitionKey partitionKey);
+
+ /**
+ * Upserts an item into container with partition key
+ * @param containerName the container name
+ * @param object upsert object
+ * @param partitionKey the partition key
+ * @param type of upsert object
+ */
+ void upsert(String containerName, T object, PartitionKey partitionKey);
+
+ /**
+ * Upserts an item and return item properties
+ * @param containerName the container name
+ * @param object upsert object
+ * @param partitionKey the partition key
+ * @param type of upsert object
+ * @return upsert object entity
+ */
+ T upsertAndReturnEntity(String containerName, T object, PartitionKey partitionKey);
+
+ /**
+ * Delete an item by id
+ *
+ * @param containerName the container name
+ * @param id the id
+ * @param partitionKey the partition key
+ */
+ void deleteById(String containerName, Object id, PartitionKey partitionKey);
+
+ /**
+ * Delete all items in a container
+ *
+ * @param containerName the container name
+ * @param domainType the partition key path
+ */
+ void deleteAll(String containerName, Class> domainType);
+
+ /**
+ * Use deleteContainer() instead
+ * @param containerName container name
+ * @deprecated Use {@link #deleteContainer(String)} instead.
+ */
+ @Deprecated
+ void deleteCollection(String containerName);
+
+ /**
+ * Delete container
+ *
+ * @param containerName the container name
+ */
+ void deleteContainer(String containerName);
+
+ /**
+ * Delete items matching query
+ *
+ * @param query the document query
+ * @param domainType type class
+ * @param containerName the container name
+ * @param type class of domaintype
+ * @return deleted items in a List
+ */
+ List delete(DocumentQuery query, Class domainType, String containerName);
+
+ /**
+ * Find query
+ *
+ * @param query the document query
+ * @param domainType type class
+ * @param containerName the container name
+ * @param type class of domaintype
+ * @return found results in a List
+ */
+ List find(DocumentQuery query, Class domainType, String containerName);
+
+ /**
+ * Find by ids
+ *
+ * @param ids iterable of ids
+ * @param domainType type class
+ * @param containerName the container name
+ * @param type of domainType
+ * @param type of ID
+ * @return Mono
+ */
+ List findByIds(Iterable ids, Class domainType, String containerName);
+
+ /**
+ * Exists
+ *
+ * @param query the document query
+ * @param domainType type class
+ * @param containerName the container name
+ * @param type of domainType
+ * @return Boolean
+ */
+ Boolean exists(DocumentQuery query, Class domainType, String containerName);
+
+ /**
+ * Find all items in a given container with partition key
+ *
+ * @param pageable Pageable object
+ * @param domainType the domainType
+ * @param containerName the container name
+ * @param type of domainType
+ * @return Page
+ */
+ Page findAll(Pageable pageable, Class domainType, String containerName);
+
+ /**
+ * Pagination query
+ * @param query the document query
+ * @param domainType type class
+ * @param containerName the container name
+ * @param type class of domaintype
+ * @return Page
+ */
+ Page paginationQuery(DocumentQuery query, Class domainType, String containerName);
+
+ /**
+ * Count
+ *
+ * @param containerName the container name
+ * @return count result
+ */
+ long count(String containerName);
+
+ /**
+ * Count
+ *
+ * @param query the document query
+ * @param domainType the domain type
+ * @param containerName the container name
+ * @param type class of domaintype
+ * @return count result
+ */
+ long count(DocumentQuery query, Class domainType, String containerName);
+
+ /**
+ * To get converter
+ * @return MappingCosmosConverter
+ */
+ MappingCosmosConverter getConverter();
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/CosmosTemplate.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/CosmosTemplate.java
new file mode 100644
index 000000000000..2e3a5c116cb7
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/CosmosTemplate.java
@@ -0,0 +1,746 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.azure.spring.data.cosmosdb.core;
+
+import com.azure.data.cosmos.CosmosItemResponse;
+import com.azure.data.cosmos.AccessCondition;
+import com.azure.data.cosmos.AccessConditionType;
+import com.azure.data.cosmos.CosmosItemProperties;
+import com.azure.data.cosmos.SqlQuerySpec;
+import com.azure.data.cosmos.CosmosItemRequestOptions;
+import com.azure.data.cosmos.FeedResponse;
+import com.azure.data.cosmos.FeedOptions;
+import com.azure.data.cosmos.CosmosClient;
+import com.azure.data.cosmos.CosmosContainerProperties;
+import com.azure.data.cosmos.CosmosContainerResponse;
+import com.azure.data.cosmos.PartitionKey;
+import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
+import com.microsoft.azure.spring.data.cosmosdb.common.Memoizer;
+import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingCosmosConverter;
+import com.microsoft.azure.spring.data.cosmosdb.core.generator.CountQueryGenerator;
+import com.microsoft.azure.spring.data.cosmosdb.core.generator.FindQuerySpecGenerator;
+import com.microsoft.azure.spring.data.cosmosdb.core.query.Criteria;
+import com.microsoft.azure.spring.data.cosmosdb.core.query.CosmosPageImpl;
+import com.microsoft.azure.spring.data.cosmosdb.core.query.CriteriaType;
+import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery;
+import com.microsoft.azure.spring.data.cosmosdb.core.query.CosmosPageRequest;
+import com.microsoft.azure.spring.data.cosmosdb.repository.support.CosmosEntityInformation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.lang.NonNull;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static com.microsoft.azure.spring.data.cosmosdb.common.CosmosdbUtils.fillAndProcessResponseDiagnostics;
+import static com.microsoft.azure.spring.data.cosmosdb.exception.CosmosDBExceptionUtils.exceptionHandler;
+import static com.microsoft.azure.spring.data.cosmosdb.exception.CosmosDBExceptionUtils.findAPIExceptionHandler;
+
+/**
+ * Template class for cosmos db
+ */
+public class CosmosTemplate implements CosmosOperations, ApplicationContextAware {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(CosmosTemplate.class);
+
+ private static final String COUNT_VALUE_KEY = "_aggregate";
+
+ private final MappingCosmosConverter mappingCosmosConverter;
+ private final String databaseName;
+ private final ResponseDiagnosticsProcessor responseDiagnosticsProcessor;
+ private final boolean isPopulateQueryMetrics;
+
+ private final CosmosClient cosmosClient;
+ private final Function, CosmosEntityInformation, ?>> entityInfoCreator =
+ Memoizer.memoize(this::getCosmosEntityInformation);
+
+ /**
+ * Initialization
+ * @param cosmosDbFactory must not be {@literal null}
+ * @param mappingCosmosConverter must not be {@literal null}
+ * @param dbName must not be {@literal null}
+ */
+ public CosmosTemplate(CosmosDbFactory cosmosDbFactory,
+ MappingCosmosConverter mappingCosmosConverter,
+ String dbName) {
+ Assert.notNull(cosmosDbFactory, "CosmosDbFactory must not be null!");
+ Assert.notNull(mappingCosmosConverter, "MappingCosmosConverter must not be null!");
+
+ this.mappingCosmosConverter = mappingCosmosConverter;
+
+ this.databaseName = dbName;
+ this.cosmosClient = cosmosDbFactory.getCosmosClient();
+ this.responseDiagnosticsProcessor = cosmosDbFactory.getConfig().getResponseDiagnosticsProcessor();
+ this.isPopulateQueryMetrics = cosmosDbFactory.getConfig().isPopulateQueryMetrics();
+ }
+
+ /**
+ * Sets the application context
+ * @param applicationContext must not be {@literal null}
+ * @throws BeansException the bean exception
+ */
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ }
+
+ /**
+ *
+ * Inserts item
+ * @param objectToSave must not be {@literal null}
+ * @param partitionKey must not be {@literal null}
+ * @param type class of domain type
+ * @return the inserted item
+ */
+ public T insert(T objectToSave, PartitionKey partitionKey) {
+ Assert.notNull(objectToSave, "domainType should not be null");
+
+ return insert(getContainerName(objectToSave.getClass()), objectToSave, partitionKey);
+ }
+
+ /**
+ * Inserts item into the given container
+ * @param containerName must not be {@literal null}
+ * @param objectToSave must not be {@literal null}
+ * @param partitionKey must not be {@literal null}
+ * @param type class of domain type
+ * @return the inserted item
+ */
+ public T insert(String containerName, T objectToSave, PartitionKey partitionKey) {
+ Assert.hasText(containerName, "containerName should not be null, empty or only whitespaces");
+ Assert.notNull(objectToSave, "objectToSave should not be null");
+
+ final CosmosItemProperties originalItem = mappingCosmosConverter.writeCosmosItemProperties(objectToSave);
+
+ LOGGER.debug("execute createItem in database {} container {}", this.databaseName, containerName);
+
+ final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
+ options.partitionKey(partitionKey);
+
+ @SuppressWarnings("unchecked")
+ final Class domainType = (Class) objectToSave.getClass();
+
+ final CosmosItemResponse response = cosmosClient
+ .getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .createItem(originalItem, options)
+ .doOnNext(cosmosItemResponse -> fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ cosmosItemResponse, null))
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to insert item", throwable))
+ .block();
+
+ assert response != null;
+ return mappingCosmosConverter.read(domainType, response.properties());
+ }
+
+ /**
+ * Finds item by id
+ * @param id must not be {@literal null}
+ * @param domainType must not be {@literal null}
+ * @param type class of domain type
+ * @return found item
+ */
+ public T findById(Object id, Class domainType) {
+ Assert.notNull(domainType, "domainType should not be null");
+
+ return findById(getContainerName(domainType), id, domainType);
+ }
+
+ @Override
+ public T findById(Object id, Class domainType, PartitionKey partitionKey) {
+ Assert.notNull(domainType, "domainType should not be null");
+ Assert.notNull(partitionKey, "partitionKey should not be null");
+ assertValidId(id);
+
+ final String containerName = getContainerName(domainType);
+ return cosmosClient
+ .getDatabase(databaseName)
+ .getContainer(containerName)
+ .getItem(id.toString(), partitionKey)
+ .read()
+ .flatMap(cosmosItemResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ cosmosItemResponse, null);
+ return Mono.justOrEmpty(toDomainObject(domainType,
+ cosmosItemResponse.properties()));
+ })
+ .onErrorResume(throwable ->
+ findAPIExceptionHandler("Failed to find item", throwable))
+ .block();
+ }
+
+ /**
+ * Finds item by id
+ * @param containerName must not be {@literal null}
+ * @param id must not be {@literal null}
+ * @param domainType must not be {@literal null}
+ * @param type class of domain type
+ * @return found item
+ */
+ public T findById(String containerName, Object id, Class domainType) {
+ Assert.hasText(containerName, "containerName should not be null, empty or only whitespaces");
+ Assert.notNull(domainType, "domainType should not be null");
+ assertValidId(id);
+
+ final String query = String.format("select * from root where root.id = '%s'", id.toString());
+ final FeedOptions options = new FeedOptions();
+ options.enableCrossPartitionQuery(true);
+ options.populateQueryMetrics(isPopulateQueryMetrics);
+ return cosmosClient
+ .getDatabase(databaseName)
+ .getContainer(containerName)
+ .queryItems(query, options)
+ .flatMap(cosmosItemFeedResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ null, cosmosItemFeedResponse);
+ return Mono.justOrEmpty(cosmosItemFeedResponse
+ .results()
+ .stream()
+ .map(cosmosItem -> mappingCosmosConverter.read(domainType, cosmosItem))
+ .findFirst());
+ })
+ .onErrorResume(throwable ->
+ findAPIExceptionHandler("Failed to find item", throwable))
+ .blockFirst();
+ }
+
+ /**
+ * Upserts an item with partition key
+ * @param object upsert object
+ * @param partitionKey the partition key
+ * @param type of upsert object
+ */
+ public void upsert(T object, PartitionKey partitionKey) {
+ Assert.notNull(object, "Upsert object should not be null");
+
+ upsert(getContainerName(object.getClass()), object, partitionKey);
+ }
+
+ /**
+ * Upserts an item into container with partition key
+ * @param containerName the container name
+ * @param object upsert object
+ * @param partitionKey the partition key
+ * @param type of upsert object
+ */
+ public void upsert(String containerName, T object, PartitionKey partitionKey) {
+ upsertAndReturnEntity(containerName, object, partitionKey);
+ }
+
+ /**
+ * Upserts an item and return item properties
+ * @param containerName the container name
+ * @param object upsert object
+ * @param partitionKey the partition key
+ * @param type of upsert object
+ * @return upsert object entity
+ */
+ public T upsertAndReturnEntity(String containerName, T object, PartitionKey partitionKey) {
+ Assert.hasText(containerName, "containerName should not be null, empty or only whitespaces");
+ Assert.notNull(object, "Upsert object should not be null");
+
+ final CosmosItemProperties originalItem = mappingCosmosConverter.writeCosmosItemProperties(object);
+
+ LOGGER.debug("execute upsert item in database {} container {}", this.databaseName, containerName);
+
+ @SuppressWarnings("unchecked")
+ final Class domainType = (Class) object.getClass();
+
+ final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
+ options.partitionKey(partitionKey);
+ applyVersioning(domainType, originalItem, options);
+
+ final CosmosItemResponse cosmosItemResponse = cosmosClient
+ .getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .upsertItem(originalItem, options)
+ .doOnNext(response -> fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ response, null))
+ .onErrorResume(throwable -> exceptionHandler("Failed to upsert item", throwable))
+ .block();
+
+ assert cosmosItemResponse != null;
+ return mappingCosmosConverter.read(domainType, cosmosItemResponse.properties());
+ }
+
+ /**
+ * Find the DocumentQuery, find all the items specified by domain type.
+ *
+ * @param domainType the domain type
+ * @param class type of domain
+ * @return found results in a List
+ */
+ public List findAll(Class domainType) {
+ Assert.notNull(domainType, "domainType should not be null");
+
+ return findAll(getContainerName(domainType), domainType);
+ }
+
+ /**
+ * Find the DocumentQuery, find all the items specified by domain type in the given container.
+ *
+ * @param containerName the container name
+ * @param domainType the domain type
+ * @param class type of domain
+ * @return found results in a List
+ */
+ public List findAll(String containerName, final Class domainType) {
+ Assert.hasText(containerName, "containerName should not be null, empty or only whitespaces");
+ Assert.notNull(domainType, "domainType should not be null");
+
+ final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL));
+
+ final List items = findItems(query, domainType, containerName);
+ return items.stream()
+ .map(d -> getConverter().read(domainType, d))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List findAll(PartitionKey partitionKey, final Class domainType) {
+ Assert.notNull(partitionKey, "partitionKey should not be null");
+ Assert.notNull(domainType, "domainType should not be null");
+
+ final String containerName = getContainerName(domainType);
+
+ final FeedOptions feedOptions = new FeedOptions();
+ feedOptions.partitionKey(partitionKey);
+ feedOptions.populateQueryMetrics(isPopulateQueryMetrics);
+
+ return cosmosClient
+ .getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .readAllItems(feedOptions)
+ .flatMap(cosmosItemFeedResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ null, cosmosItemFeedResponse);
+ return Flux.fromIterable(cosmosItemFeedResponse.results());
+ })
+ .map(cosmosItemProperties -> toDomainObject(domainType, cosmosItemProperties))
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to find items", throwable))
+ .collectList()
+ .block();
+ }
+
+ /**
+ * Delete the DocumentQuery, delete all the items in the given container.
+ *
+ * @param containerName Container name of database
+ * @param domainType the domain type
+ */
+ public void deleteAll(@NonNull String containerName, @NonNull Class> domainType) {
+ Assert.hasText(containerName, "containerName should not be null, empty or only whitespaces");
+
+ final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL));
+
+ this.delete(query, domainType, containerName);
+ }
+
+ @Override
+ public void deleteCollection(@NonNull String containerName) {
+ deleteContainer(containerName);
+ }
+
+ @Override
+ public void deleteContainer(@NonNull String containerName) {
+ Assert.hasText(containerName, "containerName should have text.");
+ cosmosClient.getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .delete()
+ .doOnNext(response -> fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ response, null))
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to delete container", throwable))
+ .block();
+ }
+
+ /**
+ * To get collection name by domaintype
+ * @param domainType class type
+ * @return String
+ */
+ public String getCollectionName(Class> domainType) {
+ return getContainerName(domainType);
+ }
+
+ @Override
+ public String getContainerName(Class> domainType) {
+ Assert.notNull(domainType, "domainType should not be null");
+
+ return entityInfoCreator.apply(domainType).getContainerName();
+ }
+
+ @Override
+ public CosmosContainerProperties createCollectionIfNotExists(@NonNull CosmosEntityInformation, ?> information) {
+ return createContainerIfNotExists(information);
+ }
+
+ @Override
+ public CosmosContainerProperties createContainerIfNotExists(CosmosEntityInformation, ?> information) {
+ final CosmosContainerResponse response = cosmosClient
+ .createDatabaseIfNotExists(this.databaseName)
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to create database", throwable))
+ .flatMap(cosmosDatabaseResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ cosmosDatabaseResponse, null);
+ final CosmosContainerProperties cosmosContainerProperties = new CosmosContainerProperties(
+ information.getContainerName(), "/"
+ + information.getPartitionKeyFieldName());
+ cosmosContainerProperties.defaultTimeToLive(information.getTimeToLive());
+ cosmosContainerProperties.indexingPolicy(information.getIndexingPolicy());
+ return cosmosDatabaseResponse
+ .database()
+ .createContainerIfNotExists(cosmosContainerProperties, information.getRequestUnit())
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to create container", throwable))
+ .doOnNext(cosmosContainerResponse ->
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ cosmosContainerResponse, null));
+ })
+ .block();
+ assert response != null;
+ return response.properties();
+ }
+
+ /**
+ * Delete the DocumentQuery, need to query by id at first, then delete the item
+ * from the result.
+ *
+ * @param containerName Container name of database
+ * @param id item id
+ * @param partitionKey the paritition key
+ */
+ public void deleteById(String containerName, Object id, PartitionKey partitionKey) {
+ Assert.hasText(containerName, "containerName should not be null, empty or only whitespaces");
+ assertValidId(id);
+
+ LOGGER.debug("execute deleteById in database {} container {}", this.databaseName, containerName);
+
+ if (partitionKey == null) {
+ partitionKey = PartitionKey.None;
+ }
+ final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
+ options.partitionKey(partitionKey);
+ cosmosClient.getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .getItem(id.toString(), partitionKey)
+ .delete(options)
+ .doOnNext(response -> fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ response, null))
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to delete item", throwable))
+ .block();
+ }
+
+ @Override
+ public List findByIds(Iterable ids, Class domainType, String containerName) {
+ Assert.notNull(ids, "Id list should not be null");
+ Assert.notNull(domainType, "domainType should not be null.");
+ Assert.hasText(containerName, "container should not be null, empty or only whitespaces");
+
+ final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.IN, "id",
+ Collections.singletonList(ids)));
+ return find(query, domainType, containerName);
+ }
+
+ /**
+ * Finds the document query items
+ * @param query The representation for query method.
+ * @param domainType Class of domain
+ * @param containerName Container name of database
+ * @param class of domainType
+ * @return All the found items as List.
+ */
+ public List find(@NonNull DocumentQuery query, @NonNull Class domainType, String containerName) {
+ Assert.notNull(query, "DocumentQuery should not be null.");
+ Assert.notNull(domainType, "domainType should not be null.");
+ Assert.hasText(containerName, "container should not be null, empty or only whitespaces");
+
+ return findItems(query, domainType, containerName)
+ .stream()
+ .map(cosmosItemProperties -> toDomainObject(domainType, cosmosItemProperties))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Checks if document query items exist
+ * @param query The representation for query method.
+ * @param domainType Class of domain
+ * @param containerName Container name of database
+ * @param class of domainType
+ * @return if items exist
+ */
+ public Boolean exists(@NonNull DocumentQuery query, @NonNull Class domainType, String containerName) {
+ return this.find(query, domainType, containerName).size() > 0;
+ }
+
+ /**
+ * Delete the DocumentQuery, need to query the domains at first, then delete the item
+ * from the result.
+ * The cosmosdb Sql API do _NOT_ support DELETE query, we cannot add one DeleteQueryGenerator.
+ *
+ * @param query The representation for query method.
+ * @param domainType Class of domain
+ * @param containerName Container name of database
+ * @param class of domainType
+ * @return All the deleted items as List.
+ */
+ @Override
+ public List delete(@NonNull DocumentQuery query, @NonNull Class domainType,
+ @NonNull String containerName) {
+ Assert.notNull(query, "DocumentQuery should not be null.");
+ Assert.notNull(domainType, "domainType should not be null.");
+ Assert.hasText(containerName, "container should not be null, empty or only whitespaces");
+
+ final List results = findItems(query, domainType, containerName);
+ final List partitionKeyName = getPartitionKeyNames(domainType);
+
+ return results.stream().map(cosmosItemProperties -> {
+ final CosmosItemResponse cosmosItemResponse = deleteItem(cosmosItemProperties,
+ partitionKeyName, containerName, domainType);
+ return getConverter().read(domainType, cosmosItemResponse.properties());
+ }).collect(Collectors.toList());
+ }
+
+ @Override
+ public Page findAll(Pageable pageable, Class domainType, String containerName) {
+ final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL)).with(pageable);
+ if (pageable.getSort().isSorted()) {
+ query.with(pageable.getSort());
+ }
+
+ return paginationQuery(query, domainType, containerName);
+ }
+
+ @Override
+ public Page paginationQuery(DocumentQuery query, Class domainType, String containerName) {
+ Assert.isTrue(query.getPageable().getPageSize() > 0, "pageable should have page size larger than 0");
+ Assert.hasText(containerName, "container should not be null, empty or only whitespaces");
+
+ final Pageable pageable = query.getPageable();
+ final FeedOptions feedOptions = new FeedOptions();
+ if (pageable instanceof CosmosPageRequest) {
+ feedOptions.requestContinuation(((CosmosPageRequest) pageable).getRequestContinuation());
+ }
+
+ feedOptions.maxItemCount(pageable.getPageSize());
+ feedOptions.enableCrossPartitionQuery(query.isCrossPartitionQuery(getPartitionKeyNames(domainType)));
+ feedOptions.populateQueryMetrics(isPopulateQueryMetrics);
+
+ final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generateCosmos(query);
+ final FeedResponse feedResponse = cosmosClient
+ .getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .queryItems(sqlQuerySpec, feedOptions)
+ .doOnNext(propertiesFeedResponse -> fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ null, propertiesFeedResponse))
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to query items", throwable))
+ .next()
+ .block();
+
+ assert feedResponse != null;
+ final Iterator it = feedResponse.results().iterator();
+
+ final List result = new ArrayList<>();
+ for (int index = 0; it.hasNext()
+ && index < pageable.getPageSize(); index++) {
+
+ final CosmosItemProperties cosmosItemProperties = it.next();
+ if (cosmosItemProperties == null) {
+ continue;
+ }
+
+ final T entity = mappingCosmosConverter.read(domainType, cosmosItemProperties);
+ result.add(entity);
+ }
+
+ final long total = count(query, domainType, containerName);
+ final int contentSize = result.size();
+
+ int pageSize;
+
+ if (contentSize < pageable.getPageSize()
+ && contentSize > 0) {
+ // If the content size is less than page size,
+ // this means, cosmosDB is returning less items than page size,
+ // because of either RU limit, or payload limit
+
+ // Set the page size to content size.
+ pageSize = contentSize;
+ } else {
+ pageSize = pageable.getPageSize();
+ }
+
+ final CosmosPageRequest pageRequest = CosmosPageRequest.of(pageable.getOffset(),
+ pageable.getPageNumber(),
+ pageSize,
+ feedResponse.continuationToken(),
+ query.getSort());
+
+ return new CosmosPageImpl<>(result, pageRequest, total);
+ }
+
+ @Override
+ public long count(String containerName) {
+ Assert.hasText(containerName, "container name should not be empty");
+
+ final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL));
+ final Long count = getCountValue(query, true, containerName);
+ assert count != null;
+ return count;
+ }
+
+ @Override
+ public long count(DocumentQuery query, Class domainType, String containerName) {
+ Assert.notNull(domainType, "domainType should not be null");
+ Assert.hasText(containerName, "container name should not be empty");
+
+ final boolean isCrossPartitionQuery =
+ query.isCrossPartitionQuery(getPartitionKeyNames(domainType));
+ final Long count = getCountValue(query, isCrossPartitionQuery, containerName);
+ assert count != null;
+ return count;
+ }
+
+ @Override
+ public MappingCosmosConverter getConverter() {
+ return this.mappingCosmosConverter;
+ }
+
+ private Long getCountValue(DocumentQuery query, boolean isCrossPartitionQuery, String containerName) {
+ final SqlQuerySpec querySpec = new CountQueryGenerator().generateCosmos(query);
+ final FeedOptions options = new FeedOptions();
+
+ options.enableCrossPartitionQuery(isCrossPartitionQuery);
+ options.populateQueryMetrics(isPopulateQueryMetrics);
+
+ return executeQuery(querySpec, containerName, options)
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to get count value", throwable))
+ .doOnNext(response -> fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ null, response))
+ .next()
+ .map(r -> r.results().get(0).getLong(COUNT_VALUE_KEY))
+ .block();
+ }
+
+ private Flux> executeQuery(SqlQuerySpec sqlQuerySpec, String containerName,
+ FeedOptions options) {
+ return cosmosClient.getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .queryItems(sqlQuerySpec, options)
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to execute query", throwable));
+ }
+
+ private List getPartitionKeyNames(Class> domainType) {
+ final CosmosEntityInformation, ?> entityInfo = entityInfoCreator.apply(domainType);
+
+ if (entityInfo.getPartitionKeyFieldName() == null) {
+ return new ArrayList<>();
+ }
+
+ return Collections.singletonList(entityInfo.getPartitionKeyFieldName());
+ }
+
+ private void assertValidId(Object id) {
+ Assert.notNull(id, "id should not be null");
+ if (id instanceof String) {
+ Assert.hasText(id.toString(), "id should not be empty or only whitespaces.");
+ }
+ }
+
+ private List findItems(@NonNull DocumentQuery query,
+ @NonNull Class> domainType,
+ @NonNull String containerName) {
+ final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generateCosmos(query);
+ final boolean isCrossPartitionQuery =
+ query.isCrossPartitionQuery(getPartitionKeyNames(domainType));
+ final FeedOptions feedOptions = new FeedOptions();
+ feedOptions.enableCrossPartitionQuery(isCrossPartitionQuery);
+ feedOptions.populateQueryMetrics(isPopulateQueryMetrics);
+
+ return cosmosClient
+ .getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .queryItems(sqlQuerySpec, feedOptions)
+ .flatMap(cosmosItemFeedResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ null, cosmosItemFeedResponse);
+ return Flux.fromIterable(cosmosItemFeedResponse.results());
+ })
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to find items", throwable))
+ .collectList()
+ .block();
+ }
+
+ private CosmosItemResponse deleteItem(@NonNull CosmosItemProperties cosmosItemProperties,
+ @NonNull List partitionKeyNames,
+ String containerName,
+ @NonNull Class> domainType) {
+ Assert.isTrue(partitionKeyNames.size() <= 1, "Only one Partition is supported.");
+
+ PartitionKey partitionKey = null;
+
+ if (!partitionKeyNames.isEmpty()
+ && StringUtils.hasText(partitionKeyNames.get(0))) {
+ partitionKey = new PartitionKey(cosmosItemProperties.get(partitionKeyNames.get(0)));
+ }
+
+ if (partitionKey == null) {
+ partitionKey = PartitionKey.None;
+ }
+
+ final CosmosItemRequestOptions options = new CosmosItemRequestOptions(partitionKey);
+ applyVersioning(domainType, cosmosItemProperties, options);
+
+ return cosmosClient
+ .getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .getItem(cosmosItemProperties.id(), partitionKey)
+ .delete(options)
+ .doOnNext(response -> fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ response, null))
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to delete item", throwable))
+ .block();
+ }
+
+ private T toDomainObject(@NonNull Class domainType, CosmosItemProperties cosmosItemProperties) {
+ return mappingCosmosConverter.read(domainType, cosmosItemProperties);
+ }
+
+ private void applyVersioning(Class> domainType,
+ CosmosItemProperties cosmosItemProperties,
+ CosmosItemRequestOptions options) {
+
+ if (entityInfoCreator.apply(domainType).isVersioned()) {
+ final AccessCondition accessCondition = new AccessCondition();
+ accessCondition.type(AccessConditionType.IF_MATCH);
+ accessCondition.condition(cosmosItemProperties.etag());
+ options.accessCondition(accessCondition);
+ }
+ }
+
+ private CosmosEntityInformation, ?> getCosmosEntityInformation(Class> domainType) {
+ return new CosmosEntityInformation<>(domainType);
+ }
+
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/ReactiveCosmosOperations.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/ReactiveCosmosOperations.java
new file mode 100644
index 000000000000..64bf11e91877
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/ReactiveCosmosOperations.java
@@ -0,0 +1,236 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.azure.spring.data.cosmosdb.core;
+
+import com.azure.data.cosmos.CosmosContainerResponse;
+import com.azure.data.cosmos.PartitionKey;
+import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingCosmosConverter;
+import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery;
+import com.microsoft.azure.spring.data.cosmosdb.repository.support.CosmosEntityInformation;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * Operation class of reactive cosmos
+ */
+public interface ReactiveCosmosOperations {
+
+ /**
+ * Get container name
+ *
+ * @param domainType the domainType
+ * @return container name
+ */
+ String getContainerName(Class> domainType);
+
+ /**
+ * Use createContainerIfNotExists() instead
+ * @param information cosmos entity information
+ * @return Mono of cosmos container response
+ * @deprecated use createContainerIfNotExists(CosmosEntityInformation, ?>) instead.
+ */
+ @Deprecated
+ Mono createCollectionIfNotExists(CosmosEntityInformation, ?> information);
+
+ /**
+ * Creates a container if it doesn't already exist
+ *
+ * @param information the CosmosEntityInformation
+ * @return Mono
+ */
+ Mono createContainerIfNotExists(CosmosEntityInformation, ?> information);
+
+ /**
+ * Find all items in a given container
+ *
+ * @param containerName the containerName
+ * @param domainType the domainType
+ * @param type of domainType
+ * @return Flux
+ */
+ Flux findAll(String containerName, Class domainType);
+
+ /**
+ * Find all items in a given container
+ *
+ * @param domainType the domainType
+ * @param type of domainType
+ * @return Flux
+ */
+ Flux findAll(Class domainType);
+
+ /**
+ * Find all items in a given container with partition key
+ *
+ * @param partitionKey partition Key
+ * @param domainType the domainType
+ * @param type of domainType
+ * @return Flux
+ */
+ Flux findAll(PartitionKey partitionKey, Class domainType);
+
+ /**
+ * Find by id
+ *
+ * @param id the id
+ * @param domainType the domainType
+ * @param type of domainType
+ * @return Mono
+ */
+ Mono findById(Object id, Class domainType);
+
+ /**
+ * Find by id
+ *
+ * @param containerName the containername
+ * @param id the id
+ * @param domainType type class
+ * @param type of domainType
+ * @return Mono
+ */
+ Mono findById(String containerName, Object id, Class domainType);
+
+ /**
+ * Find by id
+ *
+ * @param id the id
+ * @param domainType type class
+ * @param partitionKey partition Key
+ * @param type of domainType
+ * @return Mono
+ */
+ Mono findById(Object id, Class domainType, PartitionKey partitionKey);
+
+ /**
+ * Insert
+ *
+ * @param objectToSave the object to save
+ * @param partitionKey the partition key
+ * @param type of inserted objectToSave
+ * @return Mono
+ */
+ Mono insert(T objectToSave, PartitionKey partitionKey);
+
+ /**
+ * Insert
+ *
+ * @param type of inserted objectToSave
+ * @param containerName the container name
+ * @param objectToSave the object to save
+ * @param partitionKey the partition key
+ * @return Mono
+ */
+ Mono insert(String containerName, Object objectToSave, PartitionKey partitionKey);
+
+ /**
+ * Upsert
+ *
+ * @param object the object to upsert
+ * @param partitionKey the partition key
+ * @param type class of object
+ * @return Mono
+ */
+ Mono upsert(T object, PartitionKey partitionKey);
+
+ /**
+ * Upsert
+ *
+ * @param containerName the container name
+ * @param object the object to save
+ * @param partitionKey the partition key
+ * @param type class of object
+ * @return Mono
+ */
+ Mono upsert(String containerName, T object, PartitionKey partitionKey);
+
+ /**
+ * Delete an item by id
+ *
+ * @param containerName the container name
+ * @param id the id
+ * @param partitionKey the partition key
+ * @return void Mono
+ */
+ Mono deleteById(String containerName, Object id, PartitionKey partitionKey);
+
+ /**
+ * Delete all items in a container
+ *
+ * @param containerName the container name
+ * @param partitionKey the partition key path
+ * @return void Mono
+ */
+ Mono deleteAll(String containerName, String partitionKey);
+
+ /**
+ * Delete container
+ *
+ * @param containerName the container name
+ */
+ void deleteContainer(String containerName);
+
+ /**
+ * Delete items matching query
+ *
+ * @param query the document query
+ * @param domainType type class
+ * @param containerName the container name
+ * @param type class of domaintype
+ * @return Flux
+ */
+ Flux delete(DocumentQuery query, Class domainType, String containerName);
+
+ /**
+ * Find items
+ *
+ * @param query the document query
+ * @param domainType type class
+ * @param containerName the container name
+ * @param type class of domaintype
+ * @return Flux
+ */
+ Flux find(DocumentQuery query, Class domainType, String containerName);
+
+ /**
+ * Exists
+ *
+ * @param query the document query
+ * @param domainType type class
+ * @param containerName the container name
+ * @return Mono
+ */
+ Mono exists(DocumentQuery query, Class> domainType, String containerName);
+
+ /**
+ * Exists
+ * @param id the id
+ * @param domainType type class
+ * @param containerName the containercontainer nam,e
+ * @return Mono
+ */
+ Mono existsById(Object id, Class> domainType, String containerName);
+
+ /**
+ * Count
+ *
+ * @param containerName the container name
+ * @return Mono
+ */
+ Mono count(String containerName);
+
+ /**
+ * Count
+ *
+ * @param query the document query
+ * @param containerName the container name
+ * @return Mono
+ */
+ Mono count(DocumentQuery query, String containerName);
+
+ /**
+ * To get converter
+ * @return MappingCosmosConverter
+ */
+ MappingCosmosConverter getConverter();
+}
diff --git a/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/ReactiveCosmosTemplate.java b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/ReactiveCosmosTemplate.java
new file mode 100644
index 000000000000..38200f453b31
--- /dev/null
+++ b/sdk/cosmos/azure-spring-data-cosmosdb/src/main/java/com.microsoft.azure.spring.data.cosmosdb/core/ReactiveCosmosTemplate.java
@@ -0,0 +1,695 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.azure.spring.data.cosmosdb.core;
+
+import com.azure.data.cosmos.AccessCondition;
+import com.azure.data.cosmos.AccessConditionType;
+import com.azure.data.cosmos.CosmosItemProperties;
+import com.azure.data.cosmos.SqlQuerySpec;
+import com.azure.data.cosmos.CosmosItemRequestOptions;
+import com.azure.data.cosmos.FeedResponse;
+import com.azure.data.cosmos.FeedOptions;
+import com.azure.data.cosmos.CosmosClient;
+import com.azure.data.cosmos.CosmosContainerProperties;
+import com.azure.data.cosmos.CosmosContainerResponse;
+import com.azure.data.cosmos.PartitionKey;
+import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
+import com.microsoft.azure.spring.data.cosmosdb.common.Memoizer;
+import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingCosmosConverter;
+import com.microsoft.azure.spring.data.cosmosdb.core.generator.CountQueryGenerator;
+import com.microsoft.azure.spring.data.cosmosdb.core.generator.FindQuerySpecGenerator;
+import com.microsoft.azure.spring.data.cosmosdb.core.query.Criteria;
+import com.microsoft.azure.spring.data.cosmosdb.core.query.CriteriaType;
+import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery;
+import com.microsoft.azure.spring.data.cosmosdb.repository.support.CosmosEntityInformation;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.lang.NonNull;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+
+import static com.microsoft.azure.spring.data.cosmosdb.common.CosmosdbUtils.fillAndProcessResponseDiagnostics;
+import static com.microsoft.azure.spring.data.cosmosdb.exception.CosmosDBExceptionUtils.exceptionHandler;
+import static com.microsoft.azure.spring.data.cosmosdb.exception.CosmosDBExceptionUtils.findAPIExceptionHandler;
+
+/**
+ * Template class of reactive cosmos
+ */
+@SuppressWarnings("unchecked")
+public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, ApplicationContextAware {
+ private static final String COUNT_VALUE_KEY = "_aggregate";
+
+ private final MappingCosmosConverter mappingCosmosConverter;
+ private final String databaseName;
+
+ private final CosmosClient cosmosClient;
+ private final ResponseDiagnosticsProcessor responseDiagnosticsProcessor;
+ private final boolean isPopulateQueryMetrics;
+
+ private final Function, CosmosEntityInformation, ?>> entityInfoCreator =
+ Memoizer.memoize(this::getCosmosEntityInformation);
+
+ private final List containerNameCache;
+
+ /**
+ * Constructor
+ *
+ * @param cosmosDbFactory the cosmosdbfactory
+ * @param mappingCosmosConverter the mappingCosmosConverter
+ * @param dbName database name
+ */
+ public ReactiveCosmosTemplate(CosmosDbFactory cosmosDbFactory,
+ MappingCosmosConverter mappingCosmosConverter,
+ String dbName) {
+ Assert.notNull(cosmosDbFactory, "CosmosDbFactory must not be null!");
+ Assert.notNull(mappingCosmosConverter, "MappingCosmosConverter must not be null!");
+
+ this.mappingCosmosConverter = mappingCosmosConverter;
+ this.databaseName = dbName;
+ this.containerNameCache = new ArrayList<>();
+
+ this.cosmosClient = cosmosDbFactory.getCosmosClient();
+ this.responseDiagnosticsProcessor = cosmosDbFactory.getConfig().getResponseDiagnosticsProcessor();
+ this.isPopulateQueryMetrics = cosmosDbFactory.getConfig().isPopulateQueryMetrics();
+ }
+
+ /**
+ * @param applicationContext the application context
+ * @throws BeansException the bean exception
+ */
+ public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
+ // NOTE: When application context instance variable gets introduced, assign it here.
+ }
+
+ /**
+ * Creates a container if it doesn't already exist
+ *
+ * @param information the CosmosEntityInformation
+ * @return Mono containing CosmosContainerResponse
+ */
+ @Override
+ public Mono createCollectionIfNotExists(CosmosEntityInformation, ?> information) {
+ return createContainerIfNotExists(information);
+ }
+
+ /**
+ * Creates a container if it doesn't already exist
+ *
+ * @param information the CosmosEntityInformation
+ * @return Mono containing CosmosContainerResponse
+ */
+ @Override
+ public Mono createContainerIfNotExists(CosmosEntityInformation, ?> information) {
+
+ return cosmosClient
+ .createDatabaseIfNotExists(this.databaseName)
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to create database", throwable))
+ .flatMap(cosmosDatabaseResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ cosmosDatabaseResponse, null);
+ final CosmosContainerProperties cosmosContainerProperties = new CosmosContainerProperties(
+ information.getContainerName(),
+ "/" + information.getPartitionKeyFieldName());
+ cosmosContainerProperties.defaultTimeToLive(information.getTimeToLive());
+ cosmosContainerProperties.indexingPolicy(information.getIndexingPolicy());
+ return cosmosDatabaseResponse
+ .database()
+ .createContainerIfNotExists(cosmosContainerProperties, information.getRequestUnit())
+ .map(cosmosContainerResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ cosmosContainerResponse, null);
+ this.containerNameCache.add(information.getContainerName());
+ return cosmosContainerResponse;
+ })
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to create container", throwable));
+ });
+
+ }
+
+ /**
+ * Find all items in a given container
+ *
+ * @param containerName the containerName
+ * @param domainType the domainType
+ * @return Flux with all the found items or error
+ */
+ @Override
+ public Flux findAll(String containerName, Class domainType) {
+ final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL));
+
+ return find(query, domainType, containerName);
+ }
+
+ /**
+ * Find all items in a given container
+ *
+ * @param domainType the domainType
+ * @return Flux with all the found items or error
+ */
+ @Override
+ public Flux findAll(Class domainType) {
+ return findAll(domainType.getSimpleName(), domainType);
+ }
+
+ @Override
+ public Flux findAll(PartitionKey partitionKey, Class domainType) {
+ Assert.notNull(partitionKey, "partitionKey should not be null");
+ Assert.notNull(domainType, "domainType should not be null");
+
+ final String containerName = getContainerName(domainType);
+
+ final FeedOptions feedOptions = new FeedOptions();
+ feedOptions.partitionKey(partitionKey);
+ feedOptions.populateQueryMetrics(isPopulateQueryMetrics);
+
+ return cosmosClient
+ .getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .readAllItems(feedOptions)
+ .flatMap(cosmosItemFeedResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ null, cosmosItemFeedResponse);
+ return Flux.fromIterable(cosmosItemFeedResponse.results());
+ })
+ .map(cosmosItemProperties -> toDomainObject(domainType, cosmosItemProperties))
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to find items", throwable));
+ }
+
+ /**
+ * Find by id
+ *
+ * @param id the id
+ * @param domainType the domainType
+ * @return Mono with the item or error
+ */
+ @Override
+ public Mono findById(Object id, Class domainType) {
+ Assert.notNull(domainType, "domainType should not be null");
+ return findById(getContainerName(domainType), id, domainType);
+ }
+
+ /**
+ * Find by id
+ *
+ * @param containerName the containername
+ * @param id the id
+ * @param domainType the entity class
+ * @return Mono with the item or error
+ */
+ @Override
+ public Mono findById(String containerName, Object id, Class domainType) {
+ Assert.hasText(containerName, "containerName should not be null, empty or only whitespaces");
+ Assert.notNull(domainType, "domainType should not be null");
+ assertValidId(id);
+
+ final String query = String.format("select * from root where root.id = '%s'", id.toString());
+ final FeedOptions options = new FeedOptions();
+ options.enableCrossPartitionQuery(true);
+ options.populateQueryMetrics(isPopulateQueryMetrics);
+
+ return cosmosClient.getDatabase(databaseName)
+ .getContainer(containerName)
+ .queryItems(query, options)
+ .flatMap(cosmosItemFeedResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ null, cosmosItemFeedResponse);
+ return Mono.justOrEmpty(cosmosItemFeedResponse
+ .results()
+ .stream()
+ .map(cosmosItem -> toDomainObject(domainType, cosmosItem))
+ .findFirst());
+ })
+ .onErrorResume(throwable ->
+ findAPIExceptionHandler("Failed to find item", throwable))
+ .next();
+ }
+
+ /**
+ * Find by id
+ *
+ * @param id the id
+ * @param domainType the entity class
+ * @param partitionKey partition Key
+ * @return Mono with the item or error
+ */
+ @Override
+ public Mono findById(Object id, Class domainType, PartitionKey partitionKey) {
+ Assert.notNull(domainType, "domainType should not be null");
+ assertValidId(id);
+
+ final String containerName = getContainerName(domainType);
+ return cosmosClient.getDatabase(databaseName)
+ .getContainer(containerName)
+ .getItem(id.toString(), partitionKey)
+ .read()
+ .flatMap(cosmosItemResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ cosmosItemResponse, null);
+ return Mono.justOrEmpty(toDomainObject(domainType,
+ cosmosItemResponse.properties()));
+ })
+ .onErrorResume(throwable ->
+ findAPIExceptionHandler("Failed to find item", throwable));
+ }
+
+ /**
+ * Insert
+ *
+ * @param type of inserted objectToSave
+ * @param objectToSave the object to save
+ * @param partitionKey the partition key
+ * @return Mono with the item or error
+ */
+ public Mono insert(T objectToSave, PartitionKey partitionKey) {
+ Assert.notNull(objectToSave, "domainType should not be null");
+
+ return insert(getContainerName(objectToSave.getClass()), objectToSave, partitionKey);
+ }
+
+ /**
+ * Insert
+ *
+ * @param objectToSave the object to save
+ * @param type of inserted objectToSave
+ * @return Mono with the item or error
+ */
+ public Mono insert(T objectToSave) {
+ Assert.notNull(objectToSave, "objectToSave should not be null");
+
+ final Class domainType = (Class) objectToSave.getClass();
+ final CosmosItemProperties originalItem = mappingCosmosConverter.writeCosmosItemProperties(objectToSave);
+ return cosmosClient.getDatabase(this.databaseName)
+ .getContainer(getContainerName(objectToSave.getClass()))
+ .createItem(originalItem, new CosmosItemRequestOptions())
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to insert item", throwable))
+ .flatMap(cosmosItemResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ cosmosItemResponse, null);
+ return Mono.just(toDomainObject(domainType, cosmosItemResponse.properties()));
+ });
+ }
+
+ /**
+ * Insert
+ *
+ * @param type of inserted objectToSave
+ * @param containerName the container name
+ * @param objectToSave the object to save
+ * @param partitionKey the partition key
+ * @return Mono with the item or error
+ */
+ public Mono insert(String containerName, Object objectToSave, PartitionKey partitionKey) {
+ Assert.hasText(containerName, "containerName should not be null, empty or only whitespaces");
+ Assert.notNull(objectToSave, "objectToSave should not be null");
+
+ final Class domainType = (Class) objectToSave.getClass();
+ final CosmosItemProperties originalItem = mappingCosmosConverter.writeCosmosItemProperties(objectToSave);
+ final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
+ if (partitionKey != null) {
+ options.partitionKey(partitionKey);
+ }
+ return cosmosClient.getDatabase(this.databaseName)
+ .getContainer(containerName)
+ .createItem(originalItem, options)
+ .onErrorResume(throwable ->
+ exceptionHandler("Failed to insert item", throwable))
+ .flatMap(cosmosItemResponse -> {
+ fillAndProcessResponseDiagnostics(responseDiagnosticsProcessor,
+ cosmosItemResponse, null);
+ return Mono.just(toDomainObject(domainType, cosmosItemResponse.properties()));
+ });
+ }
+
+ /**
+ * Upsert
+ *
+ * @param object the object to upsert
+ * @param partitionKey the partition key
+ * @return Mono with the item or error
+ */
+ @Override
+ public Mono upsert(T object, PartitionKey partitionKey) {
+ return upsert(getContainerName(object.getClass()), object, partitionKey);
+ }
+
+ /**
+ * Upsert
+ *
+ * @param containerName the container name
+ * @param object the object to save
+ * @param partitionKey the partition key
+ * @return Mono with the item or error
+ */
+ @Override
+ public