diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00a9c64 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# idea files +/.idea/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# Eclipse project file +*.project + +# Maven autogenerated classpath +*.classpath + +#VSCode Directories +/*.settings/ +/*.vscode/ +/target/ + +*.iml diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f9ba8cf --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9e841e7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/README.md b/README.md new file mode 100644 index 0000000..4ad3657 --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +--- +page_type: sample +languages: +- java +products: +- java sdk +description: "Sample code repo for Azure Cosmos DB Java SDK for SQL API" +urlFragment: "" +--- + +# Azure Cosmos DB Java SQL API Samples + + + +Sample code repo for Azure Cosmos DB Java SDK for SQL API. By cloning and running these samples, and then studying their implementations, you will have an example for sending various requests to Azure Cosmos DB from Java SDK via the SQL API. + +## Contents + +| File/folder | Description | +|-------------------|--------------------------------------------| +| `src` | Java sample source code. Many samples have 'sync' and 'async' variants | +| `.gitignore` | Define what to ignore at commit time. | +| `CHANGELOG.md` | List of changes to the sample. | +| `CONTRIBUTING.md` | Guidelines for contributing to the sample. | +| `README.md` | This README file. | +| `LICENSE` | The license for the sample. | +| `pom.xml` | Maven Project Object Model File + +## Prerequisites + +* Maven +* Java SE JRE 8 +* Setting up an Azure Cosmos DB account through the Azure Portal. The **Create a database account** section of [this guide](https://docs.microsoft.com/en-us/azure/cosmos-db/create-sql-api-java) walks you through account creation. +* The hostname and master key for your Azure Cosmos DB account + +## Setup + +Clone the sample to your PC. Using your Java IDE, open pom.xml as a Maven project. + +## Running the sample + +These environment variables must be set + +``` +ACCOUNT_HOST=your account hostname;ACCOUNT_KEY=your account master key +``` + +in order to give the samples read/write access to your account. + +To run a sample, specify its Main Class + +``` +com.azure.cosmos.examples.sample.synchronicity.MainClass +``` + +where *sample.synchronicity.MainClass* can be +* crudquickstart.sync.SampleCRUDQuickstart +* crudquickstart.async.SampleCRUDQuickstartAsync +* indexmanagement.sync.SampleIndexManagement +* indexmanagement.async.SampleIndexManagementAsync +* storedprocedure.sync.SampleStoredProcedure +* storedprocedure.async.SampleStoredProcedureAsync +* changefeed.SampleChangeFeedProcessor *(Changefeed has only an async sample, no sync sample.)* + +*Build and execute from command line without an IDE:* From top-level directory of repo: +``` +mvn clean package +mvn exec:java -Dexec.mainClass="com.azure.cosmos.examples.changefeed.sample" -DACCOUNT_HOST=your account hostname -DACCOUNT_KEY=your account master key +``` + +where *sample*, *your account hostname*, and *your account master key* are to be filled in as above. This will rebuild and run the selected sample. + +## Key concepts + +These samples cover a range of Azure Cosmos DB usage topics from more to less basic: +* Basic management of databases, containers and items +* Indexing, stored procedures +* Change Feed + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e0dfff5 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..69ece04 --- /dev/null +++ b/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.azure + azure-cosmos-java-sql-api-samples + 1.0-SNAPSHOT + Get Started With Sync / Async Java SDK for SQL API of Azure Cosmos DB Database Service + + + UTF-8 + + + + + + + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.8 + + + + org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8 + + + + + + + + + com.azure + azure-cosmos + 4.0.1-beta.1 + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.13.0 + test + + + + org.apache.logging.log4j + log4j-api + 2.11.1 + test + + + + org.slf4j + slf4j-jdk14 + 1.7.28 + + + \ No newline at end of file diff --git a/src/main/java/com/azure/cosmos/examples/changefeed/SampleChangeFeedProcessor.java b/src/main/java/com/azure/cosmos/examples/changefeed/SampleChangeFeedProcessor.java new file mode 100644 index 0000000..5c07fea --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/changefeed/SampleChangeFeedProcessor.java @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.cosmos.examples.changefeed; + +import com.azure.cosmos.ChangeFeedProcessor; +import com.azure.cosmos.ConnectionPolicy; +import com.azure.cosmos.ConsistencyLevel; +import com.azure.cosmos.CosmosAsyncClient; +import com.azure.cosmos.CosmosAsyncContainer; +import com.azure.cosmos.CosmosAsyncDatabase; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.CosmosClientException; +import com.azure.cosmos.examples.common.CustomPOJO; +import com.azure.cosmos.implementation.Utils; +import com.azure.cosmos.models.CosmosAsyncContainerResponse; +import com.azure.cosmos.models.CosmosContainerProperties; +import com.azure.cosmos.models.CosmosContainerRequestOptions; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.RandomStringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.scheduler.Schedulers; + +import java.time.Duration; +import java.util.List; + +/** + * Sample for Change Feed Processor. + * This sample models an application where documents are being inserted into one container (the "feed container"), + * and meanwhile another worker thread or worker application is pulling inserted documents from the feed container's Change Feed + * and operating on them in some way. For one or more workers to process the Change Feed of a container, the workers must first contact the server + * and "lease" access to monitor one or more partitions of the feed container. The Change Feed Processor Library + * handles leasing automatically for you, however you must create a separate "lease container" where the Change Feed + * Processor Library can store and track leases container partitions. + */ +public class SampleChangeFeedProcessor { + + public static int WAIT_FOR_WORK = 60000; + public static final String DATABASE_NAME = "db_" + RandomStringUtils.randomAlphabetic(7); + public static final String COLLECTION_NAME = "coll_" + RandomStringUtils.randomAlphabetic(7); + private static final ObjectMapper OBJECT_MAPPER = Utils.getSimpleObjectMapper(); + protected static Logger logger = LoggerFactory.getLogger(SampleChangeFeedProcessor.class.getSimpleName()); + + + private static ChangeFeedProcessor changeFeedProcessorInstance; + private static boolean isWorkCompleted = false; + + public static void main(String[] args) { + logger.info("BEGIN Sample"); + + try { + + //Summary of the next four commands: + //-Create an asynchronous Azure Cosmos DB client and database so that we can issue async requests to the DB + //-Create a "feed container" and a "lease container" in the DB + logger.info("-->CREATE DocumentClient"); + CosmosAsyncClient client = getCosmosClient(); + + logger.info("-->CREATE sample's database: " + DATABASE_NAME); + CosmosAsyncDatabase cosmosDatabase = createNewDatabase(client, DATABASE_NAME); + + logger.info("-->CREATE container for documents: " + COLLECTION_NAME); + CosmosAsyncContainer feedContainer = createNewCollection(client, DATABASE_NAME, COLLECTION_NAME); + + logger.info("-->CREATE container for lease: " + COLLECTION_NAME + "-leases"); + CosmosAsyncContainer leaseContainer = createNewLeaseCollection(client, DATABASE_NAME, COLLECTION_NAME + "-leases"); + + //Model of a worker thread or application which leases access to monitor one or more feed container + //partitions via the Change Feed. In a real-world application you might deploy this code in an Azure function. + //The next line causes the worker to create and start an instance of the Change Feed Processor. See the implementation of getChangeFeedProcessor() for guidance + //on creating a handler for Change Feed events. In this stream, we also trigger the insertion of 10 documents on a separate + //thread. + logger.info("-->START Change Feed Processor on worker (handles changes asynchronously)"); + changeFeedProcessorInstance = getChangeFeedProcessor("SampleHost_1", feedContainer, leaseContainer); + changeFeedProcessorInstance.start() + .subscribeOn(Schedulers.elastic()) + .doOnSuccess(aVoid -> { + //pass + }) + .subscribe(); + + //These two lines model an application which is inserting ten documents into the feed container + logger.info("-->START application that inserts documents into feed container"); + createNewDocumentsCustomPOJO(feedContainer, 10, Duration.ofSeconds(3)); + isWorkCompleted = true; + + //This loop models the Worker main loop, which spins while its Change Feed Processor instance asynchronously + //handles incoming Change Feed events from the feed container. Of course in this sample, polling + //isWorkCompleted is unnecessary because items are being added to the feed container on the same thread, and you + //can see just above isWorkCompleted is set to true. + //But conceptually the worker is part of a different thread or application than the one which is inserting + //into the feed container; so this code illustrates the worker waiting and listening for changes to the feed container + long remainingWork = WAIT_FOR_WORK; + while (!isWorkCompleted && remainingWork > 0) { + Thread.sleep(100); + remainingWork -= 100; + } + + //When all documents have been processed, clean up + if (isWorkCompleted) { + if (changeFeedProcessorInstance != null) { + changeFeedProcessorInstance.stop().subscribe(); + } + } else { + throw new RuntimeException("The change feed processor initialization and automatic create document feeding process did not complete in the expected time"); + } + + logger.info("-->DELETE sample's database: " + DATABASE_NAME); + deleteDatabase(cosmosDatabase); + + Thread.sleep(500); + + } catch (Exception e) { + e.printStackTrace(); + } + + logger.info("END Sample"); + } + + public static ChangeFeedProcessor getChangeFeedProcessor(String hostName, CosmosAsyncContainer feedContainer, CosmosAsyncContainer leaseContainer) { + return ChangeFeedProcessor.changeFeedProcessorBuilder() + .setHostName(hostName) + .setFeedContainer(feedContainer) + .setLeaseContainer(leaseContainer) + .setHandleChanges((List docs) -> { + logger.info("--->setHandleChanges() START"); + + for (JsonNode document : docs) { + try { + //Change Feed hands the document to you in the form of a JsonNode + //As a developer you have two options for handling the JsonNode document provided to you by Change Feed + //One option is to operate on the document in the form of a JsonNode, as shown below. This is great + //especially if you do not have a single uniform data model for all documents. + logger.info("---->DOCUMENT RECEIVED: " + OBJECT_MAPPER.writerWithDefaultPrettyPrinter() + .writeValueAsString(document)); + + //You can also transform the JsonNode to a POJO having the same structure as the JsonNode, + //as shown below. Then you can operate on the POJO. + CustomPOJO pojo_doc = OBJECT_MAPPER.treeToValue(document, CustomPOJO.class); + logger.info("----=>id: " + pojo_doc.getId()); + + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + logger.info("--->handleChanges() END"); + + }) + .build(); + } + + public static CosmosAsyncClient getCosmosClient() { + + return new CosmosClientBuilder() + .setEndpoint(SampleConfigurations.HOST) + .setKey(SampleConfigurations.MASTER_KEY) + .setConnectionPolicy(ConnectionPolicy.getDefaultPolicy()) + .setConsistencyLevel(ConsistencyLevel.EVENTUAL) + .buildAsyncClient(); + } + + public static CosmosAsyncDatabase createNewDatabase(CosmosAsyncClient client, String databaseName) { + return client.createDatabaseIfNotExists(databaseName).block().getDatabase(); + } + + public static void deleteDatabase(CosmosAsyncDatabase cosmosDatabase) { + cosmosDatabase.delete().block(); + } + + public static CosmosAsyncContainer createNewCollection(CosmosAsyncClient client, String databaseName, String collectionName) { + CosmosAsyncDatabase databaseLink = client.getDatabase(databaseName); + CosmosAsyncContainer collectionLink = databaseLink.getContainer(collectionName); + CosmosAsyncContainerResponse containerResponse = null; + + try { + containerResponse = collectionLink.read().block(); + + if (containerResponse != null) { + throw new IllegalArgumentException(String.format("Collection %s already exists in database %s.", collectionName, databaseName)); + } + } catch (RuntimeException ex) { + if (ex instanceof CosmosClientException) { + CosmosClientException cosmosClientException = (CosmosClientException) ex; + + if (cosmosClientException.getStatusCode() != 404) { + throw ex; + } + } else { + throw ex; + } + } + + CosmosContainerProperties containerSettings = new CosmosContainerProperties(collectionName, "/id"); + CosmosContainerRequestOptions requestOptions = new CosmosContainerRequestOptions(); + containerResponse = databaseLink.createContainer(containerSettings, 10000, requestOptions).block(); + + if (containerResponse == null) { + throw new RuntimeException(String.format("Failed to create collection %s in database %s.", collectionName, databaseName)); + } + + return containerResponse.getContainer(); + } + + public static CosmosAsyncContainer createNewLeaseCollection(CosmosAsyncClient client, String databaseName, String leaseCollectionName) { + CosmosAsyncDatabase databaseLink = client.getDatabase(databaseName); + CosmosAsyncContainer leaseCollectionLink = databaseLink.getContainer(leaseCollectionName); + CosmosAsyncContainerResponse leaseContainerResponse = null; + + try { + leaseContainerResponse = leaseCollectionLink.read().block(); + + if (leaseContainerResponse != null) { + leaseCollectionLink.delete().block(); + + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + } catch (RuntimeException ex) { + if (ex instanceof CosmosClientException) { + CosmosClientException cosmosClientException = (CosmosClientException) ex; + + if (cosmosClientException.getStatusCode() != 404) { + throw ex; + } + } else { + throw ex; + } + } + + CosmosContainerProperties containerSettings = new CosmosContainerProperties(leaseCollectionName, "/id"); + CosmosContainerRequestOptions requestOptions = new CosmosContainerRequestOptions(); + + leaseContainerResponse = databaseLink.createContainer(containerSettings, 400, requestOptions).block(); + + if (leaseContainerResponse == null) { + throw new RuntimeException(String.format("Failed to create collection %s in database %s.", leaseCollectionName, databaseName)); + } + + return leaseContainerResponse.getContainer(); + } + + public static void createNewDocumentsCustomPOJO(CosmosAsyncContainer containerClient, int count, Duration delay) { + String suffix = RandomStringUtils.randomAlphabetic(10); + for (int i = 0; i <= count; i++) { + CustomPOJO document = new CustomPOJO(); + document.setId(String.format("0%d-%s", i, suffix)); + + containerClient.createItem(document).subscribe(doc -> { + logger.info("---->DOCUMENT WRITE: " + doc); + }); + + long remainingWork = delay.toMillis(); + try { + while (remainingWork > 0) { + Thread.sleep(100); + remainingWork -= 100; + } + } catch (InterruptedException iex) { + // exception caught + break; + } + } + } +} diff --git a/src/main/java/com/azure/cosmos/examples/changefeed/SampleConfigurations.java b/src/main/java/com/azure/cosmos/examples/changefeed/SampleConfigurations.java new file mode 100644 index 0000000..f373f7e --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/changefeed/SampleConfigurations.java @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.cosmos.examples.changefeed; + +import com.google.common.base.Strings; +import org.apache.commons.lang3.StringUtils; + +/** + * Contains the configurations for tests. + *

