Working with CosmosDB emulator and Java clients - SQL API

Step 1. Install the Cosmos DB emulator.

  • Refer the Microsoft documentation for installation link

    Issues I faced to start the Cosmos DB service :

Issue 1:

After installation the emulator complained it was not able to open on 8081 port as it was being used by McAfee in Windows 10 machine.

Solution:

In order to address this issue, i navigated to the cosmosDB executable path in command prompt and started the service by passing argument /port=8085, by default the cosmosDB will start the emulator, no need to explicitly pass start there.

Issue 2 During development the java client was not able to access the emulator, I was facing certificate authentication issue.

Solution I followed below steps:

  • Reference: Microsoft answers link
  • Check documentation on how to set the certificate locally, Link
  • From windows certificate manager select personal, search for DocumentDbEmulatorCertificate.
  • Open and copy as file. (don't include private key, choose x509 cer)
  • Move the .cer file to diffrent location, this will be used to create keystore, using below command
    keytool -importcert -alias cosmosdbemu -file documentemulatorcert.cer -keystore cosmosemulator.keystore
    
  • When prompted for password provide it.
  • Since we don't want the jacerts (java) to be updated with this keystore, we created a seprate keystore.
  • We can include this cert within the jvm context using, below in the java code directly.
    System.properties("javax.net.trustStore","/path/to/truststore");
    

Other mentions:

  • In order to use Open JDK 11, I updated the maven pom.xml with the java version

    <properties> <maven.target.version>11</maven.target.version></properties>
    

    and also the maven plugin. Refer the pom.xml in this post.

  • The CosmosDB library also requires SLF4J dependencies and apache commons.

  • Note id in the document is mandatory, without an id in the document cosmosdb will throws exception.

  • Use container.close without this the jvm will be contiously running, simply the client session will be on.

Terminology:

  • Database
  • Container
  • Item (row)
    • Partition key

All the queries are executed via rest api call

IndexPolicy can also be defined, from java. By default, CosmosDb performs indexing.

/*
 //Sample indexes list, which is needs to be indexed

 String indexes = new String[] {
    "modelId/*", // This is the partition key
        "/make/*", 
        "/company/*",
    }
*/
// Pass the indexes that is required for the document and create the indexpolicy
protected IndexingPolicy getIndexingPolicy(String[] indexes) {
        IndexingPolicy indexingPolicy = new IndexingPolicy();

        List<ExcludedPath> excludedPaths = new ArrayList<>();
        excludedPaths.add(new ExcludedPath("/*"));

        List<IncludedPath> includedPaths = new ArrayList<>();
        for(String ind : indexes) {
            includedPaths.add(new IncludedPath(ind));
        }
        indexingPolicy.setIncludedPaths(includedPaths);
        indexingPolicy.setExcludedPaths(excludedPaths);
        return indexingPolicy;
    }

The indexpolicy is set in the CosmosContainerProperties

      CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerName,partitionKeyOfContainer);
      containerProperties.setIndexingPolicy(indexingPolicy); //policy returned by above method
      containerProperties.setDefaultTimeToLiveInSeconds(24*10*3600); //ttl seconds 10 days
      // pass this part of the container creation

      CosmosDatabaseResponse res = client.createDatabaseIfNotExists(databaseName);
      CosmosDatabase database = client.getDatabase(res.getProperties().getId()); 
      CosmosContainerResponse containerResponse = database.createContainerIfNotExists(containerProperties); //passing the index policy
      CosmosContainer container = database.getContainer(containerResponse.getProperties().getId());

Complete java program to working with CosmosDB emulator

package cosmosdbdemo;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
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.ThrottlingRetryOptions;
import com.azure.cosmos.models.CosmosContainerProperties;
import com.azure.cosmos.models.CosmosContainerResponse;
import com.azure.cosmos.models.CosmosDatabaseResponse;
import com.azure.cosmos.models.CosmosItemRequestOptions;
import com.azure.cosmos.models.CosmosItemResponse;
import com.azure.cosmos.models.CosmosQueryRequestOptions;
import com.azure.cosmos.models.FeedResponse;
import com.azure.cosmos.models.PartitionKey;
import com.azure.cosmos.models.SqlParameter;
import com.azure.cosmos.models.SqlQuerySpec;

public class SimpleMainCosmoDemo {

