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:
Mukundan Sundararajan 2022-03-21 09:33:11 +05:30 committed by GitHub
parent e3fa24f67b
commit 06d92dafca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 2560 additions and 126 deletions

View File

@ -20,7 +20,7 @@ jobs:
matrix: matrix:
java: [ 11, 13, 15, 16 ] java: [ 11, 13, 15, 16 ]
env: env:
GOVER: 1.15.0 GOVER: 1.17.7
GOOS: linux GOOS: linux
GOARCH: amd64 GOARCH: amd64
GOPROXY: https://proxy.golang.org GOPROXY: https://proxy.golang.org
@ -29,7 +29,7 @@ jobs:
DAPR_RUNTIME_VER: 1.6.0-rc.2 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_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.6.0-rc.1/install/install.sh
DAPR_CLI_REF: DAPR_CLI_REF:
DAPR_REF: DAPR_REF: 5a307f3deaa1b322f7945179adad0403de80eb7e
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up OpenJDK ${{ env.JDK_VER }} - name: Set up OpenJDK ${{ env.JDK_VER }}
@ -91,6 +91,10 @@ jobs:
run: | run: |
docker-compose -f ./sdk-tests/deploy/local-test-vault.yml up -d docker-compose -f ./sdk-tests/deploy/local-test-vault.yml up -d
docker ps 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 - name: Clean up files
run: mvn clean run: mvn clean
- name: Build sdk - name: Build sdk

View File

@ -31,7 +31,7 @@ jobs:
matrix: matrix:
java: [ 11, 13, 15, 16 ] java: [ 11, 13, 15, 16 ]
env: env:
GOVER: 1.15.0 GOVER: 1.17.7
GOOS: linux GOOS: linux
GOARCH: amd64 GOARCH: amd64
GOPROXY: https://proxy.golang.org GOPROXY: https://proxy.golang.org
@ -40,7 +40,7 @@ jobs:
DAPR_RUNTIME_VER: 1.6.0-rc.2 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_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.6.0-rc.1/install/install.sh
DAPR_CLI_REF: DAPR_CLI_REF:
DAPR_REF: DAPR_REF: 5a307f3deaa1b322f7945179adad0403de80eb7e
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up OpenJDK ${{ env.JDK_VER }} - name: Set up OpenJDK ${{ env.JDK_VER }}
@ -108,6 +108,10 @@ jobs:
sudo apt-get install vault sudo apt-get install vault
# Verify vault is installed # Verify vault is installed
vault -h 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 - name: Clean up files
run: mvn clean run: mvn clean
- name: Build sdk - name: Build sdk
@ -154,3 +158,7 @@ jobs:
working-directory: ./examples working-directory: ./examples
run: | 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

View File

@ -241,6 +241,8 @@ public interface DemoActor {
### Get & Subscribe to application configurations ### 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 ```java
import io.dapr.client.DaprClientBuilder; import io.dapr.client.DaprClientBuilder;
import io.dapr.client.DaprPreviewClient; 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 >}}). - 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. - 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 ## Related links
- [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples) - [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples)

View File

@ -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

View File

@ -61,7 +61,7 @@
<dependency> <dependency>
<groupId>com.github.os72</groupId> <groupId>com.github.os72</groupId>
<artifactId>protoc-jar-maven-plugin</artifactId> <artifactId>protoc-jar-maven-plugin</artifactId>
<version>3.10.1</version> <version>3.11.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -130,7 +130,7 @@
<plugin> <plugin>
<groupId>com.github.os72</groupId> <groupId>com.github.os72</groupId>
<artifactId>protoc-jar-maven-plugin</artifactId> <artifactId>protoc-jar-maven-plugin</artifactId>
<version>3.10.1</version> <version>3.11.4</version>
<executions> <executions>
<execution> <execution>
<phase>generate-sources</phase> <phase>generate-sources</phase>

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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 -->

View File