+ * For running tests, you can pass a customized endpoint configuration in one of the following + * ways: + *

+ *

+ * If none of the above is set, emulator endpoint will be used. + */ +public final class SampleConfigurations { + // REPLACE MASTER_KEY and HOST with values from your Azure Cosmos DB account. + // The default values are credentials of the local emulator, which are not used in any production environment. + // + public static String MASTER_KEY = + System.getProperty("ACCOUNT_KEY", + StringUtils.defaultString(Strings.emptyToNull( + System.getenv().get("ACCOUNT_KEY")), + "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==")); + + public static String HOST = + System.getProperty("ACCOUNT_HOST", + StringUtils.defaultString(Strings.emptyToNull( + System.getenv().get("ACCOUNT_HOST")), + "https://localhost:8081/")); +} diff --git a/src/main/java/com/azure/cosmos/examples/common/AccountSettings.java b/src/main/java/com/azure/cosmos/examples/common/AccountSettings.java new file mode 100644 index 0000000..64a7776 --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/common/AccountSettings.java @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.common; + +import org.apache.commons.lang3.StringUtils; + +/** + * Contains the account configurations for Sample. + *

+ * For running tests, you can pass a customized endpoint configuration in one of the following + * ways: + *

+ *

+ * If none of the above is set, emulator endpoint will be used. + * Emulator http cert is self signed. If you are using emulator, + * make sure emulator https certificate is imported + * to java trusted cert store: + * https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator-export-ssl-certificates + */ +public class AccountSettings { + // Replace MASTER_KEY and HOST with values from your Azure Cosmos DB account. + // The default values are credentials of the local emulator, which are not used in any production environment. + // + public static String MASTER_KEY = + System.getProperty("ACCOUNT_KEY", + StringUtils.defaultString(StringUtils.trimToNull( + System.getenv().get("ACCOUNT_KEY")), + "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==")); + + public static String HOST = + System.getProperty("ACCOUNT_HOST", + StringUtils.defaultString(StringUtils.trimToNull( + System.getenv().get("ACCOUNT_HOST")), + "https://localhost:443/")); +} diff --git a/src/main/java/com/azure/cosmos/examples/common/Address.java b/src/main/java/com/azure/cosmos/examples/common/Address.java new file mode 100644 index 0000000..ec7d5b3 --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/common/Address.java @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.common; + +public class Address { + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getCounty() { + return county; + } + + public void setCounty(String county) { + this.county = county; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + private String state; + private String county; + private String city; +} + diff --git a/src/main/java/com/azure/cosmos/examples/common/Child.java b/src/main/java/com/azure/cosmos/examples/common/Child.java new file mode 100644 index 0000000..98cdd5c --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/common/Child.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.common; + +public class Child { + public String getFamilyName() { + return familyName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + + public int getGrade() { + return grade; + } + + public void setGrade(int grade) { + this.grade = grade; + } + + public Pet[] getPets() { + return pets; + } + + public void setPets(Pet[] pets) { + this.pets = pets; + } + + private String familyName; + private String firstName; + private String gender; + private int grade; + private Pet[] pets; +} + diff --git a/src/main/java/com/azure/cosmos/examples/common/CustomPOJO.java b/src/main/java/com/azure/cosmos/examples/common/CustomPOJO.java new file mode 100644 index 0000000..0341d1a --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/common/CustomPOJO.java @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.common; + +public class CustomPOJO { + private String id; + + public CustomPOJO() { + + } + + public CustomPOJO(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/src/main/java/com/azure/cosmos/examples/common/Families.java b/src/main/java/com/azure/cosmos/examples/common/Families.java new file mode 100644 index 0000000..1f658ae --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/common/Families.java @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.common; + +public class Families { + + public static Family getAndersenFamilyItem() { + Family andersenFamily = new Family(); + andersenFamily.setId("Andersen-" + System.currentTimeMillis()); + andersenFamily.setLastName("Andersen"); + + Parent parent1 = new Parent(); + parent1.setFirstName("Thomas"); + + Parent parent2 = new Parent(); + parent2.setFirstName("Mary Kay"); + + andersenFamily.setParents(new Parent[]{parent1, parent2}); + + Child child1 = new Child(); + child1.setFirstName("Henriette Thaulow"); + child1.setGender("female"); + child1.setGrade(5); + + Pet pet1 = new Pet(); + pet1.setGivenName("Fluffy"); + + child1.setPets(new Pet[]{pet1}); + + andersenFamily.setDistrict("WA5"); + Address address = new Address(); + address.setCity("Seattle"); + address.setCounty("King"); + address.setState("WA"); + + andersenFamily.setAddress(address); + andersenFamily.setRegistered(true); + + return andersenFamily; + } + + public static Family getWakefieldFamilyItem() { + Family wakefieldFamily = new Family(); + wakefieldFamily.setId("Wakefield-" + System.currentTimeMillis()); + wakefieldFamily.setLastName("Wakefield"); + + Parent parent1 = new Parent(); + parent1.setFamilyName("Wakefield"); + parent1.setFirstName("Robin"); + + Parent parent2 = new Parent(); + parent2.setFamilyName("Miller"); + parent2.setFirstName("Ben"); + + wakefieldFamily.setParents(new Parent[]{parent1, parent2}); + + Child child1 = new Child(); + child1.setFirstName("Jesse"); + child1.setFamilyName("Merriam"); + child1.setGrade(8); + + Pet pet1 = new Pet(); + pet1.setGivenName("Goofy"); + + Pet pet2 = new Pet(); + pet2.setGivenName("Shadow"); + + child1.setPets(new Pet[]{pet1, pet2}); + + Child child2 = new Child(); + child2.setFirstName("Lisa"); + child2.setFamilyName("Miller"); + child2.setGrade(1); + child2.setGender("female"); + + wakefieldFamily.setChildren(new Child[]{child1, child2}); + + Address address = new Address(); + address.setCity("NY"); + address.setCounty("Manhattan"); + address.setState("NY"); + + wakefieldFamily.setAddress(address); + wakefieldFamily.setDistrict("NY23"); + wakefieldFamily.setRegistered(true); + return wakefieldFamily; + } + + public static Family getJohnsonFamilyItem() { + Family andersenFamily = new Family(); + andersenFamily.setId("Johnson-" + System.currentTimeMillis()); + andersenFamily.setLastName("Johnson"); + + Parent parent1 = new Parent(); + parent1.setFirstName("John"); + + Parent parent2 = new Parent(); + parent2.setFirstName("Lili"); + + return andersenFamily; + } + + public static Family getSmithFamilyItem() { + Family andersenFamily = new Family(); + andersenFamily.setId("Smith-" + System.currentTimeMillis()); + andersenFamily.setLastName("Smith"); + + Parent parent1 = new Parent(); + parent1.setFirstName("John"); + + Parent parent2 = new Parent(); + parent2.setFirstName("Cynthia"); + + return andersenFamily; + } +} diff --git a/src/main/java/com/azure/cosmos/examples/common/Family.java b/src/main/java/com/azure/cosmos/examples/common/Family.java new file mode 100644 index 0000000..4e337b3 --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/common/Family.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.common; + +public class Family { + public Family() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getDistrict() { + return district; + } + + public void setDistrict(String district) { + this.district = district; + } + + public Parent[] getParents() { + return parents; + } + + public void setParents(Parent[] parents) { + this.parents = parents; + } + + public Child[] getChildren() { + return children; + } + + public void setChildren(Child[] children) { + this.children = children; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public boolean isRegistered() { + return isRegistered; + } + + public void setRegistered(boolean isRegistered) { + this.isRegistered = isRegistered; + } + + private String id; + private String lastName; + private String district; + private Parent[] parents; + private Child[] children; + private Address address; + private boolean isRegistered; +} + diff --git a/src/main/java/com/azure/cosmos/examples/common/Parent.java b/src/main/java/com/azure/cosmos/examples/common/Parent.java new file mode 100644 index 0000000..d7753a8 --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/common/Parent.java @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.common; + +public class Parent { + + public Parent() { + } + + public Parent(String firstName) { + this.firstName = firstName; + } + + public String getFamilyName() { + return familyName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + private String familyName; + private String firstName; +} diff --git a/src/main/java/com/azure/cosmos/examples/common/Pet.java b/src/main/java/com/azure/cosmos/examples/common/Pet.java new file mode 100644 index 0000000..062ce93 --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/common/Pet.java @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.common; + +public class Pet { + public String getGivenName() { + return givenName; + } + + public void setGivenName(String givenName) { + this.givenName = givenName; + } + + private String givenName; +} diff --git a/src/main/java/com/azure/cosmos/examples/crudquickstart/async/SampleCRUDQuickstartAsync.java b/src/main/java/com/azure/cosmos/examples/crudquickstart/async/SampleCRUDQuickstartAsync.java new file mode 100644 index 0000000..379ee18 --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/crudquickstart/async/SampleCRUDQuickstartAsync.java @@ -0,0 +1,359 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.crudquickstart.async; + + +import com.azure.cosmos.ConnectionPolicy; +import com.azure.cosmos.ConsistencyLevel; +import com.azure.cosmos.CosmosAsyncClient; +import com.azure.cosmos.CosmosAsyncContainer; +import com.azure.cosmos.CosmosAsyncDatabase; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.CosmosClientException; +import com.azure.cosmos.CosmosPagedFlux; +import com.azure.cosmos.examples.changefeed.SampleChangeFeedProcessor; +import com.azure.cosmos.examples.common.AccountSettings; +import com.azure.cosmos.examples.common.Families; +import com.azure.cosmos.examples.common.Family; +import com.azure.cosmos.models.CosmosAsyncContainerResponse; +import com.azure.cosmos.models.CosmosAsyncDatabaseResponse; +import com.azure.cosmos.models.CosmosAsyncItemResponse; +import com.azure.cosmos.models.CosmosContainerProperties; +import com.azure.cosmos.models.FeedOptions; +import com.azure.cosmos.models.PartitionKey; +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +public class SampleCRUDQuickstartAsync { + + private CosmosAsyncClient client; + + private final String databaseName = "AzureSampleFamilyDB"; + private final String containerName = "FamilyContainer"; + + private CosmosAsyncDatabase database; + private CosmosAsyncContainer container; + + protected static Logger logger = LoggerFactory.getLogger(SampleChangeFeedProcessor.class.getSimpleName()); + + public void close() { + client.close(); + } + + /** + * Run a Hello CosmosDB console application. + *

+ * This is a simple sample application intended to demonstrate Create, Read, Update, Delete (CRUD) operations + * with Azure Cosmos DB Java SDK, as applied to databases, containers and items. This sample will + * 1. Create asynchronous client, database and container instances + * 2. Create several items + * 3. Upsert one of the items + * 4. Perform a query over the items + * 5. Delete an item + * 6. Delete the Cosmos DB database and container resources and close the client. + */ + //

+ public static void main(String[] args) { + SampleCRUDQuickstartAsync p = new SampleCRUDQuickstartAsync(); + + try { + logger.info("Starting ASYNC main"); + p.getStartedDemo(); + logger.info("Demo complete, please hold while resources are released"); + } catch (Exception e) { + e.printStackTrace(); + logger.error(String.format("Cosmos getStarted failed with %s", e)); + } finally { + logger.info("Closing the client"); + p.shutdown(); + } + } + + //
+ + private void getStartedDemo() throws Exception { + + logger.info("Using Azure Cosmos DB endpoint: " + AccountSettings.HOST); + + ConnectionPolicy defaultPolicy = ConnectionPolicy.getDefaultPolicy(); + // Setting the preferred location to Cosmos DB Account region + // West US is just an example. User should set preferred location to the Cosmos DB region closest to the application + defaultPolicy.setPreferredLocations(Lists.newArrayList("West US")); + + // Create async client + // + client = new CosmosClientBuilder() + .setEndpoint(AccountSettings.HOST) + .setKey(AccountSettings.MASTER_KEY) + .setConnectionPolicy(defaultPolicy) + .setConsistencyLevel(ConsistencyLevel.EVENTUAL) + .buildAsyncClient(); + + // + + createDatabaseIfNotExists(); + createContainerIfNotExists(); + + Family andersenFamilyItem = Families.getAndersenFamilyItem(); + Family wakefieldFamilyItem = Families.getWakefieldFamilyItem(); + Family johnsonFamilyItem = Families.getJohnsonFamilyItem(); + Family smithFamilyItem = Families.getSmithFamilyItem(); + + // Setup family items to create + Flux familiesToCreate = Flux.just(andersenFamilyItem, + wakefieldFamilyItem, + johnsonFamilyItem, + smithFamilyItem); + + // Creates several items in the container + createFamilies(familiesToCreate); + + // Upsert one of the items in the container + upsertFamily(wakefieldFamilyItem); + + familiesToCreate = Flux.just(andersenFamilyItem, + wakefieldFamilyItem, + johnsonFamilyItem, + smithFamilyItem); + + logger.info("Reading items."); + readItems(familiesToCreate); + + logger.info("Querying items."); + queryItems(); + + logger.info("Deleting an item."); + deleteItem(andersenFamilyItem); + } + + private void createDatabaseIfNotExists() throws Exception { + logger.info("Create database " + databaseName + " if not exists."); + + // Create database if not exists + // + Mono databaseIfNotExists = client.createDatabaseIfNotExists(databaseName); + databaseIfNotExists.flatMap(databaseResponse -> { + database = databaseResponse.getDatabase(); + logger.info("Checking database " + database.getId() + " completed!\n"); + return Mono.empty(); + }).block(); + // + } + + private void createContainerIfNotExists() throws Exception { + logger.info("Create container " + containerName + " if not exists."); + + // Create container if not exists + // + + CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerName, "/lastName"); + Mono containerIfNotExists = database.createContainerIfNotExists(containerProperties, 400); + + // Create container with 400 RU/s + containerIfNotExists.flatMap(containerResponse -> { + container = containerResponse.getContainer(); + logger.info("Checking container " + container.getId() + " completed!\n"); + return Mono.empty(); + }).block(); + + // + } + + private void createFamilies(Flux families) throws Exception { + + // + + final CountDownLatch completionLatch = new CountDownLatch(1); + + // Combine multiple item inserts, associated success println's, and a final aggregate stats println into one Reactive stream. + families.flatMap(family -> { + return container.createItem(family); + }) //Flux of item request responses + .flatMap(itemResponse -> { + logger.info(String.format("Created item with request charge of %.2f within" + + " duration %s", + itemResponse.getRequestCharge(), itemResponse.getRequestLatency())); + logger.info(String.format("Item ID: %s\n", itemResponse.getItem().getId())); + return Mono.just(itemResponse.getRequestCharge()); + }) //Flux of request charges + .reduce(0.0, + (charge_n, charge_nplus1) -> charge_n + charge_nplus1 + ) //Mono of total charge - there will be only one item in this stream + .subscribe(charge -> { + logger.info(String.format("Created items with total request charge of %.2f\n", + charge)); + }, + err -> { + if (err instanceof CosmosClientException) { + //Client-specific errors + CosmosClientException cerr = (CosmosClientException) err; + cerr.printStackTrace(); + logger.error(String.format("Read Item failed with %s\n", cerr)); + } else { + //General errors + err.printStackTrace(); + } + + completionLatch.countDown(); + }, + () -> { + completionLatch.countDown(); + } + ); //Preserve the total charge and print aggregate charge/item count stats. + + try { + completionLatch.await(); + } catch (InterruptedException err) { + throw new AssertionError("Unexpected Interruption", err); + } + + // + } + + private void upsertFamily(Family family_to_upsert) { + //Modify a field of the family object + logger.info(String.format("Upserting the item with id %s after modifying the isRegistered field...", family_to_upsert.getId())); + family_to_upsert.setRegistered(!family_to_upsert.isRegistered()); + + //Upsert the modified item + Mono.just(family_to_upsert).flatMap(item -> { + CosmosAsyncItemResponse item_resp = container.upsertItem(family_to_upsert).block(); + + // Get upsert request charge and other properties like latency, and diagnostics strings, etc. + logger.info(String.format("Upserted item with request charge of %.2f within duration %s", + item_resp.getRequestCharge(), item_resp.getRequestLatency())); + + return Mono.empty(); + }).subscribe(); + } + + private void readItems(Flux familiesToCreate) { + // Using partition key for point read scenarios. + // This will help fast look up of items because of partition key + // + + final CountDownLatch completionLatch = new CountDownLatch(1); + + familiesToCreate.flatMap(family -> { + Mono> asyncItemResponseMono = container.readItem(family.getId(), new PartitionKey(family.getLastName()), Family.class); + return asyncItemResponseMono; + }) + .subscribe( + itemResponse -> { + double requestCharge = itemResponse.getRequestCharge(); + Duration requestLatency = itemResponse.getRequestLatency(); + logger.info(String.format("Item successfully read with id %s with a charge of %.2f and within duration %s", + itemResponse.getItem().getId(), requestCharge, requestLatency)); + }, + err -> { + if (err instanceof CosmosClientException) { + //Client-specific errors + CosmosClientException cerr = (CosmosClientException) err; + cerr.printStackTrace(); + logger.error(String.format("Read Item failed with %s\n", cerr)); + } else { + //General errors + err.printStackTrace(); + } + + completionLatch.countDown(); + }, + () -> { + completionLatch.countDown(); + } + ); + + try { + completionLatch.await(); + } catch (InterruptedException err) { + throw new AssertionError("Unexpected Interruption", err); + } + + // + } + + private void queryItems() { + // + // Set some common query options + + FeedOptions queryOptions = new FeedOptions(); + queryOptions.setMaxItemCount(10); + //queryOptions.setEnableCrossPartitionQuery(true); //No longer needed in SDK v4 + // Set populate query metrics to get metrics around query executions + queryOptions.setPopulateQueryMetrics(true); + + CosmosPagedFlux pagedFluxResponse = container.queryItems( + "SELECT * FROM Family WHERE Family.lastName IN ('Andersen', 'Wakefield', 'Johnson')", queryOptions, Family.class); + + final CountDownLatch completionLatch = new CountDownLatch(1); + + pagedFluxResponse.byPage().subscribe( + fluxResponse -> { + logger.info("Got a page of query result with " + + fluxResponse.getResults().size() + " items(s)" + + " and request charge of " + fluxResponse.getRequestCharge()); + + logger.info("Item Ids " + fluxResponse + .getResults() + .stream() + .map(Family::getId) + .collect(Collectors.toList())); + }, + err -> { + if (err instanceof CosmosClientException) { + //Client-specific errors + CosmosClientException cerr = (CosmosClientException) err; + cerr.printStackTrace(); + logger.error(String.format("Read Item failed with %s\n", cerr)); + } else { + //General errors + err.printStackTrace(); + } + + completionLatch.countDown(); + }, + () -> { + completionLatch.countDown(); + } + ); + + try { + completionLatch.await(); + } catch (InterruptedException err) { + throw new AssertionError("Unexpected Interruption", err); + } + + // + } + + private void deleteItem(Family item) { + container.deleteItem(item.getId(), new PartitionKey(item.getLastName())).block(); + } + + private void shutdown() { + try { + //Clean shutdown + logger.info("Deleting Cosmos DB resources"); + logger.info("-Deleting container..."); + if (container != null) + container.delete().subscribe(); + logger.info("-Deleting database..."); + if (database != null) + database.delete().subscribe(); + logger.info("-Closing the client..."); + } catch (Exception err) { + logger.error("Deleting Cosmos DB resources failed, will still attempt to close the client. See stack trace below."); + err.printStackTrace(); + } + client.close(); + logger.info("Done."); + } +} diff --git a/src/main/java/com/azure/cosmos/examples/crudquickstart/sync/SampleCRUDQuickstart.java b/src/main/java/com/azure/cosmos/examples/crudquickstart/sync/SampleCRUDQuickstart.java new file mode 100644 index 0000000..0659d25 --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/crudquickstart/sync/SampleCRUDQuickstart.java @@ -0,0 +1,249 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.crudquickstart.sync; + +import com.azure.cosmos.ConnectionPolicy; +import com.azure.cosmos.ConsistencyLevel; +import com.azure.cosmos.CosmosClient; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.CosmosClientException; +import com.azure.cosmos.CosmosContainer; +import com.azure.cosmos.CosmosDatabase; +import com.azure.cosmos.CosmosPagedIterable; +import com.azure.cosmos.examples.changefeed.SampleChangeFeedProcessor; +import com.azure.cosmos.examples.common.AccountSettings; +import com.azure.cosmos.examples.common.Families; +import com.azure.cosmos.examples.common.Family; +import com.azure.cosmos.models.CosmosContainerProperties; +import com.azure.cosmos.models.CosmosItemRequestOptions; +import com.azure.cosmos.models.CosmosItemResponse; +import com.azure.cosmos.models.FeedOptions; +import com.azure.cosmos.models.PartitionKey; +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class SampleCRUDQuickstart { + + private CosmosClient client; + + private final String databaseName = "AzureSampleFamilyDB"; + private final String containerName = "FamilyContainer"; + + private CosmosDatabase database; + private CosmosContainer container; + + protected static Logger logger = LoggerFactory.getLogger(SampleChangeFeedProcessor.class.getSimpleName()); + + public void close() { + client.close(); + } + + /** + * Run a Hello CosmosDB console application. + *

+ * This is a simple sample application intended to demonstrate Create, Read, Update, Delete (CRUD) operations + * with Azure Cosmos DB Java SDK, as applied to databases, containers and items. This sample will + * 1. Create synchronous client, database and container instances + * 2. Create several items + * 3. Upsert one of the items + * 4. Perform a query over the items + * 5. Delete an item + * 6. Delete the Cosmos DB database and container resources and close the client. * + */ + //

+ public static void main(String[] args) { + SampleCRUDQuickstart p = new SampleCRUDQuickstart(); + + try { + logger.info("Starting SYNC main"); + p.getStartedDemo(); + logger.info("Demo complete, please hold while resources are released"); + } catch (Exception e) { + e.printStackTrace(); + logger.error(String.format("Cosmos getStarted failed with %s", e)); + } finally { + logger.info("Closing the client"); + p.shutdown(); + } + } + + //
+ + private void getStartedDemo() throws Exception { + + logger.info("Using Azure Cosmos DB endpoint: " + AccountSettings.HOST); + + ConnectionPolicy defaultPolicy = ConnectionPolicy.getDefaultPolicy(); + // Setting the preferred location to Cosmos DB Account region + // West US is just an example. User should set preferred location to the Cosmos DB region closest to the application + defaultPolicy.setPreferredLocations(Lists.newArrayList("West US")); + + // Create sync client + // + client = new CosmosClientBuilder() + .setEndpoint(AccountSettings.HOST) + .setKey(AccountSettings.MASTER_KEY) + .setConnectionPolicy(defaultPolicy) + .setConsistencyLevel(ConsistencyLevel.EVENTUAL) + .buildClient(); + + // + + createDatabaseIfNotExists(); + createContainerIfNotExists(); + + // Setup family items to create + ArrayList familiesToCreate = new ArrayList<>(); + familiesToCreate.add(Families.getAndersenFamilyItem()); + familiesToCreate.add(Families.getWakefieldFamilyItem()); + familiesToCreate.add(Families.getJohnsonFamilyItem()); + familiesToCreate.add(Families.getSmithFamilyItem()); + + // Creates several items in the container + // Also applies an upsert operation to one of the items (create if not present, otherwise replace) + createFamilies(familiesToCreate); + + logger.info("Reading items."); + readItems(familiesToCreate); + + logger.info("Querying items."); + queryItems(); + + logger.info("Delete an item."); + deleteItem(familiesToCreate.get(0)); + } + + private void createDatabaseIfNotExists() throws Exception { + logger.info("Create database " + databaseName + " if not exists."); + + // Create database if not exists + // + database = client.createDatabaseIfNotExists(databaseName).getDatabase(); + // + + logger.info("Checking database " + database.getId() + " completed!\n"); + } + + private void createContainerIfNotExists() throws Exception { + logger.info("Create container " + containerName + " if not exists."); + + // Create container if not exists + // + CosmosContainerProperties containerProperties = + new CosmosContainerProperties(containerName, "/lastName"); + + // Create container with 400 RU/s + container = database.createContainerIfNotExists(containerProperties, 400).getContainer(); + // + + logger.info("Checking container " + container.getId() + " completed!\n"); + } + + private void createFamilies(List families) throws Exception { + double totalRequestCharge = 0; + for (Family family : families) { + + // + // Create item using container that we created using sync client + + // Use lastName as partitionKey for cosmos item + // Using appropriate partition key improves the performance of database operations + CosmosItemRequestOptions cosmosItemRequestOptions = new CosmosItemRequestOptions(); + CosmosItemResponse item = container.createItem(family, new PartitionKey(family.getLastName()), cosmosItemRequestOptions); + // + + // Get request charge and other properties like latency, and diagnostics strings, etc. + logger.info(String.format("Created item with request charge of %.2f within duration %s", + item.getRequestCharge(), item.getRequestLatency())); + + totalRequestCharge += item.getRequestCharge(); + } + logger.info(String.format("Created %d items with total request charge of %.2f", + families.size(), totalRequestCharge)); + + Family family_to_upsert = families.get(0); + logger.info(String.format("Upserting the item with id %s after modifying the isRegistered field...", family_to_upsert.getId())); + family_to_upsert.setRegistered(!family_to_upsert.isRegistered()); + + CosmosItemResponse item = container.upsertItem(family_to_upsert); + + // Get upsert request charge and other properties like latency, and diagnostics strings, etc. + logger.info(String.format("Upserted item with request charge of %.2f within duration %s", + item.getRequestCharge(), item.getRequestLatency())); + } + + private void readItems(ArrayList familiesToCreate) { + // Using partition key for point read scenarios. + // This will help fast look up of items because of partition key + familiesToCreate.forEach(family -> { + // + try { + CosmosItemResponse item = container.readItem(family.getId(), new PartitionKey(family.getLastName()), Family.class); + double requestCharge = item.getRequestCharge(); + Duration requestLatency = item.getRequestLatency(); + logger.info(String.format("Item successfully read with id %s with a charge of %.2f and within duration %s", + item.getResource().getId(), requestCharge, requestLatency)); + } catch (CosmosClientException e) { + e.printStackTrace(); + logger.info(String.format("Read Item failed with %s", e)); + } + // + }); + } + + private void queryItems() { + // + // Set some common query options + FeedOptions queryOptions = new FeedOptions(); + queryOptions.setMaxItemCount(10); + //queryOptions.setEnableCrossPartitionQuery(true); //No longer necessary in SDK v4 + // Set populate query metrics to get metrics around query executions + queryOptions.setPopulateQueryMetrics(true); + + CosmosPagedIterable familiesPagedIterable = container.queryItems( + "SELECT * FROM Family WHERE Family.lastName IN ('Andersen', 'Wakefield', 'Johnson')", queryOptions, Family.class); + + familiesPagedIterable.iterableByPage().forEach(cosmosItemPropertiesFeedResponse -> { + logger.info("Got a page of query result with " + + cosmosItemPropertiesFeedResponse.getResults().size() + " items(s)" + + " and request charge of " + cosmosItemPropertiesFeedResponse.getRequestCharge()); + + logger.info("Item Ids " + cosmosItemPropertiesFeedResponse + .getResults() + .stream() + .map(Family::getId) + .collect(Collectors.toList())); + }); + // + } + + private void deleteItem(Family item) { + container.deleteItem(item.getId(), new PartitionKey(item.getLastName()), new CosmosItemRequestOptions()); + } + + private void shutdown() { + try { + //Clean shutdown + logger.info("Deleting Cosmos DB resources"); + logger.info("-Deleting container..."); + if (container != null) + container.delete(); + logger.info("-Deleting database..."); + if (database != null) + database.delete(); + logger.info("-Closing the client..."); + } catch (Exception err) { + logger.error("Deleting Cosmos DB resources failed, will still attempt to close the client. See stack trace below."); + err.printStackTrace(); + } + client.close(); + logger.info("Done."); + } +} diff --git a/src/main/java/com/azure/cosmos/examples/indexmanagement/async/SampleIndexManagementAsync.java b/src/main/java/com/azure/cosmos/examples/indexmanagement/async/SampleIndexManagementAsync.java new file mode 100644 index 0000000..34628ff --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/indexmanagement/async/SampleIndexManagementAsync.java @@ -0,0 +1,389 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.indexmanagement.async; + +import com.azure.cosmos.ConnectionPolicy; +import com.azure.cosmos.ConsistencyLevel; +import com.azure.cosmos.CosmosAsyncClient; +import com.azure.cosmos.CosmosAsyncContainer; +import com.azure.cosmos.CosmosAsyncDatabase; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.CosmosClientException; +import com.azure.cosmos.CosmosPagedFlux; +import com.azure.cosmos.examples.changefeed.SampleChangeFeedProcessor; +import com.azure.cosmos.examples.common.AccountSettings; +import com.azure.cosmos.examples.common.Families; +import com.azure.cosmos.examples.common.Family; +import com.azure.cosmos.models.CosmosAsyncContainerResponse; +import com.azure.cosmos.models.CosmosAsyncDatabaseResponse; +import com.azure.cosmos.models.CosmosAsyncItemResponse; +import com.azure.cosmos.models.CosmosContainerProperties; +import com.azure.cosmos.models.ExcludedPath; +import com.azure.cosmos.models.FeedOptions; +import com.azure.cosmos.models.IncludedPath; +import com.azure.cosmos.models.IndexingMode; +import com.azure.cosmos.models.IndexingPolicy; +import com.azure.cosmos.models.PartitionKey; +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +public class SampleIndexManagementAsync { + + private CosmosAsyncClient client; + + private final String databaseName = "AzureSampleFamilyDB"; + private final String containerName = "FamilyContainer"; + + private CosmosAsyncDatabase database; + private CosmosAsyncContainer container; + + protected static Logger logger = LoggerFactory.getLogger(SampleChangeFeedProcessor.class.getSimpleName()); + + public void close() { + client.close(); + } + + /** + * Run a Hello CosmosDB console application. + *

+ * This sample is similar to SampleCRUDQuickstartAsync, but modified to show indexing capabilities of Cosmos DB. + * Look at the implementation of createContainerIfNotExistsWithSpecifiedIndex() for the demonstration of + * indexing capabilities. + */ + //

+ public static void main(String[] args) { + SampleIndexManagementAsync p = new SampleIndexManagementAsync(); + + try { + logger.info("Starting ASYNC main"); + p.indexManagementDemo(); + logger.info("Demo complete, please hold while resources are released"); + } catch (Exception e) { + e.printStackTrace(); + logger.error(String.format("Cosmos getStarted failed with %s", e)); + } finally { + logger.info("Closing the client"); + p.shutdown(); + } + } + + //
+ + private void indexManagementDemo() throws Exception { + + logger.info("Using Azure Cosmos DB endpoint: " + AccountSettings.HOST); + + ConnectionPolicy defaultPolicy = ConnectionPolicy.getDefaultPolicy(); + // Setting the preferred location to Cosmos DB Account region + // West US is just an example. User should set preferred location to the Cosmos DB region closest to the application + defaultPolicy.setPreferredLocations(Lists.newArrayList("West US")); + + // Create async client + // + client = new CosmosClientBuilder() + .setEndpoint(AccountSettings.HOST) + .setKey(AccountSettings.MASTER_KEY) + .setConnectionPolicy(defaultPolicy) + .setConsistencyLevel(ConsistencyLevel.EVENTUAL) + .buildAsyncClient(); + + // + + createDatabaseIfNotExists(); + + //Here is where index management is performed + createContainerIfNotExistsWithSpecifiedIndex(); + + Family andersenFamilyItem = Families.getAndersenFamilyItem(); + Family wakefieldFamilyItem = Families.getWakefieldFamilyItem(); + Family johnsonFamilyItem = Families.getJohnsonFamilyItem(); + Family smithFamilyItem = Families.getSmithFamilyItem(); + + // Setup family items to create + Flux familiesToCreate = Flux.just(andersenFamilyItem, + wakefieldFamilyItem, + johnsonFamilyItem, + smithFamilyItem); + + createFamilies(familiesToCreate); + + familiesToCreate = Flux.just(andersenFamilyItem, + wakefieldFamilyItem, + johnsonFamilyItem, + smithFamilyItem); + + logger.info("Reading items."); + readItems(familiesToCreate); + + logger.info("Querying items."); + queryItems(); + } + + private void createDatabaseIfNotExists() throws Exception { + logger.info("Create database " + databaseName + " if not exists."); + + // Create database if not exists + // + Mono databaseIfNotExists = client.createDatabaseIfNotExists(databaseName); + databaseIfNotExists.flatMap(databaseResponse -> { + database = databaseResponse.getDatabase(); + logger.info("Checking database " + database.getId() + " completed!\n"); + return Mono.empty(); + }).block(); + // + } + + private void createContainerIfNotExistsWithSpecifiedIndex() throws Exception { + logger.info("Create container " + containerName + " if not exists."); + + // Create container if not exists + // + + CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerName, "/lastName"); + + // + IndexingPolicy indexingPolicy = new IndexingPolicy(); + indexingPolicy.setIndexingMode(IndexingMode.CONSISTENT); //To turn indexing off set IndexingMode.NONE + + // Included paths + List includedPaths = new ArrayList<>(); + IncludedPath includedPath = new IncludedPath(); + includedPath.setPath("/*"); + includedPaths.add(includedPath); + indexingPolicy.setIncludedPaths(includedPaths); + + // Excluded paths + List excludedPaths = new ArrayList<>(); + ExcludedPath excludedPath = new ExcludedPath(); + excludedPath.setPath("/name/*"); + excludedPaths.add(excludedPath); + indexingPolicy.setExcludedPaths(excludedPaths); + + // Spatial indices - if you need them, here is how to set them up: + /* + List spatialIndexes = new ArrayList(); + List collectionOfSpatialTypes = new ArrayList(); + + SpatialSpec spec = new SpatialSpec(); + spec.setPath("/locations/*"); + collectionOfSpatialTypes.add(SpatialType.Point); + spec.setSpatialTypes(collectionOfSpatialTypes); + spatialIndexes.add(spec); + + indexingPolicy.setSpatialIndexes(spatialIndexes); + */ + + // Composite indices - if you need them, here is how to set them up: + /* + List> compositeIndexes = new ArrayList<>(); + List compositePaths = new ArrayList<>(); + + CompositePath nameCompositePath = new CompositePath(); + nameCompositePath.setPath("/name"); + nameCompositePath.setOrder(CompositePathSortOrder.ASCENDING); + + CompositePath ageCompositePath = new CompositePath(); + ageCompositePath.setPath("/age"); + ageCompositePath.setOrder(CompositePathSortOrder.DESCENDING); + + compositePaths.add(ageCompositePath); + compositePaths.add(nameCompositePath); + + compositeIndexes.add(compositePaths); + indexingPolicy.setCompositeIndexes(compositeIndexes); + */ + + containerProperties.setIndexingPolicy(indexingPolicy); + + // + + Mono containerIfNotExists = database.createContainerIfNotExists(containerProperties, 400); + + // Create container with 400 RU/s + containerIfNotExists.flatMap(containerResponse -> { + container = containerResponse.getContainer(); + logger.info("Checking container " + container.getId() + " completed!\n"); + return Mono.empty(); + }).block(); + + // + } + + private void createFamilies(Flux families) throws Exception { + + // + + final CountDownLatch completionLatch = new CountDownLatch(1); + + // Combine multiple item inserts, associated success println's, and a final aggregate stats println into one Reactive stream. + families.flatMap(family -> { + return container.createItem(family); + }) //Flux of item request responses + .flatMap(itemResponse -> { + logger.info(String.format("Created item with request charge of %.2f within" + + " duration %s", + itemResponse.getRequestCharge(), itemResponse.getRequestLatency())); + logger.info(String.format("Item ID: %s\n", itemResponse.getItem().getId())); + return Mono.just(itemResponse.getRequestCharge()); + }) //Flux of request charges + .reduce(0.0, + (charge_n, charge_nplus1) -> charge_n + charge_nplus1 + ) //Mono of total charge - there will be only one item in this stream + .subscribe(charge -> { + logger.info(String.format("Created items with total request charge of %.2f\n", + charge)); + }, + err -> { + if (err instanceof CosmosClientException) { + //Client-specific errors + CosmosClientException cerr = (CosmosClientException) err; + cerr.printStackTrace(); + logger.info(String.format("Read Item failed with %s\n", cerr)); + } else { + //General errors + err.printStackTrace(); + } + + completionLatch.countDown(); + }, + () -> { + completionLatch.countDown(); + } + ); //Preserve the total charge and print aggregate charge/item count stats. + + try { + completionLatch.await(); + } catch (InterruptedException err) { + throw new AssertionError("Unexpected Interruption", err); + } + + // + } + + private void readItems(Flux familiesToCreate) { + // Using partition key for point read scenarios. + // This will help fast look up of items because of partition key + // + + final CountDownLatch completionLatch = new CountDownLatch(1); + + familiesToCreate.flatMap(family -> { + Mono> asyncItemResponseMono = container.readItem(family.getId(), new PartitionKey(family.getLastName()), Family.class); + return asyncItemResponseMono; + }) + .subscribe( + itemResponse -> { + double requestCharge = itemResponse.getRequestCharge(); + Duration requestLatency = itemResponse.getRequestLatency(); + logger.info(String.format("Item successfully read with id %s with a charge of %.2f and within duration %s", + itemResponse.getItem().getId(), requestCharge, requestLatency)); + }, + err -> { + if (err instanceof CosmosClientException) { + //Client-specific errors + CosmosClientException cerr = (CosmosClientException) err; + cerr.printStackTrace(); + logger.info(String.format("Read Item failed with %s\n", cerr)); + } else { + //General errors + err.printStackTrace(); + } + + completionLatch.countDown(); + }, + () -> { + completionLatch.countDown(); + } + ); + + try { + completionLatch.await(); + } catch (InterruptedException err) { + throw new AssertionError("Unexpected Interruption", err); + } + + // + } + + private void queryItems() { + // + // Set some common query options + + FeedOptions queryOptions = new FeedOptions(); + queryOptions.setMaxItemCount(10); + // Set populate query metrics to get metrics around query executions + queryOptions.setPopulateQueryMetrics(true); + + CosmosPagedFlux pagedFluxResponse = container.queryItems( + "SELECT * FROM Family WHERE Family.lastName IN ('Andersen', 'Wakefield', 'Johnson')", queryOptions, Family.class); + + final CountDownLatch completionLatch = new CountDownLatch(1); + + pagedFluxResponse.byPage().subscribe( + fluxResponse -> { + logger.info("Got a page of query result with " + + fluxResponse.getResults().size() + " items(s)" + + " and request charge of " + fluxResponse.getRequestCharge()); + + logger.info("Item Ids " + fluxResponse + .getResults() + .stream() + .map(Family::getId) + .collect(Collectors.toList())); + }, + err -> { + if (err instanceof CosmosClientException) { + //Client-specific errors + CosmosClientException cerr = (CosmosClientException) err; + cerr.printStackTrace(); + logger.error(String.format("Read Item failed with %s\n", cerr)); + } else { + //General errors + err.printStackTrace(); + } + + completionLatch.countDown(); + }, + () -> { + completionLatch.countDown(); + } + ); + + try { + completionLatch.await(); + } catch (InterruptedException err) { + throw new AssertionError("Unexpected Interruption", err); + } + + // + } + + private void shutdown() { + try { + //Clean shutdown + logger.info("Deleting Cosmos DB resources"); + logger.info("-Deleting container..."); + if (container != null) + container.delete().subscribe(); + logger.info("-Deleting database..."); + if (database != null) + database.delete().subscribe(); + logger.info("-Closing the client..."); + } catch (Exception err) { + logger.error("Deleting Cosmos DB resources failed, will still attempt to close the client. See stack trace below."); + err.printStackTrace(); + } + client.close(); + logger.info("Done."); + } +} diff --git a/src/main/java/com/azure/cosmos/examples/indexmanagement/sync/SampleIndexManagement.java b/src/main/java/com/azure/cosmos/examples/indexmanagement/sync/SampleIndexManagement.java new file mode 100644 index 0000000..748dbfe --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/indexmanagement/sync/SampleIndexManagement.java @@ -0,0 +1,287 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.indexmanagement.sync; + +import com.azure.cosmos.ConnectionPolicy; +import com.azure.cosmos.ConsistencyLevel; +import com.azure.cosmos.CosmosClient; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.CosmosClientException; +import com.azure.cosmos.CosmosContainer; +import com.azure.cosmos.CosmosDatabase; +import com.azure.cosmos.CosmosPagedIterable; +import com.azure.cosmos.examples.changefeed.SampleChangeFeedProcessor; +import com.azure.cosmos.examples.common.AccountSettings; +import com.azure.cosmos.examples.common.Families; +import com.azure.cosmos.examples.common.Family; +import com.azure.cosmos.models.CosmosContainerProperties; +import com.azure.cosmos.models.CosmosItemRequestOptions; +import com.azure.cosmos.models.CosmosItemResponse; +import com.azure.cosmos.models.ExcludedPath; +import com.azure.cosmos.models.FeedOptions; +import com.azure.cosmos.models.IncludedPath; +import com.azure.cosmos.models.IndexingMode; +import com.azure.cosmos.models.IndexingPolicy; +import com.azure.cosmos.models.PartitionKey; +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class SampleIndexManagement { + + private CosmosClient client; + + private final String databaseName = "AzureSampleFamilyDB"; + private final String containerName = "FamilyContainer"; + + private CosmosDatabase database; + private CosmosContainer container; + + protected static Logger logger = LoggerFactory.getLogger(SampleChangeFeedProcessor.class.getSimpleName()); + + public void close() { + client.close(); + } + + /** + * Run a Hello CosmosDB console application. + *

+ * This sample is similar to SampleCRUDQuickstart, but modified to show indexing capabilities of Cosmos DB. + * Look at the implementation of createContainerIfNotExistsWithSpecifiedIndex() for the demonstration of + * indexing capabilities. + */ + //

+ public static void main(String[] args) { + + SampleIndexManagement p = new SampleIndexManagement(); + + try { + logger.info("Starting SYNC main"); + p.indexManagementDemo(); + logger.info("Demo complete, please hold while resources are released"); + } catch (Exception e) { + e.printStackTrace(); + logger.error(String.format("Cosmos getStarted failed with %s", e)); + } finally { + logger.info("Closing the client"); + p.shutdown(); + } + } + + //
+ + private void indexManagementDemo() throws Exception { + + logger.info("Using Azure Cosmos DB endpoint: " + AccountSettings.HOST); + + ConnectionPolicy defaultPolicy = ConnectionPolicy.getDefaultPolicy(); + // Setting the preferred location to Cosmos DB Account region + // West US is just an example. User should set preferred location to the Cosmos DB region closest to the application + defaultPolicy.setPreferredLocations(Lists.newArrayList("West US")); + + // Create sync client + // + client = new CosmosClientBuilder() + .setEndpoint(AccountSettings.HOST) + .setKey(AccountSettings.MASTER_KEY) + .setConnectionPolicy(defaultPolicy) + .setConsistencyLevel(ConsistencyLevel.EVENTUAL) + .buildClient(); + + // + + createDatabaseIfNotExists(); + + //Here is where index management is performed + createContainerIfNotExistsWithSpecifiedIndex(); + + // Setup family items to create + ArrayList familiesToCreate = new ArrayList<>(); + familiesToCreate.add(Families.getAndersenFamilyItem()); + familiesToCreate.add(Families.getWakefieldFamilyItem()); + familiesToCreate.add(Families.getJohnsonFamilyItem()); + familiesToCreate.add(Families.getSmithFamilyItem()); + + createFamilies(familiesToCreate); + + logger.info("Reading items."); + readItems(familiesToCreate); + + logger.info("Querying items."); + queryItems(); + } + + private void createDatabaseIfNotExists() throws Exception { + logger.info("Create database " + databaseName + " if not exists."); + + // Create database if not exists + // + database = client.createDatabaseIfNotExists(databaseName).getDatabase(); + // + + logger.info("Checking database " + database.getId() + " completed!\n"); + } + + private void createContainerIfNotExistsWithSpecifiedIndex() throws Exception { + logger.info("Create container " + containerName + " if not exists."); + + // Create container if not exists + CosmosContainerProperties containerProperties = + new CosmosContainerProperties(containerName, "/lastName"); + + // + IndexingPolicy indexingPolicy = new IndexingPolicy(); + indexingPolicy.setIndexingMode(IndexingMode.CONSISTENT); //To turn indexing off set IndexingMode.NONE + + // Included paths + List includedPaths = new ArrayList<>(); + IncludedPath includedPath = new IncludedPath(); + includedPath.setPath("/*"); + includedPaths.add(includedPath); + indexingPolicy.setIncludedPaths(includedPaths); + + // Excluded paths + List excludedPaths = new ArrayList<>(); + ExcludedPath excludedPath = new ExcludedPath(); + excludedPath.setPath("/name/*"); + excludedPaths.add(excludedPath); + indexingPolicy.setExcludedPaths(excludedPaths); + + // Spatial indices - if you need them, here is how to set them up: + /* + List spatialIndexes = new ArrayList(); + List collectionOfSpatialTypes = new ArrayList(); + + SpatialSpec spec = new SpatialSpec(); + spec.setPath("/locations/*"); + collectionOfSpatialTypes.add(SpatialType.Point); + spec.setSpatialTypes(collectionOfSpatialTypes); + spatialIndexes.add(spec); + + indexingPolicy.setSpatialIndexes(spatialIndexes); + */ + + // Composite indices - if you need them, here is how to set them up: + /* + List> compositeIndexes = new ArrayList<>(); + List compositePaths = new ArrayList<>(); + + CompositePath nameCompositePath = new CompositePath(); + nameCompositePath.setPath("/name"); + nameCompositePath.setOrder(CompositePathSortOrder.ASCENDING); + + CompositePath ageCompositePath = new CompositePath(); + ageCompositePath.setPath("/age"); + ageCompositePath.setOrder(CompositePathSortOrder.DESCENDING); + + compositePaths.add(ageCompositePath); + compositePaths.add(nameCompositePath); + + compositeIndexes.add(compositePaths); + indexingPolicy.setCompositeIndexes(compositeIndexes); + */ + + containerProperties.setIndexingPolicy(indexingPolicy); + + // + + // Create container with 400 RU/s + container = database.createContainerIfNotExists(containerProperties, 400).getContainer(); + + logger.info("Checking container " + container.getId() + " completed!\n"); + } + + private void createFamilies(List families) throws Exception { + double totalRequestCharge = 0; + for (Family family : families) { + + // + // Create item using container that we created using sync client + + // Use lastName as partitionKey for cosmos item + // Using appropriate partition key improves the performance of database operations + CosmosItemRequestOptions cosmosItemRequestOptions = new CosmosItemRequestOptions(); + CosmosItemResponse item = container.createItem(family, new PartitionKey(family.getLastName()), cosmosItemRequestOptions); + // + + // Get request charge and other properties like latency, and diagnostics strings, etc. + logger.info(String.format("Created item with request charge of %.2f within" + + " duration %s", + item.getRequestCharge(), item.getRequestLatency())); + totalRequestCharge += item.getRequestCharge(); + } + logger.info(String.format("Created %d items with total request " + + "charge of %.2f", + families.size(), + totalRequestCharge)); + } + + private void readItems(ArrayList familiesToCreate) { + // Using partition key for point read scenarios. + // This will help fast look up of items because of partition key + familiesToCreate.forEach(family -> { + // + try { + CosmosItemResponse item = container.readItem(family.getId(), new PartitionKey(family.getLastName()), Family.class); + double requestCharge = item.getRequestCharge(); + Duration requestLatency = item.getRequestLatency(); + logger.info(String.format("Item successfully read with id %s with a charge of %.2f and within duration %s", + item.getResource().getId(), requestCharge, requestLatency)); + } catch (CosmosClientException e) { + e.printStackTrace(); + logger.error(String.format("Read Item failed with %s", e)); + } + // + }); + } + + private void queryItems() { + // + // Set some common query options + FeedOptions queryOptions = new FeedOptions(); + queryOptions.setMaxItemCount(10); + // Set populate query metrics to get metrics around query executions + queryOptions.setPopulateQueryMetrics(true); + + CosmosPagedIterable familiesPagedIterable = container.queryItems( + "SELECT * FROM Family WHERE Family.lastName IN ('Andersen', 'Wakefield', 'Johnson')", queryOptions, Family.class); + + familiesPagedIterable.iterableByPage().forEach(cosmosItemPropertiesFeedResponse -> { + logger.info("Got a page of query result with " + + cosmosItemPropertiesFeedResponse.getResults().size() + " items(s)" + + " and request charge of " + cosmosItemPropertiesFeedResponse.getRequestCharge()); + + logger.info("Item Ids " + cosmosItemPropertiesFeedResponse + .getResults() + .stream() + .map(Family::getId) + .collect(Collectors.toList())); + }); + // + } + + private void shutdown() { + try { + //Clean shutdown + logger.info("Deleting Cosmos DB resources"); + logger.info("-Deleting container..."); + if (container != null) + container.delete(); + logger.info("-Deleting database..."); + if (database != null) + database.delete(); + logger.info("-Closing the client..."); + } catch (Exception err) { + logger.error("Deleting Cosmos DB resources failed, will still attempt to close the client. See stack trace below."); + err.printStackTrace(); + } + client.close(); + logger.info("Done."); + } +} diff --git a/src/main/java/com/azure/cosmos/examples/storedprocedure/async/SampleStoredProcedureAsync.java b/src/main/java/com/azure/cosmos/examples/storedprocedure/async/SampleStoredProcedureAsync.java new file mode 100644 index 0000000..21ca683 --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/storedprocedure/async/SampleStoredProcedureAsync.java @@ -0,0 +1,222 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.storedprocedure.async; + +import com.azure.cosmos.ConnectionPolicy; +import com.azure.cosmos.ConsistencyLevel; +import com.azure.cosmos.CosmosAsyncClient; +import com.azure.cosmos.CosmosAsyncContainer; +import com.azure.cosmos.CosmosAsyncDatabase; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.CosmosClientException; +import com.azure.cosmos.CosmosPagedFlux; +import com.azure.cosmos.examples.changefeed.SampleChangeFeedProcessor; +import com.azure.cosmos.examples.common.AccountSettings; +import com.azure.cosmos.examples.common.CustomPOJO; +import com.azure.cosmos.models.CosmosAsyncItemResponse; +import com.azure.cosmos.models.CosmosContainerProperties; +import com.azure.cosmos.models.CosmosStoredProcedureProperties; +import com.azure.cosmos.models.CosmosStoredProcedureRequestOptions; +import com.azure.cosmos.models.FeedOptions; +import com.azure.cosmos.models.PartitionKey; +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; + +import java.util.concurrent.CountDownLatch; + +public class SampleStoredProcedureAsync { + + private CosmosAsyncClient client; + + private final String databaseName = "SprocTestDB"; + private final String containerName = "SprocTestContainer"; + + private CosmosAsyncDatabase database; + private CosmosAsyncContainer container; + + private String sprocId; + + protected static Logger logger = LoggerFactory.getLogger(SampleChangeFeedProcessor.class.getSimpleName()); + + public void close() { + client.close(); + } + + /** + * Stored Procedure Example + *

+ * This sample code demonstrates creation, execution, and effects of stored procedures + * using Java SDK. A stored procedure is created which will insert a JSON object into + * a Cosmos DB container. The sample executes the stored procedure and then performs + * a point-read to confirm that the stored procedure had the intended effect. + */ + //

+ public static void main(String[] args) { + SampleStoredProcedureAsync p = new SampleStoredProcedureAsync(); + + try { + p.sprocDemo(); + logger.info("Demo complete, please hold while resources are released"); + p.shutdown(); + logger.info("Done.\n"); + } catch (Exception e) { + e.printStackTrace(); + logger.info(String.format("Cosmos getStarted failed with %s", e)); + p.close(); + } finally { + } + } + + //
+ + private void sprocDemo() throws Exception { + //Setup client, DB, and the container for which we will create stored procedures + //The container partition key will be id + setUp(); + + //Create stored procedure and list all stored procedures that have been created. + createStoredProcedure(); + readAllSprocs(); + + //Execute the stored procedure, which we expect will create an item with id test_doc + executeStoredProcedure(); + + //Perform a point-read to confirm that the item with id test_doc exists + logger.info("Checking that a document was created by the stored procedure..."); + CosmosAsyncItemResponse test_resp = + container.readItem("test_doc", new PartitionKey("test_doc"), CustomPOJO.class).block(); + logger.info(String.format( + "Status return value of point-read for document created by stored procedure (200 indicates success): %d", test_resp.getStatusCode())); + } + + public void setUp() throws Exception { + logger.info("Using Azure Cosmos DB endpoint: " + AccountSettings.HOST); + + ConnectionPolicy defaultPolicy = ConnectionPolicy.getDefaultPolicy(); + // Setting the preferred location to Cosmos DB Account region + // West US is just an example. User should set preferred location to the Cosmos DB region closest to the application + defaultPolicy.setPreferredLocations(Lists.newArrayList("West US")); + + // Create sync client + // + client = new CosmosClientBuilder() + .setEndpoint(AccountSettings.HOST) + .setKey(AccountSettings.MASTER_KEY) + .setConnectionPolicy(defaultPolicy) + .setConsistencyLevel(ConsistencyLevel.EVENTUAL) + .buildAsyncClient(); + + logger.info("Create database " + databaseName + " with container " + containerName + " if either does not already exist.\n"); + + client.createDatabaseIfNotExists(databaseName).flatMap(databaseResponse -> { + database = databaseResponse.getDatabase(); + return Mono.empty(); + }).block(); + + CosmosContainerProperties containerProperties = + new CosmosContainerProperties(containerName, "/id"); + database.createContainerIfNotExists(containerProperties, 400).flatMap(containerResponse -> { + container = containerResponse.getContainer(); + return Mono.empty(); + }).block(); + } + + public void shutdown() throws Exception { + //Safe clean & close + deleteStoredProcedure(); + } + + public void createStoredProcedure() throws Exception { + logger.info("Creating stored procedure...\n"); + + sprocId = "createMyDocument"; + String sprocBody = "function createMyDocument() {\n" + + "var documentToCreate = {\"id\":\"test_doc\"}\n" + + "var context = getContext();\n" + + "var collection = context.getCollection();\n" + + "var accepted = collection.createDocument(collection.getSelfLink(), documentToCreate,\n" + + " function (err, documentCreated) {\n" + + "if (err) throw new Error('Error' + err.message);\n" + + "context.getResponse().setBody(documentCreated.id)\n" + + "});\n" + + "if (!accepted) return;\n" + + "}"; + CosmosStoredProcedureProperties storedProcedureDef = new CosmosStoredProcedureProperties(sprocId, sprocBody); + container.getScripts() + .createStoredProcedure(storedProcedureDef, + new CosmosStoredProcedureRequestOptions()).block(); + } + + private void readAllSprocs() throws Exception { + + FeedOptions feedOptions = new FeedOptions(); + CosmosPagedFlux fluxResponse = + container.getScripts().readAllStoredProcedures(feedOptions); + + final CountDownLatch completionLatch = new CountDownLatch(1); + + + fluxResponse.flatMap(storedProcedureProperties -> { + logger.info(String.format("Stored Procedure: %s\n", storedProcedureProperties.getId())); + return Mono.empty(); + }).subscribe( + s -> { + }, + err -> { + if (err instanceof CosmosClientException) { + //Client-specific errors + CosmosClientException cerr = (CosmosClientException) err; + cerr.printStackTrace(); + logger.info(String.format("Read Item failed with %s\n", cerr)); + } else { + //General errors + err.printStackTrace(); + } + + completionLatch.countDown(); + }, + () -> { + completionLatch.countDown(); + } + ); + + completionLatch.await(); + } + + public void executeStoredProcedure() throws Exception { + logger.info(String.format("Executing stored procedure %s...\n\n", sprocId)); + + CosmosStoredProcedureRequestOptions options = new CosmosStoredProcedureRequestOptions(); + options.setPartitionKey(new PartitionKey("test_doc")); + + container.getScripts() + .getStoredProcedure(sprocId) + .execute(null, options) + .flatMap(executeResponse -> { + logger.info(String.format("Stored procedure %s returned %s (HTTP %d), at cost %.3f RU.\n", + sprocId, + executeResponse.getResponseAsString(), + executeResponse.getStatusCode(), + executeResponse.getRequestCharge())); + return Mono.empty(); + }).block(); + } + + public void deleteStoredProcedure() throws Exception { + logger.info("-Deleting stored procedure...\n"); + container.getScripts() + .getStoredProcedure(sprocId) + .delete().block(); + logger.info("-Deleting database...\n"); + database.delete().block(); + logger.info("-Closing client instance...\n"); + client.close(); + } +} diff --git a/src/main/java/com/azure/cosmos/examples/storedprocedure/sync/SampleStoredProcedure.java b/src/main/java/com/azure/cosmos/examples/storedprocedure/sync/SampleStoredProcedure.java new file mode 100644 index 0000000..2061c5a --- /dev/null +++ b/src/main/java/com/azure/cosmos/examples/storedprocedure/sync/SampleStoredProcedure.java @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.examples.storedprocedure.sync; + +import com.azure.cosmos.ConnectionPolicy; +import com.azure.cosmos.ConsistencyLevel; +import com.azure.cosmos.CosmosClient; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.CosmosContainer; +import com.azure.cosmos.CosmosDatabase; +import com.azure.cosmos.CosmosPagedIterable; +import com.azure.cosmos.examples.changefeed.SampleChangeFeedProcessor; +import com.azure.cosmos.examples.common.AccountSettings; +import com.azure.cosmos.examples.common.CustomPOJO; +import com.azure.cosmos.models.CosmosContainerProperties; +import com.azure.cosmos.models.CosmosItemResponse; +import com.azure.cosmos.models.CosmosStoredProcedureProperties; +import com.azure.cosmos.models.CosmosStoredProcedureRequestOptions; +import com.azure.cosmos.models.CosmosStoredProcedureResponse; +import com.azure.cosmos.models.FeedOptions; +import com.azure.cosmos.models.PartitionKey; +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Iterator; + +public class SampleStoredProcedure { + + private CosmosClient client; + + private final String databaseName = "SprocTestDB"; + private final String containerName = "SprocTestContainer"; + + private CosmosDatabase database; + private CosmosContainer container; + + private String sprocId; + + protected static Logger logger = LoggerFactory.getLogger(SampleChangeFeedProcessor.class.getSimpleName()); + + public void close() { + client.close(); + } + + /** + * Stored Procedure Example + *

+ * This sample code demonstrates creation, execution, and effects of stored procedures + * using Java SDK. A stored procedure is created which will insert a JSON object into + * a Cosmos DB container. The sample executes the stored procedure and then performs + * a point-read to confirm that the stored procedure had the intended effect. + */ + //

+ public static void main(String[] args) { + SampleStoredProcedure p = new SampleStoredProcedure(); + + try { + p.sprocDemo(); + logger.info("Demo complete, please hold while resources are released"); + p.shutdown(); + logger.info("Done.\n"); + } catch (Exception e) { + e.printStackTrace(); + logger.error(String.format("Cosmos getStarted failed with %s", e)); + p.close(); + } finally { + } + } + + //
+ + private void sprocDemo() throws Exception { + //Setup client, DB, and the container for which we will create stored procedures + //The container partition key will be id + setUp(); + + //Create stored procedure and list all stored procedures that have been created. + createStoredProcedure(); + readAllSprocs(); + + //Execute the stored procedure, which we expect will create an item with id test_doc + executeStoredProcedure(); + + //Perform a point-read to confirm that the item with id test_doc exists + logger.info("Checking that a document was created by the stored procedure..."); + CosmosItemResponse test_resp = container.readItem("test_doc", new PartitionKey("test_doc"), CustomPOJO.class); + logger.info(String.format( + "Result of point-read for document created by stored procedure (200 indicates success): %d", test_resp.getStatusCode())); + } + + public void setUp() throws Exception { + logger.info("Using Azure Cosmos DB endpoint: " + AccountSettings.HOST); + + ConnectionPolicy defaultPolicy = ConnectionPolicy.getDefaultPolicy(); + // Setting the preferred location to Cosmos DB Account region + // West US is just an example. User should set preferred location to the Cosmos DB region closest to the application + defaultPolicy.setPreferredLocations(Lists.newArrayList("West US")); + + // Create sync client + // + client = new CosmosClientBuilder() + .setEndpoint(AccountSettings.HOST) + .setKey(AccountSettings.MASTER_KEY) + .setConnectionPolicy(defaultPolicy) + .setConsistencyLevel(ConsistencyLevel.EVENTUAL) + .buildClient(); + + logger.info("Create database " + databaseName + " with container " + containerName + " if either does not already exist.\n"); + + database = client.createDatabaseIfNotExists(databaseName).getDatabase(); + + CosmosContainerProperties containerProperties = + new CosmosContainerProperties(containerName, "/id"); + container = database.createContainerIfNotExists(containerProperties, 400).getContainer(); + } + + public void shutdown() throws Exception { + //Safe clean & close + deleteStoredProcedure(); + } + + public void createStoredProcedure() throws Exception { + logger.info("Creating stored procedure..."); + + sprocId = "createMyDocument"; + String sprocBody = "function createMyDocument() {\n" + + "var documentToCreate = {\"id\":\"test_doc\"}\n" + + "var context = getContext();\n" + + "var collection = context.getCollection();\n" + + "var accepted = collection.createDocument(collection.getSelfLink(), documentToCreate,\n" + + " function (err, documentCreated) {\n" + + "if (err) throw new Error('Error' + err.message);\n" + + "context.getResponse().setBody(documentCreated.id)\n" + + "});\n" + + "if (!accepted) return;\n" + + "}"; + CosmosStoredProcedureProperties storedProcedureDef = new CosmosStoredProcedureProperties(sprocId, sprocBody); + container.getScripts() + .createStoredProcedure(storedProcedureDef, + new CosmosStoredProcedureRequestOptions()); + } + + private void readAllSprocs() throws Exception { + logger.info("Listing all stored procedures associated with container " + containerName + "\n"); + + FeedOptions feedOptions = new FeedOptions(); + CosmosPagedIterable feedResponseIterable = + container.getScripts().readAllStoredProcedures(feedOptions); + + Iterator feedResponseIterator = feedResponseIterable.iterator(); + + while (feedResponseIterator.hasNext()) { + CosmosStoredProcedureProperties storedProcedureProperties = feedResponseIterator.next(); + logger.info(String.format("Stored Procedure: %s", storedProcedureProperties)); + } + } + + public void executeStoredProcedure() throws Exception { + logger.info(String.format("Executing stored procedure %s...\n\n", sprocId)); + + CosmosStoredProcedureRequestOptions options = new CosmosStoredProcedureRequestOptions(); + options.setPartitionKey(new PartitionKey("test_doc")); + CosmosStoredProcedureResponse executeResponse = container.getScripts() + .getStoredProcedure(sprocId) + .execute(null, options); + + logger.info(String.format("Stored procedure %s returned %s (HTTP %d), at cost %.3f RU.\n", + sprocId, + executeResponse.responseAsString(), + executeResponse.getStatusCode(), + executeResponse.getRequestCharge())); + } + + public void deleteStoredProcedure() throws Exception { + logger.info("-Deleting stored procedure...\n"); + container.getScripts() + .getStoredProcedure(sprocId) + .delete(); + logger.info("-Deleting database...\n"); + database.delete(); + logger.info("-Closing client instance...\n"); + client.close(); + logger.info("Done."); + } +} diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties new file mode 100644 index 0000000..f1178ab --- /dev/null +++ b/src/main/resources/log4j2.properties @@ -0,0 +1,14 @@ +# this is the log4j configuration for tests +# Set root logger level to WARN and its appender to STDOUT. +rootLogger.level=INFO +rootLogger.appenderRef.stdout.ref=STDOUT +logger.netty.name=io.netty +logger.netty.level=INFO +logger.cosmos.name=com.azure.cosmos +logger.cosmos.level=INFO +# STDOUT is a ConsoleAppender and uses PatternLayout. +appender.console.name=STDOUT +appender.console.type=Console +appender.console.layout.type=PatternLayout +appender.console.layout.pattern=%m%n + diff --git a/src/main/resources/multi-master-sample-config.properties b/src/main/resources/multi-master-sample-config.properties new file mode 100644 index 0000000..42c2030 --- /dev/null +++ b/src/main/resources/multi-master-sample-config.properties @@ -0,0 +1,8 @@ +endpoint= +key= +regions=North Central US;North Europe;Southeast Asia +databaseName=multiMasterDemoDB +manualCollectionName=myManualCollection +lwwCollectionName=myLwwCollection +udpCollectionName=myUdpCollection +basicCollectionName=myBasicCollection \ No newline at end of file diff --git a/src/main/resources/resolver-storedproc.txt b/src/main/resources/resolver-storedproc.txt new file mode 100644 index 0000000..e856721 --- /dev/null +++ b/src/main/resources/resolver-storedproc.txt @@ -0,0 +1,45 @@ +function resolver(incomingRecord, existingRecord, isTombstone, conflictingRecords) { + var collection = getContext().getCollection(); + if (!incomingRecord) { + if (existingRecord) { + collection.deleteDocument(existingRecord._self, {}, function(err, responseOptions) { + if (err) throw err; + }); + } + } else if (isTombstone) { + // delete always wins. + } else { + var documentToUse = incomingRecord; + if (existingRecord) { + if (documentToUse.regionId < existingRecord.regionId) { + documentToUse = existingRecord; + } + } + var i; + for (i = 0; i < conflictingRecords.length; i++) { + if (documentToUse.regionId < conflictingRecords[i].regionId) { + documentToUse = conflictingRecords[i]; + } + } + tryDelete(conflictingRecords, incomingRecord, existingRecord, documentToUse); + } + function tryDelete(documents, incoming, existing, documentToInsert) { + if (documents.length > 0) { + collection.deleteDocument(documents[0]._self, {}, function(err, responseOptions) { + if (err) throw err; + documents.shift(); + tryDelete(documents, incoming, existing, documentToInsert); + }); + } else if (existing) { + collection.replaceDocument(existing._self, documentToInsert, + function(err, documentCreated) { + if (err) throw err; + }); + } else { + collection.createDocument(collection.getSelfLink(), documentToInsert, + function(err, documentCreated) { + if (err) throw err; + }); + } + } +} \ No newline at end of file