Implemented reactive API replacing sync API for DocumentDbTemplate (#411)

* Implemented reactive API replacing sync API for DocumentDbTemplate

* Updated Sample code
Updated examples
Use of Cosmos Key Credential
Bumped up versions for spring-data sdk, examples, samples

* Code review comments. Improvements with APIs, and codacy comments
This commit is contained in:
Kushagra Thapar 2019-08-15 21:06:15 -07:00 коммит произвёл Xiaolu Dai
Родитель b9476204cc
Коммит d1304164a1
23 изменённых файлов: 497 добавлений и 437 удалений

Просмотреть файл

@ -80,6 +80,9 @@ If you are using Maven, add the following dependency.
### Setup Configuration ### Setup Configuration
Setup configuration class. 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.
```java ```java
@Configuration @Configuration
@EnableDocumentDbRepositories @EnableDocumentDbRepositories
@ -91,18 +94,29 @@ public class AppConfiguration extends AbstractDocumentDbConfiguration {
@Value("${azure.cosmosdb.key}") @Value("${azure.cosmosdb.key}")
private String key; private String key;
@Value("${azure.cosmosdb.secondaryKey}")
private String secondaryKey;
@Value("${azure.cosmosdb.database}") @Value("${azure.cosmosdb.database}")
private String dbName; private String dbName;
private CosmosKeyCredential cosmosKeyCredential;
public DocumentDBConfig getConfig() { public DocumentDBConfig getConfig() {
return DocumentDBConfig.builder(uri, key, dbName).build(); this.cosmosKeyCredential = new CosmosKeyCredential(key);
return DocumentDBConfig.builder(uri, this.cosmosKeyCredential, dbName).build();
}
public void switchToSecondaryKey() {
this.cosmosKeyCredential.key(secondaryKey);
} }
} }
``` ```
Or if you want to customize your config: Or if you want to customize your config:
```java ```java
public DocumentDBConfig getConfig() { public DocumentDBConfig getConfig() {
DocumentDBConfig dbConfig = DocumentDBConfig.builder(uri, key, dbName).build(); this.cosmosKeyCredential = new CosmosKeyCredential(key);
DocumentDBConfig dbConfig = DocumentDBConfig.builder(uri, this.cosmosKeyCredential, dbName).build();
dbConfig.getConnectionPolicy().setConnectionMode(ConnectionMode.DirectHttps); dbConfig.getConnectionPolicy().setConnectionMode(ConnectionMode.DirectHttps);
dbConfig.getConnectionPolicy().setMaxPoolSize(1000); dbConfig.getConnectionPolicy().setMaxPoolSize(1000);
return dbConfig; return dbConfig;
@ -187,6 +201,9 @@ public class SampleApplication implements CommandLineRunner {
@Autowired @Autowired
private UserRepository repository; private UserRepository repository;
@Autowired
private ApplicationContext applicationContext;
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args); SpringApplication.run(SampleApplication.class, args);
@ -203,6 +220,15 @@ public class SampleApplication implements CommandLineRunner {
final User result = repository.findOne(testUser.getId(), testUser.getLastName); final User result = repository.findOne(testUser.getId(), testUser.getLastName);
// if emailAddress is mapped to id, then // if emailAddress is mapped to id, then
// final User result = respository.findOne(testUser.getEmailAddress(), testUser.getLastName()); // 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);
} }
} }
``` ```

Просмотреть файл

@ -6,7 +6,7 @@
<groupId>com.microsoft.azure</groupId> <groupId>com.microsoft.azure</groupId>
<artifactId>spring-data-cosmosdb</artifactId> <artifactId>spring-data-cosmosdb</artifactId>
<version>2.1.3-SNAPSHOT</version> <version>2.1.7</version>
<name>Spring Data for Azure Cosmos DB SQL API</name> <name>Spring Data for Azure Cosmos DB SQL API</name>
<description>Spring Data for Azure Cosmos DB SQL API</description> <description>Spring Data for Azure Cosmos DB SQL API</description>

Просмотреть файл

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.microsoft.azure</groupId> <groupId>com.microsoft.azure</groupId>
<artifactId>spring-data-cosmosdb-samples</artifactId> <artifactId>spring-data-cosmosdb-samples</artifactId>
<version>2.0.5-SNAPSHOT</version> <version>2.1.7</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
@ -20,7 +20,7 @@
<dependency> <dependency>
<groupId>com.microsoft.azure</groupId> <groupId>com.microsoft.azure</groupId>
<artifactId>spring-data-cosmosdb</artifactId> <artifactId>spring-data-cosmosdb</artifactId>
<version>2.1.0</version> <version>2.1.7</version>
</dependency> </dependency>
<dependency> <dependency>

Просмотреть файл

@ -28,5 +28,7 @@ public class DocumentDbProperties {
private String key; private String key;
private String secondaryKey;
private String database; private String database;
} }

Просмотреть файл

@ -15,11 +15,13 @@
*/ */
package example.springdata.cosmosdb; package example.springdata.cosmosdb;
import com.azure.data.cosmos.CosmosKeyCredential;
import com.microsoft.azure.spring.data.cosmosdb.config.AbstractDocumentDbConfiguration; import com.microsoft.azure.spring.data.cosmosdb.config.AbstractDocumentDbConfiguration;
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig; import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
import com.microsoft.azure.spring.data.cosmosdb.repository.config.EnableDocumentDbRepositories; import com.microsoft.azure.spring.data.cosmosdb.repository.config.EnableDocumentDbRepositories;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
@ -33,8 +35,23 @@ public class UserRepositoryConfiguration extends AbstractDocumentDbConfiguration
@Autowired @Autowired
private DocumentDbProperties properties; private DocumentDbProperties properties;
@Override private CosmosKeyCredential cosmosKeyCredential;
public DocumentDBConfig getConfig() {
return DocumentDBConfig.builder(properties.getUri(), properties.getKey(), properties.getDatabase()).build(); @Bean
public DocumentDBConfig documentDBConfig() {
this.cosmosKeyCredential = new CosmosKeyCredential(properties.getKey());
return DocumentDBConfig.builder(properties.getUri(), cosmosKeyCredential, properties.getDatabase()).build();
}
public void switchToSecondaryKey() {
this.cosmosKeyCredential.key(properties.getSecondaryKey());
}
public void switchToPrimaryKey() {
this.cosmosKeyCredential.key(properties.getKey());
}
public void switchKey(String key) {
this.cosmosKeyCredential.key(key);
} }
} }

Просмотреть файл