@ -167,43 +167,26 @@ dapr run --components-path ./components/state --app-id state_example -- java -ja
<!-- END_STEP --> <!-- END_STEP -->
Once running, the OutputBindingExample should print the output as follows: Once running, the StateClient should print the output as follows:
```txt ```txt
== APP == Waiting for Dapr sidecar ... == APP == Waiting for Dapr sidecar ...
== APP == Dapr sidecar is ready. == APP == Dapr sidecar is ready.
== APP == Saving class with message: my message == APP == Saving class with message: my message
== APP == Retrieved class message from state: my message == APP == Retrieved class message from state: my message
== APP == Updating previous state and adding another state 'test state'... == APP == Updating previous state and adding another state 'test state'...
== APP == Saving updated class with message: my message updated == APP == Saving updated class with message: my message updated
== APP == Retrieved messages using bulk get: == APP == Retrieved messages using bulk get:
== APP == StateKeyValue{key='myKey', value=my message updated, etag='2', metadata={'{}'}, error='null', options={'null'}} == 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 == StateKeyValue{key='myKey2', value=test message, etag='1', metadata={'{}'}, error='null', options={'null'}}
== APP == Deleting states... == APP == Deleting states...
== APP == Verify delete key request is aborted if an etag different from stored is passed. == 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 == 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 delete again with correct etag.
== APP == Trying to retrieve deleted states: == APP == Trying to retrieve deleted states:
== APP == StateKeyValue{key='myKey', value=null, etag='null', metadata={'{}'}, error='null', options={'null'}} == 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 == StateKeyValue{key='myKey2', value=null, etag='null', metadata={'{}'}, error='null', options={'null'}}
== APP == Done == APP == Done
``` ```
### Cleanup ### Cleanup

View File

@ -14,9 +14,9 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<grpc.version>1.39.0</grpc.version> <grpc.version>1.42.1</grpc.version>
<protobuf.version>3.13.0</protobuf.version> <protobuf.version>3.17.3</protobuf.version>
<dapr.proto.baseurl>https://raw.githubusercontent.com/dapr/dapr/v1.6.0-rc.3/dapr/proto</dapr.proto.baseurl> <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> <os-maven-plugin.version>1.6.2</os-maven-plugin.version>
<maven-dependency-plugin.version>3.1.1</maven-dependency-plugin.version> <maven-dependency-plugin.version>3.1.1</maven-dependency-plugin.version>
<maven-antrun-plugin.version>1.8</maven-antrun-plugin.version> <maven-antrun-plugin.version>1.8</maven-antrun-plugin.version>

View File

@ -18,7 +18,7 @@
<properties> <properties>
<maven.deploy.skip>false</maven.deploy.skip> <maven.deploy.skip>false</maven.deploy.skip>
<grpc.version>1.39.0</grpc.version> <grpc.version>1.42.1</grpc.version>
</properties> </properties>
<dependencies> <dependencies>

View File

@ -20,7 +20,7 @@
<protobuf.output.directory>${project.build.directory}/generated-sources</protobuf.output.directory> <protobuf.output.directory>${project.build.directory}/generated-sources</protobuf.output.directory>
<protobuf.input.directory>${project.build.directory}/proto</protobuf.input.directory> <protobuf.input.directory>${project.build.directory}/proto</protobuf.input.directory>
<maven.deploy.skip>false</maven.deploy.skip> <maven.deploy.skip>false</maven.deploy.skip>
<grpc.version>1.39.0</grpc.version> <grpc.version>1.42.1</grpc.version>
</properties> </properties>
<dependencies> <dependencies>

View File

@ -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

View File

@ -0,0 +1,6 @@
version: '2'
services:
mongo:
image: mongo
ports:
- "27017:27017"

View File

@ -17,8 +17,8 @@
<dapr.sdk.version>1.5.0-SNAPSHOT</dapr.sdk.version> <dapr.sdk.version>1.5.0-SNAPSHOT</dapr.sdk.version>
<protobuf.output.directory>${project.build.directory}/generated-sources</protobuf.output.directory> <protobuf.output.directory>${project.build.directory}/generated-sources</protobuf.output.directory>
<protobuf.input.directory>${project.basedir}/proto</protobuf.input.directory> <protobuf.input.directory>${project.basedir}/proto</protobuf.input.directory>
<grpc.version>1.39.0</grpc.version> <grpc.version>1.42.1</grpc.version>
<protobuf.version>3.13.0</protobuf.version> <protobuf.version>3.17.3</protobuf.version>
<opentelemetry.version>0.14.0</opentelemetry.version> <opentelemetry.version>0.14.0</opentelemetry.version>
<spring-boot.version>2.3.5.RELEASE</spring-boot.version> <spring-boot.version>2.3.5.RELEASE</spring-boot.version>
</properties> </properties>
@ -52,7 +52,7 @@
<dependency> <dependency>
<groupId>com.github.os72</groupId> <groupId>com.github.os72</groupId>
<artifactId>protoc-jar-maven-plugin</artifactId> <artifactId>protoc-jar-maven-plugin</artifactId>
<version>3.10.1</version> <version>3.11.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.opentelemetry</groupId> <groupId>io.opentelemetry</groupId>
@ -149,7 +149,7 @@
<plugin> <plugin>
<groupId>com.github.os72</groupId> <groupId>com.github.os72</groupId>
<artifactId>protoc-jar-maven-plugin</artifactId> <artifactId>protoc-jar-maven-plugin</artifactId>
<version>3.10.1</version> <version>3.11.4</version>
<executions> <executions>
<execution> <execution>
<phase>generate-sources</phase> <phase>generate-sources</phase>

View File

