mirror of https://github.com/dapr/java-sdk.git
				
				
				
			Query State Preview API implementation. (#697)
* Query State Preview API implementation. Signed-off-by: Mukundan Sundararajan <msundar.ms@outlook.com> * Use latest dapr ref and fix grpc query state api Signed-off-by: Mukundan Sundararajan <msundar.ms@outlook.com> * fix service invocation automatic unesacpe Signed-off-by: Mukundan Sundararajan <msundar.ms@outlook.com> * add more unit tests Signed-off-by: Mukundan Sundararajan <msundar.ms@outlook.com> * Add query state API docs Signed-off-by: Mukundan Sundararajan <msundar.ms@outlook.com> * Fix example to be user friendly Signed-off-by: Mukundan Sundararajan <msundar.ms@outlook.com> * Fix example in docs Signed-off-by: Mukundan Sundararajan <msundar.ms@outlook.com> * make pagination immutable Signed-off-by: Mukundan Sundararajan <msundar.ms@outlook.com> Co-authored-by: Artur Souza <artursouza.ms@outlook.com>
This commit is contained in:
		
							parent
							
								
									e3fa24f67b
								
							
						
					
					
						commit
						06d92dafca
					
				|  | @ -20,7 +20,7 @@ jobs: | |||
|       matrix: | ||||
|         java: [ 11, 13, 15, 16 ] | ||||
|     env: | ||||
|       GOVER: 1.15.0 | ||||
|       GOVER: 1.17.7 | ||||
|       GOOS: linux | ||||
|       GOARCH: amd64 | ||||
|       GOPROXY: https://proxy.golang.org | ||||
|  | @ -29,7 +29,7 @@ jobs: | |||
|       DAPR_RUNTIME_VER: 1.6.0-rc.2 | ||||
|       DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.6.0-rc.1/install/install.sh | ||||
|       DAPR_CLI_REF: | ||||
|       DAPR_REF: | ||||
|       DAPR_REF: 5a307f3deaa1b322f7945179adad0403de80eb7e | ||||
|     steps: | ||||
|     - uses: actions/checkout@v3 | ||||
|     - name: Set up OpenJDK ${{ env.JDK_VER }} | ||||
|  | @ -91,6 +91,10 @@ jobs: | |||
|       run: | | ||||
|         docker-compose -f ./sdk-tests/deploy/local-test-vault.yml up -d | ||||
|         docker ps | ||||
|     - name: Install Local mongo database using docker-compose | ||||
|       run: | | ||||
|         docker-compose -f ./sdk-tests/deploy/local-test-mongo.yml up -d | ||||
|         docker ps | ||||
|     - name: Clean up files | ||||
|       run: mvn clean | ||||
|     - name: Build sdk | ||||
|  |  | |||
|  | @ -31,7 +31,7 @@ jobs: | |||
|       matrix: | ||||
|         java: [ 11, 13, 15, 16 ] | ||||
|     env: | ||||
|       GOVER: 1.15.0 | ||||
|       GOVER: 1.17.7 | ||||
|       GOOS: linux | ||||
|       GOARCH: amd64 | ||||
|       GOPROXY: https://proxy.golang.org | ||||
|  | @ -40,7 +40,7 @@ jobs: | |||
|       DAPR_RUNTIME_VER: 1.6.0-rc.2 | ||||
|       DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.6.0-rc.1/install/install.sh | ||||
|       DAPR_CLI_REF: | ||||
|       DAPR_REF: | ||||
|       DAPR_REF: 5a307f3deaa1b322f7945179adad0403de80eb7e | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - name: Set up OpenJDK ${{ env.JDK_VER }} | ||||
|  | @ -108,6 +108,10 @@ jobs: | |||
|           sudo apt-get install vault | ||||
|           # Verify vault is installed | ||||
|           vault -h | ||||
|       - name: Install Local mongo database using docker-compose | ||||
|         run: | | ||||
|           docker-compose -f ./sdk-tests/deploy/local-test-mongo.yml up -d | ||||
|           docker ps | ||||
|       - name: Clean up files | ||||
|         run: mvn clean | ||||
|       - name: Build sdk | ||||
|  | @ -153,4 +157,8 @@ jobs: | |||
|       - name: Validate Configuration API example | ||||
|         working-directory: ./examples | ||||
|         run: | | ||||
|             mm.py ./src/main/java/io/dapr/examples/configuration/grpc/README.md | ||||
|             mm.py ./src/main/java/io/dapr/examples/configuration/grpc/README.md | ||||
|       - name: Validate query state HTTP example | ||||
|         working-directory: ./examples | ||||
|         run: | | ||||
|             mm.py ./src/main/java/io/dapr/examples/querystate/README.md | ||||
|  | @ -241,6 +241,8 @@ public interface DemoActor { | |||
| 
 | ||||
| ### Get & Subscribe to application configurations | ||||
| 
 | ||||
| > Note this is a preview API and thus will only be accessible via the DaprPreviewClient interface and not the normal DaprClient interface | ||||
| 
 | ||||
| ```java | ||||
| import io.dapr.client.DaprClientBuilder; | ||||
| import io.dapr.client.DaprPreviewClient; | ||||
|  | @ -267,5 +269,76 @@ try (DaprPreviewClient client = (new DaprClientBuilder()).buildPreviewClient()) | |||
| - For a full list of configuration operations visit [How-To: Manage configuration from a store]({{< ref howto-manage-configuration.md >}}). | ||||
| - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/configuration) for code samples and instructions to try out different configuration operations. | ||||
| 
 | ||||
| ### Query saved state | ||||
| 
 | ||||
| > Note this is a preview API and thus will only be accessible via the DaprPreviewClient interface and not the normal DaprClient interface | ||||
| 
 | ||||
| ```java | ||||
| import io.dapr.client.DaprClient; | ||||
| import io.dapr.client.DaprClientBuilder; | ||||
| import io.dapr.client.DaprPreviewClient; | ||||
| import io.dapr.client.domain.QueryStateItem; | ||||
| import io.dapr.client.domain.QueryStateRequest; | ||||
| import io.dapr.client.domain.QueryStateResponse; | ||||
| import io.dapr.client.domain.query.Query; | ||||
| import io.dapr.client.domain.query.Sorting; | ||||
| import io.dapr.client.domain.query.filters.EqFilter; | ||||
| 
 | ||||
| try (DaprClient client = builder.build(); DaprPreviewClient previewClient = builder.buildPreviewClient()) { | ||||
|         String searchVal = args.length == 0 ? "searchValue" : args[0]; | ||||
|          | ||||
|         // Create JSON data | ||||
|         Listing first = new Listing(); | ||||
|         first.setPropertyType("apartment"); | ||||
|         first.setId("1000"); | ||||
|         ... | ||||
|         Listing second = new Listing(); | ||||
|         second.setPropertyType("row-house"); | ||||
|         second.setId("1002"); | ||||
|         ... | ||||
|         Listing third = new Listing(); | ||||
|         third.setPropertyType("apartment"); | ||||
|         third.setId("1003"); | ||||
|         ... | ||||
|         Listing fourth = new Listing(); | ||||
|         fourth.setPropertyType("apartment"); | ||||
|         fourth.setId("1001"); | ||||
|         ... | ||||
|         Map<String, String> meta = new HashMap<>(); | ||||
|         meta.put("contentType", "application/json"); | ||||
| 
 | ||||
|         // Save state | ||||
|         SaveStateRequest request = new SaveStateRequest(STATE_STORE_NAME).setStates( | ||||
|           new State<>("1", first, null, meta, null), | ||||
|           new State<>("2", second, null, meta, null), | ||||
|           new State<>("3", third, null, meta, null), | ||||
|           new State<>("4", fourth, null, meta, null) | ||||
|         ); | ||||
|         client.saveBulkState(request).block(); | ||||
|          | ||||
|          | ||||
|         // Create query and query state request | ||||
| 
 | ||||
|         Query query = new Query() | ||||
|           .setFilter(new EqFilter<>("propertyType", "apartment")) | ||||
|           .setSort(Arrays.asList(new Sorting("id", Sorting.Order.DESC))); | ||||
|         QueryStateRequest request = new QueryStateRequest(STATE_STORE_NAME) | ||||
|           .setQuery(query); | ||||
| 
 | ||||
|         // Use preview client to call query state API | ||||
|         QueryStateResponse<MyData> result = previewClient.queryState(request, MyData.class).block(); | ||||
|          | ||||
|         // View Query state response  | ||||
|         System.out.println("Found " + result.getResults().size() + " items."); | ||||
|         for (QueryStateItem<Listing> item : result.getResults()) { | ||||
|           System.out.println("Key: " + item.getKey()); | ||||
|           System.out.println("Data: " + item.getValue()); | ||||
|         } | ||||
| } | ||||
| 
 | ||||
| ``` | ||||
| - For a full list of configuration operations visit [How-To: Query state]({{< ref howto-state-query-api.md >}}). | ||||
| - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/querystate) for complete code sample. | ||||
| 
 | ||||
| ## Related links | ||||
| - [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples) | ||||
|  |  | |||
|  | @ -0,0 +1,14 @@ | |||
| apiVersion: dapr.io/v1alpha1 | ||||
| kind: Component | ||||
| metadata: | ||||
|   name: mongo-statestore | ||||
| spec: | ||||
|   type: state.mongodb | ||||
|   version: v1 | ||||
|   metadata: | ||||
|     - name: host | ||||
|       value: localhost:27017 | ||||
|     - name: databaseName | ||||
|       value: local | ||||
|     - name: collectionName | ||||
|       value: propertyCollection | ||||
|  | @ -61,7 +61,7 @@ | |||
|     <dependency> | ||||
|       <groupId>com.github.os72</groupId> | ||||
|       <artifactId>protoc-jar-maven-plugin</artifactId> | ||||
|       <version>3.10.1</version> | ||||
|       <version>3.11.4</version> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.springframework.boot</groupId> | ||||
|  | @ -130,7 +130,7 @@ | |||
|       <plugin> | ||||
|         <groupId>com.github.os72</groupId> | ||||
|         <artifactId>protoc-jar-maven-plugin</artifactId> | ||||
|         <version>3.10.1</version> | ||||
|         <version>3.11.4</version> | ||||
|         <executions> | ||||
|           <execution> | ||||
|             <phase>generate-sources</phase> | ||||
|  |  | |||
|  | @ -0,0 +1,98 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.examples.querystate; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||||
| 
 | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| public class Listing { | ||||
| 
 | ||||
|   @JsonProperty | ||||
|   private String propertyType; | ||||
| 
 | ||||
|   @JsonProperty | ||||
|   private String id; | ||||
| 
 | ||||
|   @JsonProperty | ||||
|   private String city; | ||||
| 
 | ||||
|   @JsonProperty | ||||
|   private String state; | ||||
| 
 | ||||
|   public Listing() { | ||||
|   } | ||||
| 
 | ||||
|   public String getPropertyType() { | ||||
|     return propertyType; | ||||
|   } | ||||
| 
 | ||||
|   public void setPropertyType(String propertyType) { | ||||
|     this.propertyType = propertyType; | ||||
|   } | ||||
| 
 | ||||
|   public String getId() { | ||||
|     return id; | ||||
|   } | ||||
| 
 | ||||
|   public void setId(String id) { | ||||
|     this.id = id; | ||||
|   } | ||||
| 
 | ||||
|   public String getCity() { | ||||
|     return city; | ||||
|   } | ||||
| 
 | ||||
|   public void setCity(String city) { | ||||
|     this.city = city; | ||||
|   } | ||||
| 
 | ||||
|   public String getState() { | ||||
|     return state; | ||||
|   } | ||||
| 
 | ||||
|   public void setState(String state) { | ||||
|     this.state = state; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return "Listing{" | ||||
|         + "propertyType='" + propertyType + '\'' | ||||
|         + ", id=" + id | ||||
|         + ", city='" + city + '\'' | ||||
|         + ", state='" + state + '\'' | ||||
|         + '}'; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean equals(Object o) { | ||||
|     if (this == o) { | ||||
|       return true; | ||||
|     } | ||||
|     if (o == null || getClass() != o.getClass()) { | ||||
|       return false; | ||||
|     } | ||||
|     Listing listing = (Listing) o; | ||||
|     return id == listing.id | ||||
|         && propertyType.equals(listing.propertyType) | ||||
|         && Objects.equals(city, listing.city) | ||||
|         && Objects.equals(state, listing.state); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public int hashCode() { | ||||
|     return Objects.hash(propertyType, id, city, state); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,114 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.examples.querystate; | ||||
| 
 | ||||
| import io.dapr.client.DaprClient; | ||||
| import io.dapr.client.DaprClientBuilder; | ||||
| import io.dapr.client.DaprPreviewClient; | ||||
| import io.dapr.client.domain.QueryStateItem; | ||||
| import io.dapr.client.domain.QueryStateRequest; | ||||
| import io.dapr.client.domain.QueryStateResponse; | ||||
| import io.dapr.client.domain.SaveStateRequest; | ||||
| import io.dapr.client.domain.State; | ||||
| import io.dapr.client.domain.query.Query; | ||||
| import io.dapr.client.domain.query.Sorting; | ||||
| import io.dapr.client.domain.query.filters.EqFilter; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * 1. Build and install jars: | ||||
|  * mvn clean install | ||||
|  * 2. cd [repo root]/examples | ||||
|  * 3. send a message to be saved as state: | ||||
|  * dapr run --components-path ./components/state -- \ | ||||
|  *   java -Ddapr.api.protocol=HTTP -jar target/dapr-java-sdk-examples-exec.jar \ | ||||
|  *   io.dapr.examples.querystate.QuerySavedState 'my message' | ||||
|  */ | ||||
| public class QuerySavedState { | ||||
| 
 | ||||
|   private static final String STATE_STORE_NAME = "mongo-statestore"; | ||||
| 
 | ||||
|   /** | ||||
|    * Executes the sate actions. | ||||
|    * @param args messages to be sent as state value. | ||||
|    */ | ||||
|   public static void main(String[] args) throws Exception { | ||||
|     DaprClientBuilder builder = new DaprClientBuilder(); | ||||
|     try (DaprClient client = builder.build(); DaprPreviewClient previewClient = builder.buildPreviewClient()) { | ||||
|       System.out.println("Waiting for Dapr sidecar ..."); | ||||
|       client.waitForSidecar(10000).block(); | ||||
|       System.out.println("Dapr sidecar is ready."); | ||||
| 
 | ||||
|       Listing first = new Listing(); | ||||
|       first.setPropertyType("apartment"); | ||||
|       first.setId("1000"); | ||||
|       first.setCity("Seattle"); | ||||
|       first.setState("WA"); | ||||
|       Listing second = new Listing(); | ||||
|       second.setPropertyType("row-house"); | ||||
|       second.setId("1002"); | ||||
|       second.setCity("Seattle"); | ||||
|       second.setState("WA"); | ||||
|       Listing third = new Listing(); | ||||
|       third.setPropertyType("apartment"); | ||||
|       third.setId("1003"); | ||||
|       third.setCity("Portland"); | ||||
|       third.setState("OR"); | ||||
|       Listing fourth = new Listing(); | ||||
|       fourth.setPropertyType("apartment"); | ||||
|       fourth.setId("1001"); | ||||
|       fourth.setCity("Portland"); | ||||
|       fourth.setState("OR"); | ||||
|       Map<String, String> meta = new HashMap<>(); | ||||
|       meta.put("contentType", "application/json"); | ||||
| 
 | ||||
|       SaveStateRequest request = new SaveStateRequest(STATE_STORE_NAME).setStates( | ||||
|           new State<>("1", first, null, meta, null), | ||||
|           new State<>("2", second, null, meta, null), | ||||
|           new State<>("3", third, null, meta, null), | ||||
|           new State<>("4", fourth, null, meta, null) | ||||
|       ); | ||||
|       client.saveBulkState(request).block(); | ||||
| 
 | ||||
|       System.out.println("Insert key: 1" + ", data: " + first); | ||||
|       System.out.println("Insert key: 2" + ", data: " + second); | ||||
|       System.out.println("Insert key: 3" + ", data: " + third); | ||||
|       System.out.println("Insert key: 4" + ", data: " + fourth); | ||||
| 
 | ||||
| 
 | ||||
|       Query query = new Query() | ||||
|           .setFilter(new EqFilter<>("propertyType", "apartment")) | ||||
|           .setSort(Arrays.asList(new Sorting("id", Sorting.Order.DESC))); | ||||
| 
 | ||||
|       QueryStateRequest queryStateRequest = new QueryStateRequest(STATE_STORE_NAME) | ||||
|           .setQuery(query); | ||||
| 
 | ||||
|       QueryStateResponse<Listing> result = previewClient.queryState(queryStateRequest, Listing.class).block(); | ||||
| 
 | ||||
|       System.out.println("Found " + result.getResults().size() + " items."); | ||||
|       for (QueryStateItem<Listing> item : result.getResults()) { | ||||
|         System.out.println("Key: " + item.getKey()); | ||||
|         System.out.println("Data: " + item.getValue()); | ||||
|       } | ||||
| 
 | ||||
|       // This is an example, so for simplicity we are just exiting here. | ||||
|       // Normally a dapr app would be a web service and not exit main. | ||||
|       System.out.println("Done"); | ||||
|       System.exit(0); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,279 @@ | |||
| ## State management sample | ||||
| 
 | ||||
| This sample illustrates the capabilities provided by Dapr Java SDK for querying states. For further information about querying saved state please refer to [this link](https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-state-query-api/) | ||||
| 
 | ||||
| ## Pre-requisites | ||||
| 
 | ||||
| * [Dapr and Dapr Cli](https://docs.dapr.io/getting-started/install-dapr/). | ||||
| * Java JDK 11 (or greater): [Oracle JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) or [OpenJDK](https://jdk.java.net/13/). | ||||
| * [Apache Maven](https://maven.apache.org/install.html) version 3.x. | ||||
| 
 | ||||
| ### Checking out the code | ||||
| 
 | ||||
| Clone this repository: | ||||
| 
 | ||||
| ```sh | ||||
| git clone https://github.com/dapr/java-sdk.git | ||||
| cd java-sdk | ||||
| ``` | ||||
| 
 | ||||
| Then build the Maven project: | ||||
| 
 | ||||
| ```sh | ||||
| # make sure you are in the `java-sdk` directory. | ||||
| mvn install | ||||
| ``` | ||||
| 
 | ||||
| Then change into the `examples` directory: | ||||
| ```sh | ||||
| cd examples | ||||
| ``` | ||||
| 
 | ||||
| ### Running the StateClient | ||||
| This example uses the Java SDK Dapr client in order to save bulk state and query state, in this case, an instance of a class. See the code snippets below: | ||||
| 
 | ||||
| The class saved and queried for is as below: | ||||
| 
 | ||||
| ```java | ||||
| public class Listing { | ||||
| 
 | ||||
|   @JsonProperty | ||||
|   private String propertyType; | ||||
| 
 | ||||
|   @JsonProperty | ||||
|   private String id; | ||||
| 
 | ||||
|   @JsonProperty | ||||
|   private String city; | ||||
| 
 | ||||
|   @JsonProperty | ||||
|   private String state; | ||||
| 
 | ||||
|   public Listing() { | ||||
|   } | ||||
| 
 | ||||
|   public String getPropertyType() { | ||||
|     return propertyType; | ||||
|   } | ||||
| 
 | ||||
|   public void setPropertyType(String propertyType) { | ||||
|     this.propertyType = propertyType; | ||||
|   } | ||||
| 
 | ||||
|   public String getId() { | ||||
|     return id; | ||||
|   } | ||||
| 
 | ||||
|   public void setId(String id) { | ||||
|     this.id = id; | ||||
|   } | ||||
| 
 | ||||
|   public String getCity() { | ||||
|     return city; | ||||
|   } | ||||
| 
 | ||||
|   public void setCity(String city) { | ||||
|     this.city = city; | ||||
|   } | ||||
| 
 | ||||
|   public String getState() { | ||||
|     return state; | ||||
|   } | ||||
| 
 | ||||
|   public void setState(String state) { | ||||
|     this.state = state; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return "Listing{" | ||||
|         + "propertyType='" + propertyType + '\'' | ||||
|         + ", id=" + id | ||||
|         + ", city='" + city + '\'' | ||||
|         + ", state='" + state + '\'' | ||||
|         + '}'; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean equals(Object o) { | ||||
|     if (this == o) { | ||||
|       return true; | ||||
|     } | ||||
|     if (o == null || getClass() != o.getClass()) { | ||||
|       return false; | ||||
|     } | ||||
|     Listing listing = (Listing) o; | ||||
|     return id == listing.id | ||||
|         && propertyType.equals(listing.propertyType) | ||||
|         && Objects.equals(city, listing.city) | ||||
|         && Objects.equals(state, listing.state); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public int hashCode() { | ||||
|     return Objects.hash(propertyType, id, city, state); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| The main application class for the example is as follows:  | ||||
| 
 | ||||
| ```java | ||||
| public class QuerySavedState { | ||||
| 
 | ||||
|   public static class MyData { | ||||
|     ///... | ||||
|   } | ||||
| 
 | ||||
|   private static final String STATE_STORE_NAME = "querystatestore"; | ||||
| 
 | ||||
|   private static final String FIRST_KEY_NAME = "key1"; | ||||
| 
 | ||||
|   private static final String SECOND_KEY_NAME = "key2"; | ||||
| 
 | ||||
|   private static final String THIRD_KEY_NAME = "key3"; | ||||
| 
 | ||||
|   /** | ||||
|    * Executes the sate actions. | ||||
|    * @param args messages to be sent as state value. | ||||
|    */ | ||||
|   public static void main(String[] args) throws Exception { | ||||
|     DaprClientBuilder builder = new DaprClientBuilder(); | ||||
|     try (DaprClient client = builder.build(); DaprPreviewClient previewClient = builder.buildPreviewClient()) { | ||||
|       System.out.println("Waiting for Dapr sidecar ..."); | ||||
|       client.waitForSidecar(10000).block(); | ||||
|       System.out.println("Dapr sidecar is ready."); | ||||
| 
 | ||||
|       Listing first = new Listing(); | ||||
|       first.setPropertyType("apartment"); | ||||
|       first.setId("1000"); | ||||
|       first.setCity("Seattle"); | ||||
|       first.setState("WA"); | ||||
|       Listing second = new Listing(); | ||||
|       second.setPropertyType("row-house"); | ||||
|       second.setId("1002"); | ||||
|       second.setCity("Seattle"); | ||||
|       second.setState("WA"); | ||||
|       Listing third = new Listing(); | ||||
|       third.setPropertyType("apartment"); | ||||
|       third.setId("1003"); | ||||
|       third.setCity("Portland"); | ||||
|       third.setState("OR"); | ||||
|       Listing fourth = new Listing(); | ||||
|       fourth.setPropertyType("apartment"); | ||||
|       fourth.setId("1001"); | ||||
|       fourth.setCity("Portland"); | ||||
|       fourth.setState("OR"); | ||||
|       Map<String, String> meta = new HashMap<>(); | ||||
|       meta.put("contentType", "application/json"); | ||||
| 
 | ||||
|       SaveStateRequest request = new SaveStateRequest(STATE_STORE_NAME).setStates( | ||||
|           new State<>("1", first, null, meta, null), | ||||
|           new State<>("2", second, null, meta, null), | ||||
|           new State<>("3", third, null, meta, null), | ||||
|           new State<>("4", fourth, null, meta, null) | ||||
|       ); | ||||
|       client.saveBulkState(request).block(); | ||||
| 
 | ||||
|       System.out.println("Insert key: 1" + ", data: " + first); | ||||
|       System.out.println("Insert key: 2" + ", data: " + second); | ||||
|       System.out.println("Insert key: 3" + ", data: " + third); | ||||
|       System.out.println("Insert key: 4" + ", data: " + fourth); | ||||
| 
 | ||||
| 
 | ||||
|       Query query = new Query() | ||||
|           .setFilter(new EqFilter<>("propertyType", "apartment")) | ||||
|           .setSort(Arrays.asList(new Sorting("id", Sorting.Order.DESC))); | ||||
| 
 | ||||
|       QueryStateRequest queryStateRequest = new QueryStateRequest(STATE_STORE_NAME) | ||||
|           .setQuery(query); | ||||
| 
 | ||||
|       QueryStateResponse<Listing> result = previewClient.queryState(queryStateRequest, Listing.class).block(); | ||||
| 
 | ||||
|       System.out.println("Found " + result.getResults().size() + " items."); | ||||
|       for (QueryStateItem<Listing> item : result.getResults()) { | ||||
|         System.out.println("Key: " + item.getKey()); | ||||
|         System.out.println("Data: " + item.getValue()); | ||||
|       } | ||||
| 
 | ||||
|       // This is an example, so for simplicity we are just exiting here. | ||||
|       // Normally a dapr app would be a web service and not exit main. | ||||
|       System.out.println("Done"); | ||||
|       System.exit(0); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| The code uses the `DaprClient` created by the `DaprClientBuilder` for waiting for sidecar to start as well as to save state. Notice that this builder uses default settings. Internally, it is using `DefaultObjectSerializer` for two properties: `objectSerializer` is for Dapr's sent and received objects, and `stateSerializer` is for objects to be persisted. | ||||
| 
 | ||||
| The code uses the `DaprPreviewClient` created by the `DaprClientBuilder` is used for the `queryState` preview API. | ||||
| 
 | ||||
| This example performs multiple operations: | ||||
| * `client.waitForSidecar(...)` for waiting until Dapr sidecar is ready. | ||||
| * `client.saveBulkState(...)` for persisting an instance of `Listing`. | ||||
| * `client.query(...)` operation in order to query for persisted state. | ||||
| 
 | ||||
| The Dapr clients are also within a try-with-resource block to properly close the clients at the end. | ||||
| 
 | ||||
| ### Running the example | ||||
| <!-- STEP | ||||
| name: Check state example | ||||
| expected_stdout_lines: | ||||
|   - "== APP == Waiting for Dapr sidecar ..." | ||||
|   - "== APP == Dapr sidecar is ready." | ||||
|   - "== APP == Insert key: 1, data: Listing{propertyType='apartment', id=1000, city='Seattle', state='WA'}" | ||||
|   - "== APP == Insert key: 2, data: Listing{propertyType='row-house', id=1002, city='Seattle', state='WA'}" | ||||
|   - "== APP == Insert key: 3, data: Listing{propertyType='apartment', id=1003, city='Portland', state='OR'}" | ||||
|   - "== APP == Insert key: 4, data: Listing{propertyType='apartment', id=1001, city='Portland', state='OR'}" | ||||
|   - "== APP == Found 3 items." | ||||
|   - "== APP == Key: 3" | ||||
|   - "== APP == Data: Listing{propertyType='apartment', id=1003, city='Portland', state='OR'}" | ||||
|   - "== APP == Key: 4" | ||||
|   - "== APP == Data: Listing{propertyType='apartment', id=1001, city='Portland', state='OR'}" | ||||
|   - "== APP == Key: 1" | ||||
|   - "== APP == Data: Listing{propertyType='apartment', id=1000, city='Seattle', state='WA'}" | ||||
|   - "== APP == Done" | ||||
| background: true | ||||
| sleep: 10  | ||||
| --> | ||||
| 
 | ||||
| Run this example with the following command: | ||||
| ```bash | ||||
| dapr run --components-path ./components/state --app-id query_state_example -H 3600 -- java -Ddapr.api.protocol=HTTP -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.querystate.QuerySavedState | ||||
| ``` | ||||
| 
 | ||||
| <!-- END_STEP --> | ||||
| 
 | ||||
| Once running, the QuerySaveState example should print the output as follows: | ||||
| 
 | ||||
| ```txt | ||||
| == APP == Waiting for Dapr sidecar ... | ||||
| == APP == Dapr sidecar is ready. | ||||
| == APP == Insert key: 1, data: Listing{propertyType='apartment', id=1000, city='Seattle', state='WA'} | ||||
| == APP == Insert key: 2, data: Listing{propertyType='row-house', id=1002, city='Seattle', state='WA'} | ||||
| == APP == Insert key: 3, data: Listing{propertyType='apartment', id=1003, city='Portland', state='OR'} | ||||
| == APP == Insert key: 4, data: Listing{propertyType='apartment', id=1001, city='Portland', state='OR'} | ||||
| == APP == Found 3 items. | ||||
| == APP == Key: 3 | ||||
| == APP == Data: Listing{propertyType='apartment', id=1003, city='Portland', state='OR'} | ||||
| == APP == Key: 4 | ||||
| == APP == Data: Listing{propertyType='apartment', id=1001, city='Portland', state='OR'} | ||||
| == APP == Key: 1 | ||||
| == APP == Data: Listing{propertyType='apartment', id=1000, city='Seattle', state='WA'} | ||||
| == APP == Done | ||||
| ``` | ||||
| Note that the output is got in the descending order of the field `id` and all the `propertyType` field values are the same `apartment`. | ||||
| 
 | ||||
| ### Cleanup | ||||
| 
 | ||||
| To close the app either press `CTRL+C` or run | ||||
| 
 | ||||
| <!-- STEP | ||||
| name: Cleanup | ||||
| --> | ||||
| 
 | ||||
| ```bash | ||||
| dapr stop --app-id query_state_example | ||||
| ``` | ||||
| 
 | ||||
| <!-- END_STEP --> | ||||
|  | @ -167,43 +167,26 @@ dapr run --components-path ./components/state --app-id state_example -- java -ja | |||
| 
 | ||||
| <!-- END_STEP --> | ||||
| 
 | ||||
| Once running, the OutputBindingExample should print the output as follows: | ||||
| Once running, the StateClient should print the output as follows: | ||||
| 
 | ||||
| ```txt | ||||
| == APP == Waiting for Dapr sidecar ... | ||||
| 
 | ||||
| == APP == Dapr sidecar is ready. | ||||
| 
 | ||||
| == APP == Saving class with message: my message | ||||
| 
 | ||||
| == APP == Retrieved class message from state: my message | ||||
| 
 | ||||
| == APP == Updating previous state and adding another state 'test state'...  | ||||
| 
 | ||||
| == APP == Saving updated class with message: my message updated | ||||
| 
 | ||||
| == APP == Retrieved messages using bulk get: | ||||
| 
 | ||||
| == APP == StateKeyValue{key='myKey', value=my message updated, etag='2', metadata={'{}'}, error='null', options={'null'}} | ||||
| 
 | ||||
| == APP == StateKeyValue{key='myKey2', value=test message, etag='1', metadata={'{}'}, error='null', options={'null'}} | ||||
| 
 | ||||
| == APP == Deleting states... | ||||
| 
 | ||||
| == APP == Verify delete key request is aborted if an etag different from stored is passed. | ||||
| 
 | ||||
| == APP == Expected failure. ABORTED: failed deleting state with key myKey: possible etag mismatch. error from state store: ERR Error running script (call to f_9b5da7354cb61e2ca9faff50f6c43b81c73c0b94): @user_script:1: user_script:1: failed to delete Tailmad-Fang||myKey  | ||||
| 
 | ||||
| == APP == Trying to delete again with correct etag. | ||||
| 
 | ||||
| == APP == Trying to retrieve deleted states:  | ||||
| 
 | ||||
| == APP == StateKeyValue{key='myKey', value=null, etag='null', metadata={'{}'}, error='null', options={'null'}} | ||||
| 
 | ||||
| == APP == StateKeyValue{key='myKey2', value=null, etag='null', metadata={'{}'}, error='null', options={'null'}} | ||||
| 
 | ||||
| == APP == Done | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| ### Cleanup | ||||
|  |  | |||
							
								
								
									
										6
									
								
								pom.xml
								
								
								
								
							
							
						
						
									
										6
									
								
								pom.xml
								
								
								
								
							|  | @ -14,9 +14,9 @@ | |||
| 
 | ||||
|   <properties> | ||||
|     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||
|     <grpc.version>1.39.0</grpc.version> | ||||
|     <protobuf.version>3.13.0</protobuf.version> | ||||
|     <dapr.proto.baseurl>https://raw.githubusercontent.com/dapr/dapr/v1.6.0-rc.3/dapr/proto</dapr.proto.baseurl> | ||||
|     <grpc.version>1.42.1</grpc.version> | ||||
|     <protobuf.version>3.17.3</protobuf.version> | ||||
|     <dapr.proto.baseurl>https://raw.githubusercontent.com/dapr/dapr/5a307f3deaa1b322f7945179adad0403de80eb7e/dapr/proto</dapr.proto.baseurl> | ||||
|     <os-maven-plugin.version>1.6.2</os-maven-plugin.version> | ||||
|     <maven-dependency-plugin.version>3.1.1</maven-dependency-plugin.version> | ||||
|     <maven-antrun-plugin.version>1.8</maven-antrun-plugin.version> | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ | |||
| 
 | ||||
|   <properties> | ||||
|     <maven.deploy.skip>false</maven.deploy.skip> | ||||
|     <grpc.version>1.39.0</grpc.version> | ||||
|     <grpc.version>1.42.1</grpc.version> | ||||
|   </properties> | ||||
| 
 | ||||
|   <dependencies> | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ | |||
|     <protobuf.output.directory>${project.build.directory}/generated-sources</protobuf.output.directory> | ||||
|     <protobuf.input.directory>${project.build.directory}/proto</protobuf.input.directory> | ||||
|     <maven.deploy.skip>false</maven.deploy.skip> | ||||
|     <grpc.version>1.39.0</grpc.version> | ||||
|     <grpc.version>1.42.1</grpc.version> | ||||
|   </properties> | ||||
| 
 | ||||
|   <dependencies> | ||||
|  |  | |||
|  | @ -0,0 +1,14 @@ | |||
| apiVersion: dapr.io/v1alpha1 | ||||
| kind: Component | ||||
| metadata: | ||||
|   name: mongo-statestore | ||||
| spec: | ||||
|   type: state.mongodb | ||||
|   version: v1 | ||||
|   metadata: | ||||
|     - name: host | ||||
|       value: localhost:27017 | ||||
|     - name: databaseName | ||||
|       value: local | ||||
|     - name: collectionName | ||||
|       value: testCollection | ||||
|  | @ -0,0 +1,6 @@ | |||
| version: '2' | ||||
| services: | ||||
|   mongo: | ||||
|     image: mongo | ||||
|     ports: | ||||
|       - "27017:27017" | ||||
|  | @ -17,8 +17,8 @@ | |||
|     <dapr.sdk.version>1.5.0-SNAPSHOT</dapr.sdk.version> | ||||
|     <protobuf.output.directory>${project.build.directory}/generated-sources</protobuf.output.directory> | ||||
|     <protobuf.input.directory>${project.basedir}/proto</protobuf.input.directory> | ||||
|     <grpc.version>1.39.0</grpc.version> | ||||
|     <protobuf.version>3.13.0</protobuf.version> | ||||
|     <grpc.version>1.42.1</grpc.version> | ||||
|     <protobuf.version>3.17.3</protobuf.version> | ||||
|     <opentelemetry.version>0.14.0</opentelemetry.version> | ||||
|     <spring-boot.version>2.3.5.RELEASE</spring-boot.version> | ||||
|   </properties> | ||||
|  | @ -52,7 +52,7 @@ | |||
|     <dependency> | ||||
|       <groupId>com.github.os72</groupId> | ||||
|       <artifactId>protoc-jar-maven-plugin</artifactId> | ||||
|       <version>3.10.1</version> | ||||
|       <version>3.11.4</version> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>io.opentelemetry</groupId> | ||||
|  | @ -149,7 +149,7 @@ | |||
|       <plugin> | ||||
|         <groupId>com.github.os72</groupId> | ||||
|         <artifactId>protoc-jar-maven-plugin</artifactId> | ||||
|         <version>3.10.1</version> | ||||
|         <version>3.11.4</version> | ||||
|         <executions> | ||||
|           <execution> | ||||
|             <phase>generate-sources</phase> | ||||
|  |  | |||
|  | @ -30,6 +30,8 @@ public abstract class BaseIT { | |||
| 
 | ||||
|   protected static final String STATE_STORE_NAME = "statestore"; | ||||
| 
 | ||||
|   protected static final String QUERY_STATE_STORE = "mongo-statestore"; | ||||
| 
 | ||||
|   private static final Map<String, DaprRun.Builder> DAPR_RUN_BUILDERS = new HashMap<>(); | ||||
| 
 | ||||
|   private static final Queue<Stoppable> TO_BE_STOPPED = new LinkedList<>(); | ||||
|  |  | |||
|  | @ -13,10 +13,19 @@ limitations under the License. | |||
| 
 | ||||
| package io.dapr.it.state; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import io.dapr.client.DaprClient; | ||||
| import io.dapr.client.DaprPreviewClient; | ||||
| import io.dapr.client.domain.QueryStateItem; | ||||
| import io.dapr.client.domain.QueryStateRequest; | ||||
| import io.dapr.client.domain.QueryStateResponse; | ||||
| import io.dapr.client.domain.SaveStateRequest; | ||||
| import io.dapr.client.domain.State; | ||||
| import io.dapr.client.domain.StateOptions; | ||||
| import io.dapr.client.domain.TransactionalStateOperation; | ||||
| import io.dapr.client.domain.query.Query; | ||||
| import io.dapr.client.domain.query.Sorting; | ||||
| import io.dapr.client.domain.query.filters.EqFilter; | ||||
| import io.dapr.exceptions.DaprException; | ||||
| import io.dapr.it.BaseIT; | ||||
| import org.junit.Test; | ||||
|  | @ -24,8 +33,11 @@ import reactor.core.publisher.Mono; | |||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
| import java.util.logging.Logger; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
|  | @ -39,6 +51,7 @@ import static org.junit.Assert.assertTrue; | |||
|  * Common test cases for Dapr client (GRPC and HTTP). | ||||
|  */ | ||||
| public abstract class AbstractStateClientIT extends BaseIT { | ||||
|   private static final Logger logger = Logger.getLogger(AbstractStateClientIT.class.getName()); | ||||
| 
 | ||||
|   @Test | ||||
|   public void saveAndGetState() { | ||||
|  | @ -126,6 +139,78 @@ public abstract class AbstractStateClientIT extends BaseIT { | |||
|     assertNull(result.stream().skip(2).findFirst().get().getError()); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void saveAndQueryAndDeleteState() throws JsonProcessingException { | ||||
|     final String stateKeyOne = UUID.randomUUID().toString(); | ||||
|     final String stateKeyTwo = UUID.randomUUID().toString(); | ||||
|     final String stateKeyThree = UUID.randomUUID().toString(); | ||||
|     final String commonSearchValue = UUID.randomUUID().toString(); | ||||
|     Map<String,String> meta = new HashMap<>(); | ||||
|     meta.put("contentType", "application/json"); | ||||
| 
 | ||||
|     DaprClient daprClient = buildDaprClient(); | ||||
|     DaprPreviewClient previewApiClient = (DaprPreviewClient) daprClient; | ||||
| 
 | ||||
|     //saves the states. | ||||
|     MyData data = new MyData(); | ||||
|     data.setPropertyA(commonSearchValue); | ||||
|     data.setPropertyB("query"); | ||||
|     State<MyData> state = new State<>(stateKeyOne, data, null, meta, null ); | ||||
|     SaveStateRequest request = new SaveStateRequest(QUERY_STATE_STORE).setStates(state); | ||||
|     daprClient.saveBulkState(request).block(); | ||||
|     data = new MyData(); | ||||
|     data.setPropertyA(commonSearchValue); | ||||
|     data.setPropertyB("query"); | ||||
|     state = new State<>(stateKeyTwo, data, null, meta, null ); | ||||
|     request = new SaveStateRequest(QUERY_STATE_STORE).setStates(state); | ||||
|     daprClient.saveBulkState(request).block(); | ||||
|     data = new MyData(); | ||||
|     data.setPropertyA("CA"); | ||||
|     data.setPropertyB("no query"); | ||||
|     state = new State<>(stateKeyThree, data, null, meta, null ); | ||||
|     request = new SaveStateRequest(QUERY_STATE_STORE).setStates(state); | ||||
|     daprClient.saveBulkState(request).block(); | ||||
| 
 | ||||
| 
 | ||||
|     QueryStateRequest queryStateRequest = new QueryStateRequest(QUERY_STATE_STORE); | ||||
|     Query query = new Query().setFilter(new EqFilter<>("propertyA", commonSearchValue)) | ||||
|             .setSort(Arrays.asList(new Sorting("propertyB", Sorting.Order.ASC))); | ||||
|     queryStateRequest.setQuery(query).setMetadata(meta); | ||||
| 
 | ||||
|     Mono<QueryStateResponse<MyData>> response = previewApiClient.queryState(queryStateRequest, MyData.class); | ||||
|     QueryStateResponse<MyData>  result = response.block(); | ||||
| 
 | ||||
|     // Assert that the response is not null | ||||
|     assertNotNull(result); | ||||
|     List<QueryStateItem<MyData>> items = result.getResults(); | ||||
|     assertNotNull(items); | ||||
| 
 | ||||
|     QueryStateItem<MyData> item; | ||||
|     //Assert that the response is the correct one | ||||
|     assertEquals(2, items.size()); | ||||
|     assertTrue(items.stream().anyMatch(f -> f.getKey().equals(stateKeyOne))); | ||||
|     item = items.stream().filter(f -> f.getKey().equals(stateKeyOne)).findFirst().get(); | ||||
|     assertNotNull(item); | ||||
|     assertEquals(commonSearchValue, item.getValue().getPropertyA()); | ||||
|     assertEquals("query", item.getValue().getPropertyB()); | ||||
|     assertNull(item.getError()); | ||||
| 
 | ||||
|     assertTrue(items.stream().anyMatch(f -> f.getKey().equals(stateKeyTwo))); | ||||
|     item = items.stream().filter(f -> f.getKey().equals(stateKeyTwo)).findFirst().get(); | ||||
|     assertEquals(commonSearchValue, item.getValue().getPropertyA()); | ||||
|     assertEquals("query", item.getValue().getPropertyB()); | ||||
|     assertNull(item.getError()); | ||||
| 
 | ||||
|     assertFalse(items.stream().anyMatch(f -> f.getKey().equals(stateKeyThree))); | ||||
| 
 | ||||
|     assertEquals(2L, items.stream().filter(f -> f.getValue().getPropertyB().equals("query")).count()); | ||||
| 
 | ||||
|     //delete all states | ||||
|     daprClient.deleteState(QUERY_STATE_STORE, stateKeyOne).block(); | ||||
|     daprClient.deleteState(QUERY_STATE_STORE, stateKeyTwo).block(); | ||||
|     daprClient.deleteState(QUERY_STATE_STORE, stateKeyThree).block(); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void saveUpdateAndGetState() { | ||||
| 
 | ||||
|  | @ -159,6 +244,7 @@ public abstract class AbstractStateClientIT extends BaseIT { | |||
|     State<MyData> myDataResponse = response.block(); | ||||
| 
 | ||||
|     //review that the update was success action | ||||
|     assertNotNull("expected non null response", myDataResponse); | ||||
|     assertEquals("data in property A", myDataResponse.getValue().getPropertyA()); | ||||
|     assertEquals("data in property B2", myDataResponse.getValue().getPropertyB()); | ||||
|   } | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ | |||
| 
 | ||||
|   <properties> | ||||
|     <maven.deploy.skip>false</maven.deploy.skip> | ||||
|     <grpc.version>1.39.0</grpc.version> | ||||
|     <grpc.version>1.42.1</grpc.version> | ||||
|     <argLine> | ||||
|       --add-opens java.base/java.util=ALL-UNNAMED | ||||
|     </argLine> | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ limitations under the License. | |||
| 
 | ||||
| package io.dapr.client; | ||||
| 
 | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import io.dapr.client.domain.ConfigurationItem; | ||||
| import io.dapr.client.domain.DeleteStateRequest; | ||||
| import io.dapr.client.domain.ExecuteStateTransactionRequest; | ||||
|  | @ -25,11 +26,14 @@ import io.dapr.client.domain.HttpExtension; | |||
| import io.dapr.client.domain.InvokeBindingRequest; | ||||
| import io.dapr.client.domain.InvokeMethodRequest; | ||||
| import io.dapr.client.domain.PublishEventRequest; | ||||
| import io.dapr.client.domain.QueryStateRequest; | ||||
| import io.dapr.client.domain.QueryStateResponse; | ||||
| import io.dapr.client.domain.SaveStateRequest; | ||||
| import io.dapr.client.domain.State; | ||||
| import io.dapr.client.domain.StateOptions; | ||||
| import io.dapr.client.domain.SubscribeConfigurationRequest; | ||||
| import io.dapr.client.domain.TransactionalStateOperation; | ||||
| import io.dapr.client.domain.query.Query; | ||||
| import io.dapr.serializer.DaprObjectSerializer; | ||||
| import io.dapr.utils.TypeRef; | ||||
| import reactor.core.publisher.Flux; | ||||
|  | @ -50,6 +54,11 @@ import java.util.stream.Collectors; | |||
|  */ | ||||
| abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { | ||||
| 
 | ||||
|   /** | ||||
|    * A mapper to serialize JSON request objects. | ||||
|    */ | ||||
|   protected static final ObjectMapper JSON_REQUEST_MAPPER = new ObjectMapper(); | ||||
| 
 | ||||
|   /** | ||||
|    * A utility class for serialize and deserialize the transient objects. | ||||
|    */ | ||||
|  | @ -130,7 +139,7 @@ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { | |||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<T> invokeMethod( | ||||
|           String appId, String methodName, HttpExtension httpExtension, Map<String, String> metadata, TypeRef<T> type) { | ||||
|       String appId, String methodName, HttpExtension httpExtension, Map<String, String> metadata, TypeRef<T> type) { | ||||
|     return this.invokeMethod(appId, methodName, null, httpExtension, metadata, type); | ||||
|   } | ||||
| 
 | ||||
|  | @ -139,7 +148,7 @@ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { | |||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<T> invokeMethod( | ||||
|           String appId, String methodName, HttpExtension httpExtension, Map<String, String> metadata, Class<T> clazz) { | ||||
|       String appId, String methodName, HttpExtension httpExtension, Map<String, String> metadata, Class<T> clazz) { | ||||
|     return this.invokeMethod(appId, methodName, null, httpExtension, metadata, TypeRef.get(clazz)); | ||||
|   } | ||||
| 
 | ||||
|  | @ -174,7 +183,7 @@ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { | |||
|    */ | ||||
|   @Override | ||||
|   public Mono<Void> invokeMethod( | ||||
|           String appId, String methodName, Object request, HttpExtension httpExtension, Map<String, String> metadata) { | ||||
|       String appId, String methodName, Object request, HttpExtension httpExtension, Map<String, String> metadata) { | ||||
|     return this.invokeMethod(appId, methodName, request, httpExtension, metadata, TypeRef.BYTE_ARRAY).then(); | ||||
|   } | ||||
| 
 | ||||
|  | @ -183,7 +192,7 @@ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { | |||
|    */ | ||||
|   @Override | ||||
|   public Mono<Void> invokeMethod( | ||||
|           String appId, String methodName, HttpExtension httpExtension, Map<String, String> metadata) { | ||||
|       String appId, String methodName, HttpExtension httpExtension, Map<String, String> metadata) { | ||||
|     return this.invokeMethod(appId, methodName, null, httpExtension, metadata, TypeRef.BYTE_ARRAY).then(); | ||||
|   } | ||||
| 
 | ||||
|  | @ -192,7 +201,7 @@ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { | |||
|    */ | ||||
|   @Override | ||||
|   public Mono<byte[]> invokeMethod( | ||||
|           String appId, String methodName, byte[] request, HttpExtension httpExtension, Map<String, String> metadata) { | ||||
|       String appId, String methodName, byte[] request, HttpExtension httpExtension, Map<String, String> metadata) { | ||||
|     return this.invokeMethod(appId, methodName, request, httpExtension, metadata, TypeRef.BYTE_ARRAY); | ||||
|   } | ||||
| 
 | ||||
|  | @ -233,7 +242,7 @@ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { | |||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<T> invokeBinding( | ||||
|           String bindingName, String operation, Object data, Map<String, String> metadata, TypeRef<T> type) { | ||||
|       String bindingName, String operation, Object data, Map<String, String> metadata, TypeRef<T> type) { | ||||
|     InvokeBindingRequest request = new InvokeBindingRequest(bindingName, operation) | ||||
|         .setData(data) | ||||
|         .setMetadata(metadata); | ||||
|  | @ -246,7 +255,7 @@ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { | |||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<T> invokeBinding( | ||||
|           String bindingName, String operation, Object data, Map<String, String> metadata, Class<T> clazz) { | ||||
|       String bindingName, String operation, Object data, Map<String, String> metadata, Class<T> clazz) { | ||||
|     return this.invokeBinding(bindingName, operation, data, metadata, TypeRef.get(clazz)); | ||||
|   } | ||||
| 
 | ||||
|  | @ -287,7 +296,7 @@ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { | |||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<State<T>> getState( | ||||
|           String storeName, String key, StateOptions options, TypeRef<T> type) { | ||||
|       String storeName, String key, StateOptions options, TypeRef<T> type) { | ||||
|     GetStateRequest request = new GetStateRequest(storeName, key) | ||||
|         .setStateOptions(options); | ||||
|     return this.getState(request, type); | ||||
|  | @ -299,10 +308,86 @@ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { | |||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<State<T>> getState( | ||||
|           String storeName, String key, StateOptions options, Class<T> clazz) { | ||||
|       String storeName, String key, StateOptions options, Class<T> clazz) { | ||||
|     return this.getState(storeName, key, options, TypeRef.get(clazz)); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(String storeName, String query, Map<String, String> metadata, | ||||
|                                                     Class<T> clazz) { | ||||
|     return this.queryState(new QueryStateRequest(storeName).setQueryString(query).setMetadata(metadata), clazz); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(String storeName, String query, Map<String, String> metadata, | ||||
|                                                     TypeRef<T> type) { | ||||
|     return this.queryState(new QueryStateRequest(storeName).setQueryString(query).setMetadata(metadata), type); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(String storeName, String query, Class<T> clazz) { | ||||
|     return this.queryState(new QueryStateRequest(storeName).setQueryString(query), clazz); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(String storeName, String query, TypeRef<T> type) { | ||||
|     return this.queryState(new QueryStateRequest(storeName).setQueryString(query), type); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(String storeName, Query query, Map<String, String> metadata, | ||||
|                                                     Class<T> clazz) { | ||||
|     return this.queryState(new QueryStateRequest(storeName).setQuery(query).setMetadata(metadata), clazz); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(String storeName, Query query, Map<String, String> metadata, | ||||
|                                                     TypeRef<T> type) { | ||||
|     return this.queryState(new QueryStateRequest(storeName).setQuery(query).setMetadata(metadata), type); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(String storeName, Query query, Class<T> clazz) { | ||||
|     return this.queryState(new QueryStateRequest(storeName).setQuery(query), clazz); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(String storeName, Query query, TypeRef<T> type) { | ||||
|     return this.queryState(new QueryStateRequest(storeName).setQuery(query), type); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(QueryStateRequest request, Class<T> clazz) { | ||||
|     return this.queryState(request, TypeRef.get(clazz)); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|  |  | |||
|  | @ -29,6 +29,9 @@ import io.dapr.client.domain.HttpExtension; | |||
| import io.dapr.client.domain.InvokeBindingRequest; | ||||
| import io.dapr.client.domain.InvokeMethodRequest; | ||||
| import io.dapr.client.domain.PublishEventRequest; | ||||
| import io.dapr.client.domain.QueryStateItem; | ||||
| import io.dapr.client.domain.QueryStateRequest; | ||||
| import io.dapr.client.domain.QueryStateResponse; | ||||
| import io.dapr.client.domain.SaveStateRequest; | ||||
| import io.dapr.client.domain.State; | ||||
| import io.dapr.client.domain.StateOptions; | ||||
|  | @ -88,7 +91,7 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|    * Default access level constructor, in order to create an instance of this class use io.dapr.client.DaprClientBuilder | ||||
|    * | ||||
|    * @param closeableChannel A closeable for a Managed GRPC channel | ||||
|    * @param asyncStub     async gRPC stub | ||||
|    * @param asyncStub        async gRPC stub | ||||
|    * @param objectSerializer Serializer for transient request/response objects. | ||||
|    * @param stateSerializer  Serializer for state objects. | ||||
|    * @see DaprClientBuilder | ||||
|  | @ -167,10 +170,10 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|       } | ||||
| 
 | ||||
|       return Mono.subscriberContext().flatMap( | ||||
|               context -> | ||||
|                   this.<Empty>createMono( | ||||
|                       it -> intercept(context, asyncStub).publishEvent(envelopeBuilder.build(), it) | ||||
|                   ) | ||||
|           context -> | ||||
|               this.<Empty>createMono( | ||||
|                   it -> intercept(context, asyncStub).publishEvent(envelopeBuilder.build(), it) | ||||
|               ) | ||||
|       ).then(); | ||||
|     } catch (Exception ex) { | ||||
|       return DaprException.wrapMono(ex); | ||||
|  | @ -204,7 +207,7 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|               it -> { | ||||
|                 try { | ||||
|                   return Mono.justOrEmpty(objectSerializer.deserialize(it.getData().getValue().toByteArray(), type)); | ||||
|                 } catch (IOException e) { | ||||
|                 } catch (IOException e)  { | ||||
|                   throw DaprException.propagate(e); | ||||
|                 } | ||||
|               } | ||||
|  | @ -247,7 +250,7 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|               context -> this.<DaprProtos.InvokeBindingResponse>createMono( | ||||
|                   it -> intercept(context, asyncStub).invokeBinding(envelope, it) | ||||
|               ) | ||||
|       ).flatMap( | ||||
|           ).flatMap( | ||||
|               it -> { | ||||
|                 try { | ||||
|                   return Mono.justOrEmpty(objectSerializer.deserialize(it.getData().toByteArray(), type)); | ||||
|  | @ -343,7 +346,7 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|               context -> this.<DaprProtos.GetBulkStateResponse>createMono(it -> intercept(context, asyncStub) | ||||
|                   .getBulkState(envelope, it) | ||||
|               ) | ||||
|       ).map( | ||||
|           ).map( | ||||
|               it -> | ||||
|                 it | ||||
|                   .getItemsList() | ||||
|  | @ -414,7 +417,7 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|       if (metadata != null) { | ||||
|         builder.putAllMetadata(metadata); | ||||
|       } | ||||
|       for (TransactionalStateOperation<?> operation: operations) { | ||||
|       for (TransactionalStateOperation<?> operation : operations) { | ||||
|         DaprProtos.TransactionalStateOperation.Builder operationBuilder = DaprProtos.TransactionalStateOperation | ||||
|             .newBuilder(); | ||||
|         operationBuilder.setOperationType(operation.getOperation().toString().toLowerCase()); | ||||
|  | @ -603,8 +606,8 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|     } | ||||
| 
 | ||||
|     DaprProtos.GetSecretRequest.Builder requestBuilder = DaprProtos.GetSecretRequest.newBuilder() | ||||
|           .setStoreName(secretStoreName) | ||||
|           .setKey(key); | ||||
|               .setStoreName(secretStoreName) | ||||
|               .setKey(key); | ||||
| 
 | ||||
|     if (metadata != null) { | ||||
|       requestBuilder.putAllMetadata(metadata); | ||||
|  | @ -639,7 +642,7 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|       return Mono.subscriberContext().flatMap( | ||||
|           context -> | ||||
|             this.<DaprProtos.GetBulkSecretResponse>createMono( | ||||
|               it -> intercept(context, asyncStub).getBulkSecret(envelope, it) | ||||
|                 it -> intercept(context, asyncStub).getBulkSecret(envelope, it) | ||||
|             ) | ||||
|       ).map(it -> { | ||||
|         Map<String, DaprProtos.SecretResponse> secretsMap = it.getDataMap(); | ||||
|  | @ -656,10 +659,87 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(QueryStateRequest request, TypeRef<T> type) { | ||||
|     try { | ||||
|       if (request == null) { | ||||
|         throw new IllegalArgumentException("Query state request cannot be null."); | ||||
|       } | ||||
|       final String storeName = request.getStoreName(); | ||||
|       final Map<String, String> metadata = request.getMetadata(); | ||||
|       if ((storeName == null) || (storeName.trim().isEmpty())) { | ||||
|         throw new IllegalArgumentException("State store name cannot be null or empty."); | ||||
|       } | ||||
| 
 | ||||
|       String queryString; | ||||
|       if (request.getQuery() != null) { | ||||
|         queryString = JSON_REQUEST_MAPPER.writeValueAsString(request.getQuery()); | ||||
|       } else if (request.getQueryString() != null) { | ||||
|         queryString = request.getQueryString(); | ||||
|       } else { | ||||
|         throw new IllegalArgumentException("Both query and queryString fields are not set."); | ||||
|       } | ||||
| 
 | ||||
|       DaprProtos.QueryStateRequest.Builder builder = DaprProtos.QueryStateRequest.newBuilder() | ||||
|           .setStoreName(storeName) | ||||
|           .setQuery(queryString); | ||||
|       if (metadata != null) { | ||||
|         builder.putAllMetadata(metadata); | ||||
|       } | ||||
| 
 | ||||
|       DaprProtos.QueryStateRequest envelope = builder.build(); | ||||
| 
 | ||||
|       return Mono.subscriberContext().flatMap( | ||||
|           context -> this.<DaprProtos.QueryStateResponse>createMono( | ||||
|               it -> intercept(context, asyncStub).queryStateAlpha1(envelope, it) | ||||
|           ) | ||||
|       ).map( | ||||
|           it -> { | ||||
|             Map<String, String> resultMeta = it.getMetadataMap(); | ||||
|             String token = it.getToken(); | ||||
|             List<QueryStateItem<T>> res = it.getResultsList() | ||||
|                 .stream() | ||||
|                 .map(v -> { | ||||
|                   try { | ||||
|                     return buildQueryStateKeyValue(v, type); | ||||
|                   } catch (Exception e) { | ||||
|                     throw  DaprException.propagate(e); | ||||
|                   } | ||||
|                 }) | ||||
|                 .collect(Collectors.toList()); | ||||
|             return new QueryStateResponse<>(res, token).setMetadata(metadata); | ||||
|           }); | ||||
|     } catch (Exception ex) { | ||||
|       return DaprException.wrapMono(ex); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private <T> QueryStateItem<T> buildQueryStateKeyValue( | ||||
|       DaprProtos.QueryStateItem item, | ||||
|       TypeRef<T> type) throws IOException { | ||||
|     String key = item.getKey(); | ||||
|     String error = item.getError(); | ||||
|     if (!Strings.isNullOrEmpty(error)) { | ||||
|       return new QueryStateItem<>(key, null, error); | ||||
|     } | ||||
|     ByteString payload = item.getData(); | ||||
|     byte[] data = payload == null ? null : payload.toByteArray(); | ||||
|     T value = stateSerializer.deserialize(data, type); | ||||
|     String etag = item.getEtag(); | ||||
|     if (etag.equals("")) { | ||||
|       etag = null; | ||||
|     } | ||||
|     return new QueryStateItem<>(key, value, etag); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Closes the ManagedChannel for GRPC. | ||||
|    * @see io.grpc.ManagedChannel#shutdown() | ||||
|    * | ||||
|    * @throws IOException on exception. | ||||
|    * @see io.grpc.ManagedChannel#shutdown() | ||||
|    */ | ||||
|   @Override | ||||
|   public void close() throws Exception { | ||||
|  | @ -677,8 +757,8 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|   @Override | ||||
|   public Mono<Void> shutdown() { | ||||
|     return Mono.subscriberContext().flatMap( | ||||
|             context -> this.<Empty>createMono( | ||||
|                     it -> intercept(context, asyncStub).shutdown(Empty.getDefaultInstance(), it)) | ||||
|         context -> this.<Empty>createMono( | ||||
|             it -> intercept(context, asyncStub).shutdown(Empty.getDefaultInstance(), it)) | ||||
|     ).then(); | ||||
|   } | ||||
| 
 | ||||
|  | @ -810,7 +890,7 @@ public class DaprClientGrpc extends AbstractDaprClient { | |||
|    * Populates GRPC client with interceptors for telemetry. | ||||
|    * | ||||
|    * @param context Reactor's context. | ||||
|    * @param client GRPC client for Dapr. | ||||
|    * @param client  GRPC client for Dapr. | ||||
|    * @return Client after adding interceptors. | ||||
|    */ | ||||
|   private static DaprGrpc.DaprStub intercept(Context context, DaprGrpc.DaprStub client) { | ||||
|  |  | |||
|  | @ -27,6 +27,9 @@ import io.dapr.client.domain.HttpExtension; | |||
| import io.dapr.client.domain.InvokeBindingRequest; | ||||
| import io.dapr.client.domain.InvokeMethodRequest; | ||||
| import io.dapr.client.domain.PublishEventRequest; | ||||
| import io.dapr.client.domain.QueryStateItem; | ||||
| import io.dapr.client.domain.QueryStateRequest; | ||||
| import io.dapr.client.domain.QueryStateResponse; | ||||
| import io.dapr.client.domain.SaveStateRequest; | ||||
| import io.dapr.client.domain.State; | ||||
| import io.dapr.client.domain.StateOptions; | ||||
|  | @ -44,6 +47,7 @@ import reactor.core.publisher.Mono; | |||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.Iterator; | ||||
|  | @ -162,7 +166,7 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|       Map<String, List<String>> queryArgs = metadataToQueryArgs(metadata); | ||||
|       return Mono.subscriberContext().flatMap( | ||||
|           context -> this.client.invokeApi( | ||||
|             DaprHttp.HttpMethods.POST.name(), pathSegments, queryArgs, serializedEvent, headers, context | ||||
|               DaprHttp.HttpMethods.POST.name(), pathSegments, queryArgs, serializedEvent, headers, context | ||||
|           ) | ||||
|       ).then(); | ||||
|     } catch (Exception ex) { | ||||
|  | @ -192,17 +196,21 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|       if (method == null || method.trim().isEmpty()) { | ||||
|         throw new IllegalArgumentException("Method name cannot be null or empty."); | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       String[] methodSegments = method.split("/"); | ||||
| 
 | ||||
|       List<String> pathSegments = new ArrayList<>(Arrays.asList(DaprHttp.API_VERSION, "invoke", appId, "method")); | ||||
|       pathSegments.addAll(Arrays.asList(methodSegments)); | ||||
| 
 | ||||
|       byte[] serializedRequestBody = objectSerializer.serialize(request); | ||||
| 
 | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "invoke", appId, "method", method }; | ||||
| 
 | ||||
|       final Map<String, String> headers = new HashMap<>(); | ||||
|       if (contentType != null && !contentType.isEmpty()) { | ||||
|         headers.put("content-type", contentType); | ||||
|       } | ||||
|       headers.putAll(httpExtension.getHeaders()); | ||||
|       Mono<DaprHttp.Response> response = Mono.subscriberContext().flatMap( | ||||
|           context -> this.client.invokeApi(httpMethod, pathSegments, | ||||
|           context -> this.client.invokeApi(httpMethod, pathSegments.toArray(new String[0]), | ||||
|               httpExtension.getQueryParams(), serializedRequestBody, headers, context) | ||||
|       ); | ||||
|       return response.flatMap(r -> getMono(type, r)); | ||||
|  | @ -282,7 +290,7 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|       return DaprException.wrapMono(ex); | ||||
|     } | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|  | @ -310,25 +318,25 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
| 
 | ||||
|       byte[] requestBody = INTERNAL_SERIALIZER.serialize(jsonMap); | ||||
| 
 | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, "bulk"}; | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, "bulk" }; | ||||
| 
 | ||||
|       Map<String, List<String>> queryArgs = metadataToQueryArgs(metadata); | ||||
|       return Mono.subscriberContext().flatMap( | ||||
|               context -> this.client | ||||
|                   .invokeApi(DaprHttp.HttpMethods.POST.name(), pathSegments, queryArgs, requestBody, null, context) | ||||
|           ).flatMap(s -> { | ||||
|             try { | ||||
|               return Mono.just(buildStates(s, type)); | ||||
|             } catch (Exception ex) { | ||||
|               return DaprException.wrapMono(ex); | ||||
|             } | ||||
|           }); | ||||
|           context -> this.client | ||||
|               .invokeApi(DaprHttp.HttpMethods.POST.name(), pathSegments, queryArgs, requestBody, null, context) | ||||
|       ).flatMap(s -> { | ||||
|         try { | ||||
|           return Mono.just(buildStates(s, type)); | ||||
|         } catch (Exception ex) { | ||||
|           return DaprException.wrapMono(ex); | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|     } catch (Exception ex) { | ||||
|       return DaprException.wrapMono(ex); | ||||
|     } | ||||
|   } | ||||
|    | ||||
| 
 | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|  | @ -356,11 +364,11 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|       queryParams.putAll(optionsMap.entrySet().stream().collect( | ||||
|           Collectors.toMap(kv -> kv.getKey(), kv -> Collections.singletonList(kv.getValue())))); | ||||
| 
 | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, key}; | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, key }; | ||||
| 
 | ||||
|       return Mono.subscriberContext().flatMap( | ||||
|         context -> this.client | ||||
|           .invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, queryParams, null, context) | ||||
|           context -> this.client | ||||
|               .invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, queryParams, null, context) | ||||
|       ).flatMap(s -> { | ||||
|         try { | ||||
|           return Mono.justOrEmpty(buildState(s, key, options, type)); | ||||
|  | @ -414,7 +422,7 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|       TransactionalStateRequest<Object> req = new TransactionalStateRequest<>(internalOperationObjects, metadata); | ||||
|       byte[] serializedOperationBody = INTERNAL_SERIALIZER.serialize(req); | ||||
| 
 | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, "transaction"}; | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, "transaction" }; | ||||
| 
 | ||||
|       return Mono.subscriberContext().flatMap( | ||||
|           context -> this.client.invokeApi( | ||||
|  | @ -458,11 +466,11 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|         byte[] data = this.stateSerializer.serialize(state.getValue()); | ||||
|         // Custom serializer, so everything is byte[]. | ||||
|         internalStateObjects.add(new State<>(state.getKey(), data, state.getEtag(), state.getMetadata(), | ||||
|                 state.getOptions())); | ||||
|             state.getOptions())); | ||||
|       } | ||||
|       byte[] serializedStateBody = INTERNAL_SERIALIZER.serialize(internalStateObjects); | ||||
| 
 | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName}; | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName }; | ||||
| 
 | ||||
|       return Mono.subscriberContext().flatMap( | ||||
|           context -> this.client.invokeApi( | ||||
|  | @ -505,7 +513,7 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|       queryParams.putAll(optionsMap.entrySet().stream().collect( | ||||
|           Collectors.toMap(kv -> kv.getKey(), kv -> Collections.singletonList(kv.getValue())))); | ||||
| 
 | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, key}; | ||||
|       String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, key }; | ||||
| 
 | ||||
|       return Mono.subscriberContext().flatMap( | ||||
|           context -> this.client.invokeApi( | ||||
|  | @ -521,7 +529,7 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|    * | ||||
|    * @param response     The response of the HTTP Call | ||||
|    * @param requestedKey The Key Requested. | ||||
|    * @param type        The Class of the Value of the state | ||||
|    * @param type         The Class of the Value of the state | ||||
|    * @param <T>          The Type of the Value of the state | ||||
|    * @return A State instance | ||||
|    * @throws IOException If there's a issue deserializing the response. | ||||
|  | @ -540,9 +548,9 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|   /** | ||||
|    * Builds a State object based on the Response. | ||||
|    * | ||||
|    * @param response     The response of the HTTP Call | ||||
|    * @param type        The Class of the Value of the state | ||||
|    * @param <T>          The Type of the Value of the state | ||||
|    * @param response The response of the HTTP Call | ||||
|    * @param type     The Class of the Value of the state | ||||
|    * @param <T>      The Type of the Value of the state | ||||
|    * @return A list of states. | ||||
|    * @throws IOException If there's a issue deserializing the response. | ||||
|    */ | ||||
|  | @ -593,24 +601,24 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|     } | ||||
| 
 | ||||
|     Map<String, List<String>> queryArgs = metadataToQueryArgs(metadata); | ||||
|     String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "secrets", secretStoreName, key}; | ||||
|     String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "secrets", secretStoreName, key }; | ||||
| 
 | ||||
|     return Mono.subscriberContext().flatMap( | ||||
|           context -> this.client | ||||
|               .invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, queryArgs, (String)null, null, context) | ||||
|       ).flatMap(response -> { | ||||
|         try { | ||||
|           Map m =  INTERNAL_SERIALIZER.deserialize(response.getBody(), Map.class); | ||||
|           if (m == null) { | ||||
|             return Mono.just(Collections.EMPTY_MAP); | ||||
|           } | ||||
|             context -> this.client | ||||
|                 .invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, queryArgs, (String) null, null, context) | ||||
|         ).flatMap(response -> { | ||||
|           try { | ||||
|             Map m = INTERNAL_SERIALIZER.deserialize(response.getBody(), Map.class); | ||||
|             if (m == null) { | ||||
|               return Mono.just(Collections.EMPTY_MAP); | ||||
|             } | ||||
| 
 | ||||
|           return Mono.just(m); | ||||
|         } catch (IOException e) { | ||||
|           return DaprException.wrapMono(e); | ||||
|         } | ||||
|       }) | ||||
|       .map(m -> (Map<String, String>)m); | ||||
|             return Mono.just(m); | ||||
|           } catch (IOException e) { | ||||
|             return DaprException.wrapMono(e); | ||||
|           } | ||||
|         }) | ||||
|         .map(m -> (Map<String, String>) m); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -629,14 +637,14 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|     } | ||||
| 
 | ||||
|     Map<String, List<String>> queryArgs = metadataToQueryArgs(metadata); | ||||
|     String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "secrets", secretStoreName, "bulk"}; | ||||
|     String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "secrets", secretStoreName, "bulk" }; | ||||
| 
 | ||||
|     return Mono.subscriberContext().flatMap( | ||||
|             context -> this.client | ||||
|                 .invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, queryArgs, (String)null, null, context) | ||||
|                 .invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, queryArgs, (String) null, null, context) | ||||
|         ).flatMap(response -> { | ||||
|           try { | ||||
|             Map m =  INTERNAL_SERIALIZER.deserialize(response.getBody(), Map.class); | ||||
|             Map m = INTERNAL_SERIALIZER.deserialize(response.getBody(), Map.class); | ||||
|             if (m == null) { | ||||
|               return Mono.just(Collections.EMPTY_MAP); | ||||
|             } | ||||
|  | @ -646,7 +654,47 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|             return DaprException.wrapMono(e); | ||||
|           } | ||||
|         }) | ||||
|         .map(m -> (Map<String, Map<String, String>>)m); | ||||
|         .map(m -> (Map<String, Map<String, String>>) m); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    */ | ||||
|   @Override | ||||
|   public <T> Mono<QueryStateResponse<T>> queryState(QueryStateRequest request, TypeRef<T> type) { | ||||
|     try { | ||||
|       if (request == null) { | ||||
|         throw new IllegalArgumentException("Query state request cannot be null."); | ||||
|       } | ||||
|       String stateStoreName = request.getStoreName(); | ||||
|       Map<String, String> metadata = request.getMetadata(); | ||||
|       if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { | ||||
|         throw new IllegalArgumentException("State store name cannot be null or empty."); | ||||
|       } | ||||
|       Map<String, List<String>> queryArgs = metadataToQueryArgs(metadata); | ||||
|       String[] pathSegments = new String[]{ DaprHttp.ALPHA_1_API_VERSION, "state", stateStoreName, "query" }; | ||||
|       String serializedRequest; | ||||
|       if (request.getQuery() != null) { | ||||
|         serializedRequest = JSON_REQUEST_MAPPER.writeValueAsString(request.getQuery()); | ||||
|       } else if (request.getQueryString() != null) { | ||||
|         serializedRequest = request.getQueryString(); | ||||
|       } else { | ||||
|         throw new IllegalArgumentException("Both query and queryString fields are not set."); | ||||
|       } | ||||
|       return Mono.subscriberContext().flatMap( | ||||
|               context -> this.client | ||||
|                   .invokeApi(DaprHttp.HttpMethods.POST.name(), pathSegments, | ||||
|                       queryArgs, serializedRequest, null, context) | ||||
|           ).flatMap(response -> { | ||||
|             try { | ||||
|               return Mono.justOrEmpty(buildQueryStateResponse(response, type)); | ||||
|             } catch (Exception e) { | ||||
|               return DaprException.wrapMono(e); | ||||
|             } | ||||
|           }); | ||||
|     } catch (Exception e) { | ||||
|       return DaprException.wrapMono(e); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -665,8 +713,49 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
|     String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "shutdown" }; | ||||
|     return Mono.subscriberContext().flatMap( | ||||
|             context -> client.invokeApi(DaprHttp.HttpMethods.POST.name(), pathSegments, | ||||
|                     null, null, context)) | ||||
|             .then(); | ||||
|                 null, null, context)) | ||||
|         .then(); | ||||
|   } | ||||
| 
 | ||||
|   private <T> QueryStateResponse<T> buildQueryStateResponse(DaprHttp.Response response, | ||||
|                                                             TypeRef<T> type) throws IOException { | ||||
|     JsonNode root = INTERNAL_SERIALIZER.parseNode(response.getBody()); | ||||
|     if (!root.has("results")) { | ||||
|       return new QueryStateResponse<>(Collections.emptyList(), null); | ||||
|     } | ||||
|     String token = null; | ||||
|     if (root.has("token")) { | ||||
|       token = root.path("token").asText(); | ||||
|     } | ||||
|     Map<String, String> metadata = new HashMap<>(); | ||||
|     if (root.has("metadata")) { | ||||
|       for (Iterator<Map.Entry<String, JsonNode>> it = root.get("metadata").fields(); it.hasNext(); ) { | ||||
|         Map.Entry<String, JsonNode> entry = it.next(); | ||||
|         metadata.put(entry.getKey(), entry.getValue().asText()); | ||||
|       } | ||||
|     } | ||||
|     List<QueryStateItem<T>> result = new ArrayList<>(); | ||||
|     for (Iterator<JsonNode> it = root.get("results").elements(); it.hasNext(); ) { | ||||
|       JsonNode node = it.next(); | ||||
|       String key = node.path("key").asText(); | ||||
|       String error = node.path("error").asText(); | ||||
|       if (!Strings.isNullOrEmpty(error)) { | ||||
|         result.add(new QueryStateItem<>(key, null, error)); | ||||
|         continue; | ||||
|       } | ||||
| 
 | ||||
|       String etag = node.path("etag").asText(); | ||||
|       if (etag.equals("")) { | ||||
|         etag = null; | ||||
|       } | ||||
|       // TODO(artursouza): JSON cannot differentiate if data returned is String or byte[], it is ambiguous. | ||||
|       // This is not a high priority since GRPC is the default (and recommended) client implementation. | ||||
|       byte[] data = node.path("data").toString().getBytes(Properties.STRING_CHARSET.get()); | ||||
|       T value = stateSerializer.deserialize(data, type); | ||||
|       result.add(new QueryStateItem<>(key, value, etag)); | ||||
|     } | ||||
| 
 | ||||
|     return new QueryStateResponse<>(result, token).setMetadata(metadata); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -687,6 +776,7 @@ public class DaprClientHttp extends AbstractDaprClient { | |||
| 
 | ||||
|   /** | ||||
|    * Converts metadata map into Query params. | ||||
|    * | ||||
|    * @param metadata metadata map | ||||
|    * @return Query params | ||||
|    */ | ||||
|  |  | |||
|  | @ -23,6 +23,8 @@ import io.dapr.client.domain.HttpExtension; | |||
| import io.dapr.client.domain.InvokeBindingRequest; | ||||
| import io.dapr.client.domain.InvokeMethodRequest; | ||||
| import io.dapr.client.domain.PublishEventRequest; | ||||
| import io.dapr.client.domain.QueryStateRequest; | ||||
| import io.dapr.client.domain.QueryStateResponse; | ||||
| import io.dapr.client.domain.SaveStateRequest; | ||||
| import io.dapr.client.domain.State; | ||||
| import io.dapr.client.domain.StateOptions; | ||||
|  |  | |||
|  | @ -31,7 +31,6 @@ import reactor.core.publisher.Mono; | |||
| import reactor.util.context.Context; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.lang.reflect.Array; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
|  | @ -51,6 +50,11 @@ public class DaprHttp implements AutoCloseable { | |||
|    */ | ||||
|   public static final String API_VERSION = "v1.0"; | ||||
| 
 | ||||
|   /** | ||||
|    * Dapr alpha API used in this client. | ||||
|    */ | ||||
|   public static final String ALPHA_1_API_VERSION = "v1.0-alpha1"; | ||||
| 
 | ||||
|   /** | ||||
|    * Header used for request id in Dapr. | ||||
|    */ | ||||
|  |  | |||
|  | @ -15,7 +15,11 @@ package io.dapr.client; | |||
| 
 | ||||
| import io.dapr.client.domain.ConfigurationItem; | ||||
| import io.dapr.client.domain.GetConfigurationRequest; | ||||
| import io.dapr.client.domain.QueryStateRequest; | ||||
| import io.dapr.client.domain.QueryStateResponse; | ||||
| import io.dapr.client.domain.SubscribeConfigurationRequest; | ||||
| import io.dapr.client.domain.query.Query; | ||||
| import io.dapr.utils.TypeRef; | ||||
| import reactor.core.publisher.Flux; | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
|  | @ -103,4 +107,120 @@ public interface DaprPreviewClient extends AutoCloseable { | |||
|    * @return Flux of List of configuration items | ||||
|    */ | ||||
|   Flux<List<ConfigurationItem>> subscribeToConfiguration(SubscribeConfigurationRequest request); | ||||
| 
 | ||||
|   /** | ||||
|    * Query for states using a query string. | ||||
|    * | ||||
|    * @param storeName Name of the state store to query. | ||||
|    * @param query String value of the query. | ||||
|    * @param metadata Optional metadata passed to the state store. | ||||
|    * @param clazz The type needed as return for the call. | ||||
|    * @param <T> The Type of the return, use byte[] to skip serialization. | ||||
|    * @return A Mono of QueryStateResponse of type T. | ||||
|    */ | ||||
|   <T> Mono<QueryStateResponse<T>> queryState(String storeName, String query, | ||||
|                                              Map<String, String> metadata, Class<T> clazz); | ||||
| 
 | ||||
|   /** | ||||
|    * Query for states using a query string. | ||||
|    * | ||||
|    * @param storeName Name of the state store to query. | ||||
|    * @param query String value of the query. | ||||
|    * @param metadata Optional metadata passed to the state store. | ||||
|    * @param type The type needed as return for the call. | ||||
|    * @param <T> The Type of the return, use byte[] to skip serialization. | ||||
|    * @return A Mono of QueryStateResponse of type T. | ||||
|    */ | ||||
|   <T> Mono<QueryStateResponse<T>> queryState(String storeName, String query, | ||||
|                                              Map<String, String> metadata, TypeRef<T> type); | ||||
| 
 | ||||
|   /** | ||||
|    * Query for states using a query string. | ||||
|    * | ||||
|    * @param storeName Name of the state store to query. | ||||
|    * @param query String value of the query. | ||||
|    * @param clazz The type needed as return for the call. | ||||
|    * @param <T> The Type of the return, use byte[] to skip serialization. | ||||
|    * @return A Mono of QueryStateResponse of type T. | ||||
|    */ | ||||
|   <T> Mono<QueryStateResponse<T>> queryState(String storeName, String query, Class<T> clazz); | ||||
| 
 | ||||
|   /** | ||||
|    * Query for states using a query string. | ||||
|    * | ||||
|    * @param storeName Name of the state store to query. | ||||
|    * @param query String value of the query. | ||||
|    * @param type The type needed as return for the call. | ||||
|    * @param <T> The Type of the return, use byte[] to skip serialization. | ||||
|    * @return A Mono of QueryStateResponse of type T. | ||||
|    */ | ||||
|   <T> Mono<QueryStateResponse<T>> queryState(String storeName, String query, TypeRef<T> type); | ||||
| 
 | ||||
|   /** | ||||
|    * Query for states using a query domain object. | ||||
|    * | ||||
|    * @param storeName Name of the state store to query. | ||||
|    * @param query Query value domain object. | ||||
|    * @param metadata Optional metadata passed to the state store. | ||||
|    * @param clazz The type needed as return for the call. | ||||
|    * @param <T> The Type of the return, use byte[] to skip serialization. | ||||
|    * @return A Mono of QueryStateResponse of type T. | ||||
|    */ | ||||
|   <T> Mono<QueryStateResponse<T>> queryState(String storeName, Query query, | ||||
|                                              Map<String, String> metadata, Class<T> clazz); | ||||
| 
 | ||||
|   /** | ||||
|    * Query for states using a query domain object. | ||||
|    * | ||||
|    * @param storeName Name of the state store to query. | ||||
|    * @param query Query value domain object. | ||||
|    * @param metadata Optional metadata passed to the state store. | ||||
|    * @param type The type needed as return for the call. | ||||
|    * @param <T> The Type of the return, use byte[] to skip serialization. | ||||
|    * @return A Mono of QueryStateResponse of type T. | ||||
|    */ | ||||
|   <T> Mono<QueryStateResponse<T>> queryState(String storeName, Query query, | ||||
|                                              Map<String, String> metadata, TypeRef<T> type); | ||||
| 
 | ||||
|   /** | ||||
|    * Query for states using a query domain object. | ||||
|    * | ||||
|    * @param storeName Name of the state store to query. | ||||
|    * @param query Query value domain object. | ||||
|    * @param clazz The type needed as return for the call. | ||||
|    * @param <T> The Type of the return, use byte[] to skip serialization. | ||||
|    * @return A Mono of QueryStateResponse of type T. | ||||
|    */ | ||||
|   <T> Mono<QueryStateResponse<T>> queryState(String storeName, Query query, Class<T> clazz); | ||||
| 
 | ||||
|   /** | ||||
|    * Query for states using a query domain object. | ||||
|    * | ||||
|    * @param storeName Name of the state store to query. | ||||
|    * @param query Query value domain object. | ||||
|    * @param type The type needed as return for the call. | ||||
|    * @param <T> The Type of the return, use byte[] to skip serialization. | ||||
|    * @return A Mono of QueryStateResponse of type T. | ||||
|    */ | ||||
|   <T> Mono<QueryStateResponse<T>> queryState(String storeName, Query query, TypeRef<T> type); | ||||
| 
 | ||||
|   /** | ||||
|    * Query for states using a query request. | ||||
|    * | ||||
|    * @param request Query request object. | ||||
|    * @param clazz The type needed as return for the call. | ||||
|    * @param <T> The Type of the return, use byte[] to skip serialization. | ||||
|    * @return A Mono of QueryStateResponse of type T. | ||||
|    */ | ||||
|   <T> Mono<QueryStateResponse<T>> queryState(QueryStateRequest request, Class<T> clazz); | ||||
| 
 | ||||
|   /** | ||||
|    * Query for states using a query request. | ||||
|    * | ||||
|    * @param request Query request object. | ||||
|    * @param type The type needed as return for the call. | ||||
|    * @param <T> The Type of the return, use byte[] to skip serialization. | ||||
|    * @return A Mono of QueryStateResponse of type T. | ||||
|    */ | ||||
|   <T> Mono<QueryStateResponse<T>> queryState(QueryStateRequest request, TypeRef<T> type); | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,167 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain; | ||||
| 
 | ||||
| 
 | ||||
| public class QueryStateItem<T> { | ||||
| 
 | ||||
| 
 | ||||
|   /** | ||||
|    * The value of the state. | ||||
|    */ | ||||
|   private final T value; | ||||
| 
 | ||||
|   /** | ||||
|    * The key of the state. | ||||
|    */ | ||||
|   private final String key; | ||||
| 
 | ||||
|   /** | ||||
|    * The ETag to be used | ||||
|    * Keep in mind that for some state stores (like redis) only numbers are supported. | ||||
|    */ | ||||
|   private final String etag; | ||||
| 
 | ||||
|   /** | ||||
|    * The error in case the key could not be retrieved. | ||||
|    */ | ||||
|   private final String error; | ||||
| 
 | ||||
|   /** | ||||
|    * Create an immutable state reference to be retrieved or deleted. | ||||
|    * This Constructor CAN be used anytime you need to retrieve or delete a state. | ||||
|    * | ||||
|    * @param key - The key of the state | ||||
|    */ | ||||
|   public QueryStateItem(String key) { | ||||
|     this.key = key; | ||||
|     this.value = null; | ||||
|     this.etag = null; | ||||
|     this.error = null; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Create an immutable state reference to be retrieved or deleted. | ||||
|    * This Constructor CAN be used anytime you need to retrieve or delete a state. | ||||
|    * | ||||
|    * @param key   - The key of the state | ||||
|    * @param etag  - The etag of the state - Keep in mind that for some state stores (like redis) only numbers | ||||
|    *              are supported. | ||||
|    * @param error - Error when fetching the state. | ||||
|    */ | ||||
|   public QueryStateItem(String key, String etag, String error) { | ||||
|     this.value = null; | ||||
|     this.key = key; | ||||
|     this.etag = etag; | ||||
|     this.error = error; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Create an immutable state. | ||||
|    * This Constructor CAN be used anytime you want the state to be saved. | ||||
|    * | ||||
|    * @param key   - The key of the state. | ||||
|    * @param value - The value of the state. | ||||
|    * @param etag  - The etag of the state - for some state stores (like redis) only numbers are supported. | ||||
|    */ | ||||
|   public QueryStateItem(String key, T value, String etag) { | ||||
|     this.value = value; | ||||
|     this.key = key; | ||||
|     this.etag = etag; | ||||
|     this.error = null; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Retrieves the Value of the state. | ||||
|    * | ||||
|    * @return The value of the state | ||||
|    */ | ||||
|   public T getValue() { | ||||
|     return value; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Retrieves the Key of the state. | ||||
|    * | ||||
|    * @return The key of the state | ||||
|    */ | ||||
|   public String getKey() { | ||||
|     return key; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Retrieve the ETag of this state. | ||||
|    * | ||||
|    * @return The etag of the state | ||||
|    */ | ||||
|   public String getEtag() { | ||||
|     return etag; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Retrieve the error for this state. | ||||
|    * | ||||
|    * @return The error for this state. | ||||
|    */ | ||||
| 
 | ||||
|   public String getError() { | ||||
|     return error; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean equals(Object o) { | ||||
|     if (this == o) { | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     if (!(o instanceof QueryStateItem)) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     QueryStateItem<?> that = (QueryStateItem<?>) o; | ||||
| 
 | ||||
|     if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     if (getKey() != null ? !getKey().equals(that.getKey()) : that.getKey() != null) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     if (getEtag() != null ? !getEtag().equals(that.getEtag()) : that.getEtag() != null) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     return getError() != null ? getError().equals(that.getError()) : that.getError() == null; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public int hashCode() { | ||||
|     int result = getValue() != null ? getValue().hashCode() : 0; | ||||
|     result = 31 * result + (getKey() != null ? getKey().hashCode() : 0); | ||||
|     result = 31 * result + (getEtag() != null ? getEtag().hashCode() : 0); | ||||
|     result = 31 * result + (getError() != null ? getError().hashCode() : 0); | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return "QueryStateItem{" | ||||
|         + "key='" + key + "'" | ||||
|         + ", value=" + value | ||||
|         + ", etag='" + etag + "'" | ||||
|         + ", error='" + error + "'" | ||||
|         + "}"; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,92 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import io.dapr.client.domain.query.Query; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| public class QueryStateRequest { | ||||
| 
 | ||||
|   @JsonIgnore | ||||
|   private final String storeName; | ||||
| 
 | ||||
|   private Query query; | ||||
| 
 | ||||
|   private String queryString; | ||||
| 
 | ||||
|   @JsonIgnore | ||||
|   private Map<String, String> metadata; | ||||
| 
 | ||||
|   public QueryStateRequest(String storeName) { | ||||
|     this.storeName = storeName; | ||||
|   } | ||||
| 
 | ||||
|   public String getStoreName() { | ||||
|     return storeName; | ||||
|   } | ||||
| 
 | ||||
|   public Query getQuery() { | ||||
|     return query; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Validate and set the query field. Mutually exclusive with the queryString field in this instance. | ||||
|    * | ||||
|    * @param query Valid Query domain object. | ||||
|    * @return This instance. | ||||
|    */ | ||||
|   public QueryStateRequest setQuery(Query query) { | ||||
|     if (this.queryString != null) { | ||||
|       throw new IllegalArgumentException("queryString filed is already set in the request. query field cannot be set."); | ||||
|     } | ||||
|     if (query == null || query.getFilter() == null) { | ||||
|       throw new IllegalArgumentException("query cannot be null or with null filter"); | ||||
|     } | ||||
|     this.query = query; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   public String getQueryString() { | ||||
|     return queryString; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Validate and set the queryString field. Mutually exclusive with the query field in this instance. | ||||
|    * | ||||
|    * @param queryString String value of the query. | ||||
|    * @return This request object for fluent API. | ||||
|    */ | ||||
|   public QueryStateRequest setQueryString(String queryString) { | ||||
|     if (this.query != null) { | ||||
|       throw new IllegalArgumentException("query filed is already set in the request. queryString field cannot be set."); | ||||
|     } | ||||
|     if (queryString == null || queryString.isEmpty()) { | ||||
|       throw new IllegalArgumentException("queryString cannot be null or blank"); | ||||
|     } | ||||
|     this.queryString = queryString; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   public Map<String, String> getMetadata() { | ||||
|     return metadata; | ||||
|   } | ||||
| 
 | ||||
|   public QueryStateRequest setMetadata(Map<String, String> metadata) { | ||||
|     this.metadata = metadata == null ? null : Collections.unmodifiableMap(metadata); | ||||
|     return this; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,50 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain; | ||||
| 
 | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| public class QueryStateResponse<T> { | ||||
| 
 | ||||
|   private final List<QueryStateItem<T>> results; | ||||
| 
 | ||||
|   private final String token; | ||||
| 
 | ||||
|   private Map<String, String> metadata; | ||||
| 
 | ||||
|   public QueryStateResponse(List<QueryStateItem<T>> results, String token) { | ||||
|     this.results = results == null ? null : Collections.unmodifiableList(results); | ||||
|     this.token = token; | ||||
|   } | ||||
| 
 | ||||
|   public List<QueryStateItem<T>> getResults() { | ||||
|     return results; | ||||
|   } | ||||
| 
 | ||||
|   public String getToken() { | ||||
|     return token; | ||||
|   } | ||||
| 
 | ||||
|   public Map<String, String> getMetadata() { | ||||
|     return metadata; | ||||
|   } | ||||
| 
 | ||||
|   public QueryStateResponse<T> setMetadata(Map<String, String> metadata) { | ||||
|     this.metadata = metadata == null ? null : Collections.unmodifiableMap(metadata); | ||||
|     return this; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,39 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain.query; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||||
| 
 | ||||
| @JsonInclude(JsonInclude.Include.NON_EMPTY) | ||||
| public class Pagination { | ||||
|   private int limit; | ||||
|   private String token; | ||||
| 
 | ||||
|   Pagination() { | ||||
|     // For JSON | ||||
|   } | ||||
| 
 | ||||
|   public Pagination(int limit, String token) { | ||||
|     this.limit = limit; | ||||
|     this.token = token; | ||||
|   } | ||||
| 
 | ||||
|   public int getLimit() { | ||||
|     return limit; | ||||
|   } | ||||
| 
 | ||||
|   public String getToken() { | ||||
|     return token; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,75 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain.query; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||||
| import io.dapr.client.domain.query.filters.Filter; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class Query { | ||||
|   private Filter<?> filter; | ||||
| 
 | ||||
|   @JsonProperty | ||||
|   private Sorting[] sort = new Sorting[]{}; | ||||
| 
 | ||||
|   @JsonProperty("page") | ||||
|   private Pagination pagination = new Pagination(); | ||||
| 
 | ||||
|   public Filter<?> getFilter() { | ||||
|     return filter; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Set the filter field in the instance. | ||||
|    * @param filter Valid filter value. | ||||
|    * @return this instance. | ||||
|    */ | ||||
|   public Query setFilter(Filter<?> filter) { | ||||
|     if (!filter.isValid()) { | ||||
|       throw new IllegalArgumentException("the given filter is invalid configuration"); | ||||
|     } | ||||
|     this.filter = filter; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   public List<Sorting> getSort() { | ||||
|     return Collections.unmodifiableList(Arrays.asList(sort)); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Validate and set sorting field. | ||||
|    * | ||||
|    * @param sort List of sorting objects. | ||||
|    * @return This instance. | ||||
|    */ | ||||
|   public Query setSort(List<Sorting> sort) { | ||||
|     if (sort == null || sort.size() == 0) { | ||||
|       throw new IllegalArgumentException("Sorting list is null or empty"); | ||||
|     } | ||||
|     this.sort =  sort.toArray(new Sorting[0]); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   public Pagination getPagination() { | ||||
|     return pagination; | ||||
|   } | ||||
| 
 | ||||
|   public Query setPagination(Pagination pagination) { | ||||
|     this.pagination = pagination; | ||||
|     return this; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,62 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain.query; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||||
| import com.fasterxml.jackson.annotation.JsonValue; | ||||
| 
 | ||||
| @JsonInclude(JsonInclude.Include.NON_EMPTY) | ||||
| public class Sorting { | ||||
|   private String key; | ||||
|   private Order order; | ||||
| 
 | ||||
|   Sorting() { | ||||
|     // For JSON | ||||
|   } | ||||
| 
 | ||||
|   public Sorting(String key, Order order) { | ||||
|     this.key = key; | ||||
|     this.order = order; | ||||
|   } | ||||
| 
 | ||||
|   public String getKey() { | ||||
|     return key; | ||||
|   } | ||||
| 
 | ||||
|   public Order getOrder() { | ||||
|     return order; | ||||
|   } | ||||
| 
 | ||||
|   public enum Order { | ||||
|     ASC("ASC"), | ||||
|     DESC("DESC"); | ||||
| 
 | ||||
|     private String name; | ||||
| 
 | ||||
|     Order(String name) { | ||||
|       this.name = name; | ||||
|     } | ||||
| 
 | ||||
|     @JsonValue | ||||
|     public String getValue() { | ||||
|       return this.name; | ||||
|     } | ||||
| 
 | ||||
|     @JsonCreator | ||||
|     public static Order fromValue(String value) { | ||||
|       return Order.valueOf(value); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,68 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain.query.filters; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import com.fasterxml.jackson.annotation.JsonValue; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| public class AndFilter extends Filter<Void> { | ||||
|   @JsonIgnore | ||||
|   private final List<Filter<?>> and; | ||||
| 
 | ||||
|   public AndFilter() { | ||||
|     super("AND"); | ||||
|     this.and = new ArrayList<>(); | ||||
|   } | ||||
| 
 | ||||
|   @JsonCreator | ||||
|   AndFilter(Filter<?>[] filters) { | ||||
|     super("AND"); | ||||
|     this.and = Arrays.asList(filters); | ||||
|   } | ||||
| 
 | ||||
|   public <V extends Filter<?>> AndFilter addClause(V filter) { | ||||
|     this.and.add(filter); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   @JsonValue | ||||
|   public Filter<?>[] getClauses() { | ||||
|     return this.and.toArray(new Filter[0]); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   @JsonIgnore | ||||
|   public String getRepresentation() { | ||||
|     return this.and.stream().map(Filter::getRepresentation).collect(Collectors.joining(" AND ")); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Boolean isValid() { | ||||
|     boolean validAnd = and != null && and.size() >= 2; | ||||
|     if (validAnd) { | ||||
|       for (Filter<?> filter : and) { | ||||
|         if (!filter.isValid()) { | ||||
|           return false; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return validAnd; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,62 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain.query.filters; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import com.fasterxml.jackson.annotation.JsonValue; | ||||
| 
 | ||||
| import java.util.AbstractMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| public class EqFilter<T> extends Filter<T> { | ||||
| 
 | ||||
|   @JsonValue | ||||
|   private Map.Entry<String, T> eq; | ||||
| 
 | ||||
|   public EqFilter() { | ||||
|     super("EQ"); | ||||
|   } | ||||
| 
 | ||||
|   public EqFilter(String key, T value) { | ||||
|     super("EQ"); | ||||
|     eq = new AbstractMap.SimpleImmutableEntry<>(key, value); | ||||
|   } | ||||
| 
 | ||||
|   @JsonCreator | ||||
|   EqFilter(Map.Entry<String, T> eq) { | ||||
|     super("EQ"); | ||||
|     this.eq = eq; | ||||
|   } | ||||
| 
 | ||||
|   @JsonIgnore | ||||
|   public String getKey() { | ||||
|     return eq != null ? eq.getKey() : null; | ||||
|   } | ||||
| 
 | ||||
|   @JsonIgnore | ||||
|   public T getValue() { | ||||
|     return eq != null ? eq.getValue() : null; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String getRepresentation() { | ||||
|     return this.getKey() + " EQ " + this.getValue(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Boolean isValid() { | ||||
|     return eq != null && eq.getKey() != null && !eq.getKey().isEmpty() && !eq.getKey().trim().isEmpty(); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,52 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain.query.filters; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import com.fasterxml.jackson.annotation.JsonSubTypes; | ||||
| import com.fasterxml.jackson.annotation.JsonTypeInfo; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| 
 | ||||
| @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) | ||||
| @JsonSubTypes({ | ||||
|     @JsonSubTypes.Type(value = AndFilter.class, name = "AND"), | ||||
|     @JsonSubTypes.Type(value = InFilter.class, name = "IN"), | ||||
|     @JsonSubTypes.Type(value = OrFilter.class, name = "OR"), | ||||
|     @JsonSubTypes.Type(value = EqFilter.class, name = "EQ") | ||||
| }) | ||||
| public abstract class Filter<T> { | ||||
| 
 | ||||
|   private static final ObjectMapper MAPPER = new ObjectMapper(); | ||||
| 
 | ||||
|   @JsonIgnore | ||||
|   private String name; | ||||
| 
 | ||||
|   Filter() { | ||||
|     // For JSON Serialization | ||||
|   } | ||||
| 
 | ||||
|   public Filter(String name) { | ||||
|     this.name = name; | ||||
|   } | ||||
| 
 | ||||
|   public String getName() { | ||||
|     return name; | ||||
|   } | ||||
| 
 | ||||
|   @JsonIgnore | ||||
|   public abstract String getRepresentation(); | ||||
| 
 | ||||
|   @JsonIgnore | ||||
|   public abstract Boolean isValid(); | ||||
| } | ||||
|  | @ -0,0 +1,81 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain.query.filters; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import com.fasterxml.jackson.annotation.JsonValue; | ||||
| 
 | ||||
| import java.util.AbstractMap; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| public class InFilter<T> extends Filter<T> { | ||||
|   @JsonValue | ||||
|   private Map.Entry<String, List<T>> in; | ||||
| 
 | ||||
|   public InFilter() { | ||||
|     super("IN"); | ||||
|   } | ||||
| 
 | ||||
|   public InFilter(String key, List<T> value) { | ||||
|     super("IN"); | ||||
|     in = new AbstractMap.SimpleEntry<>(key, value); | ||||
|   } | ||||
| 
 | ||||
|   @JsonCreator | ||||
|   InFilter(Map.Entry<String, List<T>> in) { | ||||
|     super("IN"); | ||||
|     this.in = in; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * constructor for InFilter. | ||||
|    * @param key value of the key in the state store. | ||||
|    * @param values var args values list. | ||||
|    */ | ||||
|   public InFilter(String key, T... values) { | ||||
|     super("IN"); | ||||
|     if (values == null || values.length == 0) { | ||||
|       throw new IllegalArgumentException("list of values must be at least 1"); | ||||
|     } | ||||
|     in = new AbstractMap.SimpleImmutableEntry<>(key, Collections.unmodifiableList(Arrays.asList(values))); | ||||
|   } | ||||
| 
 | ||||
|   @JsonIgnore | ||||
|   public String getKey() { | ||||
|     return in != null ? in.getKey() : null; | ||||
|   } | ||||
| 
 | ||||
|   @JsonIgnore | ||||
|   public List<T> getValues() { | ||||
|     return in != null ? in.getValue() : null; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String getRepresentation() { | ||||
|     return this.getKey() + " IN [" | ||||
|         + this.getValues().stream().map(Object::toString).collect(Collectors.joining(",")) | ||||
|         + "]"; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Boolean isValid() { | ||||
|     return in != null && in.getKey() != null && !in.getKey().isEmpty() && !in.getKey().trim().isEmpty() | ||||
|         && in.getValue() != null && in.getValue().size() > 0; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,70 @@ | |||
| /* | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client.domain.query.filters; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import com.fasterxml.jackson.annotation.JsonValue; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| @SuppressWarnings("rawtypes") | ||||
| public class OrFilter extends Filter { | ||||
| 
 | ||||
|   @JsonIgnore | ||||
|   private List<Filter<?>> or; | ||||
| 
 | ||||
|   public OrFilter() { | ||||
|     super("OR"); | ||||
|     this.or = new ArrayList<>(); | ||||
|   } | ||||
| 
 | ||||
|   @JsonCreator | ||||
|   OrFilter(Filter<?>[] filters) { | ||||
|     super("OR"); | ||||
|     this.or = Arrays.asList(filters); | ||||
|   } | ||||
| 
 | ||||
|   public <V extends Filter> OrFilter addClause(V filter) { | ||||
|     this.or.add(filter); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   @JsonValue | ||||
|   public Filter<?>[] getClauses() { | ||||
|     return this.or.toArray(new Filter[0]); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   @JsonIgnore | ||||
|   public String getRepresentation() { | ||||
|     return this.or.stream().map(Filter::getRepresentation).collect(Collectors.joining(" OR ")); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Boolean isValid() { | ||||
|     boolean validAnd = or != null && or.size() >= 2; | ||||
|     if (validAnd) { | ||||
|       for (Filter<?> filter : or) { | ||||
|         if (!filter.isValid()) { | ||||
|           return false; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return validAnd; | ||||
|   } | ||||
| } | ||||
|  | @ -14,9 +14,16 @@ limitations under the License. | |||
| package io.dapr.client; | ||||
| 
 | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.google.protobuf.ByteString; | ||||
| import io.dapr.client.domain.ConfigurationItem; | ||||
| import io.dapr.client.domain.GetConfigurationRequest; | ||||
| import io.dapr.client.domain.QueryStateItem; | ||||
| import io.dapr.client.domain.QueryStateRequest; | ||||
| import io.dapr.client.domain.QueryStateResponse; | ||||
| import io.dapr.client.domain.SubscribeConfigurationRequest; | ||||
| import io.dapr.client.domain.query.Query; | ||||
| import io.dapr.serializer.DefaultObjectSerializer; | ||||
| import io.dapr.v1.CommonProtos; | ||||
| import io.dapr.v1.DaprGrpc; | ||||
|  | @ -29,6 +36,7 @@ import org.mockito.stubbing.Answer; | |||
| 
 | ||||
| import java.io.Closeable; | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.Iterator; | ||||
|  | @ -37,14 +45,24 @@ import java.util.Map; | |||
| import java.util.concurrent.ExecutionException; | ||||
| 
 | ||||
| import static io.dapr.utils.TestUtils.assertThrowsDaprException; | ||||
| import static org.junit.Assert.*; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.junit.jupiter.api.Assertions.assertThrows; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.Mockito.*; | ||||
| import static org.mockito.Mockito.doAnswer; | ||||
| import static org.mockito.Mockito.doNothing; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.verify; | ||||
| import static org.mockito.Mockito.verifyNoMoreInteractions; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| public class DaprPreviewClientGrpcTest { | ||||
| 
 | ||||
| 	private static final ObjectMapper MAPPER = new ObjectMapper(); | ||||
| 	private static final String CONFIG_STORE_NAME = "MyConfigStore"; | ||||
| 	private static final String QUERY_STORE_NAME = "testQueryStore"; | ||||
| 
 | ||||
| 	private Closeable closeable; | ||||
| 	private DaprGrpc.DaprStub daprStub; | ||||
|  | @ -269,4 +287,105 @@ public class DaprPreviewClientGrpcTest { | |||
| 				.build(); | ||||
| 		return responseEnvelope; | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void queryStateExceptionsTest() { | ||||
| 		assertThrows(IllegalArgumentException.class, () -> { | ||||
| 			previewClient.queryState("", "query", String.class).block(); | ||||
| 		}); | ||||
| 		assertThrows(IllegalArgumentException.class, () -> { | ||||
| 			previewClient.queryState("storeName", "", String.class).block(); | ||||
| 		}); | ||||
| 		assertThrows(IllegalArgumentException.class, () -> { | ||||
| 			previewClient.queryState("storeName", (Query) null, String.class).block(); | ||||
| 		}); | ||||
| 		assertThrows(IllegalArgumentException.class, () -> { | ||||
| 			previewClient.queryState("storeName", (String) null, String.class).block(); | ||||
| 		}); | ||||
| 		assertThrows(IllegalArgumentException.class, () -> { | ||||
| 			previewClient.queryState(new QueryStateRequest("storeName"), String.class).block(); | ||||
| 		}); | ||||
| 		assertThrows(IllegalArgumentException.class, () -> { | ||||
| 			previewClient.queryState(null, String.class).block(); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void queryState() throws JsonProcessingException { | ||||
| 		List<QueryStateItem<?>> resp = new ArrayList<>(); | ||||
| 		resp.add(new QueryStateItem<Object>("1", (Object)"testData", "6f54ad94-dfb9-46f0-a371-e42d550adb7d")); | ||||
| 		DaprProtos.QueryStateResponse responseEnvelope = buildQueryStateResponse(resp, ""); | ||||
| 		doAnswer((Answer<Void>) invocation -> { | ||||
| 			DaprProtos.QueryStateRequest req = invocation.getArgument(0); | ||||
| 			assertEquals(QUERY_STORE_NAME, req.getStoreName()); | ||||
| 			assertEquals("query", req.getQuery()); | ||||
| 			assertEquals(0, req.getMetadataCount()); | ||||
| 
 | ||||
| 			StreamObserver<DaprProtos.QueryStateResponse> observer = (StreamObserver<DaprProtos.QueryStateResponse>) | ||||
| 					invocation.getArguments()[1]; | ||||
| 			observer.onNext(responseEnvelope); | ||||
| 			observer.onCompleted(); | ||||
| 			return null; | ||||
| 		}).when(daprStub).queryStateAlpha1(any(DaprProtos.QueryStateRequest.class), any()); | ||||
| 
 | ||||
| 		QueryStateResponse<String> response = previewClient.queryState(QUERY_STORE_NAME, "query", String.class).block(); | ||||
| 		assertNotNull(response); | ||||
| 		assertEquals("result size must be 1", 1, response.getResults().size()); | ||||
| 		assertEquals("result must be same", "1", response.getResults().get(0).getKey()); | ||||
| 		assertEquals("result must be same", "testData", response.getResults().get(0).getValue()); | ||||
| 		assertEquals("result must be same", "6f54ad94-dfb9-46f0-a371-e42d550adb7d", response.getResults().get(0).getEtag()); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void queryStateMetadataError() throws JsonProcessingException { | ||||
| 		List<QueryStateItem<?>> resp = new ArrayList<>(); | ||||
| 		resp.add(new QueryStateItem<Object>("1", null, "error data")); | ||||
| 		DaprProtos.QueryStateResponse responseEnvelope = buildQueryStateResponse(resp, ""); | ||||
| 		doAnswer((Answer<Void>) invocation -> { | ||||
| 			DaprProtos.QueryStateRequest req = invocation.getArgument(0); | ||||
| 			assertEquals(QUERY_STORE_NAME, req.getStoreName()); | ||||
| 			assertEquals("query", req.getQuery()); | ||||
| 			assertEquals(1, req.getMetadataCount()); | ||||
| 			assertEquals(1, req.getMetadataCount()); | ||||
| 
 | ||||
| 			StreamObserver<DaprProtos.QueryStateResponse> observer = (StreamObserver<DaprProtos.QueryStateResponse>) | ||||
| 					invocation.getArguments()[1]; | ||||
| 			observer.onNext(responseEnvelope); | ||||
| 			observer.onCompleted(); | ||||
| 			return null; | ||||
| 		}).when(daprStub).queryStateAlpha1(any(DaprProtos.QueryStateRequest.class), any()); | ||||
| 
 | ||||
| 		QueryStateResponse<String> response = previewClient.queryState(QUERY_STORE_NAME, "query", | ||||
| 				new HashMap<String, String>(){{ put("key", "error"); }}, String.class).block(); | ||||
| 		assertNotNull(response); | ||||
| 		assertEquals("result size must be 1", 1, response.getResults().size()); | ||||
| 		assertEquals("result must be same", "1", response.getResults().get(0).getKey()); | ||||
| 		assertEquals("result must be same", "error data", response.getResults().get(0).getError()); | ||||
| 	} | ||||
| 
 | ||||
| 	private DaprProtos.QueryStateResponse buildQueryStateResponse(List<QueryStateItem<?>> resp,String token) | ||||
| 			throws JsonProcessingException { | ||||
| 		List<DaprProtos.QueryStateItem> items = new ArrayList<>(); | ||||
| 		for (QueryStateItem<?> item: resp) { | ||||
| 			items.add(buildQueryStateItem(item)); | ||||
| 		} | ||||
| 		return DaprProtos.QueryStateResponse.newBuilder() | ||||
| 				.addAllResults(items) | ||||
| 				.setToken(token) | ||||
| 				.build(); | ||||
| 	} | ||||
| 
 | ||||
| 	private DaprProtos.QueryStateItem buildQueryStateItem(QueryStateItem<?> item) throws JsonProcessingException { | ||||
| 		DaprProtos.QueryStateItem.Builder it = DaprProtos.QueryStateItem.newBuilder().setKey(item.getKey()); | ||||
| 		if (item.getValue() != null) { | ||||
| 			it.setData(ByteString.copyFrom(MAPPER.writeValueAsBytes(item.getValue()))); | ||||
| 		} | ||||
| 		if (item.getEtag() != null) { | ||||
| 			it.setEtag(item.getEtag()); | ||||
| 		} | ||||
| 		if (item.getError() != null) { | ||||
| 			it.setError(item.getError()); | ||||
| 		} | ||||
| 		return it.build(); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,30 +1,32 @@ | |||
| /* | ||||
|  * Copyright (c) Microsoft Corporation and Dapr Contributors. | ||||
|  * Licensed under the MIT License. | ||||
|  */ | ||||
|  * Copyright 2021 The Dapr Authors | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package io.dapr.client; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.type.TypeReference; | ||||
| import com.fasterxml.jackson.dataformat.xml.XmlMapper; | ||||
| import io.dapr.client.domain.QueryStateRequest; | ||||
| import io.dapr.client.domain.QueryStateResponse; | ||||
| import io.dapr.client.domain.query.Query; | ||||
| import io.dapr.config.Properties; | ||||
| import io.dapr.exceptions.DaprException; | ||||
| import io.dapr.serializer.DaprObjectSerializer; | ||||
| import io.dapr.utils.TypeRef; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.mock.Behavior; | ||||
| import okhttp3.mock.MockInterceptor; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.mockito.Mockito; | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.net.ServerSocket; | ||||
| import java.net.Socket; | ||||
| 
 | ||||
| import static io.dapr.utils.TestUtils.findFreePort; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.jupiter.api.Assertions.assertThrows; | ||||
| 
 | ||||
| public class DaprPreviewClientHttpTest { | ||||
|  | @ -66,4 +68,56 @@ public class DaprPreviewClientHttpTest { | |||
|       daprPreviewClientHttp.subscribeToConfiguration(CONFIG_STORE_NAME, "key1", "key2").blockFirst(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void queryStateExceptionsTest() { | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState("", "query", TypeRef.BOOLEAN).block(); | ||||
|     }); | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState("", "query", String.class).block(); | ||||
|     }); | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState("storeName", "", TypeRef.BOOLEAN).block(); | ||||
|     }); | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState("storeName", "", String.class).block(); | ||||
|     }); | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState("storeName", (Query) null, TypeRef.BOOLEAN).block(); | ||||
|     }); | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState("storeName", (Query) null, String.class).block(); | ||||
|     }); | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState("storeName", (String) null, TypeRef.BOOLEAN).block(); | ||||
|     }); | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState("storeName", (String) null, String.class).block(); | ||||
|     }); | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState(null, TypeRef.BOOLEAN).block(); | ||||
|     }); | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState(new QueryStateRequest("storeName"), TypeRef.BOOLEAN).block(); | ||||
|     }); | ||||
|     assertThrows(IllegalArgumentException.class, () -> { | ||||
|       daprPreviewClientHttp.queryState(null, String.class).block(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void queryStateTest() { | ||||
|     mockInterceptor.addRule() | ||||
|         .post() | ||||
|         .path("/v1.0-alpha1/state/testStore/query") | ||||
|         .respond("{\"results\": [{\"key\": \"1\",\"data\": \"testData\"," | ||||
|             + "\"etag\": \"6f54ad94-dfb9-46f0-a371-e42d550adb7d\"}]}"); | ||||
|     QueryStateResponse<String> response = daprPreviewClientHttp.queryState("testStore", "query", String.class).block(); | ||||
|     assertNotNull(response); | ||||
|     assertEquals("result size must be 1", 1, response.getResults().size()); | ||||
|     assertEquals("result must be same", "1", response.getResults().get(0).getKey()); | ||||
|     assertEquals("result must be same", "testData", response.getResults().get(0).getValue()); | ||||
|     assertEquals("result must be same", "6f54ad94-dfb9-46f0-a371-e42d550adb7d", response.getResults().get(0).getEtag()); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,54 @@ | |||
| package io.dapr.client.domain; | ||||
| 
 | ||||
| import io.dapr.client.domain.query.Query; | ||||
| import io.dapr.client.domain.query.filters.EqFilter; | ||||
| import org.junit.Assert; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import static org.junit.Assert.assertNull; | ||||
| 
 | ||||
| public class QueryStateRequestTest { | ||||
| 
 | ||||
|   private String STORE_NAME = "STORE"; | ||||
|   private String KEY = "KEY"; | ||||
| 
 | ||||
|   @Test | ||||
|   public void testSetMetadata() { | ||||
|     QueryStateRequest request = new QueryStateRequest(STORE_NAME); | ||||
|     // Null check | ||||
|     request.setMetadata(null); | ||||
|     assertNull(request.getMetadata()); | ||||
|     // Modifiability check | ||||
|     Map<String, String> metadata = new HashMap<>(); | ||||
|     metadata.put("test", "testval"); | ||||
|     request.setMetadata(metadata); | ||||
|     Map<String, String> initial = request.getMetadata(); | ||||
|     request.setMetadata(metadata); | ||||
|     Assert.assertNotSame("Should not be same map", request.getMetadata(), initial); | ||||
|   } | ||||
| 
 | ||||
|   @Test(expected = IllegalArgumentException.class) | ||||
|   public void testSetNullQuery() { | ||||
|     QueryStateRequest request = new QueryStateRequest(STORE_NAME); | ||||
|     request.setQuery(null); | ||||
|   } | ||||
| 
 | ||||
|   @Test(expected = IllegalArgumentException.class) | ||||
|   public void testSetNullFilterQuery() { | ||||
|     QueryStateRequest request = new QueryStateRequest(STORE_NAME); | ||||
|     Query query = new Query(); | ||||
|     request.setQuery(query); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void testSetQuery() { | ||||
|     QueryStateRequest request = new QueryStateRequest(STORE_NAME); | ||||
|     Query query = new Query(); | ||||
|     query.setFilter(new EqFilter<>("key", "value")); | ||||
|     request.setQuery(query); | ||||
|     Assert.assertEquals(query, request.getQuery()); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,118 @@ | |||
| package io.dapr.client.domain.query; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import io.dapr.client.domain.query.filters.*; | ||||
| import org.junit.Assert; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| 
 | ||||
| public class QueryTest { | ||||
| 
 | ||||
|   ObjectMapper mapper = new ObjectMapper(); | ||||
|   String json = "{\"filter\":{\"AND\":[{\"EQ\":{\"key\":\"value\"}},{\"IN\":{\"key2\":[\"v1\",\"v2\"]}}," + | ||||
|       "{\"OR\":[{\"EQ\":{\"v2\":true}},{\"IN\":{\"v3\":[1.3,1.5]}}]}]}," + | ||||
|       "\"sort\":[{\"key\":\"value.person.org\",\"order\":\"ASC\"},{\"key\":\"value.state\",\"order\":\"DESC\"}]," + | ||||
|       "\"page\":{\"limit\":10,\"token\":\"test-token\"}}"; | ||||
| 
 | ||||
|   @Test | ||||
|   public void testQuerySerialize() throws JsonProcessingException { | ||||
|     Query q = new Query(); | ||||
| 
 | ||||
|     AndFilter filter = new AndFilter(); | ||||
|     filter.addClause(new EqFilter<>("key", "value")); | ||||
|     filter.addClause(new InFilter<>("key2", "v1", "v2")); | ||||
| 
 | ||||
|     OrFilter orFilter = new OrFilter(); | ||||
|     orFilter.addClause(new EqFilter<>("v2", true)); | ||||
|     orFilter.addClause(new InFilter<>("v3", 1.3, 1.5)); | ||||
| 
 | ||||
|     filter.addClause(orFilter); | ||||
| 
 | ||||
|     // Add Filter | ||||
|     q.setFilter(filter); | ||||
|     q.setPagination(new Pagination(10, "test-token")); | ||||
|     q.setSort(Arrays.asList(new Sorting("value.person.org", Sorting.Order.ASC), | ||||
|         new Sorting("value.state", Sorting.Order.DESC))); | ||||
|     Assert.assertEquals(json, mapper.writeValueAsString(q)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   @Test | ||||
|   public void testQueryDeserialize() throws JsonProcessingException { | ||||
| 
 | ||||
| 
 | ||||
|     Query query = mapper.readValue(json, Query.class); | ||||
|     Assert.assertNotNull(query.getPagination()); | ||||
|     Assert.assertNotNull(query.getFilter()); | ||||
|     Assert.assertNotNull(query.getSort()); | ||||
| 
 | ||||
|     // Assert Pagination | ||||
|     Assert.assertEquals(10, query.getPagination().getLimit()); | ||||
|     Assert.assertEquals("test-token", query.getPagination().getToken()); | ||||
| 
 | ||||
|     // Assert Sort | ||||
|     Assert.assertEquals(2, query.getSort().size()); | ||||
|     Assert.assertEquals("value.person.org", query.getSort().get(0).getKey()); | ||||
|     Assert.assertEquals(Sorting.Order.ASC, query.getSort().get(0).getOrder()); | ||||
|     Assert.assertEquals("value.state", query.getSort().get(1).getKey()); | ||||
|     Assert.assertEquals(Sorting.Order.DESC, query.getSort().get(1).getOrder()); | ||||
| 
 | ||||
|     // Assert Filter | ||||
|     // Top level AND filter | ||||
|     Assert.assertEquals("AND", query.getFilter().getName()); | ||||
|     // Type cast to AND filter | ||||
|     AndFilter filter = (AndFilter) query.getFilter(); | ||||
|     // Assert 3 AND clauses | ||||
|     Assert.assertEquals(3, filter.getClauses().length); | ||||
|     Filter<?>[] andClauses = filter.getClauses(); | ||||
|     // First EQ | ||||
|     Assert.assertEquals("EQ", andClauses[0].getName()); | ||||
|     Assert.assertSame(EqFilter.class, andClauses[0].getClass()); | ||||
|     EqFilter<?> eq = (EqFilter<?>) andClauses[0]; | ||||
|     Assert.assertEquals("key", eq.getKey()); | ||||
|     Assert.assertEquals("value", eq.getValue()); | ||||
|     // Second IN | ||||
|     Assert.assertEquals("IN", andClauses[1].getName()); | ||||
|     Assert.assertSame(InFilter.class, andClauses[1].getClass()); | ||||
|     InFilter<?> in = (InFilter<?>) andClauses[1]; | ||||
|     Assert.assertEquals("key2", in.getKey()); | ||||
|     Assert.assertArrayEquals(new String[]{ "v1", "v2" }, in.getValues().toArray()); | ||||
|     // Third OR | ||||
|     Assert.assertEquals("OR", andClauses[2].getName()); | ||||
|     Assert.assertSame(OrFilter.class, andClauses[2].getClass()); | ||||
|     OrFilter orFilter = (OrFilter) andClauses[2]; | ||||
|     Filter<?>[] orClauses = orFilter.getClauses(); | ||||
|     // First EQ in OR | ||||
|     Assert.assertEquals("EQ", orClauses[0].getName()); | ||||
|     Assert.assertSame(EqFilter.class, orClauses[0].getClass()); | ||||
|     eq = (EqFilter<?>) orClauses[0]; | ||||
|     Assert.assertEquals("v2", eq.getKey()); | ||||
|     Assert.assertEquals(true, eq.getValue()); | ||||
|     // Second IN in OR | ||||
|     Assert.assertEquals("IN", orClauses[1].getName()); | ||||
|     Assert.assertSame(InFilter.class, orClauses[1].getClass()); | ||||
|     in = (InFilter<?>) orClauses[1]; | ||||
|     Assert.assertEquals("v3", in.getKey()); | ||||
|     Assert.assertArrayEquals(new Double[]{ 1.3, 1.5 }, in.getValues().toArray()); | ||||
|   } | ||||
| 
 | ||||
|   @Test(expected = IllegalArgumentException.class) | ||||
|   public void testQueryInValidFilter() throws JsonProcessingException { | ||||
|     Query q = new Query(); | ||||
| 
 | ||||
|     AndFilter filter = new AndFilter(); | ||||
|     filter.addClause(new EqFilter<>("key", "value")); | ||||
|     filter.addClause(new InFilter<>("key2", "v1", "v2")); | ||||
| 
 | ||||
|     OrFilter orFilter = new OrFilter(); | ||||
|     orFilter.addClause(new EqFilter<>("v2", true)); | ||||
|     // invalid OR filter | ||||
| 
 | ||||
|     filter.addClause(orFilter); | ||||
| 
 | ||||
|     // Add Filter | ||||
|     q.setFilter(filter); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,59 @@ | |||
| package io.dapr.client.domain.query.filters; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import org.junit.Assert; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| public class AndFilterTest { | ||||
| 
 | ||||
|   private final static ObjectMapper MAPPER = new ObjectMapper(); | ||||
| 
 | ||||
|   private String json = "{\"AND\":[{\"EQ\":{\"key\":\"value\"}},{\"IN\":{\"key2\":[\"v1\",\"v2\"]}}]}"; | ||||
| 
 | ||||
|   @SuppressWarnings("rawtypes") | ||||
|   @Test | ||||
|   public void testSerialization() throws JsonProcessingException { | ||||
|     AndFilter filter = new AndFilter(); | ||||
|     filter.addClause(new EqFilter<>("key", "value")); | ||||
|     filter.addClause(new InFilter<>("key2", "v1", "v2")); | ||||
| 
 | ||||
|     Assert.assertEquals(json, MAPPER.writeValueAsString((Filter) filter)); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void testDeserialization() throws JsonProcessingException { | ||||
|     Filter<?> res = MAPPER.readValue(json, Filter.class); | ||||
| 
 | ||||
|     // Check for AndFilter | ||||
|     Assert.assertEquals("AND", res.getName()); | ||||
|     Assert.assertSame(AndFilter.class, res.getClass()); | ||||
| 
 | ||||
|     AndFilter filter = (AndFilter) res; | ||||
|     // Check 2 clauses | ||||
|     Assert.assertEquals(2, filter.getClauses().length); | ||||
|     // First EQ | ||||
|     Assert.assertSame(EqFilter.class, filter.getClauses()[0].getClass()); | ||||
|     EqFilter<?> eq = (EqFilter<?>) filter.getClauses()[0]; | ||||
|     Assert.assertEquals("key", eq.getKey()); | ||||
|     Assert.assertEquals("value", eq.getValue()); | ||||
|     // Second IN | ||||
|     Assert.assertSame(InFilter.class, filter.getClauses()[1].getClass()); | ||||
|     InFilter<?> in = (InFilter<?>) filter.getClauses()[1]; | ||||
|     Assert.assertEquals("key2", in.getKey()); | ||||
|     Assert.assertArrayEquals(new String[]{ "v1", "v2" }, in.getValues().toArray()); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void testValidation() { | ||||
|     AndFilter filter = new AndFilter(); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
|     filter.addClause(new EqFilter<>("key1", "v2")); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
|     filter.addClause(new EqFilter<>("key2", "v3")); | ||||
|     Assert.assertTrue(filter.isValid()); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,45 @@ | |||
| package io.dapr.client.domain.query.filters; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import org.junit.Assert; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| public class EqFilterTest { | ||||
|   private static final ObjectMapper MAPPER = new ObjectMapper(); | ||||
| 
 | ||||
|   String json = "{\"EQ\":{\"key\":1.5}}"; | ||||
| 
 | ||||
|   @Test | ||||
|   public void testSerialization() throws JsonProcessingException { | ||||
|     EqFilter<?> filter = new EqFilter<>("key", 1.5); | ||||
| 
 | ||||
|     Assert.assertEquals(json, MAPPER.writeValueAsString(filter)); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void testDeserialization() throws JsonProcessingException { | ||||
|     EqFilter<?> filter = MAPPER.readValue(json, EqFilter.class); | ||||
|     Assert.assertEquals("key", filter.getKey()); | ||||
|     Assert.assertEquals(1.5, filter.getValue()); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void testValidation() { | ||||
|     EqFilter<?> filter = new EqFilter<>(null, "val"); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
| 
 | ||||
|     filter = new EqFilter<>("", ""); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
|     filter = new EqFilter<>("", true); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
|     filter = new EqFilter<>("   ", "valid"); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
|     filter = new EqFilter<>("valid", ""); | ||||
|     Assert.assertTrue(filter.isValid()); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,48 @@ | |||
| package io.dapr.client.domain.query.filters; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import org.junit.Assert; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| public class InFilterTest { | ||||
|   private static final ObjectMapper MAPPER = new ObjectMapper(); | ||||
| 
 | ||||
|   String json = "{\"IN\":{\"key\":[1.5,44.0]}}"; | ||||
| 
 | ||||
|   @Test | ||||
|   public void testSerialization() throws JsonProcessingException { | ||||
|     InFilter<?> filter = new InFilter<>("key", 1.5, 44.0); | ||||
| 
 | ||||
|     Assert.assertEquals(json, MAPPER.writeValueAsString(filter)); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void testDeserialization() throws JsonProcessingException { | ||||
|     InFilter<?> filter = MAPPER.readValue(json, InFilter.class); | ||||
|     Assert.assertEquals("key", filter.getKey()); | ||||
|     Assert.assertArrayEquals(new Double[]{ 1.5, 44.0 }, filter.getValues().toArray()); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void testValidation() { | ||||
|     InFilter<?> filter = new InFilter<>(null, "val"); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
| 
 | ||||
|     filter = new InFilter<>("", ""); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
|     filter = new InFilter<>("", true); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
|     filter = new InFilter<>("   ", "valid"); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
|     filter = new InFilter<>("valid", ""); | ||||
|     Assert.assertTrue(filter.isValid()); | ||||
| 
 | ||||
|     filter = new InFilter<>("valid", "1.5", "2.5"); | ||||
|     Assert.assertTrue(filter.isValid()); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,57 @@ | |||
| package io.dapr.client.domain.query.filters; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import org.junit.Assert; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| public class OrFilterTest { | ||||
|   private final static ObjectMapper MAPPER = new ObjectMapper(); | ||||
| 
 | ||||
|   private String json = "{\"OR\":[{\"EQ\":{\"key\":\"value\"}},{\"IN\":{\"key2\":[\"v1\",\"v2\"]}}]}"; | ||||
| 
 | ||||
|   @SuppressWarnings("rawtypes") | ||||
|   @Test | ||||
|   public void testSerialization() throws JsonProcessingException { | ||||
|     OrFilter filter = new OrFilter(); | ||||
|     filter.addClause(new EqFilter<>("key", "value")); | ||||
|     filter.addClause(new InFilter<>("key2", "v1", "v2")); | ||||
| 
 | ||||
|     Assert.assertEquals(json, MAPPER.writeValueAsString((Filter) filter)); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void testDeserialization() throws JsonProcessingException { | ||||
|     Filter<?> res = MAPPER.readValue(json, Filter.class); | ||||
| 
 | ||||
|     // Check for AndFilter | ||||
|     Assert.assertEquals("OR", res.getName()); | ||||
|     Assert.assertSame(OrFilter.class, res.getClass()); | ||||
| 
 | ||||
|     OrFilter filter = (OrFilter) res; | ||||
|     // Check 2 clauses | ||||
|     Assert.assertEquals(2, filter.getClauses().length); | ||||
|     // First EQ | ||||
|     Assert.assertSame(EqFilter.class, filter.getClauses()[0].getClass()); | ||||
|     EqFilter<?> eq = (EqFilter<?>) filter.getClauses()[0]; | ||||
|     Assert.assertEquals("key", eq.getKey()); | ||||
|     Assert.assertEquals("value", eq.getValue()); | ||||
|     // Second IN | ||||
|     Assert.assertSame(InFilter.class, filter.getClauses()[1].getClass()); | ||||
|     InFilter<?> in = (InFilter<?>) filter.getClauses()[1]; | ||||
|     Assert.assertEquals("key2", in.getKey()); | ||||
|     Assert.assertArrayEquals(new String[]{ "v1", "v2" }, in.getValues().toArray()); | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void testValidation() { | ||||
|     OrFilter filter = new OrFilter(); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
|     filter.addClause(new EqFilter<>("key1", "v2")); | ||||
|     Assert.assertFalse(filter.isValid()); | ||||
| 
 | ||||
|     filter.addClause(new EqFilter<>("key2", "v3")); | ||||
|     Assert.assertTrue(filter.isValid()); | ||||
|   } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue