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 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
@Configuration
@EnableDocumentDbRepositories
@ -91,18 +94,29 @@ public class AppConfiguration extends AbstractDocumentDbConfiguration {
@Value("${azure.cosmosdb.key}")
private String key;
@Value("${azure.cosmosdb.secondaryKey}")
private String secondaryKey;
@Value("${azure.cosmosdb.database}")
private String dbName;
private CosmosKeyCredential cosmosKeyCredential;
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:
```java
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().setMaxPoolSize(1000);
return dbConfig;
@ -187,6 +201,9 @@ public class SampleApplication implements CommandLineRunner {
@Autowired
private UserRepository repository;
@Autowired
private ApplicationContext applicationContext;
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
@ -203,6 +220,15 @@ public class SampleApplication implements CommandLineRunner {
final User result = repository.findOne(testUser.getId(), testUser.getLastName);
// if emailAddress is mapped to id, then
// final User result = respository.findOne(testUser.getEmailAddress(), testUser.getLastName());
// Switch to secondary key
UserRepositoryConfiguration bean =
applicationContext.getBean(UserRepositoryConfiguration.class);
bean.switchToSecondaryKey();
// Now repository will use secondary key
repository.save(testUser);
}
}
```

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

@ -6,7 +6,7 @@
<groupId>com.microsoft.azure</groupId>
<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>
<description>Spring Data for Azure Cosmos DB SQL API</description>

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

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

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

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

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

@ -15,11 +15,13 @@
*/
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.DocumentDBConfig;
import com.microsoft.azure.spring.data.cosmosdb.repository.config.EnableDocumentDbRepositories;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@ -33,8 +35,23 @@ public class UserRepositoryConfiguration extends AbstractDocumentDbConfiguration
@Autowired
private DocumentDbProperties properties;
@Override
public DocumentDBConfig getConfig() {
return DocumentDBConfig.builder(properties.getUri(), properties.getKey(), properties.getDatabase()).build();
private CosmosKeyCredential cosmosKeyCredential;
@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;
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.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@ -50,6 +52,9 @@ public class UserRepositoryIntegrationTest {
@Autowired
private UserRepository repository;
@Autowired
private ApplicationContext applicationContext;
@Before
public void setup() {
this.repository.deleteAll();
@ -57,6 +62,11 @@ public class UserRepositoryIntegrationTest {
@After
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();
}
@ -119,5 +129,57 @@ public class UserRepositoryIntegrationTest {
result = resultList.get(0);
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>
<artifactId>spring-data-cosmosdb-samples</artifactId>
<version>2.0.5-SNAPSHOT</version>
<version>2.1.7</version>
<packaging>pom</packaging>
<parent>

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

@ -15,6 +15,7 @@ import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
import lombok.Getter;
import lombok.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
@ -48,12 +49,25 @@ public class DocumentDbFactory {
final String userAgent = getUserAgentSuffix() + ";" + policy.getUserAgentSuffix();
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());
}
private void validateConfig(@NonNull DocumentDBConfig config) {
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.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.microsoft.azure.documentdb.DocumentClient;
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.core.DocumentDbTemplate;
import com.microsoft.azure.spring.data.cosmosdb.core.ReactiveCosmosTemplate;
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@ -33,12 +35,23 @@ public abstract class AbstractDocumentDbConfiguration extends DocumentDbConfigur
return new DocumentDbFactory(config);
}
@Bean
public CosmosDbFactory cosmosDbFactory(DocumentDBConfig config) {
return new CosmosDbFactory(config);
}
@Bean
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());
}
@Bean
public ReactiveCosmosTemplate cosmosDbTemplate(DocumentDBConfig config) throws ClassNotFoundException {
return new ReactiveCosmosTemplate(this.cosmosDbFactory(config), this.mappingDocumentDbConverter(),
config.getDatabase());
}
@Bean
public MappingDocumentDbConverter mappingDocumentDbConverter() throws ClassNotFoundException {
return new MappingDocumentDbConverter(this.documentDbMappingContext(), objectMapper);

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

@ -6,23 +6,25 @@
package com.microsoft.azure.spring.data.cosmosdb.core;
import com.microsoft.azure.documentdb.*;
import com.microsoft.azure.documentdb.internal.HttpConstants;
import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory;
import com.microsoft.azure.spring.data.cosmosdb.common.CosmosdbUtils;
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
import com.azure.data.cosmos.CosmosClient;
import com.azure.data.cosmos.CosmosContainerResponse;
import com.azure.data.cosmos.CosmosItemProperties;
import com.azure.data.cosmos.CosmosItemRequestOptions;
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.generator.CountQueryGenerator;
import com.microsoft.azure.spring.data.cosmosdb.core.generator.FindQuerySpecGenerator;
import com.microsoft.azure.spring.data.cosmosdb.core.query.Criteria;
import com.microsoft.azure.spring.data.cosmosdb.core.query.CriteriaType;
import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentDbPageRequest;
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.repository.support.DocumentDbEntityInformation;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
@ -33,8 +35,9 @@ import org.springframework.data.domain.Pageable;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@ -43,28 +46,24 @@ import java.util.stream.Collectors;
@Slf4j
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 ReactiveCosmosTemplate reactiveCosmosTemplate;
private final String databaseName;
private Database databaseCache;
private List<String> collectionCache;
private final CosmosClient cosmosClient;
public DocumentDbTemplate(DocumentDbFactory documentDbFactory,
public DocumentDbTemplate(CosmosDbFactory cosmosDbFactory,
MappingDocumentDbConverter mappingDocumentDbConverter,
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!");
this.databaseName = dbName;
this.documentDbFactory = documentDbFactory;
this.documentClient = this.documentDbFactory.getDocumentClient();
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 {
@ -80,24 +79,30 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
Assert.hasText(collectionName, "collectionName should not be null, empty or only whitespaces");
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);
try {
final Resource result = getDocumentClient()
.createDocument(getCollectionLink(this.databaseName, collectionName), document,
getRequestOptions(partitionKey, null), false).getResource();
final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
options.partitionKey(toCosmosPartitionKey(partitionKey));
if (result instanceof Document) {
final Document documentInserted = (Document) result;
@SuppressWarnings("unchecked") final Class<T> domainClass = (Class<T>) objectToSave.getClass();
@SuppressWarnings("unchecked")
final Class<T> domainClass = (Class<T>) objectToSave.getClass();
return mappingDocumentDbConverter.read(domainClass, documentInserted);
} else {
return null;
final CosmosItemResponse response = cosmosClient.getDatabase(this.databaseName)
.getContainer(collectionName)
.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);
}
}
@ -108,37 +113,29 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
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) {
Assert.hasText(collectionName, "collectionName should not be null, empty or only whitespaces");
Assert.notNull(domainClass, "entityClass should not be null");
assertValidId(id);
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 Resource document = getDocumentClient().readDocument(documentLink, options).getResource();
if (document instanceof Document) {
return mappingDocumentDbConverter.read(domainClass, (Document) document);
} else {
return null;
}
} catch (DocumentClientException e) {
if (e.getStatusCode() == HttpConstants.StatusCodes.NOTFOUND) {
return null;
}
final String query = String.format("select * from root where root.id = '%s'", id.toString());
final FeedOptions options = new FeedOptions();
options.enableCrossPartitionQuery(true);
return cosmosClient
.getDatabase(databaseName)
.getContainer(collectionName)
.queryItems(query, options)
.flatMap(cosmosItemFeedResponse -> Mono.justOrEmpty(cosmosItemFeedResponse
.results()
.stream()
.map(cosmosItem -> mappingDocumentDbConverter.read(domainClass, cosmosItem))
.findFirst()))
.onErrorResume(Mono::error)
.blockFirst();
} catch (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");
try {
Document originalDoc;
if (object instanceof Document) {
originalDoc = (Document) object;
} else {
originalDoc = mappingDocumentDbConverter.writeDoc(object);
}
final CosmosItemProperties originalItem = mappingDocumentDbConverter.writeCosmosItemProperties(object);
log.debug("execute upsert document in database {} collection {}", this.databaseName, collectionName);
final String collectionLink = getCollectionSelfLink(collectionName);
final RequestOptions options = getRequestOptions(partitionKey, null);
final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
options.partitionKey(toCosmosPartitionKey(partitionKey));
getDocumentClient().upsertDocument(collectionLink, originalDoc, options, false);
} catch (DocumentClientException ex) {
final CosmosItemResponse cosmosItemResponse = cosmosClient.getDatabase(this.databaseName)
.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);
}
}
@ -184,9 +183,11 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
Assert.notNull(domainClass, "entityClass should not be null");
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) {
@ -200,13 +201,7 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
@Override
public void deleteCollection(@NonNull String collectionName) {
Assert.hasText(collectionName, "collectionName should have text.");
try {
getDocumentClient().deleteCollection(getCollectionLink(this.databaseName, collectionName), null);
this.collectionCache.remove(collectionName);
} catch (DocumentClientException ex) {
throw new DocumentDBAccessException("failed to delete collection: " + collectionName, ex);
}
reactiveCosmosTemplate.deleteContainer(collectionName);
}
public String getCollectionName(Class<?> domainClass) {
@ -215,98 +210,15 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
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
public DocumentCollection createCollectionIfNotExists(@NonNull DocumentDbEntityInformation information) {
if (this.databaseCache == null) {
this.databaseCache = createDatabaseIfNotExists(this.databaseName);
}
final String collectionName = information.getCollectionName();
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);
final CosmosContainerResponse response = reactiveCosmosTemplate
.createCollectionIfNotExists(information)
.block();
if (response == null) {
throw new DocumentDBAccessException("Failed to create collection");
}
return new DocumentCollection(response.properties().toJson());
}
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);
com.azure.data.cosmos.PartitionKey pk = toCosmosPartitionKey(partitionKey);
if (pk == null) {
pk = com.azure.data.cosmos.PartitionKey.None;
}
try {
final RequestOptions options = getRequestOptions(partitionKey, null);
getDocumentClient().deleteDocument(getDocumentLink(databaseName, collectionName, id.toString()), options);
} catch (DocumentClientException ex) {
throw new DocumentDBAccessException("deleteById exception", ex);
reactiveCosmosTemplate.deleteById(collectionName, id, pk).block();
} catch (Exception e) {
throw new DocumentDBAccessException("deleteById exception", e);
}
}
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
public <T, ID> List<T> findByIds(Iterable<ID> ids, Class<T> entityClass, String collectionName) {
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");
try {
final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generate(query);
final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
return this.executeQuery(sqlQuerySpec, isCrossPartitionQuery, domainClass, collectionName);
} catch (IllegalStateException | IllegalArgumentException e) {
return reactiveCosmosTemplate.find(query, domainClass, collectionName).collectList().block();
} catch (Exception 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;
}
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
* from the result.
@ -458,12 +283,14 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
Assert.notNull(domainClass, "domainClass should not be null.");
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);
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
@ -484,33 +311,42 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
final Pageable pageable = query.getPageable();
final FeedOptions feedOptions = new FeedOptions();
if (pageable instanceof DocumentDbPageRequest) {
feedOptions.setRequestContinuation(((DocumentDbPageRequest) pageable).getRequestContinuation());
feedOptions.requestContinuation(((DocumentDbPageRequest) pageable).getRequestContinuation());
}
feedOptions.setPageSize(pageable.getPageSize());
feedOptions.setEnableCrossPartitionQuery(query.isCrossPartitionQuery(getPartitionKeyNames(domainClass)));
feedOptions.maxItemCount(pageable.getPageSize());
feedOptions.enableCrossPartitionQuery(query.isCrossPartitionQuery(getPartitionKeyNames(domainClass)));
final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generate(query);
final FeedResponse<Document> response = executeQuery(sqlQuerySpec, feedOptions, collectionName);
final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generateCosmos(query);
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<>();
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();
if (doc == null) {
final CosmosItemProperties cosmosItemProperties = it.next();
if (cosmosItemProperties == null) {
continue;
}
final T entity = mappingDocumentDbConverter.read(domainClass, doc);
final T entity = mappingDocumentDbConverter.read(domainClass, cosmosItemProperties);
result.add(entity);
}
final DocumentDbPageRequest pageRequest = DocumentDbPageRequest.of(pageable.getPageNumber(),
pageable.getPageSize(),
response.getResponseContinuation(),
query.getSort());
pageable.getPageSize(),
feedResponse.continuationToken(),
query.getSort());
return new PageImpl<>(result, pageRequest, count(query, domainClass, collectionName));
}
@ -519,10 +355,11 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
public long count(String collectionName) {
Assert.hasText(collectionName, "collectionName should not be empty");
final DocumentQuery query = new DocumentQuery(Criteria.getInstance(CriteriaType.ALL));
final SqlQuerySpec querySpec = new CountQueryGenerator().generate(query);
return getCountValue(querySpec, true, collectionName);
final Long count = reactiveCosmosTemplate.count(collectionName).block();
if (count == null) {
throw new DocumentDBAccessException("Failed to get count for collectionName: " + collectionName);
}
return count;
}
@Override
@ -530,27 +367,12 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
Assert.notNull(domainClass, "domainClass should not be null");
Assert.hasText(collectionName, "collectionName should not be empty");
final SqlQuerySpec querySpec = new CountQueryGenerator().generate(query);
final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
return getCountValue(querySpec, isCrossPartitionQuery, 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);
final Long count = reactiveCosmosTemplate.count(query, isCrossPartitionQuery, collectionName).block();
if (count == null) {
throw new DocumentDBAccessException("Failed to get count for collectionName: " + collectionName);
}
}
private String getCollectionSelfLink(@NonNull String collectionName) {
return String.format("dbs/%s/colls/%s", this.databaseName, collectionName);
return count;
}
@Override
@ -569,10 +391,59 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
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) {
Assert.notNull(id, "id should not be null");
if (id instanceof String) {
Assert.hasText(id.toString(), "id should not be empty or only whitespaces.");
}
}
private List<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.exception.DocumentDBAccessException;
import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@ -39,16 +36,13 @@ import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, ApplicationContextAware {
private static final String COUNT_VALUE_KEY = "_aggregate";
public static final int DEFAULT_THROUGHPUT = 400;
private final String databaseName;
@Getter(AccessLevel.PRIVATE)
private final CosmosClient cosmosClient;
private final List<String> collectionCache;
@ -89,14 +83,12 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
@Override
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
.createDatabaseIfNotExists(this.databaseName)
.flatMap(cosmosDatabaseResponse -> cosmosDatabaseResponse
.database()
.createContainerIfNotExists(information.getCollectionName(),
"/" + information.getPartitionKeyFieldName(), DEFAULT_THROUGHPUT)
"/" + information.getPartitionKeyFieldName())
.map(cosmosContainerResponse -> {
this.collectionCache.add(information.getCollectionName());
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 FeedOptions options = new FeedOptions();
options.enableCrossPartitionQuery(true);
return getCosmosClient().getDatabase(databaseName)
return cosmosClient.getDatabase(databaseName)
.getContainer(containerName)
.queryItems(query, options)
.flatMap(cosmosItemFeedResponse -> Mono.just(cosmosItemFeedResponse
.flatMap(cosmosItemFeedResponse -> Mono.justOrEmpty(cosmosItemFeedResponse
.results()
.stream()
.map(cosmosItem -> cosmosItem.toObject(entityClass))
.collect(Collectors.toList()).get(0)))
.findFirst()))
.onErrorResume(Mono::error)
.next();
}
@ -193,10 +186,10 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
Assert.notNull(objectToSave, "objectToSave should not be null");
final Class<T> domainClass = (Class<T>) objectToSave.getClass();
return getCosmosClient().getDatabase(this.databaseName)
return cosmosClient.getDatabase(this.databaseName)
.getContainer(getContainerName(objectToSave.getClass()))
.createItem(objectToSave, new CosmosItemRequestOptions())
.doOnError(Mono::error)
.onErrorResume(Mono::error)
.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();
return getCosmosClient().getDatabase(this.databaseName)
return cosmosClient.getDatabase(this.databaseName)
.getContainer(containerName)
.createItem(objectToSave, options)
.onErrorResume(Mono::error)
@ -253,10 +246,11 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
options.partitionKey(partitionKey);
}
return getCosmosClient().getDatabase(this.databaseName)
return cosmosClient.getDatabase(this.databaseName)
.getContainer(containerName)
.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);
Assert.notNull(partitionKey, "partitionKey should not be null");
final JSONObject jo = new JSONObject();
jo.put("id", id.toString());
final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
options.partitionKey(partitionKey);
return getCosmosClient().getDatabase(this.databaseName)
return cosmosClient.getDatabase(this.databaseName)
.getContainer(containerName)
.getItem(id.toString(), partitionKey)
.delete(options)
.onErrorResume(throwable -> Mono.empty())
.onErrorResume(Mono::error)
.then();
}
@ -303,13 +295,13 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
final FeedOptions options = new FeedOptions();
final boolean isCrossPartitionQuery = query.isCrossPartitionQuery(Collections.singletonList(partitionKeyName));
options.enableCrossPartitionQuery(isCrossPartitionQuery);
return getCosmosClient().getDatabase(this.databaseName)
return cosmosClient.getDatabase(this.databaseName)
.getContainer(containerName)
.queryItems(sqlQuerySpec, options)
.onErrorResume(this::databaseAccessExceptionHandler)
.map(cosmosItemFeedResponse -> cosmosItemFeedResponse.results()
.stream()
.map(cosmosItemProperties -> getCosmosClient()
.map(cosmosItemProperties -> cosmosClient
.getDatabase(this.databaseName)
.getContainer(containerName)
.getItem(cosmosItemProperties.id(), partitionKeyName)
@ -363,7 +355,7 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
*/
@Override
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
public Mono<Long> count(String containerName) {
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
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) {
@ -417,7 +413,7 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
private Flux<FeedResponse<CosmosItemProperties>> executeQuery(SqlQuerySpec sqlQuerySpec, String collectionName,
FeedOptions options) {
return getCosmosClient().getDatabase(this.databaseName)
return cosmosClient.getDatabase(this.databaseName)
.getContainer(collectionName)
.queryItems(sqlQuerySpec, options);
}
@ -435,7 +431,7 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
public void deleteContainer(@NonNull String containerName) {
Assert.hasText(containerName, "containerName should have text.");
try {
getCosmosClient().getDatabase(this.databaseName).getContainer(containerName).delete().block();
cosmosClient.getDatabase(this.databaseName).getContainer(containerName).delete().block();
this.collectionCache.remove(containerName);
} catch (Exception 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 FeedOptions feedOptions = new FeedOptions();
feedOptions.enableCrossPartitionQuery(isCrossPartitionQuery);
return getCosmosClient()
return cosmosClient
.getDatabase(this.databaseName)
.getContainer(containerName)
.queryItems(sqlQuerySpec, feedOptions)
@ -482,9 +478,9 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
return Collections.singletonList(entityInfo.getPartitionKeyFieldName());
}
private Mono<Void> deleteDocument(@NonNull CosmosItemProperties cosmosItemProperties,
@NonNull List<String> partitionKeyNames,
String containerName) {
private Mono<CosmosItemProperties> deleteDocument(@NonNull CosmosItemProperties cosmosItemProperties,
@NonNull List<String> partitionKeyNames,
String containerName) {
Assert.isTrue(partitionKeyNames.size() <= 1, "Only one Partition is supported.");
PartitionKey partitionKey = null;
@ -495,12 +491,12 @@ public class ReactiveCosmosTemplate implements ReactiveCosmosOperations, Applica
final CosmosItemRequestOptions options = new CosmosItemRequestOptions(partitionKey);
return getCosmosClient()
return cosmosClient
.getDatabase(this.databaseName)
.getContainer(containerName)
.getItem(cosmosItemProperties.id(), partitionKey)
.delete(options)
.then();
.map(cosmosItemResponse -> cosmosItemProperties);
}
}

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

@ -5,6 +5,7 @@
*/
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.databind.DeserializationFeature;
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;
public class MappingDocumentDbConverter
implements EntityConverter<DocumentDbPersistentEntity<?>, DocumentDbPersistentProperty, Object, Document>,
ApplicationContextAware {
implements EntityConverter<DocumentDbPersistentEntity<?>, DocumentDbPersistentProperty,
Object, CosmosItemProperties>,
ApplicationContextAware {
protected final MappingContext<? extends DocumentDbPersistentEntity<?>,
DocumentDbPersistentProperty> mappingContext;
DocumentDbPersistentProperty> mappingContext;
protected GenericConversionService conversionService;
private ApplicationContext applicationContext;
private ObjectMapper objectMapper;
public MappingDocumentDbConverter(
MappingContext<? extends DocumentDbPersistentEntity<?>, DocumentDbPersistentProperty> mappingContext,
@Qualifier(Constants.OBJECTMAPPER_BEAN_NAME) ObjectMapper objectMapper) {
MappingContext<? extends DocumentDbPersistentEntity<?>, DocumentDbPersistentProperty> mappingContext,
@Qualifier(Constants.OBJECTMAPPER_BEAN_NAME) ObjectMapper objectMapper) {
this.mappingContext = mappingContext;
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
public <R extends Object> R read(Class<R> type, Document sourceDocument) {
if (sourceDocument == null) {
public <R> R read(Class<R> type, CosmosItemProperties cosmosItemProperties) {
if (cosmosItemProperties == null) {
return null;
}
final DocumentDbPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
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,
final Document sourceDocument) {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.registerModule(provideAdvancedSerializersModule());
private <R> R readInternal(final DocumentDbPersistentEntity<?> entity, Class<R> type,
final CosmosItemProperties cosmosItemProperties) {
try {
final DocumentDbPersistentProperty idProperty = entity.getIdProperty();
final Object idValue = sourceDocument.getId();
final JSONObject jsonObject = new JSONObject(sourceDocument.toJson());
final Object idValue = cosmosItemProperties.id();
final JSONObject jsonObject = new JSONObject(cosmosItemProperties.toJson());
if (idProperty != null) {
// 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);
} catch (IOException e) {
throw new IllegalStateException("Failed to read the source document " + sourceDocument.toJson()
+ " to target type " + type, e);
throw new IllegalStateException("Failed to read the source document " + cosmosItemProperties.toJson()
+ " to target type " + type, e);
}
}
@ -95,7 +98,7 @@ public class MappingDocumentDbConverter
@Override
@Deprecated
public void write(Object sourceEntity, Document document) {
public void write(Object sourceEntity, CosmosItemProperties document) {
throw new UnsupportedOperationException("The feature is not implemented yet");
}
@ -105,7 +108,7 @@ public class MappingDocumentDbConverter
}
final DocumentDbPersistentEntity<?> persistentEntity =
mappingContext.getPersistentEntity(sourceEntity.getClass());
mappingContext.getPersistentEntity(sourceEntity.getClass());
if (persistentEntity == null) {
throw new MappingException("no mapping metadata for entity type: " + sourceEntity.getClass().getName());
@ -130,6 +133,38 @@ public class MappingDocumentDbConverter
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() {
return this.applicationContext;
}
@ -150,7 +185,8 @@ public class MappingDocumentDbConverter
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.");
final PersistentPropertyAccessor accessor = entityInformation.getPropertyAccessor(entity);

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

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

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

@ -8,7 +8,7 @@ package com.microsoft.azure.spring.data.cosmosdb.core;
import com.fasterxml.jackson.databind.ObjectMapper;
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.config.DocumentDBConfig;
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.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
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.TestConstants.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
@RunWith(SpringJUnit4ClassRunner.class)
@PropertySource(value = {"classpath:application.properties"})
@ -77,7 +77,7 @@ public class DocumentDbTemplateIT {
@Before
public void setup() throws ClassNotFoundException {
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();
objectMapper = new ObjectMapper();
@ -87,7 +87,7 @@ public class DocumentDbTemplateIT {
mappingContext.setInitialEntitySet(new EntityScanner(this.applicationContext).scan(Persistent.class));
dbConverter = new MappingDocumentDbConverter(mappingContext, objectMapper);
dbTemplate = new DocumentDbTemplate(dbFactory, dbConverter, DB_NAME);
dbTemplate = new DocumentDbTemplate(cosmosDbFactory, dbConverter, DB_NAME);
collectionPerson = dbTemplate.createCollectionIfNotExists(this.personInfo);
dbTemplate.insert(Person.class.getSimpleName(), TEST_PERSON, null);
@ -114,7 +114,7 @@ public class DocumentDbTemplateIT {
public void testFindById() {
final Person result = dbTemplate.findById(Person.class.getSimpleName(),
TEST_PERSON.getId(), Person.class);
assertTrue(result.equals(TEST_PERSON));
assertEquals(result, TEST_PERSON);
final Person nullResult = dbTemplate.findById(Person.class.getSimpleName(),
TestConstants.NOT_EXIST_ID, Person.class);
@ -140,14 +140,14 @@ public class DocumentDbTemplateIT {
dbTemplate.deleteById(Person.class.getSimpleName(), TEST_PERSON.getId(), null);
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);
final List<Person> result = dbTemplate.findAll(Person.class);
assertThat(result.size()).isEqualTo(1);
assertTrue(result.get(0).getFirstName().equals(firstName));
assertEquals(result.get(0).getFirstName(), firstName);
}
@Test
@ -159,7 +159,7 @@ public class DocumentDbTemplateIT {
final Person result = dbTemplate.findById(Person.class.getSimpleName(),
updated.getId(), Person.class);
assertTrue(result.equals(updated));
assertEquals(result, updated);
}
@Test
@ -171,7 +171,7 @@ public class DocumentDbTemplateIT {
final List<Person> result = dbTemplate.findAll(Person.class);
assertThat(result.size()).isEqualTo(1);
assertTrue(result.get(0).equals(TEST_PERSON_2));
assertEquals(result.get(0), TEST_PERSON_2);
}
@Test
@ -190,7 +190,7 @@ public class DocumentDbTemplateIT {
dbTemplate.insert(TEST_PERSON_2, null);
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 long count = dbTemplate.count(query, Person.class, collectionName);
@ -217,7 +217,7 @@ public class DocumentDbTemplateIT {
dbTemplate.insert(TEST_PERSON_2, null);
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 DocumentQuery query = new DocumentQuery(criteria).with(pageRequest);

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

@ -6,10 +6,6 @@
package com.microsoft.azure.spring.data.cosmosdb.core;
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.DocumentQuery;
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.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.util.Assert;
@ -37,16 +34,12 @@ public class DocumentDbTemplateIllegalTest {
private static final String WHITESPACES_STR = " ";
private static final String CHECK_FAILURE_MSG = "Illegal argument is not checked";
@Mock(answer = Answers.CALLS_REAL_METHODS)
private DocumentDbTemplate dbTemplate;
private Class dbTemplateClass;
@Mock
MappingDocumentDbConverter dbConverter;
@Before
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();
}

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

@ -8,7 +8,7 @@ package com.microsoft.azure.spring.data.cosmosdb.core;
import com.fasterxml.jackson.databind.ObjectMapper;
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.core.convert.MappingDocumentDbConverter;
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentDbMappingContext;
@ -68,7 +68,7 @@ public class DocumentDbTemplatePartitionIT {
@Before
public void setup() throws ClassNotFoundException {
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 DocumentDbMappingContext mappingContext = new DocumentDbMappingContext();
@ -77,7 +77,7 @@ public class DocumentDbTemplatePartitionIT {
final MappingDocumentDbConverter dbConverter = new MappingDocumentDbConverter(mappingContext, objectMapper);
dbTemplate = new DocumentDbTemplate(dbFactory, dbConverter, DB_NAME);
dbTemplate = new DocumentDbTemplate(cosmosDbFactory, dbConverter, DB_NAME);
collectionName = personInfo.getCollectionName();
dbTemplate.createCollectionIfNotExists(personInfo);
@ -121,7 +121,9 @@ public class DocumentDbTemplatePartitionIT {
@Test
public void testUpsertNewDocumentPartition() {
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();
dbTemplate.upsert(PartitionPerson.class.getSimpleName(), newPerson, new PartitionKey(partitionKeyValue));

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

@ -6,7 +6,7 @@
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.config.DocumentDBConfig;
import org.junit.Test;
@ -19,8 +19,8 @@ public class DocumentDbTemplateUnitTest {
@Test(expected = IllegalArgumentException.class)
public void rejectNullDbFactory() {
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() {
final Mono<Person> findById = cosmosTemplate.findById(Person.class.getSimpleName(), TEST_PERSON.getId(),
Person.class);
StepVerifier.create(findById).consumeNextWith(actual -> {
Assert.assertThat(actual.getFirstName(), is(equalTo(TEST_PERSON.getFirstName())));
Assert.assertThat(actual.getLastName(), is(equalTo(TEST_PERSON.getLastName())));
}).verifyComplete();
StepVerifier.create(findById)
.consumeNextWith(actual -> Assert.assertEquals(actual, TEST_PERSON))
.verifyComplete();
}
@Test
@ -143,10 +142,8 @@ public class ReactiveCosmosTemplateIT {
@Test
public void testFindByIdWithContainerName() {
StepVerifier.create(cosmosTemplate.findById(Person.class.getSimpleName(), TEST_PERSON.getId(), Person.class))
.consumeNextWith(actual -> {
Assert.assertThat(actual.getFirstName(), is(equalTo(TEST_PERSON.getFirstName())));
Assert.assertThat(actual.getLastName(), is(equalTo(TEST_PERSON.getLastName())));
}).verifyComplete();
.consumeNextWith(actual -> Assert.assertEquals(actual, TEST_PERSON))
.verifyComplete();
}
@Test
@ -202,18 +199,26 @@ public class ReactiveCosmosTemplateIT {
@Test
public void testDeleteById() {
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(),
new PartitionKey(TEST_PERSON_4.getId()));
PartitionKey.None);
StepVerifier.create(voidMono).verifyComplete();
flux = cosmosTemplate.findAll(Person.class.getSimpleName(), Person.class);
StepVerifier.create(flux).expectNextCount(1).verifyComplete();
}
@Test
public void testDeleteByIdBySecondaryKey() {
cosmosKeyCredential.key(documentDbSecondaryKey);
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(),
new PartitionKey(TEST_PERSON_4.getId()));
PartitionKey.None);
StepVerifier.create(voidMono).verifyComplete();
flux = cosmosTemplate.findAll(Person.class.getSimpleName(), Person.class);
StepVerifier.create(flux).expectNextCount(1).verifyComplete();
}
@Test

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

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

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

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

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

@ -15,6 +15,7 @@ import org.assertj.core.util.Lists;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@ -156,6 +157,7 @@ public class ContactRepositoryIT {
}
@Test
@Ignore // TODO(kuthapar): v3 doesn't support creation of items without id.
public void testNullIdContact() {
final Contact nullIdContact = new Contact(null, "testTitile");
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.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.TestUtils;
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
@ -62,7 +62,7 @@ public class DocumentDBAnnotationIT {
@Before
public void setUp() throws ClassNotFoundException {
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);
sampleInfo = new DocumentDbEntityInformation<>(TimeToLiveSample.class);
@ -73,7 +73,7 @@ public class DocumentDBAnnotationIT {
mappingConverter = new MappingDocumentDbConverter(dbContext, objectMapper);
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);
collectionExample = dbTemplate.createCollectionIfNotExists(sampleInfo);
@ -103,6 +103,7 @@ public class DocumentDBAnnotationIT {
@Test
@SneakyThrows
@Ignore // TODO(kuthapar): time to live is not supported by v3 SDK.
public void testDocumentAnnotationTimeToLive() {
final TimeToLiveSample sample = new TimeToLiveSample(TestConstants.ID_1);
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.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.repository.ProjectRepository;
import com.microsoft.azure.spring.data.cosmosdb.repository.repository.QuestionRepository;
@ -72,9 +71,10 @@ public class QuestionRepositoryIT {
Assert.assertEquals(QUESTION, optional.get());
}
@Test(expected = DocumentDBAccessException.class)
public void testFindByIdException() {
this.repository.findById(QUESTION_URL);
@Test
public void testFindByIdNull() {
final Optional<Question> byId = this.repository.findById(QUESTION_URL);
Assert.assertFalse(byId.isPresent());
}
@Test