@ -30,6 +30,8 @@ public abstract class BaseIT {
protected static final String STATE_STORE_NAME = "statestore"; 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 Map<String, DaprRun.Builder> DAPR_RUN_BUILDERS = new HashMap<>();
private static final Queue<Stoppable> TO_BE_STOPPED = new LinkedList<>(); private static final Queue<Stoppable> TO_BE_STOPPED = new LinkedList<>();

View File

@ -13,10 +13,19 @@ limitations under the License.
package io.dapr.it.state; package io.dapr.it.state;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.dapr.client.DaprClient; 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.State;
import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.StateOptions;
import io.dapr.client.domain.TransactionalStateOperation; 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.exceptions.DaprException;
import io.dapr.it.BaseIT; import io.dapr.it.BaseIT;
import org.junit.Test; import org.junit.Test;
@ -24,8 +33,11 @@ import reactor.core.publisher.Mono;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Logger;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; 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). * Common test cases for Dapr client (GRPC and HTTP).
*/ */
public abstract class AbstractStateClientIT extends BaseIT { public abstract class AbstractStateClientIT extends BaseIT {
private static final Logger logger = Logger.getLogger(AbstractStateClientIT.class.getName());
@Test @Test
public void saveAndGetState() { public void saveAndGetState() {
@ -126,6 +139,78 @@ public abstract class AbstractStateClientIT extends BaseIT {
assertNull(result.stream().skip(2).findFirst().get().getError()); 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 @Test
public void saveUpdateAndGetState() { public void saveUpdateAndGetState() {
@ -159,6 +244,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
State<MyData> myDataResponse = response.block(); State<MyData> myDataResponse = response.block();
//review that the update was success action //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 A", myDataResponse.getValue().getPropertyA());
assertEquals("data in property B2", myDataResponse.getValue().getPropertyB()); assertEquals("data in property B2", myDataResponse.getValue().getPropertyB());
} }

View File

@ -18,7 +18,7 @@
<properties> <properties>
<maven.deploy.skip>false</maven.deploy.skip> <maven.deploy.skip>false</maven.deploy.skip>
<grpc.version>1.39.0</grpc.version> <grpc.version>1.42.1</grpc.version>
<argLine> <argLine>
--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED
</argLine> </argLine>

View File

@ -13,6 +13,7 @@ limitations under the License.
package io.dapr.client; package io.dapr.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dapr.client.domain.ConfigurationItem; import io.dapr.client.domain.ConfigurationItem;
import io.dapr.client.domain.DeleteStateRequest; import io.dapr.client.domain.DeleteStateRequest;
import io.dapr.client.domain.ExecuteStateTransactionRequest; 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.InvokeBindingRequest;
import io.dapr.client.domain.InvokeMethodRequest; import io.dapr.client.domain.InvokeMethodRequest;
import io.dapr.client.domain.PublishEventRequest; 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.SaveStateRequest;
import io.dapr.client.domain.State; import io.dapr.client.domain.State;
import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.StateOptions;
import io.dapr.client.domain.SubscribeConfigurationRequest; import io.dapr.client.domain.SubscribeConfigurationRequest;
import io.dapr.client.domain.TransactionalStateOperation; import io.dapr.client.domain.TransactionalStateOperation;
import io.dapr.client.domain.query.Query;
import io.dapr.serializer.DaprObjectSerializer; import io.dapr.serializer.DaprObjectSerializer;
import io.dapr.utils.TypeRef; import io.dapr.utils.TypeRef;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
@ -50,6 +54,11 @@ import java.util.stream.Collectors;
*/ */
abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { 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. * A utility class for serialize and deserialize the transient objects.
*/ */
@ -303,6 +312,82 @@ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient {
return this.getState(storeName, key, options, TypeRef.get(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} * {@inheritDoc}
*/ */

View File

@ -29,6 +29,9 @@ import io.dapr.client.domain.HttpExtension;
import io.dapr.client.domain.InvokeBindingRequest; import io.dapr.client.domain.InvokeBindingRequest;
import io.dapr.client.domain.InvokeMethodRequest; import io.dapr.client.domain.InvokeMethodRequest;
import io.dapr.client.domain.PublishEventRequest; 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.SaveStateRequest;
import io.dapr.client.domain.State; import io.dapr.client.domain.State;
import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.StateOptions;
@ -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. * Closes the ManagedChannel for GRPC.
* @see io.grpc.ManagedChannel#shutdown() *
* @throws IOException on exception. * @throws IOException on exception.
* @see io.grpc.ManagedChannel#shutdown()
*/ */
@Override @Override
public void close() throws Exception { public void close() throws Exception {

View File

@ -27,6 +27,9 @@ import io.dapr.client.domain.HttpExtension;
import io.dapr.client.domain.InvokeBindingRequest; import io.dapr.client.domain.InvokeBindingRequest;
import io.dapr.client.domain.InvokeMethodRequest; import io.dapr.client.domain.InvokeMethodRequest;
import io.dapr.client.domain.PublishEventRequest; 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.SaveStateRequest;
import io.dapr.client.domain.State; import io.dapr.client.domain.State;
import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.StateOptions;
@ -44,6 +47,7 @@ import reactor.core.publisher.Mono;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -192,17 +196,21 @@ public class DaprClientHttp extends AbstractDaprClient {
if (method == null || method.trim().isEmpty()) { if (method == null || method.trim().isEmpty()) {
throw new IllegalArgumentException("Method name cannot be null or empty."); 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); byte[] serializedRequestBody = objectSerializer.serialize(request);
String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "invoke", appId, "method", method };
final Map<String, String> headers = new HashMap<>(); final Map<String, String> headers = new HashMap<>();
if (contentType != null && !contentType.isEmpty()) { if (contentType != null && !contentType.isEmpty()) {
headers.put("content-type", contentType); headers.put("content-type", contentType);
} }
headers.putAll(httpExtension.getHeaders()); headers.putAll(httpExtension.getHeaders());
Mono<DaprHttp.Response> response = Mono.subscriberContext().flatMap( 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) httpExtension.getQueryParams(), serializedRequestBody, headers, context)
); );
return response.flatMap(r -> getMono(type, r)); return response.flatMap(r -> getMono(type, r));
@ -649,6 +657,46 @@ public class DaprClientHttp extends AbstractDaprClient {
.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);
}
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -669,6 +717,47 @@ public class DaprClientHttp extends AbstractDaprClient {
.then(); .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);
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -687,6 +776,7 @@ public class DaprClientHttp extends AbstractDaprClient {
/** /**
* Converts metadata map into Query params. * Converts metadata map into Query params.
*
* @param metadata metadata map * @param metadata metadata map
* @return Query params * @return Query params
*/ */

View File

@ -23,6 +23,8 @@ import io.dapr.client.domain.HttpExtension;
import io.dapr.client.domain.InvokeBindingRequest; import io.dapr.client.domain.InvokeBindingRequest;
import io.dapr.client.domain.InvokeMethodRequest; import io.dapr.client.domain.InvokeMethodRequest;
import io.dapr.client.domain.PublishEventRequest; 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.SaveStateRequest;
import io.dapr.client.domain.State; import io.dapr.client.domain.State;
import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.StateOptions;

View File

@ -31,7 +31,6 @@ import reactor.core.publisher.Mono;
import reactor.util.context.Context; import reactor.util.context.Context;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -51,6 +50,11 @@ public class DaprHttp implements AutoCloseable {
*/ */
public static final String API_VERSION = "v1.0"; 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. * Header used for request id in Dapr.
*/ */

View File

@ -15,7 +15,11 @@ package io.dapr.client;
import io.dapr.client.domain.ConfigurationItem; import io.dapr.client.domain.ConfigurationItem;
import io.dapr.client.domain.GetConfigurationRequest; 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.SubscribeConfigurationRequest;
import io.dapr.client.domain.query.Query;
import io.dapr.utils.TypeRef;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -103,4 +107,120 @@ public interface DaprPreviewClient extends AutoCloseable {
* @return Flux of List of configuration items * @return Flux of List of configuration items
*/ */
Flux<List<ConfigurationItem>> subscribeToConfiguration(SubscribeConfigurationRequest request); 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);
} }

View File

@ -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 + "'"
+ "}";
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -14,9 +14,16 @@ limitations under the License.
package io.dapr.client; 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.ConfigurationItem;
import io.dapr.client.domain.GetConfigurationRequest; 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.SubscribeConfigurationRequest;
import io.dapr.client.domain.query.Query;
import io.dapr.serializer.DefaultObjectSerializer; import io.dapr.serializer.DefaultObjectSerializer;
import io.dapr.v1.CommonProtos; import io.dapr.v1.CommonProtos;
import io.dapr.v1.DaprGrpc; import io.dapr.v1.DaprGrpc;
@ -29,6 +36,7 @@ import org.mockito.stubbing.Answer;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -37,14 +45,24 @@ import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static io.dapr.utils.TestUtils.assertThrowsDaprException; 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.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any; 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 { public class DaprPreviewClientGrpcTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String CONFIG_STORE_NAME = "MyConfigStore"; private static final String CONFIG_STORE_NAME = "MyConfigStore";
private static final String QUERY_STORE_NAME = "testQueryStore";
private Closeable closeable; private Closeable closeable;
private DaprGrpc.DaprStub daprStub; private DaprGrpc.DaprStub daprStub;
@ -269,4 +287,105 @@ public class DaprPreviewClientGrpcTest {
.build(); .build();
return responseEnvelope; 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();
}
} }

View File

@ -1,30 +1,32 @@
/* /*
* Copyright (c) Microsoft Corporation and Dapr Contributors. * Copyright 2021 The Dapr Authors
* Licensed under the MIT License. * 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; package io.dapr.client;
import com.fasterxml.jackson.core.type.TypeReference; import io.dapr.client.domain.QueryStateRequest;
import com.fasterxml.jackson.dataformat.xml.XmlMapper; import io.dapr.client.domain.QueryStateResponse;
import io.dapr.client.domain.query.Query;
import io.dapr.config.Properties; import io.dapr.config.Properties;
import io.dapr.exceptions.DaprException; import io.dapr.exceptions.DaprException;
import io.dapr.serializer.DaprObjectSerializer;
import io.dapr.utils.TypeRef; import io.dapr.utils.TypeRef;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.mock.Behavior; import okhttp3.mock.Behavior;
import okhttp3.mock.MockInterceptor; import okhttp3.mock.MockInterceptor;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito;
import reactor.core.publisher.Mono;
import java.io.IOException; import static org.junit.Assert.assertEquals;
import java.net.ServerSocket; import static org.junit.Assert.assertNotNull;
import java.net.Socket;
import static io.dapr.utils.TestUtils.findFreePort;
import static org.junit.Assert.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
public class DaprPreviewClientHttpTest { public class DaprPreviewClientHttpTest {
@ -66,4 +68,56 @@ public class DaprPreviewClientHttpTest {
daprPreviewClientHttp.subscribeToConfiguration(CONFIG_STORE_NAME, "key1", "key2").blockFirst(); 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());
}
} }

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}