@ -16,11 +16,13 @@
package example.springdata.cosmosdb; package example.springdata.cosmosdb;
import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentDbPageRequest; import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentDbPageRequest;
import com.microsoft.azure.spring.data.cosmosdb.exception.DocumentDBAccessException;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
@ -50,6 +52,9 @@ public class UserRepositoryIntegrationTest {
@Autowired @Autowired
private UserRepository repository; private UserRepository repository;
@Autowired
private ApplicationContext applicationContext;
@Before @Before
public void setup() { public void setup() {
this.repository.deleteAll(); this.repository.deleteAll();
@ -57,6 +62,11 @@ public class UserRepositoryIntegrationTest {
@After @After
public void cleanup() { public void cleanup() {
// Switch back to primary key to reset the invalid key
// Switch to invalid key
final UserRepositoryConfiguration bean =
applicationContext.getBean(UserRepositoryConfiguration.class);
bean.switchToPrimaryKey();
this.repository.deleteAll(); this.repository.deleteAll();
} }
@ -119,5 +129,57 @@ public class UserRepositoryIntegrationTest {
result = resultList.get(0); result = resultList.get(0);
Assert.isTrue(result.getId().equals(user.getId()), "should be the same Id"); Assert.isTrue(result.getId().equals(user.getId()), "should be the same Id");
} }
@Test
public void testSecondaryKeyRotation() {
// Switch to secondary key
final UserRepositoryConfiguration bean =
applicationContext.getBean(UserRepositoryConfiguration.class);
bean.switchToSecondaryKey();
final Address address = new Address(POSTAL_CODE, STREET, CITY);
final Role creator = new Role(ROLE_CREATOR, COST_CREATOR);
final Role contributor = new Role(ROLE_CONTRIBUTOR, COST_CONTRIBUTOR);
final User user = new User(ID, EMAIL, NAME, COUNT, address, Arrays.asList(creator, contributor));
this.repository.save(user);
// Test for findById
final User result = this.repository.findById(ID).get();
Assert.notNull(result, "should be exist in database");
Assert.isTrue(result.getId().equals(ID), "should be the same id");
// Test for findByName
final List<User> resultList = this.repository.findByName(user.getName());
Assert.isTrue(resultList.size() == 1, "should be only one user here");
Assert.isTrue(resultList.get(0).getName().equals(user.getName()), "should be same Name");
Assert.notNull(result.getRoleList(), "roleList should not be null");
Assert.isTrue(result.getRoleList().size() == user.getRoleList().size(), "must be the same list size");
}
@Test(expected = DocumentDBAccessException.class)
public void testInvalidSecondaryKey() {
final Address address = new Address(POSTAL_CODE, STREET, CITY);
final Role creator = new Role(ROLE_CREATOR, COST_CREATOR);
final Role contributor = new Role(ROLE_CONTRIBUTOR, COST_CONTRIBUTOR);
final User user = new User(ID, EMAIL, NAME, COUNT, address, Arrays.asList(creator, contributor));
this.repository.save(user);
// Test for findById
final User result = this.repository.findById(ID).get();
Assert.notNull(result, "should be exist in database");
Assert.isTrue(result.getId().equals(ID), "should be the same id");
// Switch to invalid key
final UserRepositoryConfiguration bean =
applicationContext.getBean(UserRepositoryConfiguration.class);
bean.switchKey("Invalid key");
// Test for findByName
this.repository.findByName(user.getName());
}
} }

Просмотреть файл

@ -8,7 +8,7 @@
<groupId>com.microsoft.azure</groupId> <groupId>com.microsoft.azure</groupId>
<artifactId>spring-data-cosmosdb-samples</artifactId> <artifactId>spring-data-cosmosdb-samples</artifactId>
<version>2.0.5-SNAPSHOT</version> <version>2.1.7</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<parent> <parent>

Просмотреть файл

@ -15,6 +15,7 @@ import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -48,12 +49,25 @@ public class DocumentDbFactory {
final String userAgent = getUserAgentSuffix() + ";" + policy.getUserAgentSuffix(); final String userAgent = getUserAgentSuffix() + ";" + policy.getUserAgentSuffix();
policy.setUserAgentSuffix(userAgent); policy.setUserAgentSuffix(userAgent);
// With introduction to com.azure.data.cosmos.CosmosKeyCredential,
// we are giving preference to config.getCosmosKeyCredential()
if (config.getCosmosKeyCredential() != null &&
!StringUtils.isEmpty(config.getCosmosKeyCredential().key())) {
return new DocumentClient(config.getUri(), config.getCosmosKeyCredential().key(),
policy, config.getConsistencyLevel());
}
return new DocumentClient(config.getUri(), config.getKey(), policy, config.getConsistencyLevel()); return new DocumentClient(config.getUri(), config.getKey(), policy, config.getConsistencyLevel());
} }
private void validateConfig(@NonNull DocumentDBConfig config) { private void validateConfig(@NonNull DocumentDBConfig config) {
Assert.hasText(config.getUri(), "cosmosdb host url should have text!"); Assert.hasText(config.getUri(), "cosmosdb host url should have text!");
Assert.hasText(config.getKey(), "cosmosdb host key 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.hasText(config.getDatabase(), "cosmosdb database should have text!");
Assert.notNull(config.getConnectionPolicy(), "cosmosdb connection policy should not be null!"); Assert.notNull(config.getConnectionPolicy(), "cosmosdb connection policy should not be null!");
} }

Просмотреть файл

@ -9,8 +9,10 @@ package com.microsoft.azure.spring.data.cosmosdb.config;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.azure.documentdb.DocumentClient; import com.microsoft.azure.documentdb.DocumentClient;
import com.microsoft.azure.spring.data.cosmosdb.Constants; import com.microsoft.azure.spring.data.cosmosdb.Constants;
import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory; import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory;
import com.microsoft.azure.spring.data.cosmosdb.core.DocumentDbTemplate; import com.microsoft.azure.spring.data.cosmosdb.core.DocumentDbTemplate;
import com.microsoft.azure.spring.data.cosmosdb.core.ReactiveCosmosTemplate;
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter; import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
@ -33,12 +35,23 @@ public abstract class AbstractDocumentDbConfiguration extends DocumentDbConfigur
return new DocumentDbFactory(config); return new DocumentDbFactory(config);
} }
@Bean
public CosmosDbFactory cosmosDbFactory(DocumentDBConfig config) {
return new CosmosDbFactory(config);
}
@Bean @Bean
public DocumentDbTemplate documentDbTemplate(DocumentDBConfig config) throws ClassNotFoundException { public DocumentDbTemplate documentDbTemplate(DocumentDBConfig config) throws ClassNotFoundException {
return new DocumentDbTemplate(this.documentDbFactory(config), this.mappingDocumentDbConverter(), return new DocumentDbTemplate(this.cosmosDbFactory(config), this.mappingDocumentDbConverter(),
config.getDatabase()); config.getDatabase());
} }
@Bean
public ReactiveCosmosTemplate cosmosDbTemplate(DocumentDBConfig config) throws ClassNotFoundException {
return new ReactiveCosmosTemplate(this.cosmosDbFactory(config), this.mappingDocumentDbConverter(),
config.getDatabase());
}
@Bean @Bean
public MappingDocumentDbConverter mappingDocumentDbConverter() throws ClassNotFoundException { public MappingDocumentDbConverter mappingDocumentDbConverter() throws ClassNotFoundException {
return new MappingDocumentDbConverter(this.documentDbMappingContext(), objectMapper); return new MappingDocumentDbConverter(this.documentDbMappingContext(), objectMapper);

Просмотреть файл

@ -6,23 +6,25 @@
package com.microsoft.azure.spring.data.cosmosdb.core; package com.microsoft.azure.spring.data.cosmosdb.core;
import com.microsoft.azure.documentdb.*; import com.azure.data.cosmos.CosmosClient;
import com.microsoft.azure.documentdb.internal.HttpConstants; import com.azure.data.cosmos.CosmosContainerResponse;
import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory; import com.azure.data.cosmos.CosmosItemProperties;
import com.microsoft.azure.spring.data.cosmosdb.common.CosmosdbUtils; import com.azure.data.cosmos.CosmosItemRequestOptions;
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig; import com.azure.data.cosmos.CosmosItemResponse;
import com.azure.data.cosmos.FeedOptions;
import com.azure.data.cosmos.FeedResponse;
import com.azure.data.cosmos.SqlQuerySpec;
import com.microsoft.azure.documentdb.DocumentCollection;
import com.microsoft.azure.documentdb.PartitionKey;
import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter; import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
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.generator.FindQuerySpecGenerator;
import com.microsoft.azure.spring.data.cosmosdb.core.query.Criteria; 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.CriteriaType;
import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentDbPageRequest; import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentDbPageRequest;
import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery; import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery;
import com.microsoft.azure.spring.data.cosmosdb.exception.DatabaseCreationException;
import com.microsoft.azure.spring.data.cosmosdb.exception.DocumentDBAccessException; import com.microsoft.azure.spring.data.cosmosdb.exception.DocumentDBAccessException;
import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation; import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -33,8 +35,9 @@ import org.springframework.data.domain.Pageable;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
@ -43,28 +46,24 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class DocumentDbTemplate implements DocumentDbOperations, ApplicationContextAware { public class DocumentDbTemplate implements DocumentDbOperations, ApplicationContextAware {
private static final String COUNT_VALUE_KEY = "_aggregate";
@Getter(AccessLevel.PRIVATE)
private final DocumentClient documentClient;
private final DocumentDbFactory documentDbFactory;
private final MappingDocumentDbConverter mappingDocumentDbConverter; private final MappingDocumentDbConverter mappingDocumentDbConverter;
private final ReactiveCosmosTemplate reactiveCosmosTemplate;
private final String databaseName; private final String databaseName;
private Database databaseCache; private final CosmosClient cosmosClient;
private List<String> collectionCache;
public DocumentDbTemplate(DocumentDbFactory documentDbFactory, public DocumentDbTemplate(CosmosDbFactory cosmosDbFactory,
MappingDocumentDbConverter mappingDocumentDbConverter, MappingDocumentDbConverter mappingDocumentDbConverter,
String dbName) { String dbName) {
Assert.notNull(documentDbFactory, "DocumentDbFactory must not be null!"); Assert.notNull(cosmosDbFactory, "CosmosDbFactory must not be null!");
Assert.notNull(mappingDocumentDbConverter, "MappingDocumentDbConverter must not be null!"); Assert.notNull(mappingDocumentDbConverter, "MappingDocumentDbConverter must not be null!");
this.databaseName = dbName;
this.documentDbFactory = documentDbFactory;
this.documentClient = this.documentDbFactory.getDocumentClient();
this.mappingDocumentDbConverter = mappingDocumentDbConverter; this.mappingDocumentDbConverter = mappingDocumentDbConverter;
this.collectionCache = new ArrayList<>();
this.reactiveCosmosTemplate = new ReactiveCosmosTemplate(cosmosDbFactory, mappingDocumentDbConverter, dbName);
this.databaseName = dbName;
this.cosmosClient = cosmosDbFactory.getCosmosClient();
} }
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
@ -80,24 +79,30 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
Assert.hasText(collectionName, "collectionName should not be null, empty or only whitespaces"); Assert.hasText(collectionName, "collectionName should not be null, empty or only whitespaces");
Assert.notNull(objectToSave, "objectToSave should not be null"); Assert.notNull(objectToSave, "objectToSave should not be null");
final Document document = mappingDocumentDbConverter.writeDoc(objectToSave); final CosmosItemProperties originalItem = mappingDocumentDbConverter.writeCosmosItemProperties(objectToSave);
log.debug("execute createDocument in database {} collection {}", this.databaseName, collectionName); log.debug("execute createDocument in database {} collection {}", this.databaseName, collectionName);
try { try {
final Resource result = getDocumentClient() final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
.createDocument(getCollectionLink(this.databaseName, collectionName), document, options.partitionKey(toCosmosPartitionKey(partitionKey));
getRequestOptions(partitionKey, null), false).getResource();
if (result instanceof Document) { @SuppressWarnings("unchecked")
final Document documentInserted = (Document) result; final Class<T> domainClass = (Class<T>) objectToSave.getClass();
@SuppressWarnings("unchecked") final Class<T> domainClass = (Class<T>) objectToSave.getClass();
return mappingDocumentDbConverter.read(domainClass, documentInserted); final CosmosItemResponse response = cosmosClient.getDatabase(this.databaseName)
} else { .getContainer(collectionName)
return null; .createItem(originalItem, options)
.onErrorResume(Mono::error)
.block();
if (response == null) {
throw new DocumentDBAccessException("Failed to insert item");
} }
} catch (DocumentClientException e) {
return mappingDocumentDbConverter.read(domainClass, response.properties());
} catch (Exception e) {
throw new DocumentDBAccessException("insert exception", e); throw new DocumentDBAccessException("insert exception", e);
} }
} }
@ -108,37 +113,29 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
return findById(getCollectionName(entityClass), id, entityClass); return findById(getCollectionName(entityClass), id, entityClass);
} }
private boolean isIdFieldAsPartitionKey(@NonNull Class<?> domainClass) {
@SuppressWarnings("unchecked") final DocumentDbEntityInformation information
= new DocumentDbEntityInformation(domainClass);
final String partitionKeyName = information.getPartitionKeyFieldName();
final String idName = information.getIdField().getName();
return partitionKeyName != null && partitionKeyName.equals(idName);
}
public <T> T findById(String collectionName, Object id, Class<T> domainClass) { public <T> T findById(String collectionName, Object id, Class<T> domainClass) {
Assert.hasText(collectionName, "collectionName should not be null, empty or only whitespaces"); Assert.hasText(collectionName, "collectionName should not be null, empty or only whitespaces");
Assert.notNull(domainClass, "entityClass should not be null"); Assert.notNull(domainClass, "entityClass should not be null");
assertValidId(id); assertValidId(id);
try { try {
final PartitionKey partitionKey = isIdFieldAsPartitionKey(domainClass) ? new PartitionKey(id) : null;
final RequestOptions options = getRequestOptions(partitionKey, null);
final String documentLink = getDocumentLink(this.databaseName, collectionName, id); final String query = String.format("select * from root where root.id = '%s'", id.toString());
final Resource document = getDocumentClient().readDocument(documentLink, options).getResource(); final FeedOptions options = new FeedOptions();
options.enableCrossPartitionQuery(true);
if (document instanceof Document) { return cosmosClient
return mappingDocumentDbConverter.read(domainClass, (Document) document); .getDatabase(databaseName)
} else { .getContainer(collectionName)
return null; .queryItems(query, options)
} .flatMap(cosmosItemFeedResponse -> Mono.justOrEmpty(cosmosItemFeedResponse
} catch (DocumentClientException e) { .results()
if (e.getStatusCode() == HttpConstants.StatusCodes.NOTFOUND) { .stream()
return null; .map(cosmosItem -> mappingDocumentDbConverter.read(domainClass, cosmosItem))
} .findFirst()))
.onErrorResume(Mono::error)
.blockFirst();
} catch (Exception e) {
throw new DocumentDBAccessException("findById exception", e); throw new DocumentDBAccessException("findById exception", e);
} }
} }
@ -154,21 +151,23 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
Assert.notNull(object, "Upsert object should not be null"); Assert.notNull(object, "Upsert object should not be null");
try { try {
Document originalDoc; final CosmosItemProperties originalItem = mappingDocumentDbConverter.writeCosmosItemProperties(object);
if (object instanceof Document) {
originalDoc = (Document) object;
} else {
originalDoc = mappingDocumentDbConverter.writeDoc(object);
}
log.debug("execute upsert document in database {} collection {}", this.databaseName, collectionName); log.debug("execute upsert document in database {} collection {}", this.databaseName, collectionName);
final String collectionLink = getCollectionSelfLink(collectionName); final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
final RequestOptions options = getRequestOptions(partitionKey, null); options.partitionKey(toCosmosPartitionKey(partitionKey));
getDocumentClient().upsertDocument(collectionLink, originalDoc, options, false); final CosmosItemResponse cosmosItemResponse = cosmosClient.getDatabase(this.databaseName)
} catch (DocumentClientException ex) { .getContainer(collectionName)
.upsertItem(originalItem, options)
.onErrorResume(Mono::error)
.block();
if (cosmosItemResponse == null) {
throw new DocumentDBAccessException("Failed to upsert item");
}
} catch (Exception ex) {
throw new DocumentDBAccessException("Failed to upsert document to database.", ex); throw new DocumentDBAccessException("Failed to upsert document to database.", ex);
} }
} }
@ -184,9 +183,11 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
Assert.notNull(domainClass, "entityClass should not be null"); Assert.notNull(domainClass, "entityClass should not be null");
final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL)); final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL));
final List<Document> results = findDocuments(query, domainClass, collectionName);
return results.stream().map(d -> getConverter().read(domainClass, d)).collect(Collectors.toList()); final List<CosmosItemProperties> documents = findDocuments(query, domainClass, collectionName);
return documents.stream()
.map(d -> getConverter().read(domainClass, d))
.collect(Collectors.toList());
} }
public void deleteAll(@NonNull String collectionName, @NonNull Class<?> domainClass) { public void deleteAll(@NonNull String collectionName, @NonNull Class<?> domainClass) {
@ -200,13 +201,7 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
@Override @Override
public void deleteCollection(@NonNull String collectionName) { public void deleteCollection(@NonNull String collectionName) {
Assert.hasText(collectionName, "collectionName should have text."); Assert.hasText(collectionName, "collectionName should have text.");
reactiveCosmosTemplate.deleteContainer(collectionName);
try {
getDocumentClient().deleteCollection(getCollectionLink(this.databaseName, collectionName), null);
this.collectionCache.remove(collectionName);
} catch (DocumentClientException ex) {
throw new DocumentDBAccessException("failed to delete collection: " + collectionName, ex);
}
} }
public String getCollectionName(Class<?> domainClass) { public String getCollectionName(Class<?> domainClass) {
@ -215,98 +210,15 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
return new DocumentDbEntityInformation<>(domainClass).getCollectionName(); return new DocumentDbEntityInformation<>(domainClass).getCollectionName();
} }
private Database createDatabaseIfNotExists(String dbName) {
try {
final List<Database> dbList = getDocumentClient()
.queryDatabases(new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id",
new SqlParameterCollection(new SqlParameter("@id", dbName))), null)
.getQueryIterable().toList();
if (!dbList.isEmpty()) {
return dbList.get(0);
} else {
// create new database
final Database db = new Database();
db.setId(dbName);
log.debug("execute createDatabase {}", dbName);
final Resource resource = getDocumentClient().createDatabase(db, null).getResource();
if (resource instanceof Database) {
return (Database) resource;
} else {
final String errorMessage = MessageFormat.format(
"create database {0} and get unexpected result: {1}", dbName, resource.getSelfLink());
log.error(errorMessage);
throw new DatabaseCreationException(errorMessage);
}
}
} catch (DocumentClientException ex) {
throw new DocumentDBAccessException("createOrGetDatabase exception", ex);
}
}
private DocumentCollection createCollection(@NonNull String dbName, String partitionKeyFieldName,
@NonNull DocumentDbEntityInformation information) {
DocumentCollection collection = new DocumentCollection();
final String collectionName = information.getCollectionName();
final IndexingPolicy policy = information.getIndexingPolicy();
final Integer timeToLive = information.getTimeToLive();
final RequestOptions requestOptions = getRequestOptions(null, information.getRequestUnit());
collection.setId(collectionName);
collection.setIndexingPolicy(policy);
if (information.getIndexingPolicy().getAutomatic()) {
collection.setDefaultTimeToLive(timeToLive); // If not Automatic, setDefaultTimeToLive is invalid
}
if (partitionKeyFieldName != null && !partitionKeyFieldName.isEmpty()) {
final PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition();
final ArrayList<String> paths = new ArrayList<>();
paths.add(getPartitionKeyPath(partitionKeyFieldName));
partitionKeyDefinition.setPaths(paths);
collection.setPartitionKey(partitionKeyDefinition);
}
log.debug("execute createCollection in database {} collection {}", dbName, collectionName);
try {
final Resource resource = getDocumentClient()
.createCollection(getDatabaseLink(dbName), collection, requestOptions)
.getResource();
if (resource instanceof DocumentCollection) {
collection = (DocumentCollection) resource;
}
return collection;
} catch (DocumentClientException e) {
throw new DocumentDBAccessException("createCollection exception", e);
}
}
@Override @Override
public DocumentCollection createCollectionIfNotExists(@NonNull DocumentDbEntityInformation information) { public DocumentCollection createCollectionIfNotExists(@NonNull DocumentDbEntityInformation information) {
if (this.databaseCache == null) { final CosmosContainerResponse response = reactiveCosmosTemplate
this.databaseCache = createDatabaseIfNotExists(this.databaseName); .createCollectionIfNotExists(information)
} .block();
if (response == null) {
final String collectionName = information.getCollectionName(); throw new DocumentDBAccessException("Failed to create collection");
final String partitionKeyFieldName = information.getPartitionKeyFieldName();
final List<DocumentCollection> collectionList = getDocumentClient()
.queryCollections(getDatabaseLink(this.databaseName),
new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id",
new SqlParameterCollection(new SqlParameter("@id", collectionName))), null)
.getQueryIterable().toList();
if (!collectionList.isEmpty()) {
return collectionList.get(0);
} else {
return createCollection(this.databaseName, partitionKeyFieldName, information);
} }
return new DocumentCollection(response.properties().toJson());
} }
public void deleteById(String collectionName, Object id, PartitionKey partitionKey) { public void deleteById(String collectionName, Object id, PartitionKey partitionKey) {
@ -315,74 +227,17 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
log.debug("execute deleteById in database {} collection {}", this.databaseName, collectionName); log.debug("execute deleteById in database {} collection {}", this.databaseName, collectionName);
com.azure.data.cosmos.PartitionKey pk = toCosmosPartitionKey(partitionKey);
if (pk == null) {
pk = com.azure.data.cosmos.PartitionKey.None;
}
try { try {
final RequestOptions options = getRequestOptions(partitionKey, null); reactiveCosmosTemplate.deleteById(collectionName, id, pk).block();
getDocumentClient().deleteDocument(getDocumentLink(databaseName, collectionName, id.toString()), options); } catch (Exception e) {
} catch (DocumentClientException ex) { throw new DocumentDBAccessException("deleteById exception", e);
throw new DocumentDBAccessException("deleteById exception", ex);
} }
} }
private String getDatabaseLink(String databaseName) {
return "dbs/" + databaseName;
}
private String getCollectionLink(String databaseName, String collectionName) {
return getDatabaseLink(databaseName) + "/colls/" + collectionName;
}
private String getDocumentLink(String databaseName, String collectionName, Object documentId) {
return getCollectionLink(databaseName, collectionName) + "/docs/" + documentId;
}
private String getPartitionKeyPath(String partitionKey) {
return "/" + partitionKey;
}
@NonNull
private DocumentDBConfig getDocumentDbConfig() {
return documentDbFactory.getConfig();
}
private RequestOptions getRequestOptions(PartitionKey key, Integer requestUnit) {
final RequestOptions options = CosmosdbUtils.getCopyFrom(getDocumentDbConfig().getRequestOptions());
if (key != null) {
options.setPartitionKey(key);
}
if (requestUnit != null) {
options.setOfferThroughput(requestUnit);
}
return options;
}
private <T> List<T> executeQuery(@NonNull SqlQuerySpec sqlQuerySpec, boolean isCrossPartition,
@NonNull Class<T> domainClass, String collectionName) {
final FeedResponse<Document> feedResponse = executeQuery(sqlQuerySpec, isCrossPartition, collectionName);
final List<Document> result = feedResponse.getQueryIterable().toList();
return result.stream().map(r -> getConverter().read(domainClass, r)).collect(Collectors.toList());
}
private FeedResponse<Document> executeQuery(@NonNull SqlQuerySpec sqlQuerySpec, boolean isCrossPartition,
String collectionName) {
final FeedOptions feedOptions = new FeedOptions();
final String selfLink = getCollectionSelfLink(collectionName);
feedOptions.setEnableCrossPartitionQuery(isCrossPartition);
return getDocumentClient().queryDocuments(selfLink, sqlQuerySpec, feedOptions);
}
private FeedResponse<Document> executeQuery(@NonNull SqlQuerySpec sqlQuerySpec, FeedOptions feedOptions,
String collectionName) {
final String selfLink = getCollectionSelfLink(collectionName);
return getDocumentClient().queryDocuments(selfLink, sqlQuerySpec, feedOptions);
}
@Override @Override
public <T, ID> List<T> findByIds(Iterable<ID> ids, Class<T> entityClass, String collectionName) { public <T, ID> List<T> findByIds(Iterable<ID> ids, Class<T> entityClass, String collectionName) {
Assert.notNull(ids, "Id list should not be null"); Assert.notNull(ids, "Id list should not be null");
@ -400,11 +255,8 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
Assert.hasText(collectionName, "collection should not be null, empty or only whitespaces"); Assert.hasText(collectionName, "collection should not be null, empty or only whitespaces");
try { try {
final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generate(query); return reactiveCosmosTemplate.find(query, domainClass, collectionName).collectList().block();
final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(getPartitionKeyNames(domainClass)); } catch (Exception e) {
return this.executeQuery(sqlQuerySpec, isCrossPartitionQuery, domainClass, collectionName);
} catch (IllegalStateException | IllegalArgumentException e) {
throw new DocumentDBAccessException("Failed to execute find operation from " + collectionName, e); throw new DocumentDBAccessException("Failed to execute find operation from " + collectionName, e);
} }
} }
@ -413,33 +265,6 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
return this.find(query, domainClass, collectionName).size() > 0; return this.find(query, domainClass, collectionName).size() > 0;
} }
private List<Document> findDocuments(@NonNull DocumentQuery query, @NonNull Class<?> domainClass,
@NonNull String collectionName) {
final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generate(query);
final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
final FeedResponse<Document> response = executeQuery(sqlQuerySpec, isCrossPartitionQuery, collectionName);
return response.getQueryIterable().toList();
}
private void deleteDocument(@NonNull Document document, @NonNull List<String> partitionKeyNames) {
try {
Assert.isTrue(partitionKeyNames.size() <= 1, "Only one Partition is supported.");
PartitionKey partitionKey = null;
if (!partitionKeyNames.isEmpty() && StringUtils.hasText(partitionKeyNames.get(0))) {
partitionKey = new PartitionKey(document.get(partitionKeyNames.get(0)));
}
final RequestOptions options = getRequestOptions(partitionKey, null);
getDocumentClient().deleteDocument(document.getSelfLink(), options);
} catch (DocumentClientException e) {
throw new DocumentDBAccessException("Failed to delete document: " + document.getSelfLink(), e);
}
}
/** /**
* Delete the DocumentQuery, need to query the domains at first, then delete the document * Delete the DocumentQuery, need to query the domains at first, then delete the document
* from the result. * from the result.
@ -458,12 +283,14 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
Assert.notNull(domainClass, "domainClass should not be null."); Assert.notNull(domainClass, "domainClass should not be null.");
Assert.hasText(collectionName, "collection should not be null, empty or only whitespaces"); Assert.hasText(collectionName, "collection should not be null, empty or only whitespaces");
final List<Document> results = findDocuments(query, domainClass, collectionName); final List<CosmosItemProperties> results = findDocuments(query, domainClass, collectionName);
final List<String> partitionKeyName = getPartitionKeyNames(domainClass); final List<String> partitionKeyName = getPartitionKeyNames(domainClass);
results.forEach(d -> deleteDocument(d, partitionKeyName)); results.forEach(d -> deleteDocument(d, partitionKeyName, collectionName));
return results.stream().map(d -> getConverter().read(domainClass, d)).collect(Collectors.toList()); return results.stream()
.map(d -> getConverter().read(domainClass, d))
.collect(Collectors.toList());
} }
@Override @Override
@ -484,33 +311,42 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
final Pageable pageable = query.getPageable(); final Pageable pageable = query.getPageable();
final FeedOptions feedOptions = new FeedOptions(); final FeedOptions feedOptions = new FeedOptions();
if (pageable instanceof DocumentDbPageRequest) { if (pageable instanceof DocumentDbPageRequest) {
feedOptions.setRequestContinuation(((DocumentDbPageRequest) pageable).getRequestContinuation()); feedOptions.requestContinuation(((DocumentDbPageRequest) pageable).getRequestContinuation());
} }
feedOptions.setPageSize(pageable.getPageSize()); feedOptions.maxItemCount(pageable.getPageSize());
feedOptions.setEnableCrossPartitionQuery(query.isCrossPartitionQuery(getPartitionKeyNames(domainClass))); feedOptions.enableCrossPartitionQuery(query.isCrossPartitionQuery(getPartitionKeyNames(domainClass)));
final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generate(query); final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generateCosmos(query);
final FeedResponse<Document> response = executeQuery(sqlQuerySpec, feedOptions, collectionName); final FeedResponse<CosmosItemProperties> feedResponse =
cosmosClient.getDatabase(this.databaseName)
.getContainer(collectionName)
.queryItems(sqlQuerySpec, feedOptions)
.next()
.block();
final Iterator<Document> it = response.getQueryIterator(); if (feedResponse == null) {
throw new DocumentDBAccessException("Failed to query documents");
}
final Iterator<CosmosItemProperties> it = feedResponse.results().iterator();
final List<T> result = new ArrayList<>(); final List<T> result = new ArrayList<>();
for (int index = 0; it.hasNext() && index < pageable.getPageSize(); index++) { for (int index = 0; it.hasNext() && index < pageable.getPageSize(); index++) {
// Limit iterator as inner iterator will automatically fetch the next page
final Document doc = it.next(); final CosmosItemProperties cosmosItemProperties = it.next();
if (doc == null) { if (cosmosItemProperties == null) {
continue; continue;
} }
final T entity = mappingDocumentDbConverter.read(domainClass, doc); final T entity = mappingDocumentDbConverter.read(domainClass, cosmosItemProperties);
result.add(entity); result.add(entity);
} }
final DocumentDbPageRequest pageRequest = DocumentDbPageRequest.of(pageable.getPageNumber(), final DocumentDbPageRequest pageRequest = DocumentDbPageRequest.of(pageable.getPageNumber(),
pageable.getPageSize(), pageable.getPageSize(),
response.getResponseContinuation(), feedResponse.continuationToken(),
query.getSort()); query.getSort());
return new PageImpl<>(result, pageRequest, count(query, domainClass, collectionName)); return new PageImpl<>(result, pageRequest, count(query, domainClass, collectionName));
} }
@ -519,10 +355,11 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
public long count(String collectionName) { public long count(String collectionName) {
Assert.hasText(collectionName, "collectionName should not be empty"); Assert.hasText(collectionName, "collectionName should not be empty");
final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL)); final Long count = reactiveCosmosTemplate.count(collectionName).block();
final SqlQuerySpec querySpec = new CountQueryGenerator().generate(query); if (count == null) {
throw new DocumentDBAccessException("Failed to get count for collectionName: " + collectionName);
return getCountValue(querySpec, true, collectionName); }
return count;
} }
@Override @Override
@ -530,27 +367,12 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
Assert.notNull(domainClass, "domainClass should not be null"); Assert.notNull(domainClass, "domainClass should not be null");
Assert.hasText(collectionName, "collectionName should not be empty"); Assert.hasText(collectionName, "collectionName should not be empty");
final SqlQuerySpec querySpec = new CountQueryGenerator().generate(query);
final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(getPartitionKeyNames(domainClass)); final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
final Long count = reactiveCosmosTemplate.count(query, isCrossPartitionQuery, collectionName).block();
return getCountValue(querySpec, isCrossPartitionQuery, collectionName); if (count == null) {
} throw new DocumentDBAccessException("Failed to get count for collectionName: " + collectionName);
private long getCountValue(SqlQuerySpec querySpec, boolean isCrossPartitionQuery, String collectionName) {
final FeedResponse<Document> feedResponse = executeQuery(querySpec, isCrossPartitionQuery, collectionName);
final Object value = feedResponse.getQueryIterable().toList().get(0).getHashMap().get(COUNT_VALUE_KEY);
if (value instanceof Integer) {
return Long.valueOf((Integer) value);
} else if (value instanceof Long) {
return (Long) value;
} else {
throw new IllegalStateException("Unexpected value type " + value.getClass() + " of value: " + value);
} }
} return count;
private String getCollectionSelfLink(@NonNull String collectionName) {
return String.format("dbs/%s/colls/%s", this.databaseName, collectionName);
} }
@Override @Override
@ -569,10 +391,59 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
return Collections.singletonList(entityInfo.getPartitionKeyFieldName()); return Collections.singletonList(entityInfo.getPartitionKeyFieldName());
} }
private com.azure.data.cosmos.PartitionKey toCosmosPartitionKey(PartitionKey partitionKey) {
if (partitionKey == null) {
return null;
}
return com.azure.data.cosmos.PartitionKey.fromJsonString(partitionKey.getInternalPartitionKey().toJson());
}
private void assertValidId(Object id) { private void assertValidId(Object id) {
Assert.notNull(id, "id should not be null"); Assert.notNull(id, "id should not be null");
if (id instanceof String) { if (id instanceof String) {
Assert.hasText(id.toString(), "id should not be empty or only whitespaces."); Assert.hasText(id.toString(), "id should not be empty or only whitespaces.");
} }
} }
private List<CosmosItemProperties> findDocuments(@NonNull DocumentQuery query, @NonNull Class<?> domainClass,
@NonNull String containerName) {
final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generateCosmos(query);
final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
final FeedOptions feedOptions = new FeedOptions();
feedOptions.enableCrossPartitionQuery(isCrossPartitionQuery);
return cosmosClient
.getDatabase(this.databaseName)
.getContainer(containerName)
.queryItems(sqlQuerySpec, feedOptions)
.flatMap(cosmosItemFeedResponse -> Flux.fromIterable(cosmosItemFeedResponse.results()))
.collectList()
.block();
}
private CosmosItemResponse deleteDocument(@NonNull CosmosItemProperties cosmosItemProperties,
@NonNull List<String> partitionKeyNames,
String containerName) {
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)));
}
com.azure.data.cosmos.PartitionKey pk = toCosmosPartitionKey(partitionKey);
if (pk == null) {
pk = com.azure.data.cosmos.PartitionKey.None;
}
final CosmosItemRequestOptions options = new CosmosItemRequestOptions(pk);
return cosmosClient
.getDatabase(this.databaseName)
.getContainer(containerName)
.getItem(cosmosItemProperties.id(), partitionKey)
.delete(options)
.block();
}
} }

Просмотреть файл

@ -23,10 +23,7 @@ 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.DocumentQuery;
import com.microsoft.azure.spring.data.cosmosdb.exception.DocumentDBAccessException; import com.microsoft.azure.spring.data.cosmosdb.exception.DocumentDBAccessException;
import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation; import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
@ -39,16 +36,13 @@ import reactor.core.publisher.Mono;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Slf4j @Slf4j
public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, ApplicationContextAware { public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, ApplicationContextAware {
private static final String COUNT_VALUE_KEY = "_aggregate"; private static final String COUNT_VALUE_KEY = "_aggregate";
public static final int DEFAULT_THROUGHPUT = 400;
private final String databaseName; private final String databaseName;
@Getter(AccessLevel.PRIVATE)
private final CosmosClient cosmosClient; private final CosmosClient cosmosClient;
private final List<String> collectionCache; private final List<String> collectionCache;
@ -89,14 +83,12 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
@Override @Override
public Mono<CosmosContainerResponse> createCollectionIfNotExists(DocumentDbEntityInformation information) { public Mono<CosmosContainerResponse> createCollectionIfNotExists(DocumentDbEntityInformation information) {
// Note: Once Cosmos DB publishes new build, we shouldn't pass DEFAULT_THROUGHPUT.
// There is a NPE if we call the other overloaded method which automatically passes options as null.
return cosmosClient return cosmosClient
.createDatabaseIfNotExists(this.databaseName) .createDatabaseIfNotExists(this.databaseName)
.flatMap(cosmosDatabaseResponse -> cosmosDatabaseResponse .flatMap(cosmosDatabaseResponse -> cosmosDatabaseResponse
.database() .database()
.createContainerIfNotExists(information.getCollectionName(), .createContainerIfNotExists(information.getCollectionName(),
"/" + information.getPartitionKeyFieldName(), DEFAULT_THROUGHPUT) "/" + information.getPartitionKeyFieldName())
.map(cosmosContainerResponse -> { .map(cosmosContainerResponse -> {
this.collectionCache.add(information.getCollectionName()); this.collectionCache.add(information.getCollectionName());
return cosmosContainerResponse; return cosmosContainerResponse;
@ -159,14 +151,15 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
final String query = String.format("select * from root where root.id = '%s'", id.toString()); final String query = String.format("select * from root where root.id = '%s'", id.toString());
final FeedOptions options = new FeedOptions(); final FeedOptions options = new FeedOptions();
options.enableCrossPartitionQuery(true); options.enableCrossPartitionQuery(true);
return getCosmosClient().getDatabase(databaseName) return cosmosClient.getDatabase(databaseName)
.getContainer(containerName) .getContainer(containerName)
.queryItems(query, options) .queryItems(query, options)
.flatMap(cosmosItemFeedResponse -> Mono.just(cosmosItemFeedResponse .flatMap(cosmosItemFeedResponse -> Mono.justOrEmpty(cosmosItemFeedResponse
.results() .results()
.stream() .stream()
.map(cosmosItem -> cosmosItem.toObject(entityClass)) .map(cosmosItem -> cosmosItem.toObject(entityClass))
.collect(Collectors.toList()).get(0))) .findFirst()))
.onErrorResume(Mono::error)
.next(); .next();
} }
@ -193,10 +186,10 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
Assert.notNull(objectToSave, "objectToSave should not be null"); Assert.notNull(objectToSave, "objectToSave should not be null");
final Class<T> domainClass = (Class<T>) objectToSave.getClass(); final Class<T> domainClass = (Class<T>) objectToSave.getClass();
return getCosmosClient().getDatabase(this.databaseName) return cosmosClient.getDatabase(this.databaseName)
.getContainer(getContainerName(objectToSave.getClass())) .getContainer(getContainerName(objectToSave.getClass()))
.createItem(objectToSave, new CosmosItemRequestOptions()) .createItem(objectToSave, new CosmosItemRequestOptions())
.doOnError(Mono::error) .onErrorResume(Mono::error)
.flatMap(cosmosItemResponse -> Mono.just(cosmosItemResponse.properties().toObject(domainClass))); .flatMap(cosmosItemResponse -> Mono.just(cosmosItemResponse.properties().toObject(domainClass)));
} }
@ -218,7 +211,7 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
} }
final Class<T> domainClass = (Class<T>) objectToSave.getClass(); final Class<T> domainClass = (Class<T>) objectToSave.getClass();
return getCosmosClient().getDatabase(this.databaseName) return cosmosClient.getDatabase(this.databaseName)
.getContainer(containerName) .getContainer(containerName)
.createItem(objectToSave, options) .createItem(objectToSave, options)
.onErrorResume(Mono::error) .onErrorResume(Mono::error)
@ -253,10 +246,11 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
options.partitionKey(partitionKey); options.partitionKey(partitionKey);
} }
return getCosmosClient().getDatabase(this.databaseName) return cosmosClient.getDatabase(this.databaseName)
.getContainer(containerName) .getContainer(containerName)
.upsertItem(object, options) .upsertItem(object, options)
.flatMap(cosmosItemResponse -> Mono.just(cosmosItemResponse.properties().toObject(domainClass))); .flatMap(cosmosItemResponse -> Mono.just(cosmosItemResponse.properties().toObject(domainClass)))
.onErrorResume(Mono::error);
} }
/** /**
@ -273,15 +267,13 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
assertValidId(id); assertValidId(id);
Assert.notNull(partitionKey, "partitionKey should not be null"); Assert.notNull(partitionKey, "partitionKey should not be null");
final JSONObject jo = new JSONObject();
jo.put("id", id.toString());
final CosmosItemRequestOptions options = new CosmosItemRequestOptions(); final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
options.partitionKey(partitionKey); options.partitionKey(partitionKey);
return getCosmosClient().getDatabase(this.databaseName) return cosmosClient.getDatabase(this.databaseName)
.getContainer(containerName) .getContainer(containerName)
.getItem(id.toString(), partitionKey) .getItem(id.toString(), partitionKey)
.delete(options) .delete(options)
.onErrorResume(throwable -> Mono.empty()) .onErrorResume(Mono::error)
.then(); .then();
} }
@ -303,13 +295,13 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
final FeedOptions options = new FeedOptions(); final FeedOptions options = new FeedOptions();
final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(Collections.singletonList(partitionKeyName)); final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(Collections.singletonList(partitionKeyName));
options.enableCrossPartitionQuery(isCrossPartitionQuery); options.enableCrossPartitionQuery(isCrossPartitionQuery);
return getCosmosClient().getDatabase(this.databaseName) return cosmosClient.getDatabase(this.databaseName)
.getContainer(containerName) .getContainer(containerName)
.queryItems(sqlQuerySpec, options) .queryItems(sqlQuerySpec, options)
.onErrorResume(this::databaseAccessExceptionHandler) .onErrorResume(this::databaseAccessExceptionHandler)
.map(cosmosItemFeedResponse -> cosmosItemFeedResponse.results() .map(cosmosItemFeedResponse -> cosmosItemFeedResponse.results()
.stream() .stream()
.map(cosmosItemProperties -> getCosmosClient() .map(cosmosItemProperties -> cosmosClient
.getDatabase(this.databaseName) .getDatabase(this.databaseName)
.getContainer(containerName) .getContainer(containerName)
.getItem(cosmosItemProperties.id(), partitionKeyName) .getItem(cosmosItemProperties.id(), partitionKeyName)
@ -363,7 +355,7 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
*/ */
@Override @Override
public Mono<Boolean> exists(DocumentQuery query, Class<?> entityClass, String containerName) { public Mono<Boolean> exists(DocumentQuery query, Class<?> entityClass, String containerName) {
return getCountValue(query, true, containerName).flatMap(count -> Mono.just(count > 0)); return count(query, true, containerName).flatMap(count -> Mono.just(count > 0));
} }
/** /**
@ -387,7 +379,7 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
@Override @Override
public Mono<Long> count(String containerName) { public Mono<Long> count(String containerName) {
final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL)); final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL));
return getCountValue(query, true, containerName); return count(query, true, containerName);
} }
/** /**
@ -399,7 +391,11 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
*/ */
@Override @Override
public Mono<Long> count(DocumentQuery query, String containerName) { public Mono<Long> count(DocumentQuery query, String containerName) {
return getCountValue(query, true, containerName); return count(query, true, containerName);
}
public Mono<Long> count(DocumentQuery query, boolean isCrossPartitionQuery, String containerName) {
return getCountValue(query, isCrossPartitionQuery, containerName);
} }
private Mono<Long> getCountValue(DocumentQuery query, boolean isCrossPartitionQuery, String containerName) { private Mono<Long> getCountValue(DocumentQuery query, boolean isCrossPartitionQuery, String containerName) {
@ -417,7 +413,7 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
private Flux<FeedResponse<CosmosItemProperties>> executeQuery(SqlQuerySpec sqlQuerySpec, String collectionName, private Flux<FeedResponse<CosmosItemProperties>> executeQuery(SqlQuerySpec sqlQuerySpec, String collectionName,
FeedOptions options) { FeedOptions options) {
return getCosmosClient().getDatabase(this.databaseName) return cosmosClient.getDatabase(this.databaseName)
.getContainer(collectionName) .getContainer(collectionName)
.queryItems(sqlQuerySpec, options); .queryItems(sqlQuerySpec, options);
} }
@ -435,7 +431,7 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
public void deleteContainer(@NonNull String containerName) { public void deleteContainer(@NonNull String containerName) {
Assert.hasText(containerName, "containerName should have text."); Assert.hasText(containerName, "containerName should have text.");
try { try {
getCosmosClient().getDatabase(this.databaseName).getContainer(containerName).delete().block(); cosmosClient.getDatabase(this.databaseName).getContainer(containerName).delete().block();
this.collectionCache.remove(containerName); this.collectionCache.remove(containerName);
} catch (Exception e) { } catch (Exception e) {
throw new DocumentDBAccessException("failed to delete collection: " + containerName, e); throw new DocumentDBAccessException("failed to delete collection: " + containerName, e);
@ -458,7 +454,7 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(getPartitionKeyNames(domainClass)); final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
final FeedOptions feedOptions = new FeedOptions(); final FeedOptions feedOptions = new FeedOptions();
feedOptions.enableCrossPartitionQuery(isCrossPartitionQuery); feedOptions.enableCrossPartitionQuery(isCrossPartitionQuery);
return getCosmosClient() return cosmosClient
.getDatabase(this.databaseName) .getDatabase(this.databaseName)
.getContainer(containerName) .getContainer(containerName)
.queryItems(sqlQuerySpec, feedOptions) .queryItems(sqlQuerySpec, feedOptions)
@ -482,9 +478,9 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
return Collections.singletonList(entityInfo.getPartitionKeyFieldName()); return Collections.singletonList(entityInfo.getPartitionKeyFieldName());
} }
private Mono<Void> deleteDocument(@NonNull CosmosItemProperties cosmosItemProperties, private Mono<CosmosItemProperties> deleteDocument(@NonNull CosmosItemProperties cosmosItemProperties,
@NonNull List<String> partitionKeyNames, @NonNull List<String> partitionKeyNames,
String containerName) { String containerName) {
Assert.isTrue(partitionKeyNames.size() <= 1, "Only one Partition is supported."); Assert.isTrue(partitionKeyNames.size() <= 1, "Only one Partition is supported.");
PartitionKey partitionKey = null; PartitionKey partitionKey = null;
@ -495,12 +491,12 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
final CosmosItemRequestOptions options = new CosmosItemRequestOptions(partitionKey); final CosmosItemRequestOptions options = new CosmosItemRequestOptions(partitionKey);
return getCosmosClient() return cosmosClient
.getDatabase(this.databaseName) .getDatabase(this.databaseName)
.getContainer(containerName) .getContainer(containerName)
.getItem(cosmosItemProperties.id(), partitionKey) .getItem(cosmosItemProperties.id(), partitionKey)
.delete(options) .delete(options)
.then(); .map(cosmosItemResponse -> cosmosItemProperties);
} }
} }

Просмотреть файл

@ -5,6 +5,7 @@
*/ */
package com.microsoft.azure.spring.data.cosmosdb.core.convert; package com.microsoft.azure.spring.data.cosmosdb.core.convert;
import com.azure.data.cosmos.CosmosItemProperties;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@ -35,44 +36,46 @@ import java.util.Date;
import static com.microsoft.azure.spring.data.cosmosdb.Constants.ISO_8601_COMPATIBLE_DATE_PATTERN; import static com.microsoft.azure.spring.data.cosmosdb.Constants.ISO_8601_COMPATIBLE_DATE_PATTERN;
public class MappingDocumentDbConverter public class MappingDocumentDbConverter
implements EntityConverter<DocumentDbPersistentEntity<?>, DocumentDbPersistentProperty, Object, Document>, implements EntityConverter<DocumentDbPersistentEntity<?>, DocumentDbPersistentProperty,
ApplicationContextAware { Object, CosmosItemProperties>,
ApplicationContextAware {
protected final MappingContext<? extends DocumentDbPersistentEntity<?>, protected final MappingContext<? extends DocumentDbPersistentEntity<?>,
DocumentDbPersistentProperty> mappingContext; DocumentDbPersistentProperty> mappingContext;
protected GenericConversionService conversionService; protected GenericConversionService conversionService;
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
public MappingDocumentDbConverter( public MappingDocumentDbConverter(
MappingContext<? extends DocumentDbPersistentEntity<?>, DocumentDbPersistentProperty> mappingContext, MappingContext<? extends DocumentDbPersistentEntity<?>, DocumentDbPersistentProperty> mappingContext,
@Qualifier(Constants.OBJECTMAPPER_BEAN_NAME) ObjectMapper objectMapper) { @Qualifier(Constants.OBJECTMAPPER_BEAN_NAME) ObjectMapper objectMapper) {
this.mappingContext = mappingContext; this.mappingContext = mappingContext;
this.conversionService = new GenericConversionService(); this.conversionService = new GenericConversionService();
this.objectMapper = objectMapper == null ? ObjectMapperFactory.getObjectMapper() : objectMapper; this.objectMapper = objectMapper == null ? ObjectMapperFactory.getObjectMapper() :
objectMapper;
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.objectMapper.registerModule(provideAdvancedSerializersModule());
} }
@Override @Override
public <R extends Object> R read(Class<R> type, Document sourceDocument) { public <R> R read(Class<R> type, CosmosItemProperties cosmosItemProperties) {
if (sourceDocument == null) { if (cosmosItemProperties == null) {
return null; return null;
} }
final DocumentDbPersistentEntity<?> entity = mappingContext.getPersistentEntity(type); final DocumentDbPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
Assert.notNull(entity, "Entity is null."); Assert.notNull(entity, "Entity is null.");
return readInternal(entity, type, sourceDocument); return readInternal(entity, type, cosmosItemProperties);
} }
protected <R extends Object> R readInternal(final DocumentDbPersistentEntity<?> entity, Class<R> type, private <R> R readInternal(final DocumentDbPersistentEntity<?> entity, Class<R> type,
final Document sourceDocument) { final CosmosItemProperties cosmosItemProperties) {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.registerModule(provideAdvancedSerializersModule());
try { try {
final DocumentDbPersistentProperty idProperty = entity.getIdProperty(); final DocumentDbPersistentProperty idProperty = entity.getIdProperty();
final Object idValue = sourceDocument.getId(); final Object idValue = cosmosItemProperties.id();
final JSONObject jsonObject = new JSONObject(sourceDocument.toJson()); final JSONObject jsonObject = new JSONObject(cosmosItemProperties.toJson());
if (idProperty != null) { if (idProperty != null) {
// Replace the key id to the actual id field name in domain // Replace the key id to the actual id field name in domain
@ -82,8 +85,8 @@ public class MappingDocumentDbConverter
return objectMapper.readValue(jsonObject.toString(), type); return objectMapper.readValue(jsonObject.toString(), type);
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Failed to read the source document " + sourceDocument.toJson() throw new IllegalStateException("Failed to read the source document " + cosmosItemProperties.toJson()
+ " to target type " + type, e); + " to target type " + type, e);
} }
} }
@ -95,7 +98,7 @@ public class MappingDocumentDbConverter
@Override @Override
@Deprecated @Deprecated
public void write(Object sourceEntity, Document document) { public void write(Object sourceEntity, CosmosItemProperties document) {
throw new UnsupportedOperationException("The feature is not implemented yet"); throw new UnsupportedOperationException("The feature is not implemented yet");
} }
@ -105,7 +108,7 @@ public class MappingDocumentDbConverter
} }
final DocumentDbPersistentEntity<?> persistentEntity = final DocumentDbPersistentEntity<?> persistentEntity =
mappingContext.getPersistentEntity(sourceEntity.getClass()); mappingContext.getPersistentEntity(sourceEntity.getClass());
if (persistentEntity == null) { if (persistentEntity == null) {
throw new MappingException("no mapping metadata for entity type: " + sourceEntity.getClass().getName()); throw new MappingException("no mapping metadata for entity type: " + sourceEntity.getClass().getName());
@ -130,6 +133,38 @@ public class MappingDocumentDbConverter
return document; return document;
} }
public CosmosItemProperties writeCosmosItemProperties(Object sourceEntity) {
if (sourceEntity == null) {
return null;
}
final DocumentDbPersistentEntity<?> persistentEntity =
mappingContext.getPersistentEntity(sourceEntity.getClass());
if (persistentEntity == null) {
throw new MappingException("no mapping metadata for entity type: " + sourceEntity.getClass().getName());
}
final ConvertingPropertyAccessor accessor = getPropertyAccessor(sourceEntity);
final DocumentDbPersistentProperty idProperty = persistentEntity.getIdProperty();
final CosmosItemProperties cosmosItemProperties;
try {
cosmosItemProperties =
new CosmosItemProperties(objectMapper.writeValueAsString(sourceEntity));
} catch (JsonProcessingException e) {
throw new DocumentDBAccessException("Failed to map document value.", e);
}
if (idProperty != null) {
final Object value = accessor.getProperty(idProperty);
final String id = value == null ? null : value.toString();
cosmosItemProperties.id(id);
}
return cosmosItemProperties;
}
public ApplicationContext getApplicationContext() { public ApplicationContext getApplicationContext() {
return this.applicationContext; return this.applicationContext;
} }
@ -150,7 +185,8 @@ public class MappingDocumentDbConverter
private ConvertingPropertyAccessor getPropertyAccessor(Object entity) { private ConvertingPropertyAccessor getPropertyAccessor(Object entity) {
final DocumentDbPersistentEntity<?> entityInformation = mappingContext.getPersistentEntity(entity.getClass()); final DocumentDbPersistentEntity<?> entityInformation =
mappingContext.getPersistentEntity(entity.getClass());
Assert.notNull(entityInformation, "EntityInformation should not be null."); Assert.notNull(entityInformation, "EntityInformation should not be null.");
final PersistentPropertyAccessor accessor = entityInformation.getPropertyAccessor(entity); final PersistentPropertyAccessor accessor = entityInformation.getPropertyAccessor(entity);

Просмотреть файл

@ -19,12 +19,14 @@ import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.mockito.Mock; import org.mockito.Mock;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.AbstractApplicationContext;
public class AbstractDocumentDbConfigurationUnitTest { public class AbstractDocumentDbConfigurationIT {
private static final String OBJECTMAPPER_BEAN_NAME = Constants.OBJECTMAPPER_BEAN_NAME; private static final String OBJECTMAPPER_BEAN_NAME = Constants.OBJECTMAPPER_BEAN_NAME;
@Rule @Rule
@ -72,13 +74,21 @@ public class AbstractDocumentDbConfigurationUnitTest {
} }
@Configuration @Configuration
@PropertySource(value = {"classpath:application.properties"})
static class TestDocumentDbConfiguration extends AbstractDocumentDbConfiguration { static class TestDocumentDbConfiguration extends AbstractDocumentDbConfiguration {
@Value("${cosmosdb.uri:}")
private String documentDbUri;
@Value("${cosmosdb.key:}")
private String documentDbKey;
@Mock @Mock
private DocumentClient mockClient; private DocumentClient mockClient;
@Bean @Bean
public DocumentDBConfig getConfig() { public DocumentDBConfig getConfig() {
return DocumentDBConfig.builder("http://fake-uri", "fake-key", TestConstants.DB_NAME).build(); return DocumentDBConfig.builder(documentDbUri, documentDbKey, TestConstants.DB_NAME).build();
} }
@Override @Override
@ -96,8 +106,15 @@ public class AbstractDocumentDbConfigurationUnitTest {
} }
@Configuration @Configuration
@PropertySource(value = {"classpath:application.properties"})
static class RequestOptionsConfiguration extends AbstractDocumentDbConfiguration { static class RequestOptionsConfiguration extends AbstractDocumentDbConfiguration {
@Value("${cosmosdb.uri:}")
private String documentDbUri;
@Value("${cosmosdb.key:}")
private String documentDbKey;
private RequestOptions getRequestOptions() { private RequestOptions getRequestOptions() {
final RequestOptions options = new RequestOptions(); final RequestOptions options = new RequestOptions();
@ -111,7 +128,7 @@ public class AbstractDocumentDbConfigurationUnitTest {
@Bean @Bean
public DocumentDBConfig getConfig() { public DocumentDBConfig getConfig() {
final RequestOptions options = getRequestOptions(); final RequestOptions options = getRequestOptions();
return DocumentDBConfig.builder("http://fake-uri", "fake-key", TestConstants.DB_NAME) return DocumentDBConfig.builder(documentDbUri, documentDbKey, TestConstants.DB_NAME)
.requestOptions(options) .requestOptions(options)
.build(); .build();
} }

Просмотреть файл

@ -8,7 +8,7 @@ package com.microsoft.azure.spring.data.cosmosdb.core;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.azure.documentdb.DocumentCollection; import com.microsoft.azure.documentdb.DocumentCollection;
import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory; import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants; import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig; import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter; import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
@ -36,7 +36,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Arrays; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -44,7 +44,7 @@ import static com.microsoft.azure.spring.data.cosmosdb.common.PageTestUtils.vali
import static com.microsoft.azure.spring.data.cosmosdb.common.PageTestUtils.validateNonLastPage; import static com.microsoft.azure.spring.data.cosmosdb.common.PageTestUtils.validateNonLastPage;
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.*; import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.*;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@PropertySource(value = {"classpath:application.properties"}) @PropertySource(value = {"classpath:application.properties"})
@ -77,7 +77,7 @@ public class DocumentDbTemplateIT {
@Before @Before
public void setup() throws ClassNotFoundException { public void setup() throws ClassNotFoundException {
final DocumentDBConfig dbConfig = DocumentDBConfig.builder(documentDbUri, documentDbKey, DB_NAME).build(); final DocumentDBConfig dbConfig = DocumentDBConfig.builder(documentDbUri, documentDbKey, DB_NAME).build();
final DocumentDbFactory dbFactory = new DocumentDbFactory(dbConfig); final CosmosDbFactory cosmosDbFactory = new CosmosDbFactory(dbConfig);
mappingContext = new DocumentDbMappingContext(); mappingContext = new DocumentDbMappingContext();
objectMapper = new ObjectMapper(); objectMapper = new ObjectMapper();
@ -87,7 +87,7 @@ public class DocumentDbTemplateIT {
mappingContext.setInitialEntitySet(new EntityScanner(this.applicationContext).scan(Persistent.class)); mappingContext.setInitialEntitySet(new EntityScanner(this.applicationContext).scan(Persistent.class));
dbConverter = new MappingDocumentDbConverter(mappingContext, objectMapper); dbConverter = new MappingDocumentDbConverter(mappingContext, objectMapper);
dbTemplate = new DocumentDbTemplate(dbFactory, dbConverter, DB_NAME); dbTemplate = new DocumentDbTemplate(cosmosDbFactory, dbConverter, DB_NAME);
collectionPerson = dbTemplate.createCollectionIfNotExists(this.personInfo); collectionPerson = dbTemplate.createCollectionIfNotExists(this.personInfo);
dbTemplate.insert(Person.class.getSimpleName(), TEST_PERSON, null); dbTemplate.insert(Person.class.getSimpleName(), TEST_PERSON, null);
@ -114,7 +114,7 @@ public class DocumentDbTemplateIT {
public void testFindById() { public void testFindById() {
final Person result = dbTemplate.findById(Person.class.getSimpleName(), final Person result = dbTemplate.findById(Person.class.getSimpleName(),
TEST_PERSON.getId(), Person.class); TEST_PERSON.getId(), Person.class);
assertTrue(result.equals(TEST_PERSON)); assertEquals(result, TEST_PERSON);
final Person nullResult = dbTemplate.findById(Person.class.getSimpleName(), final Person nullResult = dbTemplate.findById(Person.class.getSimpleName(),
TestConstants.NOT_EXIST_ID, Person.class); TestConstants.NOT_EXIST_ID, Person.class);
@ -140,14 +140,14 @@ public class DocumentDbTemplateIT {
dbTemplate.deleteById(Person.class.getSimpleName(), TEST_PERSON.getId(), null); dbTemplate.deleteById(Person.class.getSimpleName(), TEST_PERSON.getId(), null);
final String firstName = TestConstants.NEW_FIRST_NAME + "_" + UUID.randomUUID().toString(); final String firstName = TestConstants.NEW_FIRST_NAME + "_" + UUID.randomUUID().toString();
final Person newPerson = new Person(null, firstName, TestConstants.NEW_FIRST_NAME, null, null); final Person newPerson = new Person(TEST_PERSON.getId(), firstName, TestConstants.NEW_FIRST_NAME, null, null);
dbTemplate.upsert(Person.class.getSimpleName(), newPerson, null); dbTemplate.upsert(Person.class.getSimpleName(), newPerson, null);
final List<Person> result = dbTemplate.findAll(Person.class); final List<Person> result = dbTemplate.findAll(Person.class);
assertThat(result.size()).isEqualTo(1); assertThat(result.size()).isEqualTo(1);
assertTrue(result.get(0).getFirstName().equals(firstName)); assertEquals(result.get(0).getFirstName(), firstName);
} }
@Test @Test
@ -159,7 +159,7 @@ public class DocumentDbTemplateIT {
final Person result = dbTemplate.findById(Person.class.getSimpleName(), final Person result = dbTemplate.findById(Person.class.getSimpleName(),
updated.getId(), Person.class); updated.getId(), Person.class);
assertTrue(result.equals(updated)); assertEquals(result, updated);
} }
@Test @Test
@ -171,7 +171,7 @@ public class DocumentDbTemplateIT {
final List<Person> result = dbTemplate.findAll(Person.class); final List<Person> result = dbTemplate.findAll(Person.class);
assertThat(result.size()).isEqualTo(1); assertThat(result.size()).isEqualTo(1);
assertTrue(result.get(0).equals(TEST_PERSON_2)); assertEquals(result.get(0), TEST_PERSON_2);
} }
@Test @Test
@ -190,7 +190,7 @@ public class DocumentDbTemplateIT {
dbTemplate.insert(TEST_PERSON_2, null); dbTemplate.insert(TEST_PERSON_2, null);
final Criteria criteria = Criteria.getInstance(CriteriaType.IS_EQUAL, "firstName", final Criteria criteria = Criteria.getInstance(CriteriaType.IS_EQUAL, "firstName",
Arrays.asList(TEST_PERSON_2.getFirstName())); Collections.singletonList(TEST_PERSON_2.getFirstName()));
final DocumentQuery query = new DocumentQuery(criteria); final DocumentQuery query = new DocumentQuery(criteria);
final long count = dbTemplate.count(query, Person.class, collectionName); final long count = dbTemplate.count(query, Person.class, collectionName);
@ -217,7 +217,7 @@ public class DocumentDbTemplateIT {
dbTemplate.insert(TEST_PERSON_2, null); dbTemplate.insert(TEST_PERSON_2, null);
final Criteria criteria = Criteria.getInstance(CriteriaType.IS_EQUAL, "firstName", final Criteria criteria = Criteria.getInstance(CriteriaType.IS_EQUAL, "firstName",
Arrays.asList(TestConstants.FIRST_NAME)); Collections.singletonList(FIRST_NAME));
final PageRequest pageRequest = new DocumentDbPageRequest(0, PAGE_SIZE_2, null); final PageRequest pageRequest = new DocumentDbPageRequest(0, PAGE_SIZE_2, null);
final DocumentQuery query = new DocumentQuery(criteria).with(pageRequest); final DocumentQuery query = new DocumentQuery(criteria).with(pageRequest);

Просмотреть файл

@ -6,10 +6,6 @@
package com.microsoft.azure.spring.data.cosmosdb.core; package com.microsoft.azure.spring.data.cosmosdb.core;
import com.microsoft.azure.documentdb.PartitionKey; import com.microsoft.azure.documentdb.PartitionKey;
import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory;
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
import com.microsoft.azure.spring.data.cosmosdb.core.query.Criteria; import com.microsoft.azure.spring.data.cosmosdb.core.query.Criteria;
import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery; import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery;
import com.microsoft.azure.spring.data.cosmosdb.domain.Person; import com.microsoft.azure.spring.data.cosmosdb.domain.Person;
@ -17,6 +13,7 @@ import org.apache.commons.lang3.StringUtils;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -37,16 +34,12 @@ public class DocumentDbTemplateIllegalTest {
private static final String WHITESPACES_STR = " "; private static final String WHITESPACES_STR = " ";
private static final String CHECK_FAILURE_MSG = "Illegal argument is not checked"; private static final String CHECK_FAILURE_MSG = "Illegal argument is not checked";
@Mock(answer = Answers.CALLS_REAL_METHODS)
private DocumentDbTemplate dbTemplate; private DocumentDbTemplate dbTemplate;
private Class dbTemplateClass; private Class dbTemplateClass;
@Mock
MappingDocumentDbConverter dbConverter;
@Before @Before
public void setUp() { public void setUp() {
final DocumentDBConfig dbConfig = DocumentDBConfig.builder("http://uri", "key", TestConstants.DB_NAME).build();
this.dbTemplate = new DocumentDbTemplate(new DocumentDbFactory(dbConfig), dbConverter, TestConstants.DB_NAME);
dbTemplateClass = dbTemplate.getClass(); dbTemplateClass = dbTemplate.getClass();
} }

Просмотреть файл

@ -8,7 +8,7 @@ package com.microsoft.azure.spring.data.cosmosdb.core;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.azure.documentdb.PartitionKey; import com.microsoft.azure.documentdb.PartitionKey;
import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory; import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig; import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter; import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentDbMappingContext; import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentDbMappingContext;
@ -68,7 +68,7 @@ public class DocumentDbTemplatePartitionIT {
@Before @Before
public void setup() throws ClassNotFoundException { public void setup() throws ClassNotFoundException {
final DocumentDBConfig dbConfig = DocumentDBConfig.builder(documentDbUri, documentDbKey, DB_NAME).build(); final DocumentDBConfig dbConfig = DocumentDBConfig.builder(documentDbUri, documentDbKey, DB_NAME).build();
final DocumentDbFactory dbFactory = new DocumentDbFactory(dbConfig); final CosmosDbFactory cosmosDbFactory = new CosmosDbFactory(dbConfig);
final ObjectMapper objectMapper = new ObjectMapper(); final ObjectMapper objectMapper = new ObjectMapper();
final DocumentDbMappingContext mappingContext = new DocumentDbMappingContext(); final DocumentDbMappingContext mappingContext = new DocumentDbMappingContext();
@ -77,7 +77,7 @@ public class DocumentDbTemplatePartitionIT {
final MappingDocumentDbConverter dbConverter = new MappingDocumentDbConverter(mappingContext, objectMapper); final MappingDocumentDbConverter dbConverter = new MappingDocumentDbConverter(mappingContext, objectMapper);
dbTemplate = new DocumentDbTemplate(dbFactory, dbConverter, DB_NAME); dbTemplate = new DocumentDbTemplate(cosmosDbFactory, dbConverter, DB_NAME);
collectionName = personInfo.getCollectionName(); collectionName = personInfo.getCollectionName();
dbTemplate.createCollectionIfNotExists(personInfo); dbTemplate.createCollectionIfNotExists(personInfo);
@ -121,7 +121,9 @@ public class DocumentDbTemplatePartitionIT {
@Test @Test
public void testUpsertNewDocumentPartition() { public void testUpsertNewDocumentPartition() {
final String firstName = NEW_FIRST_NAME + "_" + UUID.randomUUID().toString(); final String firstName = NEW_FIRST_NAME + "_" + UUID.randomUUID().toString();
final PartitionPerson newPerson = new PartitionPerson(null, firstName, NEW_LAST_NAME, null, null); final PartitionPerson newPerson = new PartitionPerson(TEST_PERSON.getId(),
firstName, NEW_LAST_NAME,
null, null);
final String partitionKeyValue = newPerson.getLastName(); final String partitionKeyValue = newPerson.getLastName();
dbTemplate.upsert(PartitionPerson.class.getSimpleName(), newPerson, new PartitionKey(partitionKeyValue)); dbTemplate.upsert(PartitionPerson.class.getSimpleName(), newPerson, new PartitionKey(partitionKeyValue));

Просмотреть файл

@ -6,7 +6,7 @@
package com.microsoft.azure.spring.data.cosmosdb.core; package com.microsoft.azure.spring.data.cosmosdb.core;
import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory; import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants; import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig; import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
import org.junit.Test; import org.junit.Test;
@ -19,8 +19,8 @@ public class DocumentDbTemplateUnitTest {
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void rejectNullDbFactory() { public void rejectNullDbFactory() {
final DocumentDBConfig dbConfig = DocumentDBConfig.builder("", "", TestConstants.DB_NAME).build(); final DocumentDBConfig dbConfig = DocumentDBConfig.builder("", "", TestConstants.DB_NAME).build();
final DocumentDbFactory dbFactory = new DocumentDbFactory(dbConfig); final CosmosDbFactory cosmosDbFactory = new CosmosDbFactory(dbConfig);
new DocumentDbTemplate(dbFactory, null, TestConstants.DB_NAME); new DocumentDbTemplate(cosmosDbFactory, null, TestConstants.DB_NAME);
} }
} }

Просмотреть файл

@ -117,10 +117,9 @@ public class ReactiveCosmosTemplateIT {
public void testFindByID() { public void testFindByID() {
final Mono<Person> findById = cosmosTemplate.findById(Person.class.getSimpleName(), TEST_PERSON.getId(), final Mono<Person> findById = cosmosTemplate.findById(Person.class.getSimpleName(), TEST_PERSON.getId(),
Person.class); Person.class);
StepVerifier.create(findById).consumeNextWith(actual -> { StepVerifier.create(findById)
Assert.assertThat(actual.getFirstName(), is(equalTo(TEST_PERSON.getFirstName()))); .consumeNextWith(actual -> Assert.assertEquals(actual, TEST_PERSON))
Assert.assertThat(actual.getLastName(), is(equalTo(TEST_PERSON.getLastName()))); .verifyComplete();
}).verifyComplete();
} }
@Test @Test
@ -143,10 +142,8 @@ public class ReactiveCosmosTemplateIT {
@Test @Test
public void testFindByIdWithContainerName() { public void testFindByIdWithContainerName() {
StepVerifier.create(cosmosTemplate.findById(Person.class.getSimpleName(), TEST_PERSON.getId(), Person.class)) StepVerifier.create(cosmosTemplate.findById(Person.class.getSimpleName(), TEST_PERSON.getId(), Person.class))
.consumeNextWith(actual -> { .consumeNextWith(actual -> Assert.assertEquals(actual, TEST_PERSON))
Assert.assertThat(actual.getFirstName(), is(equalTo(TEST_PERSON.getFirstName()))); .verifyComplete();
Assert.assertThat(actual.getLastName(), is(equalTo(TEST_PERSON.getLastName())));
}).verifyComplete();
} }
@Test @Test
@ -202,18 +199,26 @@ public class ReactiveCosmosTemplateIT {
@Test @Test
public void testDeleteById() { public void testDeleteById() {
cosmosTemplate.insert(TEST_PERSON_4).block(); cosmosTemplate.insert(TEST_PERSON_4).block();
Flux<Person> flux = cosmosTemplate.findAll(Person.class.getSimpleName(), Person.class);
StepVerifier.create(flux).expectNextCount(2).verifyComplete();
final Mono<Void> voidMono = cosmosTemplate.deleteById(Person.class.getSimpleName(), TEST_PERSON_4.getId(), final Mono<Void> voidMono = cosmosTemplate.deleteById(Person.class.getSimpleName(), TEST_PERSON_4.getId(),
new PartitionKey(TEST_PERSON_4.getId())); PartitionKey.None);
StepVerifier.create(voidMono).verifyComplete(); StepVerifier.create(voidMono).verifyComplete();
flux = cosmosTemplate.findAll(Person.class.getSimpleName(), Person.class);
StepVerifier.create(flux).expectNextCount(1).verifyComplete();
} }
@Test @Test
public void testDeleteByIdBySecondaryKey() { public void testDeleteByIdBySecondaryKey() {
cosmosKeyCredential.key(documentDbSecondaryKey); cosmosKeyCredential.key(documentDbSecondaryKey);
cosmosTemplate.insert(TEST_PERSON_4).block(); cosmosTemplate.insert(TEST_PERSON_4).block();
Flux<Person> flux = cosmosTemplate.findAll(Person.class.getSimpleName(), Person.class);
StepVerifier.create(flux).expectNextCount(2).verifyComplete();
final Mono<Void> voidMono = cosmosTemplate.deleteById(Person.class.getSimpleName(), TEST_PERSON_4.getId(), final Mono<Void> voidMono = cosmosTemplate.deleteById(Person.class.getSimpleName(), TEST_PERSON_4.getId(),
new PartitionKey(TEST_PERSON_4.getId())); PartitionKey.None);
StepVerifier.create(voidMono).verifyComplete(); StepVerifier.create(voidMono).verifyComplete();
flux = cosmosTemplate.findAll(Person.class.getSimpleName(), Person.class);
StepVerifier.create(flux).expectNextCount(1).verifyComplete();
} }
@Test @Test

Просмотреть файл

@ -5,14 +5,15 @@
*/ */
package com.microsoft.azure.spring.data.cosmosdb.core.converter; package com.microsoft.azure.spring.data.cosmosdb.core.converter;
import com.azure.data.cosmos.CosmosItemProperties;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.azure.documentdb.Document;
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants; import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter; import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentDbMappingContext; import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentDbMappingContext;
import com.microsoft.azure.spring.data.cosmosdb.domain.Address; import com.microsoft.azure.spring.data.cosmosdb.domain.Address;
import com.microsoft.azure.spring.data.cosmosdb.domain.Memo; import com.microsoft.azure.spring.data.cosmosdb.domain.Memo;
import com.microsoft.azure.spring.data.cosmosdb.domain.Importance; import com.microsoft.azure.spring.data.cosmosdb.domain.Importance;
import org.json.JSONObject;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -53,22 +54,23 @@ public class MappingDocumentDbConverterUnitTest {
@Test @Test
public void covertAddressToDocumentCorrectly() { public void covertAddressToDocumentCorrectly() {
final Address testAddress = new Address(TestConstants.POSTAL_CODE, TestConstants.CITY, TestConstants.STREET); final Address testAddress = new Address(TestConstants.POSTAL_CODE, TestConstants.CITY, TestConstants.STREET);
final Document document = dbConverter.writeDoc(testAddress); final CosmosItemProperties cosmosItemProperties = dbConverter.writeCosmosItemProperties(testAddress);
assertThat(document.getId()).isEqualTo(testAddress.getPostalCode()); assertThat(cosmosItemProperties.id()).isEqualTo(testAddress.getPostalCode());
assertThat(document.getString(TestConstants.PROPERTY_CITY)).isEqualTo(testAddress.getCity()); assertThat(cosmosItemProperties.getString(TestConstants.PROPERTY_CITY)).isEqualTo(testAddress.getCity());
assertThat(document.getString(TestConstants.PROPERTY_STREET)).isEqualTo(testAddress.getStreet()); assertThat(cosmosItemProperties.getString(TestConstants.PROPERTY_STREET)).isEqualTo(testAddress.getStreet());
} }
@Test @Test
public void convertDocumentToAddressCorrectly() { public void convertDocumentToAddressCorrectly() {
final Document document = new Document(); final JSONObject jsonObject = new JSONObject();
jsonObject.put(TestConstants.PROPERTY_CITY, TestConstants.CITY);
jsonObject.put(TestConstants.PROPERTY_STREET, TestConstants.STREET);
document.setId(TestConstants.POSTAL_CODE); final CosmosItemProperties cosmosItemProperties = new CosmosItemProperties(jsonObject.toString());
document.set(TestConstants.PROPERTY_CITY, TestConstants.CITY); cosmosItemProperties.id(TestConstants.POSTAL_CODE);
document.set(TestConstants.PROPERTY_STREET, TestConstants.STREET);
final Address address = dbConverter.read(Address.class, document); final Address address = dbConverter.read(Address.class, cosmosItemProperties);
assertThat(address.getPostalCode()).isEqualTo(TestConstants.POSTAL_CODE); assertThat(address.getPostalCode()).isEqualTo(TestConstants.POSTAL_CODE);
assertThat(address.getCity()).isEqualTo(TestConstants.CITY); assertThat(address.getCity()).isEqualTo(TestConstants.CITY);
@ -79,26 +81,28 @@ public class MappingDocumentDbConverterUnitTest {
public void canWritePojoWithDateToDocument() throws ParseException { public void canWritePojoWithDateToDocument() throws ParseException {
final Memo memo = new Memo(TestConstants.ID_1, TestConstants.MESSAGE, DATE.parse(TestConstants.DATE_STRING), final Memo memo = new Memo(TestConstants.ID_1, TestConstants.MESSAGE, DATE.parse(TestConstants.DATE_STRING),
Importance.NORMAL); Importance.NORMAL);
final Document document = dbConverter.writeDoc(memo); final CosmosItemProperties cosmosItemProperties = dbConverter.writeCosmosItemProperties(memo);
assertThat(document.getId()).isEqualTo(memo.getId()); assertThat(cosmosItemProperties.id()).isEqualTo(memo.getId());
assertThat(document.getString(TestConstants.PROPERTY_MESSAGE)).isEqualTo(memo.getMessage()); assertThat(cosmosItemProperties.getString(TestConstants.PROPERTY_MESSAGE)).isEqualTo(memo.getMessage());
assertThat(document.getLong(TestConstants.PROPERTY_DATE)).isEqualTo(memo.getDate().getTime()); assertThat(cosmosItemProperties.getLong(TestConstants.PROPERTY_DATE)).isEqualTo(memo.getDate().getTime());
} }
@Test @Test
public void canReadPojoWithDateFromDocument() throws ParseException { public void canReadPojoWithDateFromDocument() throws ParseException {
final Document document = new Document(); final JSONObject jsonObject = new JSONObject();
document.setId(TestConstants.ID_1); jsonObject.put(TestConstants.PROPERTY_MESSAGE, TestConstants.MESSAGE);
document.set(TestConstants.PROPERTY_MESSAGE, TestConstants.MESSAGE);
final long date = DATE.parse(TestConstants.DATE_STRING).getTime(); final long date = DATE.parse(TestConstants.DATE_STRING).getTime();
document.set(TestConstants.PROPERTY_DATE, date); jsonObject.put(TestConstants.PROPERTY_DATE, date);
final Memo memo = dbConverter.read(Memo.class, document); final CosmosItemProperties cosmosItemProperties = new CosmosItemProperties(jsonObject.toString());
assertThat(document.getId()).isEqualTo(memo.getId()); cosmosItemProperties.id(TestConstants.ID_1);
assertThat(document.getString(TestConstants.PROPERTY_MESSAGE)).isEqualTo(TestConstants.MESSAGE);
assertThat(document.getLong(TestConstants.PROPERTY_DATE)).isEqualTo(date); final Memo memo = dbConverter.read(Memo.class, cosmosItemProperties);
assertThat(cosmosItemProperties.id()).isEqualTo(memo.getId());
assertThat(cosmosItemProperties.getString(TestConstants.PROPERTY_MESSAGE)).isEqualTo(TestConstants.MESSAGE);
assertThat(cosmosItemProperties.getLong(TestConstants.PROPERTY_DATE)).isEqualTo(date);
} }
@Test @Test

Просмотреть файл

@ -124,8 +124,7 @@ public class AddressRepositoryIT {
@Test @Test
public void deleteWithoutPartitionedColumnShouldFail() { public void deleteWithoutPartitionedColumnShouldFail() {
expectedException.expect(UnsupportedOperationException.class); expectedException.expect(Exception.class);
expectedException.expectMessage("PartitionKey value must be supplied for this operation.");
repository.deleteById(TEST_ADDRESS1_PARTITION1.getPostalCode()); repository.deleteById(TEST_ADDRESS1_PARTITION1.getPostalCode());
} }

Просмотреть файл

@ -15,6 +15,7 @@ import org.assertj.core.util.Lists;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -156,6 +157,7 @@ public class ContactRepositoryIT {
} }
@Test @Test
@Ignore // TODO(kuthapar): v3 doesn't support creation of items without id.
public void testNullIdContact() { public void testNullIdContact() {
final Contact nullIdContact = new Contact(null, "testTitile"); final Contact nullIdContact = new Contact(null, "testTitile");
final Contact savedContact = this.repository.save(nullIdContact); final Contact savedContact = this.repository.save(nullIdContact);

Просмотреть файл

@ -7,7 +7,7 @@ package com.microsoft.azure.spring.data.cosmosdb.repository.integration;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.azure.documentdb.*; import com.microsoft.azure.documentdb.*;
import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory; import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants; import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
import com.microsoft.azure.spring.data.cosmosdb.common.TestUtils; import com.microsoft.azure.spring.data.cosmosdb.common.TestUtils;
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig; import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
@ -62,7 +62,7 @@ public class DocumentDBAnnotationIT {
@Before @Before
public void setUp() throws ClassNotFoundException { public void setUp() throws ClassNotFoundException {
final DocumentDBConfig dbConfig = DocumentDBConfig.builder(dbUri, dbKey, TestConstants.DB_NAME).build(); final DocumentDBConfig dbConfig = DocumentDBConfig.builder(dbUri, dbKey, TestConstants.DB_NAME).build();
final DocumentDbFactory dbFactory = new DocumentDbFactory(dbConfig); final CosmosDbFactory cosmosDbFactory = new CosmosDbFactory(dbConfig);
roleInfo = new DocumentDbEntityInformation<>(Role.class); roleInfo = new DocumentDbEntityInformation<>(Role.class);
sampleInfo = new DocumentDbEntityInformation<>(TimeToLiveSample.class); sampleInfo = new DocumentDbEntityInformation<>(TimeToLiveSample.class);
@ -73,7 +73,7 @@ public class DocumentDBAnnotationIT {
mappingConverter = new MappingDocumentDbConverter(dbContext, objectMapper); mappingConverter = new MappingDocumentDbConverter(dbContext, objectMapper);
dbClient = new DocumentClient(dbUri, dbKey, ConnectionPolicy.GetDefault(), ConsistencyLevel.Session); dbClient = new DocumentClient(dbUri, dbKey, ConnectionPolicy.GetDefault(), ConsistencyLevel.Session);
dbTemplate = new DocumentDbTemplate(dbFactory, mappingConverter, TestConstants.DB_NAME); dbTemplate = new DocumentDbTemplate(cosmosDbFactory, mappingConverter, TestConstants.DB_NAME);
collectionRole = dbTemplate.createCollectionIfNotExists(roleInfo); collectionRole = dbTemplate.createCollectionIfNotExists(roleInfo);
collectionExample = dbTemplate.createCollectionIfNotExists(sampleInfo); collectionExample = dbTemplate.createCollectionIfNotExists(sampleInfo);
@ -103,6 +103,7 @@ public class DocumentDBAnnotationIT {
@Test @Test
@SneakyThrows @SneakyThrows
@Ignore // TODO(kuthapar): time to live is not supported by v3 SDK.
public void testDocumentAnnotationTimeToLive() { public void testDocumentAnnotationTimeToLive() {
final TimeToLiveSample sample = new TimeToLiveSample(TestConstants.ID_1); final TimeToLiveSample sample = new TimeToLiveSample(TestConstants.ID_1);
final Integer timeToLive = this.collectionExample.getDefaultTimeToLive(); final Integer timeToLive = this.collectionExample.getDefaultTimeToLive();

Просмотреть файл

@ -7,7 +7,6 @@ package com.microsoft.azure.spring.data.cosmosdb.repository.integration;
import com.microsoft.azure.spring.data.cosmosdb.core.DocumentDbTemplate; import com.microsoft.azure.spring.data.cosmosdb.core.DocumentDbTemplate;
import com.microsoft.azure.spring.data.cosmosdb.domain.Question; import com.microsoft.azure.spring.data.cosmosdb.domain.Question;
import com.microsoft.azure.spring.data.cosmosdb.exception.DocumentDBAccessException;
import com.microsoft.azure.spring.data.cosmosdb.repository.TestRepositoryConfig; import com.microsoft.azure.spring.data.cosmosdb.repository.TestRepositoryConfig;
import com.microsoft.azure.spring.data.cosmosdb.repository.repository.ProjectRepository; import com.microsoft.azure.spring.data.cosmosdb.repository.repository.ProjectRepository;
import com.microsoft.azure.spring.data.cosmosdb.repository.repository.QuestionRepository; import com.microsoft.azure.spring.data.cosmosdb.repository.repository.QuestionRepository;
@ -72,9 +71,10 @@ public class QuestionRepositoryIT {
Assert.assertEquals(QUESTION, optional.get()); Assert.assertEquals(QUESTION, optional.get());
} }
@Test(expected = DocumentDBAccessException.class) @Test
public void testFindByIdException() { public void testFindByIdNull() {
this.repository.findById(QUESTION_URL); final Optional<Question> byId = this.repository.findById(QUESTION_URL);
Assert.assertFalse(byId.isPresent());
} }
@Test @Test