    private CosmosClient client;

    private final String databaseName = "vehicle_db";
    private final String containerName = "cars";
    private final String partionKey = "/modelId";

    public static String MASTER_KEY =
            System.getProperty("ACCOUNT_KEY", 
                    StringUtils.defaultString(StringUtils.trimToNull(
                            System.getenv().get("ACCOUNT_KEY")),"*****"));
        // ******* = token generated in the emulator, check the quick start of data explorer

    public static String HOST =
            System.getProperty("ACCOUNT_HOST",
                    StringUtils.defaultString(StringUtils.trimToNull(
                            System.getenv().get("ACCOUNT_HOST")),
                            "https://localhost:8085"));
    private CosmosDatabase database;
    private CosmosContainer container;

    public static void main(String[] args) {

        System.setProperty ("javax.net.ssl.trustStore", "C:\\user\\learn\\cosmosdb\\cert\\cosmosemulator.keystore");
        System.setProperty ("javax.net.ssl.trustStorePassword", "123456");

        boolean toRead = true;
        System.out.println("STARTED...");
        List<Car> cars = new ArrayList<>();
        SimpleMainCosmoDemo demo = new SimpleMainCosmoDemo();
        cars.add(demo.createCar("100","MD001", "Corola","Toyota", "2000"));
        cars.add(demo.createCar("101","MD002", "Ultima","Nissan", "1990"));
        cars.add(demo.createCar("102","MD004", "Sonata","Hyundai", "1995"));
        cars.add(demo.createCar("103","MD007", "Civic","Honda", "1997"));
        cars.add(demo.createCar("104","MD007", "Accord","Honda", "1991"));
        cars.add(demo.createCar("105","MD009", "Versa","Nissan", "2007"));
        cars.add(demo.createCar("106","MD010", "Impala","Ford", "1999"));

        try {
            demo.createClient();
            demo.validateDatabase();
            if(!toRead) {
             demo.createItemOrRow(cars);
            }else {
             System.out.println("Calling QueryItems () method");
             demo.queryItems();
             System.out.println("Calling QueryUsingSqlSpec () method");
             demo.queryUsingSqlSpec("MD007", "Honda");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("COMPLETED...");
        demo.close();
    }

    private void close() {
        if(client !=null) {
            client.close();
        }
    }

    private Car createCar(String id, String modelId,String make, String company, String year) {
        Car car = new Car();
        car.setId(id);
        car.setModelId(modelId);
        car.setMake(make);
        car.setCompany(company);
        car.setYear(year);
        return car;
    }
    private void createItemOrRow(List<Car> Cars) throws Exception {
        double totalRequestCharge = 0;
        for (Car car : Cars) {

            //  <CreateItem>
            //  Create item using container that we created using sync client
            CosmosItemRequestOptions cosmosItemRequestOptions = new CosmosItemRequestOptions();
            CosmosItemResponse<Car> item = container.createItem(car, cosmosItemRequestOptions);
            //  </CreateItem>

            //  Get request charge and other properties like latency, and diagnostics strings, etc.
            System.out.println(String.format("Created item with request charge of %.2f within" +" duration %s",item.getRequestCharge(), item.getDuration()));
            totalRequestCharge += item.getRequestCharge();
        }
        System.out.println(String.format("Created %d items with total request " + "charge of %.2f", Cars.size(),totalRequestCharge));
    }

       private void queryItems() {
           CosmosQueryRequestOptions queryOptions = new CosmosQueryRequestOptions();

            Iterator<FeedResponse<Car>> feedResponseIterator = container.queryItems(
                "SELECT * FROM c WHERE c.modelId IN ('MD001', 'MD002', 'MD004')", queryOptions,Car.class).iterableByPage().iterator();

            feedResponseIterator.forEachRemaining(cosmosItemPropertiesFeedResponse -> {
                System.out.println("Got a page of query result with " +
                    cosmosItemPropertiesFeedResponse.getResults().size() + " items(s)"
                    + " and request charge of " + cosmosItemPropertiesFeedResponse.getRequestCharge());

                System.out.println("Item Ids ");
               cosmosItemPropertiesFeedResponse
                    .getResults()
                    .stream()
                    .forEach(itm -> System.out.println(itm.toString()));
            });
            //  </QueryItems>
        }

       public void queryUsingSqlSpec(String modelId, String company) {

            CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
            options.setPartitionKey(new PartitionKey(modelId));
            List<SqlParameter> paramList = new ArrayList<SqlParameter>();
            paramList.add(new SqlParameter("@company", company));
            SqlQuerySpec querySpec = new SqlQuerySpec("SELECT * FROM c WHERE c.company = @company", paramList);

            Iterator<FeedResponse<Car>> iter = container.queryItems(querySpec, options,Car.class).iterableByPage().iterator();
            if(iter.hasNext()) {
                FeedResponse<Car> res = iter.next();
                List<Car> resultList = res.getResults();
                while(iter.hasNext()) {
                    res = iter.next();
                    resultList.addAll(res.getResults()); 
                }
                resultList.stream().forEach(itm -> System.out.println(itm.toString()));
            }
       }
    public void validateDatabase() {
        if(client != null) {
            CosmosDatabaseResponse res = client.createDatabaseIfNotExists(databaseName);
            database = client.getDatabase(res.getProperties().getId()); 
            CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerName, partionKey);
            //Note: Cosmos db perform auto indexing based on the documents.
            // in case we need to override we can cuse beliw
            //containerProperties.setIndexingPolicy(indexPolicy);
            //containerProperties.setDefaultTimeToLiveInSeconds(10*24*3600); //10 days in seconds

            CosmosContainerResponse contRes = database.createContainerIfNotExists(containerProperties);
            container =  database.getContainer(contRes.getProperties().getId());
        }
    }

    public void createClient() throws Exception {

        ThrottlingRetryOptions throttlingRetryOptions = new ThrottlingRetryOptions();
        throttlingRetryOptions.setMaxRetryAttemptsOnThrottledRequests(10);//throttle request 
        throttlingRetryOptions.setMaxRetryWaitTime(Duration.ofSeconds(30));//wait to retry

        if(client == null) {
            System.out.println("Client blank, will create client with host and key as "+HOST+" - "+MASTER_KEY);
        }
        try {
            client = new CosmosClientBuilder().endpoint(HOST).key(MASTER_KEY)
                    .throttlingRetryOptions(throttlingRetryOptions)
                    .preferredRegions(Arrays.asList("WEST US"))
                    .contentResponseOnWriteEnabled(true)
                    .consistencyLevel(ConsistencyLevel.EVENTUAL)
                    .buildClient(); 
           // applying EVENTUAL consistency for less latency and higher performance
            if(client != null) {
                System.out.println("Client created");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception(e);
        }
    }

    public void deleteContainer(String dbName, String containerName) {
        if (client != null) {
            CosmosDatabaseResponse res = client.createDatabaseIfNotExists(dbName);
            CosmosDatabase database = client.getDatabase(res.getProperties().getId()); 
            database.getContainer(containerName).delete();
        }
    }
}
class Car{
    private String id;
    private String modelId;
    private String make;
    private String company;
    private String year;

    public String toString() {
        return this.id+", "+this.company+", "+this.make+", "+this.year;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getModelId() {
        return modelId;
    }
    public void setModelId(String modelId) {
        this.modelId = modelId;
    }
    public String getMake() {
        return make;
    }
    public void setMake(String make) {
        this.make = make;
    }
    public String getCompany() {
        return company;
    }
    public void setCompany(String company) {
        this.company = company;
    }
    public String getYear() {
        return year;
    }
    public void setYear(String year) {
        this.year = year;
    }
}

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cosmosdbdemo</groupId>
    <artifactId>cosmosdbdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.compiler.source>11</maven.compiler.source>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.azure</groupId>
            <artifactId>azure-cosmos</artifactId>
            <version>4.20.0</version>
            <!-- <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> 
                <artifactId>jackson-core</artifactId> </exclusion> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> 
                <artifactId>jackson-databind</artifactId> </exclusion> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> 
                <artifactId>jackson-annotations</artifactId> </exclusion> </exclusions> -->
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.32</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.32</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>11</release>
                    <fork>true</fork>
                    <executable>C:\Program Files\AdoptOpenJDK\jdk-11.0.10.9-hotspot\bin\javac</executable> <!-- I was getting no compiler error when using mvn install -->
          <!-- Above is not standard, a generic way to include the javac executable needs to be identified. -->
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>