diff --git a/.github/scripts/update_sdk_version.sh b/.github/scripts/update_sdk_version.sh index f51c334742..307a150c01 100755 --- a/.github/scripts/update_sdk_version.sh +++ b/.github/scripts/update_sdk_version.sh @@ -4,14 +4,23 @@ set -uex DAPR_JAVA_SDK_VERSION=$1 -# The workflows sdk tracks the regular SDK minor and patch versions, just not the major. -# Replaces the workflows SDK major version to 0 until it is stable. -DAPR_JAVA_WORKFLOWS_SDK_VERSION=`echo $DAPR_JAVA_SDK_VERSION | sed 's/^[0-9]*\./0./'` +# Alpha artifacts of the sdk tracks the regular SDK minor and patch versions, just not the major. +# Replaces the SDK major version to 0 for alpha artifacts. +DAPR_JAVA_SDK_ALPHA_VERSION=`echo $DAPR_JAVA_SDK_VERSION | sed 's/^[0-9]*\./0./'` mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_VERSION +mvn versions:set-property -Dproperty=dapr.sdk.alpha.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION mvn versions:set-property -Dproperty=dapr.sdk.version -DnewVersion=$DAPR_JAVA_SDK_VERSION -f sdk-tests/pom.xml +mvn versions:set-property -Dproperty=dapr.sdk.alpha.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f sdk-tests/pom.xml -mvn versions:set -DnewVersion=$DAPR_JAVA_WORKFLOWS_SDK_VERSION -f sdk-workflows/pom.xml -mvn versions:set-property -Dproperty=dapr.sdk-workflows.version -DnewVersion=$DAPR_JAVA_WORKFLOWS_SDK_VERSION +################### +# Alpha artifacts # +################### + +# sdk-workflows +mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f sdk-workflows/pom.xml + +# testcontainers-dapr +mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f testcontainers-dapr/pom.xml git clean -f diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5fb7bc36c3..44077e121a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,21 +24,13 @@ jobs: fail-fast: false matrix: java: [ 17 ] - spring-boot-version: [ 3.0.13 ] - spring-boot-display-version: [ 3.0.x ] + spring-boot-version: [ 3.2.6 ] + spring-boot-display-version: [ 3.2.x ] experimental: [ false ] include: - java: 17 - spring-boot-version: 2.7.18 - spring-boot-display-version: 2.7.x - experimental: false - - java: 17 - spring-boot-version: 2.6.14 - spring-boot-display-version: 2.6.x - experimental: false - - java: 17 - spring-boot-version: 2.5.7 - spring-boot-display-version: 2.5.x + spring-boot-version: 3.3.0 + spring-boot-display-version: 3.3.x experimental: false env: GOVER: "1.20" @@ -46,9 +38,9 @@ jobs: GOARCH: amd64 GOPROXY: https://proxy.golang.org JDK_VER: ${{ matrix.java }} - DAPR_CLI_VER: 1.12.0 + DAPR_CLI_VER: 1.13.0-rc.1 DAPR_RUNTIME_VER: 1.13.0-rc.2 - DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.12.0/install/install.sh + DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.13.0-rc.1/install/install.sh DAPR_CLI_REF: DAPR_REF: TOXIPROXY_URL: https://github.com/Shopify/toxiproxy/releases/download/v2.5.0/toxiproxy-server-linux-amd64 @@ -107,7 +99,7 @@ jobs: ./dist/linux_amd64/release/placement & - name: Spin local environment run: | - docker-compose -f ./sdk-tests/deploy/local-test.yml up -d mongo kafka + docker compose -f ./sdk-tests/deploy/local-test.yml up -d mongo kafka docker ps - name: Install local ToxiProxy to simulate connectivity issues to Dapr sidecar run: | diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7c73e5d3eb..897e9bfa0a 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -37,9 +37,9 @@ jobs: GOARCH: amd64 GOPROXY: https://proxy.golang.org JDK_VER: ${{ matrix.java }} - DAPR_CLI_VER: 1.12.0 + DAPR_CLI_VER: 1.13.0-rc.1 DAPR_RUNTIME_VER: 1.13.0-rc.5 - DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.12.0/install/install.sh + DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.13.0-rc.1/install/install.sh DAPR_CLI_REF: DAPR_REF: steps: @@ -100,10 +100,6 @@ jobs: echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV pip3 install setuptools wheel pip3 install mechanical-markdown - - name: Install Local mongo database using docker-compose - run: | - docker-compose -f ./sdk-tests/deploy/local-test.yml up -d mongo - docker ps - name: Clean up files run: ./mvnw clean - name: Build sdk @@ -146,14 +142,10 @@ jobs: working-directory: ./examples run: | mm.py ./src/main/java/io/dapr/examples/unittesting/README.md - - name: Validate Configuration gRPC API example - working-directory: ./examples - run: | - mm.py ./src/main/java/io/dapr/examples/configuration/grpc/README.md - - name: Validate Configuration HTTP API example + - name: Validate Configuration API example working-directory: ./examples run: | - mm.py ./src/main/java/io/dapr/examples/configuration/http/README.md + mm.py ./src/main/java/io/dapr/examples/configuration/README.md - name: Validate actors example working-directory: ./examples run: | diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index a6cd062b92..cd3bcc3182 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -14,5 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +wrapperVersion=3.3.2 +distributionType=script distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar diff --git a/.sdkmanrc b/.sdkmanrc index 093deea4b1..543d268ab2 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1,4 +1,4 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=11.0.19-tem +java=17.0.11-tem maven=3.8.5 \ No newline at end of file diff --git a/README.md b/README.md index feaada85b5..ece881458d 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ Try the following examples to learn more about Dapr's Java SDK: * [Binding with input over Http](./examples/src/main/java/io/dapr/examples/bindings/http) * [Actors](./examples/src/main/java/io/dapr/examples/actors/) * [Secrets management](./examples/src/main/java/io/dapr/examples/secrets) +* [Configuration](./examples/src/main/java/io/dapr/examples/configuration) * [Distributed tracing with OpenTelemetry SDK](./examples/src/main/java/io/dapr/examples/tracing) * [Exception handling](./examples/src/main/java/io/dapr/examples/exception) * [Unit testing](./examples/src/main/java/io/dapr/examples/unittesting) @@ -236,13 +237,13 @@ Similarly, all of these need to be run for running the ITs either individually o Run the following commands from the root of the repo to start all the docker containers that the tests depend on. ```bash -docker-compose -f ./sdk-tests/deploy/local-test.yml up -d +docker compose -f ./sdk-tests/deploy/local-test.yml up -d ``` To stop the containers and services, run the following commands. ```bash -docker-compose -f ./sdk-tests/deploy/local-test.yml down +docker compose -f ./sdk-tests/deploy/local-test.yml down ``` diff --git a/checkstyle.xml b/checkstyle.xml index 6482adacaf..e1d5dfbecb 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -13,8 +13,8 @@ --> + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - + + + + - - - - - - + + + + + - - - - - + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml b/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml new file mode 100644 index 0000000000..788735d38b --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml @@ -0,0 +1,79 @@ + + + 4.0.0 + + + io.dapr.spring + dapr-spring-parent + 0.12.0-SNAPSHOT + + + dapr-spring-boot-autoconfigure + dapr-spring-boot-autoconfigure + Dapr Spring Boot Autoconfigure + jar + + + + io.dapr.spring + dapr-spring-core + ${project.parent.version} + true + + + io.dapr.spring + dapr-spring-data + ${project.parent.version} + true + + + io.dapr.spring + dapr-spring-messaging + ${project.parent.version} + true + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-autoconfigure-processor + true + + + org.springframework.data + spring-data-keyvalue + true + + + org.springframework.boot + spring-boot-starter-web + test + + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + com.vaadin.external.google + android-json + + + + + io.dapr + testcontainers-dapr + ${testcontainers-dapr.version} + test + + + + diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java new file mode 100644 index 0000000000..67b31816b2 --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024 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.spring.boot.autoconfigure.client; + +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.spring.core.client.DaprClientCustomizer; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +import java.util.stream.Collectors; + +@AutoConfiguration +@ConditionalOnClass(DaprClient.class) +public class DaprClientAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + DaprClientBuilderConfigurer daprClientBuilderConfigurer(ObjectProvider customizerProvider) { + DaprClientBuilderConfigurer configurer = new DaprClientBuilderConfigurer(); + configurer.setDaprClientCustomizer(customizerProvider.orderedStream().collect(Collectors.toList())); + + return configurer; + } + + @Bean + @ConditionalOnMissingBean + DaprClientBuilder daprClientBuilder(DaprClientBuilderConfigurer daprClientBuilderConfigurer) { + DaprClientBuilder builder = new DaprClientBuilder(); + + return daprClientBuilderConfigurer.configure(builder); + } + + @Bean + @ConditionalOnMissingBean + DaprClient daprClient(DaprClientBuilder daprClientBuilder) { + return daprClientBuilder.build(); + } + +} diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientBuilderConfigurer.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientBuilderConfigurer.java new file mode 100644 index 0000000000..dd5876d0ee --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientBuilderConfigurer.java @@ -0,0 +1,52 @@ +/* + * Copyright 2024 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.spring.boot.autoconfigure.client; + +import io.dapr.client.DaprClientBuilder; +import io.dapr.spring.core.client.DaprClientCustomizer; + +import java.util.List; + +/** + * Builder for configuring a {@link DaprClientBuilder}. + */ +public class DaprClientBuilderConfigurer { + + private List customizers; + + void setDaprClientCustomizer(List customizers) { + this.customizers = customizers; + } + + /** + * Configure the specified {@link DaprClientBuilder}. The builder can be further + * tuned and default settings can be overridden. + * + * @param builder the {@link DaprClientBuilder} instance to configure + * @return the configured builder + */ + public DaprClientBuilder configure(DaprClientBuilder builder) { + applyCustomizers(builder); + return builder; + } + + private void applyCustomizers(DaprClientBuilder builder) { + if (this.customizers != null) { + for (DaprClientCustomizer customizer : this.customizers) { + customizer.customize(builder); + } + } + } + +} diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/pubsub/DaprPubSubProperties.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/pubsub/DaprPubSubProperties.java new file mode 100644 index 0000000000..5c04afa47a --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/pubsub/DaprPubSubProperties.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 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.spring.boot.autoconfigure.pubsub; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = DaprPubSubProperties.CONFIG_PREFIX) +public class DaprPubSubProperties { + + public static final String CONFIG_PREFIX = "dapr.pubsub"; + + /** + * Name of the PubSub Dapr component. + */ + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/statestore/DaprStateStoreProperties.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/statestore/DaprStateStoreProperties.java new file mode 100644 index 0000000000..fba5f71f6e --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/statestore/DaprStateStoreProperties.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024 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.spring.boot.autoconfigure.statestore; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = DaprStateStoreProperties.CONFIG_PREFIX) +public class DaprStateStoreProperties { + + public static final String CONFIG_PREFIX = "dapr.statestore"; + + /** + * Name of the StateStore Dapr component. + */ + private String name; + private String binding; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBinding() { + return binding; + } + + public void setBinding(String binding) { + this.binding = binding; + } +} diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..b583e70bfb --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +io.dapr.spring.boot.autoconfigure.client.DaprClientAutoConfiguration diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTests.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTests.java new file mode 100644 index 0000000000..a44be63dbb --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2024 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.spring.boot.autoconfigure.client; + +import io.dapr.client.DaprClientBuilder; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link DaprClientAutoConfiguration}. + */ +class DaprClientAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(DaprClientAutoConfiguration.class)); + + @Test + void daprClientBuilderConfigurer() { + contextRunner.run(context -> { + assertThat(context).hasSingleBean(DaprClientBuilderConfigurer.class); + }); + } + + @Test + void daprClientBuilder() { + contextRunner.run(context -> { + assertThat(context).hasSingleBean(DaprClientBuilder.class); + }); + } + +} diff --git a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml new file mode 100644 index 0000000000..67d25114eb --- /dev/null +++ b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + io.dapr.spring + dapr-spring-parent + 0.12.0-SNAPSHOT + ../../pom.xml + + + dapr-spring-boot-starter + dapr-spring-boot-starter + Dapr Client Spring Boot Starter + jar + + + + org.springframework.boot + spring-boot-starter + + + io.dapr.spring + dapr-spring-core + ${project.parent.version} + + + io.dapr.spring + dapr-spring-boot-autoconfigure + ${project.parent.version} + + + + diff --git a/dapr-spring/dapr-spring-boot-testcontainers/pom.xml b/dapr-spring/dapr-spring-boot-testcontainers/pom.xml new file mode 100644 index 0000000000..68cae98621 --- /dev/null +++ b/dapr-spring/dapr-spring-boot-testcontainers/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + io.dapr.spring + dapr-spring-parent + 0.12.0-SNAPSHOT + + + dapr-spring-boot-testcontainers + dapr-spring-boot-testcontainers + Dapr Spring Boot Testcontainers + jar + + + + org.springframework + spring-test + + + org.testcontainers + testcontainers + + + org.testcontainers + junit-jupiter + + + com.vaadin.external.google + android-json + + + + + io.dapr + testcontainers-dapr + ${testcontainers-dapr.version} + + + diff --git a/dapr-spring/dapr-spring-boot-testcontainers/src/main/java/io/dapr/testcontainers/DaprModule.java b/dapr-spring/dapr-spring-boot-testcontainers/src/main/java/io/dapr/testcontainers/DaprModule.java new file mode 100644 index 0000000000..2deebe99d3 --- /dev/null +++ b/dapr-spring/dapr-spring-boot-testcontainers/src/main/java/io/dapr/testcontainers/DaprModule.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 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.testcontainers; + +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.Testcontainers; +import org.testcontainers.junit.jupiter.Container; + +import java.util.Collections; + +public interface DaprModule { + + @Container + DaprContainer dapr = new DaprContainer("daprio/daprd:1.13.2") + .withAppName("local-dapr-app") + //Enable Workflows + .withComponent(new Component("kvstore", "state.in-memory", "v1", + Collections.singletonMap("actorStateStore", "true"))) + .withComponent(new Component("pubsub", "pubsub.in-memory", "v1", Collections.emptyMap())) + .withAppPort(8080) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withAppChannelAddress("host.testcontainers.internal"); + + /** + * Expose the Dapr ports to the host. + * + * @param registry the dynamic property registry + */ + @DynamicPropertySource + static void daprProperties(DynamicPropertyRegistry registry) { + Testcontainers.exposeHostPorts(8080); + dapr.start(); + System.setProperty("dapr.grpc.port", Integer.toString(dapr.getGrpcPort())); + System.setProperty("dapr.http.port", Integer.toString(dapr.getHttpPort())); + } + +} diff --git a/dapr-spring/dapr-spring-core/pom.xml b/dapr-spring/dapr-spring-core/pom.xml new file mode 100644 index 0000000000..359f9b8103 --- /dev/null +++ b/dapr-spring/dapr-spring-core/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + + io.dapr.spring + dapr-spring-parent + 0.12.0-SNAPSHOT + + + dapr-spring-core + dapr-spring-core + Dapr Spring Core + jar + + diff --git a/dapr-spring/dapr-spring-core/src/main/java/io/dapr/spring/core/client/DaprClientCustomizer.java b/dapr-spring/dapr-spring-core/src/main/java/io/dapr/spring/core/client/DaprClientCustomizer.java new file mode 100644 index 0000000000..425d7cbf89 --- /dev/null +++ b/dapr-spring/dapr-spring-core/src/main/java/io/dapr/spring/core/client/DaprClientCustomizer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024 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.spring.core.client; + +import io.dapr.client.DaprClientBuilder; + +/** + * Callback interface that can be used to customize a {@link DaprClientBuilder}. + */ +@FunctionalInterface +public interface DaprClientCustomizer { + + /** + * Callback to customize a {@link DaprClientBuilder} instance. + * + * @param daprClientBuilder the client builder to customize + */ + void customize(DaprClientBuilder daprClientBuilder); + +} diff --git a/dapr-spring/dapr-spring-data/pom.xml b/dapr-spring/dapr-spring-data/pom.xml new file mode 100644 index 0000000000..d38e0316a6 --- /dev/null +++ b/dapr-spring/dapr-spring-data/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + + io.dapr.spring + dapr-spring-parent + 0.12.0-SNAPSHOT + + + dapr-spring-data + dapr-spring-data + Dapr Spring Data + jar + + + + org.springframework.data + spring-data-keyvalue + + + + io.dapr + testcontainers-dapr + ${testcontainers-dapr.version} + + + org.testcontainers + junit-jupiter + ${testcontainers-test.version} + test + + + org.testcontainers + postgresql + ${testcontainers-test.version} + test + + + org.testcontainers + mysql + ${testcontainers-test.version} + test + + + + com.mysql + mysql-connector-j + 8.2.0 + test + + + + diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/AbstractDaprKeyValueAdapter.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/AbstractDaprKeyValueAdapter.java new file mode 100644 index 0000000000..ecacda243a --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/AbstractDaprKeyValueAdapter.java @@ -0,0 +1,147 @@ +/* + * Copyright 2024 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.spring.data; + +import io.dapr.client.DaprClient; +import io.dapr.client.domain.GetStateRequest; +import io.dapr.client.domain.SaveStateRequest; +import io.dapr.client.domain.State; +import io.dapr.utils.TypeRef; +import org.springframework.data.keyvalue.core.KeyValueAdapter; +import org.springframework.data.util.CloseableIterator; +import org.springframework.util.Assert; +import reactor.core.publisher.Mono; + +import java.util.Collections; +import java.util.Map; + +public abstract class AbstractDaprKeyValueAdapter implements KeyValueAdapter { + private static final Map CONTENT_TYPE_META = Collections.singletonMap( + "contentType", "application/json"); + + private final DaprClient daprClient; + private final String stateStoreName; + + protected AbstractDaprKeyValueAdapter(DaprClient daprClient, String stateStoreName) { + Assert.notNull(daprClient, "DaprClient must not be null"); + Assert.hasText(stateStoreName, "State store name must not be empty"); + + this.daprClient = daprClient; + this.stateStoreName = stateStoreName; + } + + @Override + public void destroy() throws Exception { + daprClient.close(); + } + + @Override + public void clear() { + // Ignore + } + + @Override + public Object put(Object id, Object item, String keyspace) { + Assert.notNull(id, "Id must not be null"); + Assert.notNull(item, "Item must not be null"); + Assert.hasText(keyspace, "Keyspace must not be empty"); + + String key = resolveKey(keyspace, id); + State state = new State<>(key, item, null, CONTENT_TYPE_META, null); + SaveStateRequest request = new SaveStateRequest(stateStoreName).setStates(state); + + daprClient.saveBulkState(request).block(); + + return item; + } + + @Override + public boolean contains(Object id, String keyspace) { + return get(id, keyspace) != null; + } + + @Override + public Object get(Object id, String keyspace) { + Assert.notNull(id, "Id must not be null"); + Assert.hasText(keyspace, "Keyspace must not be empty"); + + String key = resolveKey(keyspace, id); + + return resolveValue(daprClient.getState(stateStoreName, key, Object.class)); + } + + @Override + public T get(Object id, String keyspace, Class type) { + Assert.notNull(id, "Id must not be null"); + Assert.hasText(keyspace, "Keyspace must not be empty"); + Assert.notNull(type, "Type must not be null"); + + String key = resolveKey(keyspace, id); + GetStateRequest stateRequest = new GetStateRequest(stateStoreName, key).setMetadata(CONTENT_TYPE_META); + + return resolveValue(daprClient.getState(stateRequest, TypeRef.get(type))); + } + + @Override + public Object delete(Object id, String keyspace) { + Object result = get(id, keyspace); + + if (result == null) { + return null; + } + + String key = resolveKey(keyspace, id); + + daprClient.deleteState(stateStoreName, key).block(); + + return result; + } + + @Override + public T delete(Object id, String keyspace, Class type) { + T result = get(id, keyspace, type); + + if (result == null) { + return null; + } + + String key = resolveKey(keyspace, id); + + daprClient.deleteState(stateStoreName, key).block(); + + return result; + } + + @Override + public Iterable getAllOf(String keyspace) { + return getAllOf(keyspace, Object.class); + } + + @Override + public CloseableIterator> entries(String keyspace) { + throw new UnsupportedOperationException("'entries' method is not supported"); + } + + private String resolveKey(String keyspace, Object id) { + return String.format("%s-%s", keyspace, id); + } + + private T resolveValue(Mono> state) { + if (state == null) { + return null; + } + + return state.blockOptional().map(State::getValue).orElse(null); + } +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/DaprKeyValueAdapterResolver.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/DaprKeyValueAdapterResolver.java new file mode 100644 index 0000000000..1aeb3c9c4e --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/DaprKeyValueAdapterResolver.java @@ -0,0 +1,101 @@ +/* + * Copyright 2024 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.spring.data; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.dapr.client.DaprClient; +import io.dapr.client.domain.ComponentMetadata; +import io.dapr.client.domain.DaprMetadata; +import org.springframework.data.keyvalue.core.KeyValueAdapter; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class DaprKeyValueAdapterResolver implements KeyValueAdapterResolver { + private static final List MYSQL_VALUES = Arrays.asList("state.mysql-v1", "bindings.mysql-v1"); + private static final Set MYSQL_MARKERS = new HashSet<>(MYSQL_VALUES); + private static final List POSTGRESQL_VALUES = Arrays.asList("state.postgresql-v1", "bindings.postgresql-v1"); + private static final Set POSTGRESQL_MARKERS = new HashSet<>(POSTGRESQL_VALUES); + private final DaprClient daprClient; + private final ObjectMapper mapper; + private final String stateStoreName; + private final String bindingName; + + /** + * Constructs a {@link DaprKeyValueAdapterResolver}. + * + * @param daprClient The Dapr client. + * @param mapper The object mapper. + * @param stateStoreName The state store name. + * @param bindingName The binding name. + */ + public DaprKeyValueAdapterResolver(DaprClient daprClient, ObjectMapper mapper, String stateStoreName, + String bindingName) { + this.daprClient = daprClient; + this.mapper = mapper; + this.stateStoreName = stateStoreName; + this.bindingName = bindingName; + } + + @Override + public KeyValueAdapter resolve() { + DaprMetadata metadata = daprClient.getMetadata().block(); + + if (metadata == null) { + throw new IllegalStateException("No Dapr metadata found"); + } + + List components = metadata.getComponents(); + + if (components == null || components.isEmpty()) { + throw new IllegalStateException("No components found in Dapr metadata"); + } + + if (shouldUseMySQL(components, stateStoreName, bindingName)) { + return new MySQLDaprKeyValueAdapter(daprClient, mapper, stateStoreName, bindingName); + } + + if (shouldUsePostgreSQL(components, stateStoreName, bindingName)) { + return new PostgreSQLDaprKeyValueAdapter(daprClient, mapper, stateStoreName, bindingName); + } + + throw new IllegalStateException("Could find any adapter matching the given state store and binding"); + } + + @SuppressWarnings("AbbreviationAsWordInName") + private boolean shouldUseMySQL(List components, String stateStoreName, String bindingName) { + boolean stateStoreMatched = components.stream().anyMatch(x -> matchBy(stateStoreName, MYSQL_MARKERS, x)); + boolean bindingMatched = components.stream().anyMatch(x -> matchBy(bindingName, MYSQL_MARKERS, x)); + + return stateStoreMatched && bindingMatched; + } + + @SuppressWarnings("AbbreviationAsWordInName") + private boolean shouldUsePostgreSQL(List components, String stateStoreName, String bindingName) { + boolean stateStoreMatched = components.stream().anyMatch(x -> matchBy(stateStoreName, POSTGRESQL_MARKERS, x)); + boolean bindingMatched = components.stream().anyMatch(x -> matchBy(bindingName, POSTGRESQL_MARKERS, x)); + + return stateStoreMatched && bindingMatched; + } + + private boolean matchBy(String name, Set markers, ComponentMetadata componentMetadata) { + return componentMetadata.getName().equals(name) && markers.contains(getTypeAndVersion(componentMetadata)); + } + + private String getTypeAndVersion(ComponentMetadata component) { + return component.getType() + "-" + component.getVersion(); + } +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/DaprKeyValueTemplate.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/DaprKeyValueTemplate.java new file mode 100644 index 0000000000..c9b7f9f4dd --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/DaprKeyValueTemplate.java @@ -0,0 +1,402 @@ +/* + * Copyright 2024 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.spring.data; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.domain.Sort; +import org.springframework.data.keyvalue.core.IdentifierGenerator; +import org.springframework.data.keyvalue.core.KeyValueAdapter; +import org.springframework.data.keyvalue.core.KeyValueCallback; +import org.springframework.data.keyvalue.core.KeyValueOperations; +import org.springframework.data.keyvalue.core.KeyValuePersistenceExceptionTranslator; +import org.springframework.data.keyvalue.core.event.KeyValueEvent; +import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity; +import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty; +import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; +import org.springframework.data.keyvalue.core.query.KeyValueQuery; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public class DaprKeyValueTemplate implements KeyValueOperations, ApplicationEventPublisherAware { + + private static final PersistenceExceptionTranslator DEFAULT_PERSISTENCE_EXCEPTION_TRANSLATOR = + new KeyValuePersistenceExceptionTranslator(); + + private final KeyValueAdapter adapter; + private final MappingContext, ? extends KeyValuePersistentProperty> + mappingContext; + private final IdentifierGenerator identifierGenerator; + + private PersistenceExceptionTranslator exceptionTranslator = DEFAULT_PERSISTENCE_EXCEPTION_TRANSLATOR; + private @Nullable ApplicationEventPublisher eventPublisher; + private boolean publishEvents = false; + private @SuppressWarnings("rawtypes") Set> eventTypesToPublish = Collections + .emptySet(); + + /** + * Create new {@link DaprKeyValueTemplate} using the given {@link KeyValueAdapterResolver} with a default + * {@link KeyValueMappingContext}. + * + * @param resolver must not be {@literal null}. + */ + public DaprKeyValueTemplate(KeyValueAdapterResolver resolver) { + this(resolver, new KeyValueMappingContext<>()); + } + + /** + * Create new {@link DaprKeyValueTemplate} using the given {@link KeyValueAdapterResolver} and {@link MappingContext}. + * + * @param resolver must not be {@literal null}. + * @param mappingContext must not be {@literal null}. + */ + @SuppressWarnings("LineLength") + public DaprKeyValueTemplate(KeyValueAdapterResolver resolver, + MappingContext, ? extends KeyValuePersistentProperty> mappingContext) { + this(resolver, mappingContext, DefaultIdentifierGenerator.INSTANCE); + } + + /** + * Create new {@link DaprKeyValueTemplate} using the given {@link KeyValueAdapterResolver} and {@link MappingContext}. + * + * @param resolver must not be {@literal null}. + * @param mappingContext must not be {@literal null}. + * @param identifierGenerator must not be {@literal null}. + */ + @SuppressWarnings("LineLength") + public DaprKeyValueTemplate(KeyValueAdapterResolver resolver, + MappingContext, ? extends KeyValuePersistentProperty> mappingContext, + IdentifierGenerator identifierGenerator) { + Assert.notNull(resolver, "Resolver must not be null"); + Assert.notNull(mappingContext, "MappingContext must not be null"); + Assert.notNull(identifierGenerator, "IdentifierGenerator must not be null"); + + this.adapter = resolver.resolve(); + this.mappingContext = mappingContext; + this.identifierGenerator = identifierGenerator; + } + + private static boolean typeCheck(Class requiredType, @Nullable Object candidate) { + return candidate == null || ClassUtils.isAssignable(requiredType, candidate.getClass()); + } + + public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTranslator) { + Assert.notNull(exceptionTranslator, "ExceptionTranslator must not be null"); + this.exceptionTranslator = exceptionTranslator; + } + + /** + * Set the {@link ApplicationEventPublisher} to be used to publish {@link KeyValueEvent}s. + * + * @param eventTypesToPublish must not be {@literal null}. + */ + @SuppressWarnings("rawtypes") + public void setEventTypesToPublish(Set> eventTypesToPublish) { + if (CollectionUtils.isEmpty(eventTypesToPublish)) { + this.publishEvents = false; + } else { + this.publishEvents = true; + this.eventTypesToPublish = Collections.unmodifiableSet(eventTypesToPublish); + } + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.eventPublisher = applicationEventPublisher; + } + + @Override + public T insert(T objectToInsert) { + KeyValuePersistentEntity entity = getKeyValuePersistentEntity(objectToInsert); + GeneratingIdAccessor generatingIdAccessor = new GeneratingIdAccessor( + entity.getPropertyAccessor(objectToInsert), + entity.getIdProperty(), + identifierGenerator + ); + Object id = generatingIdAccessor.getOrGenerateIdentifier(); + + return insert(id, objectToInsert); + } + + @Override + public T insert(Object id, T objectToInsert) { + Assert.notNull(id, "Id for object to be inserted must not be null"); + Assert.notNull(objectToInsert, "Object to be inserted must not be null"); + + String keyspace = resolveKeySpace(objectToInsert.getClass()); + + potentiallyPublishEvent(KeyValueEvent.beforeInsert(id, keyspace, objectToInsert.getClass(), objectToInsert)); + + execute((KeyValueCallback) adapter -> { + + if (adapter.contains(id, keyspace)) { + throw new DuplicateKeyException( + String.format("Cannot insert existing object with id %s; Please use update", id)); + } + + adapter.put(id, objectToInsert, keyspace); + return null; + }); + + potentiallyPublishEvent(KeyValueEvent.afterInsert(id, keyspace, objectToInsert.getClass(), objectToInsert)); + + return objectToInsert; + } + + @Override + public T update(T objectToUpdate) { + KeyValuePersistentEntity entity = getKeyValuePersistentEntity(objectToUpdate); + + if (!entity.hasIdProperty()) { + throw new InvalidDataAccessApiUsageException( + String.format("Cannot determine id for type %s", ClassUtils.getUserClass(objectToUpdate))); + } + + return update(entity.getIdentifierAccessor(objectToUpdate).getRequiredIdentifier(), objectToUpdate); + } + + @Override + public T update(Object id, T objectToUpdate) { + Assert.notNull(id, "Id for object to be inserted must not be null"); + Assert.notNull(objectToUpdate, "Object to be updated must not be null"); + + String keyspace = resolveKeySpace(objectToUpdate.getClass()); + + potentiallyPublishEvent(KeyValueEvent.beforeUpdate(id, keyspace, objectToUpdate.getClass(), objectToUpdate)); + + Object existing = execute(adapter -> adapter.put(id, objectToUpdate, keyspace)); + + potentiallyPublishEvent( + KeyValueEvent.afterUpdate(id, keyspace, objectToUpdate.getClass(), objectToUpdate, existing)); + + return objectToUpdate; + } + + @Override + public Optional findById(Object id, Class type) { + Assert.notNull(id, "Id for object to be found must not be null"); + Assert.notNull(type, "Type to fetch must not be null"); + + String keyspace = resolveKeySpace(type); + + potentiallyPublishEvent(KeyValueEvent.beforeGet(id, keyspace, type)); + + T result = execute(adapter -> { + Object value = adapter.get(id, keyspace, type); + + if (value == null || typeCheck(type, value)) { + return type.cast(value); + } + + return null; + }); + + potentiallyPublishEvent(KeyValueEvent.afterGet(id, keyspace, type, result)); + + return Optional.ofNullable(result); + } + + @Override + public void delete(Class type) { + Assert.notNull(type, "Type to delete must not be null"); + + String keyspace = resolveKeySpace(type); + + potentiallyPublishEvent(KeyValueEvent.beforeDropKeySpace(keyspace, type)); + + execute((KeyValueCallback) adapter -> { + + adapter.deleteAllOf(keyspace); + return null; + }); + + potentiallyPublishEvent(KeyValueEvent.afterDropKeySpace(keyspace, type)); + } + + @SuppressWarnings("unchecked") + @Override + public T delete(T objectToDelete) { + Class type = (Class) ClassUtils.getUserClass(objectToDelete); + KeyValuePersistentEntity entity = getKeyValuePersistentEntity(objectToDelete); + Object id = entity.getIdentifierAccessor(objectToDelete).getIdentifier(); + + if (id == null) { + String error = String.format("Cannot determine id for type %s", ClassUtils.getUserClass(objectToDelete)); + + throw new InvalidDataAccessApiUsageException(error); + } + + return delete(id, type); + } + + @Override + public T delete(Object id, Class type) { + Assert.notNull(id, "Id for object to be deleted must not be null"); + Assert.notNull(type, "Type to delete must not be null"); + + String keyspace = resolveKeySpace(type); + + potentiallyPublishEvent(KeyValueEvent.beforeDelete(id, keyspace, type)); + + T result = execute(adapter -> adapter.delete(id, keyspace, type)); + + potentiallyPublishEvent(KeyValueEvent.afterDelete(id, keyspace, type, result)); + + return result; + } + + @Nullable + @Override + public T execute(KeyValueCallback action) { + Assert.notNull(action, "KeyValueCallback must not be null"); + + try { + return action.doInKeyValue(this.adapter); + } catch (RuntimeException e) { + throw resolveExceptionIfPossible(e); + } + } + + protected T executeRequired(KeyValueCallback action) { + T result = execute(action); + + if (result != null) { + return result; + } + + throw new IllegalStateException(String.format("KeyValueCallback %s returned null value", action)); + } + + @Override + public Iterable find(KeyValueQuery query, Class type) { + return executeRequired((KeyValueCallback>) adapter -> { + Iterable result = adapter.find(query, resolveKeySpace(type), type); + + List filtered = new ArrayList<>(); + + for (Object candidate : result) { + if (typeCheck(type, candidate)) { + filtered.add(type.cast(candidate)); + } + } + + return filtered; + }); + } + + @Override + public Iterable findAll(Class type) { + Assert.notNull(type, "Type to fetch must not be null"); + + return executeRequired(adapter -> { + Iterable values = adapter.getAllOf(resolveKeySpace(type), type); + + ArrayList filtered = new ArrayList<>(); + for (Object candidate : values) { + if (typeCheck(type, candidate)) { + filtered.add(type.cast(candidate)); + } + } + + return filtered; + }); + } + + @SuppressWarnings("rawtypes") + @Override + public Iterable findAll(Sort sort, Class type) { + return find(new KeyValueQuery(sort), type); + } + + @SuppressWarnings("rawtypes") + @Override + public Iterable findInRange(long offset, int rows, Class type) { + return find(new KeyValueQuery().skip(offset).limit(rows), type); + } + + @SuppressWarnings("rawtypes") + @Override + public Iterable findInRange(long offset, int rows, Sort sort, Class type) { + return find(new KeyValueQuery(sort).skip(offset).limit(rows), type); + } + + @Override + public long count(Class type) { + Assert.notNull(type, "Type for count must not be null"); + return adapter.count(resolveKeySpace(type)); + } + + @Override + public long count(KeyValueQuery query, Class type) { + return executeRequired(adapter -> adapter.count(query, resolveKeySpace(type))); + } + + @Override + public boolean exists(KeyValueQuery query, Class type) { + return executeRequired(adapter -> adapter.exists(query, resolveKeySpace(type))); + } + + @Override + public MappingContext getMappingContext() { + return this.mappingContext; + } + + @Override + public KeyValueAdapter getKeyValueAdapter() { + return adapter; + } + + @Override + public void destroy() throws Exception { + this.adapter.destroy(); + } + + private KeyValuePersistentEntity getKeyValuePersistentEntity(Object objectToInsert) { + return this.mappingContext.getRequiredPersistentEntity(ClassUtils.getUserClass(objectToInsert)); + } + + private String resolveKeySpace(Class type) { + return this.mappingContext.getRequiredPersistentEntity(type).getKeySpace(); + } + + private RuntimeException resolveExceptionIfPossible(RuntimeException e) { + DataAccessException translatedException = exceptionTranslator.translateExceptionIfPossible(e); + + return translatedException != null ? translatedException : e; + } + + @SuppressWarnings("rawtypes") + private void potentiallyPublishEvent(KeyValueEvent event) { + if (eventPublisher == null) { + return; + } + + if (publishEvents && (eventTypesToPublish.isEmpty() || eventTypesToPublish.contains(event.getClass()))) { + eventPublisher.publishEvent(event); + } + } +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/DefaultIdentifierGenerator.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/DefaultIdentifierGenerator.java new file mode 100644 index 0000000000..bc4f41bca7 --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/DefaultIdentifierGenerator.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024 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.spring.data; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.keyvalue.core.IdentifierGenerator; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Default implementation of {@link IdentifierGenerator} to generate identifiers of types {@link UUID}. + */ +enum DefaultIdentifierGenerator implements IdentifierGenerator { + + INSTANCE; + + private final AtomicReference secureRandom = new AtomicReference<>(null); + + @Override + @SuppressWarnings("unchecked") + public T generateIdentifierOfType(TypeInformation identifierType) { + + Class type = identifierType.getType(); + + if (ClassUtils.isAssignable(UUID.class, type)) { + return (T) UUID.randomUUID(); + } else if (ClassUtils.isAssignable(String.class, type)) { + return (T) UUID.randomUUID().toString(); + } else if (ClassUtils.isAssignable(Integer.class, type)) { + return (T) Integer.valueOf(getSecureRandom().nextInt()); + } else if (ClassUtils.isAssignable(Long.class, type)) { + return (T) Long.valueOf(getSecureRandom().nextLong()); + } + + throw new InvalidDataAccessApiUsageException( + String.format("Identifier cannot be generated for %s; Supported types are: UUID, String, Integer, and Long", + identifierType.getType().getName())); + } + + private SecureRandom getSecureRandom() { + + SecureRandom secureRandom = this.secureRandom.get(); + if (secureRandom != null) { + return secureRandom; + } + + for (String algorithm : OsTools.secureRandomAlgorithmNames()) { + try { + secureRandom = SecureRandom.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + // ignore and try next. + } + } + + if (secureRandom == null) { + throw new InvalidDataAccessApiUsageException( + String.format("Could not create SecureRandom instance for one of the algorithms '%s'", + StringUtils.collectionToCommaDelimitedString(OsTools.secureRandomAlgorithmNames()))); + } + + this.secureRandom.compareAndSet(null, secureRandom); + + return secureRandom; + } + + private static class OsTools { + + private static final String OPERATING_SYSTEM_NAME = System.getProperty("os.name").toLowerCase(); + + private static final List SECURE_RANDOM_ALGORITHMS_LINUX_OSX_SOLARIS = Arrays.asList("NativePRNGBlocking", + "NativePRNGNonBlocking", "NativePRNG", "SHA1PRNG"); + private static final List SECURE_RANDOM_ALGORITHMS_WINDOWS = Arrays.asList("SHA1PRNG", "Windows-PRNG"); + + static List secureRandomAlgorithmNames() { + return OPERATING_SYSTEM_NAME.contains("win") ? SECURE_RANDOM_ALGORITHMS_WINDOWS + : SECURE_RANDOM_ALGORITHMS_LINUX_OSX_SOLARIS; + } + } +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/GeneratingIdAccessor.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/GeneratingIdAccessor.java new file mode 100644 index 0000000000..9374a5c387 --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/GeneratingIdAccessor.java @@ -0,0 +1,72 @@ +/* + * Copyright 2024 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.spring.data; + +import org.springframework.data.keyvalue.core.IdentifierGenerator; +import org.springframework.data.mapping.IdentifierAccessor; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.util.Assert; + +/** + * {@link IdentifierAccessor} adding a {@link #getOrGenerateIdentifier()} to automatically generate an identifier and + * set it on the underling bean instance. + * + * @see #getOrGenerateIdentifier() + */ +class GeneratingIdAccessor implements IdentifierAccessor { + + private final PersistentPropertyAccessor accessor; + private final PersistentProperty identifierProperty; + private final IdentifierGenerator generator; + + /** + * Creates a new {@link GeneratingIdAccessor} using the given {@link PersistentPropertyAccessor}, identifier property + * and {@link IdentifierGenerator}. + * + * @param accessor must not be {@literal null}. + * @param identifierProperty must not be {@literal null}. + * @param generator must not be {@literal null}. + */ + GeneratingIdAccessor(PersistentPropertyAccessor accessor, PersistentProperty identifierProperty, + IdentifierGenerator generator) { + + Assert.notNull(accessor, "PersistentPropertyAccessor must not be null"); + Assert.notNull(identifierProperty, "Identifier property must not be null"); + Assert.notNull(generator, "IdentifierGenerator must not be null"); + + this.accessor = accessor; + this.identifierProperty = identifierProperty; + this.generator = generator; + } + + @Override + public Object getIdentifier() { + return accessor.getProperty(identifierProperty); + } + + Object getOrGenerateIdentifier() { + + Object existingIdentifier = getIdentifier(); + + if (existingIdentifier != null) { + return existingIdentifier; + } + + Object generatedIdentifier = generator.generateIdentifierOfType(identifierProperty.getTypeInformation()); + accessor.setProperty(identifierProperty, generatedIdentifier); + + return generatedIdentifier; + } +} diff --git a/sdk/src/main/java/io/dapr/client/DaprApiProtocol.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/KeyValueAdapterResolver.java similarity index 68% rename from sdk/src/main/java/io/dapr/client/DaprApiProtocol.java rename to dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/KeyValueAdapterResolver.java index a4d3fc3bf3..04b64c3edc 100644 --- a/sdk/src/main/java/io/dapr/client/DaprApiProtocol.java +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/KeyValueAdapterResolver.java @@ -1,26 +1,20 @@ -/* - * 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; - -/** - * Transport protocol for Dapr's API. - * @deprecated This class will be deleted at SDK version 1.10. - */ -@Deprecated -public enum DaprApiProtocol { - - GRPC, - HTTP - -} +/* + * Copyright 2024 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.spring.data; + +import org.springframework.data.keyvalue.core.KeyValueAdapter; + +public interface KeyValueAdapterResolver { + KeyValueAdapter resolve(); +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/MySQLDaprKeyValueAdapter.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/MySQLDaprKeyValueAdapter.java new file mode 100644 index 0000000000..ad401817fe --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/MySQLDaprKeyValueAdapter.java @@ -0,0 +1,224 @@ +/* + * Copyright 2024 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.spring.data; + +import com.fasterxml.jackson.core.JsonPointer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.dapr.client.DaprClient; +import io.dapr.utils.TypeRef; +import org.springframework.data.keyvalue.core.query.KeyValueQuery; +import org.springframework.expression.spel.SpelNode; +import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; + +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * A {@link org.springframework.data.keyvalue.core.KeyValueAdapter} implementation for MySQL. + */ +@SuppressWarnings("AbbreviationAsWordInName") +public class MySQLDaprKeyValueAdapter extends AbstractDaprKeyValueAdapter { + private static final String DELETE_BY_KEYSPACE_PATTERN = "delete from state where id LIKE '%s'"; + private static final String SELECT_BY_KEYSPACE_PATTERN = "select value from state where id LIKE '%s'"; + private static final String SELECT_BY_FILTER_PATTERN = + "select value from state where id LIKE '%s' and JSON_EXTRACT(value, %s) = %s"; + private static final String COUNT_BY_KEYSPACE_PATTERN = "select count(*) as value from state where id LIKE '%s'"; + private static final String COUNT_BY_FILTER_PATTERN = + "select count(*) as value from state where id LIKE '%s' and JSON_EXTRACT(value, %s) = %s"; + + private static final TypeRef> FILTER_TYPE_REF = new TypeRef<>() { + }; + private static final TypeRef> COUNT_TYPE_REF = new TypeRef<>() { + }; + private static final SpelExpressionParser PARSER = new SpelExpressionParser(); + private static final JsonPointer VALUE_POINTER = JsonPointer.compile("/value"); + + private final DaprClient daprClient; + private final ObjectMapper mapper; + private final String stateStoreName; + private final String bindingName; + + /** + * Constructs a {@link MySQLDaprKeyValueAdapter}. + * + * @param daprClient The Dapr client. + * @param mapper The object mapper. + * @param stateStoreName The state store name. + * @param bindingName The binding name. + */ + public MySQLDaprKeyValueAdapter(DaprClient daprClient, ObjectMapper mapper, String stateStoreName, + String bindingName) { + super(daprClient, stateStoreName); + + Assert.notNull(mapper, "ObjectMapper must not be null"); + Assert.hasText(bindingName, "State store binding must not be empty"); + + this.daprClient = daprClient; + this.mapper = mapper; + this.stateStoreName = stateStoreName; + this.bindingName = bindingName; + } + + + @Override + public Iterable getAllOf(String keyspace, Class type) { + Assert.hasText(keyspace, "Keyspace must not be empty"); + Assert.notNull(type, "Type must not be null"); + + String sql = createSql(SELECT_BY_KEYSPACE_PATTERN, keyspace); + List result = queryUsingBinding(sql, FILTER_TYPE_REF); + + return convertValues(result, type); + } + + @Override + public void deleteAllOf(String keyspace) { + Assert.hasText(keyspace, "Keyspace must not be empty"); + + String sql = createSql(DELETE_BY_KEYSPACE_PATTERN, keyspace); + + execUsingBinding(sql); + } + + @Override + public Iterable find(KeyValueQuery query, String keyspace, Class type) { + Assert.notNull(query, "Query must not be null"); + Assert.hasText(keyspace, "Keyspace must not be empty"); + Assert.notNull(type, "Type must not be null"); + + Object criteria = query.getCriteria(); + + if (criteria == null) { + return getAllOf(keyspace, type); + } + + String sql = createSql(SELECT_BY_FILTER_PATTERN, keyspace, criteria); + List result = queryUsingBinding(sql, FILTER_TYPE_REF); + + return convertValues(result, type); + } + + @Override + public long count(String keyspace) { + Assert.hasText(keyspace, "Keyspace must not be empty"); + + String sql = createSql(COUNT_BY_KEYSPACE_PATTERN, keyspace); + List result = queryUsingBinding(sql, COUNT_TYPE_REF); + + return extractCount(result); + } + + @Override + public long count(KeyValueQuery query, String keyspace) { + Assert.notNull(query, "Query must not be null"); + Assert.hasText(keyspace, "Keyspace must not be empty"); + + Object criteria = query.getCriteria(); + + if (criteria == null) { + return count(keyspace); + } + + String sql = createSql(COUNT_BY_FILTER_PATTERN, keyspace, criteria); + List result = queryUsingBinding(sql, COUNT_TYPE_REF); + + return extractCount(result); + } + + private String getKeyspaceFilter(String keyspace) { + return String.format("%s||%s-%%", stateStoreName, keyspace); + } + + private String createSql(String sqlPattern, String keyspace) { + String keyspaceFilter = getKeyspaceFilter(keyspace); + + return String.format(sqlPattern, keyspaceFilter); + } + + private String createSql(String sqlPattern, String keyspace, Object criteria) { + String keyspaceFilter = getKeyspaceFilter(keyspace); + SpelExpression expression = PARSER.parseRaw(criteria.toString()); + SpelNode leftNode = expression.getAST().getChild(0); + SpelNode rightNode = expression.getAST().getChild(1); + String left = String.format("'$.%s'", leftNode.toStringAST()); + String right = rightNode.toStringAST(); + + return String.format(sqlPattern, keyspaceFilter, left, right); + } + + private void execUsingBinding(String sql) { + Map meta = Collections.singletonMap("sql", sql); + + daprClient.invokeBinding(bindingName, "exec", null, meta).block(); + } + + private T queryUsingBinding(String sql, TypeRef typeRef) { + Map meta = Collections.singletonMap("sql", sql); + + return daprClient.invokeBinding(bindingName, "query", null, meta, typeRef).block(); + } + + private List convertValues(List values, Class type) { + if (values == null || values.isEmpty()) { + return Collections.emptyList(); + } + + return values.stream() + .map(value -> convertValue(value, type)) + .collect(Collectors.toList()); + } + + private T convertValue(JsonNode value, Class type) { + JsonNode valueNode = value.at(VALUE_POINTER); + + if (valueNode.isMissingNode()) { + throw new IllegalStateException("Value is missing"); + } + + try { + // The value is stored as a base64 encoded string and wrapped in quotes + // hence we need to remove the quotes and then decode + String rawValue = valueNode.toString().replace("\"", ""); + byte[] decodedValue = Base64.getDecoder().decode(rawValue); + + return mapper.readValue(decodedValue, type); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private long extractCount(List values) { + if (values == null || values.isEmpty()) { + return 0; + } + + JsonNode valueNode = values.get(0).at(VALUE_POINTER); + + if (valueNode.isMissingNode()) { + throw new IllegalStateException("Count value is missing"); + } + + if (!valueNode.isNumber()) { + throw new IllegalStateException("Count value is not a number"); + } + + return valueNode.asLong(); + } +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/PostgreSQLDaprKeyValueAdapter.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/PostgreSQLDaprKeyValueAdapter.java new file mode 100644 index 0000000000..40c22db2ff --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/PostgreSQLDaprKeyValueAdapter.java @@ -0,0 +1,215 @@ +/* + * Copyright 2024 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.spring.data; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.dapr.client.DaprClient; +import io.dapr.spring.data.repository.query.DaprPredicate; +import io.dapr.utils.TypeRef; +import org.springframework.data.keyvalue.core.query.KeyValueQuery; +import org.springframework.expression.spel.SpelNode; +import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * A {@link org.springframework.data.keyvalue.core.KeyValueAdapter} implementation for PostgreSQL. + */ +@SuppressWarnings("AbbreviationAsWordInName") +public class PostgreSQLDaprKeyValueAdapter extends AbstractDaprKeyValueAdapter { + private static final String DELETE_BY_KEYSPACE_PATTERN = "delete from state where key LIKE '%s'"; + private static final String SELECT_BY_KEYSPACE_PATTERN = "select value from state where key LIKE '%s'"; + private static final String SELECT_BY_FILTER_PATTERN = + "select value from state where key LIKE '%s' and JSONB_EXTRACT_PATH_TEXT(value, %s) = %s"; + private static final String COUNT_BY_KEYSPACE_PATTERN = "select count(*) as value from state where key LIKE '%s'"; + private static final String COUNT_BY_FILTER_PATTERN = + "select count(*) as value from state where key LIKE '%s' and JSONB_EXTRACT_PATH_TEXT(value, %s) = %s"; + + private static final TypeRef>> FILTER_TYPE_REF = new TypeRef<>() { + }; + private static final TypeRef>> COUNT_TYPE_REF = new TypeRef<>() { + }; + private static final SpelExpressionParser PARSER = new SpelExpressionParser(); + + private final DaprClient daprClient; + private final ObjectMapper mapper; + private final String stateStoreName; + private final String bindingName; + + /** + * Constructs a {@link PostgreSQLDaprKeyValueAdapter}. + * + * @param daprClient The Dapr client. + * @param mapper The object mapper. + * @param stateStoreName The state store name. + * @param bindingName The binding name. + */ + public PostgreSQLDaprKeyValueAdapter(DaprClient daprClient, ObjectMapper mapper, String stateStoreName, + String bindingName) { + super(daprClient, stateStoreName); + + Assert.notNull(mapper, "ObjectMapper must not be null"); + Assert.hasText(bindingName, "State store binding must not be empty"); + + this.daprClient = daprClient; + this.mapper = mapper; + this.stateStoreName = stateStoreName; + this.bindingName = bindingName; + } + + @Override + public Iterable getAllOf(String keyspace, Class type) { + Assert.hasText(keyspace, "Keyspace must not be empty"); + Assert.notNull(type, "Type must not be null"); + + String sql = createSql(SELECT_BY_KEYSPACE_PATTERN, keyspace); + List> result = queryUsingBinding(sql, FILTER_TYPE_REF); + + return convertValues(result, type); + } + + @Override + public void deleteAllOf(String keyspace) { + Assert.hasText(keyspace, "Keyspace must not be empty"); + + String sql = createSql(DELETE_BY_KEYSPACE_PATTERN, keyspace); + + execUsingBinding(sql); + } + + @Override + public Iterable find(KeyValueQuery query, String keyspace, Class type) { + Assert.notNull(query, "Query must not be null"); + Assert.hasText(keyspace, "Keyspace must not be empty"); + Assert.notNull(type, "Type must not be null"); + + Object criteria = query.getCriteria(); + + if (criteria == null) { + return getAllOf(keyspace, type); + } + + String sql = createSql(SELECT_BY_FILTER_PATTERN, keyspace, criteria); + List> result = queryUsingBinding(sql, FILTER_TYPE_REF); + + return convertValues(result, type); + } + + @Override + public long count(String keyspace) { + Assert.hasText(keyspace, "Keyspace must not be empty"); + + String sql = createSql(COUNT_BY_KEYSPACE_PATTERN, keyspace); + List> result = queryUsingBinding(sql, COUNT_TYPE_REF); + + return extractCount(result); + } + + @Override + public long count(KeyValueQuery query, String keyspace) { + Assert.notNull(query, "Query must not be null"); + Assert.hasText(keyspace, "Keyspace must not be empty"); + + Object criteria = query.getCriteria(); + + if (criteria == null) { + return count(keyspace); + } + + String sql = createSql(COUNT_BY_FILTER_PATTERN, keyspace, criteria); + List> result = queryUsingBinding(sql, COUNT_TYPE_REF); + + return extractCount(result); + } + + private String getKeyspaceFilter(String keyspace) { + return String.format("%s||%s-%%", stateStoreName, keyspace); + } + + private String createSql(String sqlPattern, String keyspace) { + String keyspaceFilter = getKeyspaceFilter(keyspace); + + return String.format(sqlPattern, keyspaceFilter); + } + + private String createSql(String sqlPattern, String keyspace, Object criteria) { + String keyspaceFilter = getKeyspaceFilter(keyspace); + + if (criteria instanceof DaprPredicate daprPredicate) { + String path = daprPredicate.getPath().toString(); + String pathWithOutType = String.format("'%s'", path.substring(path.indexOf(".") + 1)); + String value = String.format("'%s'", daprPredicate.getValue().toString()); + + return String.format(sqlPattern, keyspaceFilter, pathWithOutType, value); + } else if (criteria instanceof String) { + SpelExpression expression = PARSER.parseRaw(criteria.toString()); + SpelNode leftNode = expression.getAST().getChild(0); + SpelNode rightNode = expression.getAST().getChild(1); + String left = String.format("'%s'", leftNode.toStringAST()); + String right = rightNode.toStringAST(); + + return String.format(sqlPattern, keyspaceFilter, left, right); + } + + return null; + } + + private void execUsingBinding(String sql) { + Map meta = Collections.singletonMap("sql", sql); + + daprClient.invokeBinding(bindingName, "exec", null, meta).block(); + } + + private T queryUsingBinding(String sql, TypeRef typeRef) { + Map meta = Collections.singletonMap("sql", sql); + + return daprClient.invokeBinding(bindingName, "query", null, meta, typeRef).block(); + } + + private Iterable convertValues(List> values, Class type) { + if (values == null || values.isEmpty()) { + return Collections.emptyList(); + } + + return values.stream() + .flatMap(Collection::stream) + .map(value -> convertValue(value, type)) + .collect(Collectors.toList()); + } + + private T convertValue(Object value, Class type) { + try { + return mapper.convertValue(value, type); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private long extractCount(List> values) { + if (values == null || values.isEmpty()) { + return 0; + } + + return values.stream() + .flatMap(Collection::stream) + .collect(Collectors.toList()) + .get(0); + } +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/config/DaprRepositoriesRegistrar.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/config/DaprRepositoriesRegistrar.java new file mode 100644 index 0000000000..7946e07b4d --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/config/DaprRepositoriesRegistrar.java @@ -0,0 +1,35 @@ +/* + * Copyright 2024 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.spring.data.repository.config; + +import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport; +import org.springframework.data.repository.config.RepositoryConfigurationExtension; + +import java.lang.annotation.Annotation; + +/** + * Dapr specific {@link RepositoryBeanDefinitionRegistrarSupport} implementation. + */ +public class DaprRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { + + @Override + protected Class getAnnotation() { + return EnableDaprRepositories.class; + } + + @Override + protected RepositoryConfigurationExtension getExtension() { + return new DaprRepositoryConfigurationExtension(); + } +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/config/DaprRepositoryConfigurationExtension.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/config/DaprRepositoryConfigurationExtension.java new file mode 100644 index 0000000000..a487d7aa51 --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/config/DaprRepositoryConfigurationExtension.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 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.spring.data.repository.config; + +import org.springframework.data.keyvalue.repository.config.KeyValueRepositoryConfigurationExtension; +import org.springframework.data.repository.config.RepositoryConfigurationExtension; + +/** + * {@link RepositoryConfigurationExtension} for Dapr-based repositories. + */ +@SuppressWarnings("unchecked") +public class DaprRepositoryConfigurationExtension extends KeyValueRepositoryConfigurationExtension { + + @Override + public String getModuleName() { + return "Dapr"; + } + + @Override + protected String getModulePrefix() { + return "dapr"; + } + + @Override + protected String getDefaultKeyValueTemplateRef() { + return "daprKeyValueTemplate"; + } + +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/config/EnableDaprRepositories.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/config/EnableDaprRepositories.java new file mode 100644 index 0000000000..3ede019fc3 --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/config/EnableDaprRepositories.java @@ -0,0 +1,139 @@ +/* + * Copyright 2024 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.spring.data.repository.config; + +import io.dapr.spring.data.repository.query.DaprPredicateQueryCreator; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Import; +import org.springframework.data.keyvalue.core.KeyValueOperations; +import org.springframework.data.keyvalue.repository.config.QueryCreatorType; +import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean; +import org.springframework.data.repository.config.DefaultRepositoryBaseClass; +import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.repository.query.QueryLookupStrategy.Key; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to activate Dapr repositories. If no base package is configured through either {@link #value()}, + * {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Import(DaprRepositoriesRegistrar.class) +@QueryCreatorType(DaprPredicateQueryCreator.class) +public @interface EnableDaprRepositories { + + /** + * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: + * {@code @EnableJpaRepositories("org.my.pkg")} instead of {@code @EnableJpaRepositories(basePackages="org.my.pkg")}. + * + * @return alias of the base package + */ + String[] value() default {}; + + /** + * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this + * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. + * + * @return array of base packages + */ + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The + * package of each class specified will be scanned. Consider creating a special no-op marker class or interface in + * each package that serves no purpose other than being referenced by this attribute. + * + * @return array of base classes + */ + Class[] basePackageClasses() default {}; + + /** + * Specifies which types are not eligible for component scanning. + * + * @return array of exclusion filters + */ + Filter[] excludeFilters() default {}; + + /** + * Specifies which types are eligible for component scanning. Further narrows the set of candidate components from + * everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters. + * + * @return array of inclusion filters + */ + Filter[] includeFilters() default {}; + + /** + * Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So + * for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning + * for {@code PersonRepositoryImpl}. + * + * @return repository implementation post fix + */ + String repositoryImplementationPostfix() default "Impl"; + + /** + * Configures the location of where to find the Spring Data named queries properties file. + * + * @return named queries location + */ + String namedQueriesLocation() default ""; + + /** + * Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to + * {@link Key#CREATE_IF_NOT_FOUND}. + * + * @return key lookup strategy + */ + Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND; + + /** + * Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to + * {@link KeyValueRepositoryFactoryBean}. + * + * @return repository factory bean class + */ + Class repositoryFactoryBeanClass() default KeyValueRepositoryFactoryBean.class; + + /** + * Configure the repository base class to be used to create repository proxies for this particular configuration. + * + * @return repository base class + */ + Class repositoryBaseClass() default DefaultRepositoryBaseClass.class; + + /** + * Configures the name of the {@link KeyValueOperations} bean to be used with the repositories detected. + * + * @return the Key value template bean name + */ + String keyValueTemplateRef() default "daprKeyValueTemplate"; + + /** + * Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the + * repositories infrastructure. + * + * @return whether to consider nested repository interfaces + */ + boolean considerNestedRepositories() default false; +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/query/DaprPredicate.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/query/DaprPredicate.java new file mode 100644 index 0000000000..d681ce8e60 --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/query/DaprPredicate.java @@ -0,0 +1,66 @@ +package io.dapr.spring.data.repository.query; + +import org.springframework.beans.BeanWrapper; +import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper; +import org.springframework.util.ObjectUtils; + +import java.util.function.Function; +import java.util.function.Predicate; + +public class DaprPredicate implements Predicate { + + private final PropertyPath path; + private final Function check; + private final Object value; + + public DaprPredicate(PropertyPath path, Object expected) { + this(path, expected, (valueToCompare) -> ObjectUtils.nullSafeEquals(valueToCompare, expected)); + } + + + /** + * Creates a new {@link DaprPredicate}. + * + * @param path The path to the property to compare. + * @param value The value to compare. + * @param check The function to check the value. + */ + public DaprPredicate(PropertyPath path, Object value, Function check) { + this.path = path; + this.check = check; + this.value = value; + } + + public PropertyPath getPath() { + return path; + } + + public Object getValue() { + return value; + } + + @Override + public boolean test(Object o) { + Object value = getValueByPath(o, path); + return check.apply(value); + } + + private Object getValueByPath(Object root, PropertyPath path) { + Object currentValue = root; + + for (PropertyPath currentPath : path) { + currentValue = wrap(currentValue).getPropertyValue(currentPath.getSegment()); + + if (currentValue == null) { + break; + } + } + + return currentValue; + } + + private BeanWrapper wrap(Object o) { + return new DirectFieldAccessFallbackBeanWrapper(o); + } +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/query/DaprPredicateBuilder.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/query/DaprPredicateBuilder.java new file mode 100644 index 0000000000..1eff32b98d --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/query/DaprPredicateBuilder.java @@ -0,0 +1,173 @@ +/* + * Copyright 2024 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.spring.data.repository.query; + +import org.springframework.data.repository.query.parser.Part; +import org.springframework.util.ObjectUtils; +import org.springframework.util.comparator.Comparators; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +class DaprPredicateBuilder { + + private final Part part; + + private DaprPredicateBuilder(Part part) { + this.part = part; + } + + static DaprPredicateBuilder propertyValueOf(Part part) { + return new DaprPredicateBuilder(part); + } + + Predicate isTrue() { + return new DaprPredicate(part.getProperty(), true); + } + + public Predicate isFalse() { + return new DaprPredicate(part.getProperty(), false); + } + + public Predicate isEqualTo(Object value) { + return new DaprPredicate(part.getProperty(), value, o -> { + if (!ObjectUtils.nullSafeEquals(Part.IgnoreCaseType.NEVER, part.shouldIgnoreCase())) { + if (o instanceof String s1 && value instanceof String s2) { + return s1.equalsIgnoreCase(s2); + } + } + + return ObjectUtils.nullSafeEquals(o, value); + }); + } + + public Predicate isNull() { + return new DaprPredicate(part.getProperty(), null, Objects::isNull); + } + + public Predicate isNotNull() { + return isNull().negate(); + } + + public Predicate isLessThan(Object value) { + return new DaprPredicate(part.getProperty(), value, o -> Comparators.nullsHigh().compare(o, value) < 0); + } + + public Predicate isLessThanEqual(Object value) { + return new DaprPredicate(part.getProperty(), value, o -> Comparators.nullsHigh().compare(o, value) <= 0); + } + + public Predicate isGreaterThan(Object value) { + return new DaprPredicate(part.getProperty(), value, o -> Comparators.nullsHigh().compare(o, value) > 0); + } + + public Predicate isGreaterThanEqual(Object value) { + return new DaprPredicate(part.getProperty(), value, o -> Comparators.nullsHigh().compare(o, value) >= 0); + } + + public Predicate matches(Object value) { + return new DaprPredicate(part.getProperty(), value, o -> { + if (o == null || value == null) { + return ObjectUtils.nullSafeEquals(o, value); + } + + if (value instanceof Pattern pattern) { + return pattern.matcher(o.toString()).find(); + } + + return o.toString().matches(value.toString()); + }); + } + + public Predicate in(Object value) { + return new DaprPredicate(part.getProperty(), value, o -> { + if (value instanceof Collection collection) { + if (o instanceof Collection subSet) { + return collection.containsAll(subSet); + } + + return collection.contains(o); + } + + if (ObjectUtils.isArray(value)) { + return ObjectUtils.containsElement(ObjectUtils.toObjectArray(value), value); + } + + return false; + }); + } + + public Predicate contains(Object value) { + return new DaprPredicate(part.getProperty(), value, o -> { + if (o == null) { + return false; + } + + if (o instanceof Collection collection) { + return collection.contains(value); + } + + if (ObjectUtils.isArray(o)) { + return ObjectUtils.containsElement(ObjectUtils.toObjectArray(o), value); + } + + if (o instanceof Map map) { + return map.containsValue(value); + } + + if (value == null) { + return false; + } + + String s = o.toString(); + + if (ObjectUtils.nullSafeEquals(Part.IgnoreCaseType.NEVER, part.shouldIgnoreCase())) { + return s.contains(value.toString()); + } + + return s.toLowerCase().contains(value.toString().toLowerCase()); + }); + } + + public Predicate startsWith(Object value) { + return new DaprPredicate(part.getProperty(), value, o -> { + if (!(o instanceof String s)) { + return false; + } + + if (ObjectUtils.nullSafeEquals(Part.IgnoreCaseType.NEVER, part.shouldIgnoreCase())) { + return s.startsWith(value.toString()); + } + + return s.toLowerCase().startsWith(value.toString().toLowerCase()); + }); + } + + public Predicate endsWith(Object value) { + return new DaprPredicate(part.getProperty(), value, o -> { + if (!(o instanceof String s)) { + return false; + } + + if (ObjectUtils.nullSafeEquals(Part.IgnoreCaseType.NEVER, part.shouldIgnoreCase())) { + return s.endsWith(value.toString()); + } + + return s.toLowerCase().endsWith(value.toString().toLowerCase()); + }); + } +} diff --git a/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/query/DaprPredicateQueryCreator.java b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/query/DaprPredicateQueryCreator.java new file mode 100644 index 0000000000..436f197f72 --- /dev/null +++ b/dapr-spring/dapr-spring-data/src/main/java/io/dapr/spring/data/repository/query/DaprPredicateQueryCreator.java @@ -0,0 +1,102 @@ +/* + * Copyright 2024 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.spring.data.repository.query; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Sort; +import org.springframework.data.keyvalue.core.query.KeyValueQuery; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.parser.AbstractQueryCreator; +import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.lang.Nullable; + +import java.util.Iterator; +import java.util.function.Predicate; + +/** + * This class is copied from https://github.com/spring-projects/spring-data-keyvalue/blob/ff441439124585042dd0cbff952f977a343444d2/src/main/java/org/springframework/data/keyvalue/repository/query/PredicateQueryCreator.java#L46 + * because it has private accessors to internal classes, making it impossible to extend or use the original + * This requires to be created from scratch to not use predicates, but this is only worth it if we can prove these + * abstractions are worth the time. + */ +public class DaprPredicateQueryCreator extends AbstractQueryCreator>, Predicate> { + + public DaprPredicateQueryCreator(PartTree tree, ParameterAccessor parameters) { + super(tree, parameters); + } + + @Override + protected Predicate create(Part part, Iterator iterator) { + DaprPredicateBuilder daprPredicateBuilder = DaprPredicateBuilder.propertyValueOf(part); + + switch (part.getType()) { + case TRUE: + return daprPredicateBuilder.isTrue(); + case FALSE: + return daprPredicateBuilder.isFalse(); + case SIMPLE_PROPERTY: + return daprPredicateBuilder.isEqualTo(iterator.next()); + case IS_NULL: + return daprPredicateBuilder.isNull(); + case IS_NOT_NULL: + return daprPredicateBuilder.isNotNull(); + case LIKE: + return daprPredicateBuilder.contains(iterator.next()); + case STARTING_WITH: + return daprPredicateBuilder.startsWith(iterator.next()); + case AFTER: + case GREATER_THAN: + return daprPredicateBuilder.isGreaterThan(iterator.next()); + case GREATER_THAN_EQUAL: + return daprPredicateBuilder.isGreaterThanEqual(iterator.next()); + case BEFORE: + case LESS_THAN: + return daprPredicateBuilder.isLessThan(iterator.next()); + case LESS_THAN_EQUAL: + return daprPredicateBuilder.isLessThanEqual(iterator.next()); + case ENDING_WITH: + return daprPredicateBuilder.endsWith(iterator.next()); + case BETWEEN: + return daprPredicateBuilder.isGreaterThan(iterator.next()) + .and(daprPredicateBuilder.isLessThan(iterator.next())); + case REGEX: + return daprPredicateBuilder.matches(iterator.next()); + case IN: + return daprPredicateBuilder.in(iterator.next()); + default: + throw new InvalidDataAccessApiUsageException(String.format("Found invalid part '%s' in query", part.getType())); + + } + } + + @Override + protected Predicate and(Part part, Predicate base, Iterator iterator) { + return base.and((Predicate) create(part, iterator)); + } + + @Override + protected Predicate or(Predicate base, Predicate criteria) { + return base.or((Predicate) criteria); + } + + @Override + protected KeyValueQuery> complete(@Nullable Predicate criteria, Sort sort) { + if (criteria == null) { + return new KeyValueQuery<>(it -> true, sort); + } + return new KeyValueQuery<>(criteria, sort); + } + +} diff --git a/dapr-spring/dapr-spring-messaging/pom.xml b/dapr-spring/dapr-spring-messaging/pom.xml new file mode 100644 index 0000000000..539e8fc6da --- /dev/null +++ b/dapr-spring/dapr-spring-messaging/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + + io.dapr.spring + dapr-spring-parent + 0.12.0-SNAPSHOT + + + dapr-spring-messaging + dapr-spring-messaging + Dapr Spring Messaging + jar + + diff --git a/dapr-spring/dapr-spring-messaging/src/main/java/io/dapr/spring/messaging/DaprMessagingOperations.java b/dapr-spring/dapr-spring-messaging/src/main/java/io/dapr/spring/messaging/DaprMessagingOperations.java new file mode 100644 index 0000000000..ac1092c9aa --- /dev/null +++ b/dapr-spring/dapr-spring-messaging/src/main/java/io/dapr/spring/messaging/DaprMessagingOperations.java @@ -0,0 +1,66 @@ +/* + * Copyright 2024 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.spring.messaging; + +import reactor.core.publisher.Mono; + +public interface DaprMessagingOperations { + + /** + * Sends a message to the specified topic in a blocking manner. + * + * @param topic the topic to send the message to or {@code null} to send to the + * default topic + * @param message the message to send + */ + void send(String topic, T message); + + /** + * Create a {@link SendMessageBuilder builder} for configuring and sending a message. + * + * @param message the payload of the message + * @return the builder to configure and send the message + */ + SendMessageBuilder newMessage(T message); + + /** + * Builder that can be used to configure and send a message. Provides more options + * than the basic send/sendAsync methods provided by {@link DaprMessagingOperations}. + * + * @param the message payload type + */ + interface SendMessageBuilder { + + /** + * Specify the topic to send the message to. + * + * @param topic the destination topic + * @return the current builder with the destination topic specified + */ + SendMessageBuilder withTopic(String topic); + + /** + * Send the message in a blocking manner using the configured specification. + */ + void send(); + + /** + * Uses the configured specification to send the message in a non-blocking manner. + * + * @return a Mono that completes when the message has been sent + */ + Mono sendAsync(); + } + +} diff --git a/dapr-spring/dapr-spring-messaging/src/main/java/io/dapr/spring/messaging/DaprMessagingTemplate.java b/dapr-spring/dapr-spring-messaging/src/main/java/io/dapr/spring/messaging/DaprMessagingTemplate.java new file mode 100644 index 0000000000..604849b86d --- /dev/null +++ b/dapr-spring/dapr-spring-messaging/src/main/java/io/dapr/spring/messaging/DaprMessagingTemplate.java @@ -0,0 +1,87 @@ +/* + * Copyright 2024 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.spring.messaging; + +import io.dapr.client.DaprClient; +import io.dapr.client.domain.Metadata; +import reactor.core.publisher.Mono; + +import java.util.Collections; + +public class DaprMessagingTemplate implements DaprMessagingOperations { + + private static final String MESSAGE_TTL_IN_SECONDS = "10"; + + private final DaprClient daprClient; + private final String pubsubName; + + public DaprMessagingTemplate(DaprClient daprClient, String pubsubName) { + this.daprClient = daprClient; + this.pubsubName = pubsubName; + } + + @Override + public void send(String topic, T message) { + doSend(topic, message); + } + + @Override + public SendMessageBuilder newMessage(T message) { + return new SendMessageBuilderImpl<>(this, message); + } + + private void doSend(String topic, T message) { + doSendAsync(topic, message).block(); + } + + private Mono doSendAsync(String topic, T message) { + return daprClient.publishEvent(pubsubName, + topic, + message, + Collections.singletonMap(Metadata.TTL_IN_SECONDS, MESSAGE_TTL_IN_SECONDS)); + } + + private static class SendMessageBuilderImpl implements SendMessageBuilder { + + private final DaprMessagingTemplate template; + + private final T message; + + private String topic; + + SendMessageBuilderImpl(DaprMessagingTemplate template, T message) { + this.template = template; + this.message = message; + } + + @Override + public SendMessageBuilder withTopic(String topic) { + this.topic = topic; + return this; + } + + + @Override + public void send() { + this.template.doSend(this.topic, this.message); + } + + @Override + public Mono sendAsync() { + return this.template.doSendAsync(this.topic, this.message); + } + + } + +} diff --git a/dapr-spring/pom.xml b/dapr-spring/pom.xml new file mode 100644 index 0000000000..e2cd1aa910 --- /dev/null +++ b/dapr-spring/pom.xml @@ -0,0 +1,174 @@ + + 4.0.0 + + + io.dapr + dapr-sdk-parent + 1.12.0-SNAPSHOT + + + io.dapr.spring + dapr-spring-parent + pom + 0.12.0-SNAPSHOT + dapr-spring-parent + SDK extension for Spring and Spring Boot + + + dapr-spring-core + dapr-spring-data + dapr-spring-messaging + dapr-spring-boot-autoconfigure + dapr-spring-boot-starters/dapr-spring-boot-starter + dapr-spring-boot-testcontainers + + + + 3.2.6 + 0.12.0-SNAPSHOT + 1.19.8 + 1.12.0-SNAPSHOT + 0.12.0-SNAPSHOT + 17 + 17 + 17 + + + + + + org.springframework.boot + spring-boot-dependencies + ${springboot.version} + pom + import + + + + + + + + io.dapr + dapr-sdk + ${dapr.sdk.version} + + + io.dapr + dapr-sdk-actors + ${dapr.sdk.version} + + + + + org.springframework + spring-web + true + + + org.springframework + spring-context + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.github.gmazzo.okhttp.mock + mock-client + 2.0.0 + test + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.7.0 + + + attach-javadocs + + jar + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + default-prepare-agent + + prepare-agent + + + + report + test + + report + + + target/jacoco-report/ + + + + check + + check + + + + + BUNDLE + + io.dapr.springboot.DaprBeanPostProcessor + + + + LINE + COVEREDRATIO + 80% + + + + + + + + + + + diff --git a/dapr-spring/spotbugs-exclude.xml b/dapr-spring/spotbugs-exclude.xml new file mode 100644 index 0000000000..fc80592a14 --- /dev/null +++ b/dapr-spring/spotbugs-exclude.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/project-reports.html b/docs/project-reports.html index f6e31eb46f..60adf5892d 100644 --- a/docs/project-reports.html +++ b/docs/project-reports.html @@ -42,7 +42,6 @@
Modules
  • dapr-sdk-actors
  • dapr-sdk-workflows
  • dapr-sdk-springboot
  • -
  • dapr-sdk-springboot3
  • dapr-sdk-examples
  • Project Documentation
    diff --git a/examples/pom.xml b/examples/pom.xml index d7ba7ba934..566c765048 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -24,14 +24,14 @@ true false 0.14.0 - 3.2.0 + 3.2.6 commons-cli commons-cli - 1.4 + 1.8.0 io.grpc @@ -59,21 +59,11 @@ protobuf-java-util ${protobuf.version} - - com.github.os72 - protoc-jar-maven-plugin - 3.11.4 - org.springframework.boot spring-boot-starter-web ${springboot.version} - - org.springframework.boot - spring-boot-autoconfigure - ${springboot.version} - com.jayway.jsonpath json-path @@ -112,7 +102,7 @@ io.dapr - dapr-sdk-springboot3 + dapr-sdk-springboot ${project.version} @@ -123,7 +113,7 @@ io.dapr dapr-sdk-workflows - ${dapr.sdk-workflows.version} + ${dapr.sdk.alpha.version} io.dapr @@ -140,16 +130,6 @@ javax.annotation-api 1.3.2 - - org.springframework - spring-context - 6.1.1 - - - org.springframework - spring-core - 6.1.1 - @@ -190,7 +170,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 ${java.version} @@ -206,7 +186,7 @@ org.apache.maven.plugins maven-site-plugin - 3.9.1 + 3.12.1 true @@ -236,6 +216,14 @@ true + + org.codehaus.mojo + animal-sniffer-maven-plugin + + + true + + diff --git a/examples/src/main/java/io/dapr/examples/OpenTelemetryInterceptor.java b/examples/src/main/java/io/dapr/examples/OpenTelemetryInterceptor.java index 4b62eb677e..cc250113d6 100644 --- a/examples/src/main/java/io/dapr/examples/OpenTelemetryInterceptor.java +++ b/examples/src/main/java/io/dapr/examples/OpenTelemetryInterceptor.java @@ -16,15 +16,15 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapPropagator; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; -import jakarta.servlet.DispatcherType; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import java.util.Collections; @Component diff --git a/examples/src/main/java/io/dapr/examples/actors/DemoActorClient.java b/examples/src/main/java/io/dapr/examples/actors/DemoActorClient.java index b19ca59b72..b45b87a160 100644 --- a/examples/src/main/java/io/dapr/examples/actors/DemoActorClient.java +++ b/examples/src/main/java/io/dapr/examples/actors/DemoActorClient.java @@ -26,7 +26,7 @@ * mvn clean install * 2. cd to [repo-root]/examples * 3. Run the client: - * dapr run --components-path ./components/actors --app-id demoactorclient -- java -jar \ + * dapr run --resources-path ./components/actors --app-id demoactorclient -- java -jar \ * target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorClient */ public class DemoActorClient { diff --git a/examples/src/main/java/io/dapr/examples/actors/DemoActorService.java b/examples/src/main/java/io/dapr/examples/actors/DemoActorService.java index e627fcdfe9..a1985e3ed6 100644 --- a/examples/src/main/java/io/dapr/examples/actors/DemoActorService.java +++ b/examples/src/main/java/io/dapr/examples/actors/DemoActorService.java @@ -28,7 +28,7 @@ * mvn clean install * 2. cd to [repo-root]/examples * 3. Run the server: - * dapr run --components-path ./components/actors --app-id demoactorservice --app-port 3000 \ + * dapr run --resources-path ./components/actors --app-id demoactorservice --app-port 3000 \ * -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorService -p 3000 */ public class DemoActorService { diff --git a/examples/src/main/java/io/dapr/examples/actors/README.md b/examples/src/main/java/io/dapr/examples/actors/README.md index a889eee5a2..c202e2087d 100644 --- a/examples/src/main/java/io/dapr/examples/actors/README.md +++ b/examples/src/main/java/io/dapr/examples/actors/README.md @@ -171,7 +171,7 @@ timeout_seconds: 90 Now, execute the following script in order to run DemoActorService: ```sh -dapr run --components-path ./components/actors --app-id demoactorservice --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorService -p 3000 +dapr run --resources-path ./components/actors --app-id demoactorservice --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorService -p 3000 ``` ### Running the Actor client @@ -252,7 +252,7 @@ timeout_seconds: 45 ```sh -dapr run --components-path ./components/actors --app-id demoactorclient -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorClient +dapr run --resources-path ./components/actors --app-id demoactorclient -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorClient ``` diff --git a/examples/src/main/java/io/dapr/examples/bindings/http/InputBindingExample.java b/examples/src/main/java/io/dapr/examples/bindings/http/InputBindingExample.java index f60ac6da91..f1896c0896 100644 --- a/examples/src/main/java/io/dapr/examples/bindings/http/InputBindingExample.java +++ b/examples/src/main/java/io/dapr/examples/bindings/http/InputBindingExample.java @@ -25,7 +25,7 @@ * mvn clean install * 2. cd to [repo-root]/examples * 3. Run : - * dapr run --components-path ./components/bindings --app-id inputbinding --app-port 3000 \ + * dapr run --resources-path ./components/bindings --app-id inputbinding --app-port 3000 \ * -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.bindings.http.InputBindingExample -p 3000 */ public class InputBindingExample { diff --git a/examples/src/main/java/io/dapr/examples/bindings/http/OutputBindingExample.java b/examples/src/main/java/io/dapr/examples/bindings/http/OutputBindingExample.java index 57445fbd0f..6bfd32408a 100644 --- a/examples/src/main/java/io/dapr/examples/bindings/http/OutputBindingExample.java +++ b/examples/src/main/java/io/dapr/examples/bindings/http/OutputBindingExample.java @@ -22,7 +22,7 @@ * mvn clean install * 2. cd to [repo-root]/examples * 3. Run the program: - * dapr run --components-path ./components/bindings --app-id outputbinding \ + * dapr run --resources-path ./components/bindings --app-id outputbinding \ * -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.bindings.http.OutputBindingExample */ public class OutputBindingExample { diff --git a/examples/src/main/java/io/dapr/examples/bindings/http/README.md b/examples/src/main/java/io/dapr/examples/bindings/http/README.md index 4ee7f931e5..cd9ce42062 100644 --- a/examples/src/main/java/io/dapr/examples/bindings/http/README.md +++ b/examples/src/main/java/io/dapr/examples/bindings/http/README.md @@ -54,17 +54,15 @@ Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initi Before getting into the application code, follow these steps in order to set up a local instance of Kafka. This is needed for the local instances. -1. Run the container locally: +1. Run the Kafka locally: ```bash -docker-compose -f ./src/main/java/io/dapr/examples/bindings/http/docker-compose-single-kafka.yml up -d +docker compose -f ./src/main/java/io/dapr/examples/bindings/http/docker-compose-single-kafka.yml up -d ``` @@ -125,7 +123,7 @@ sleep: 10 --> ```bash -dapr run --components-path ./components/bindings --app-id inputbinding --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.bindings.http.InputBindingExample -p 3000 +dapr run --resources-path ./components/bindings --app-id inputbinding --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.bindings.http.InputBindingExample -p 3000 ``` @@ -195,7 +193,7 @@ sleep: 30 --> ```bash -dapr run --components-path ./components/bindings --app-id outputbinding -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.bindings.http.OutputBindingExample +dapr run --resources-path ./components/bindings --app-id outputbinding -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.bindings.http.OutputBindingExample ``` @@ -248,7 +246,7 @@ name: Cleanup Kafka containers --> ```bash -docker-compose -f ./src/main/java/io/dapr/examples/bindings/http/docker-compose-single-kafka.yml down +docker compose -f ./src/main/java/io/dapr/examples/bindings/http/docker-compose-single-kafka.yml down ``` diff --git a/examples/src/main/java/io/dapr/examples/bindings/http/docker-compose-single-kafka.yml b/examples/src/main/java/io/dapr/examples/bindings/http/docker-compose-single-kafka.yml index 47af075f2a..949adf234d 100644 --- a/examples/src/main/java/io/dapr/examples/bindings/http/docker-compose-single-kafka.yml +++ b/examples/src/main/java/io/dapr/examples/bindings/http/docker-compose-single-kafka.yml @@ -1,4 +1,3 @@ -version: '2' services: zookeeper: image: confluentinc/cp-zookeeper:7.4.4 diff --git a/examples/src/main/java/io/dapr/examples/configuration/grpc/ConfigurationClient.java b/examples/src/main/java/io/dapr/examples/configuration/ConfigurationClient.java similarity index 96% rename from examples/src/main/java/io/dapr/examples/configuration/grpc/ConfigurationClient.java rename to examples/src/main/java/io/dapr/examples/configuration/ConfigurationClient.java index b8c6f9c435..020942475a 100644 --- a/examples/src/main/java/io/dapr/examples/configuration/grpc/ConfigurationClient.java +++ b/examples/src/main/java/io/dapr/examples/configuration/ConfigurationClient.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.examples.configuration.grpc; +package io.dapr.examples.configuration; import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; diff --git a/examples/src/main/java/io/dapr/examples/configuration/grpc/README.md b/examples/src/main/java/io/dapr/examples/configuration/README.md similarity index 93% rename from examples/src/main/java/io/dapr/examples/configuration/grpc/README.md rename to examples/src/main/java/io/dapr/examples/configuration/README.md index 12599b7f23..525edf7302 100644 --- a/examples/src/main/java/io/dapr/examples/configuration/grpc/README.md +++ b/examples/src/main/java/io/dapr/examples/configuration/README.md @@ -158,7 +158,7 @@ sleep: 10 --> ```bash -dapr run --components-path ./components/configuration --app-id configgrpc --log-level debug -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.configuration.grpc.ConfigurationClient +dapr run --resources-path ./components/configuration --app-id configgrpc --log-level debug -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.configuration.ConfigurationClient ``` diff --git a/examples/src/main/java/io/dapr/examples/configuration/http/ConfigurationClient.java b/examples/src/main/java/io/dapr/examples/configuration/http/ConfigurationClient.java deleted file mode 100644 index de19607376..0000000000 --- a/examples/src/main/java/io/dapr/examples/configuration/http/ConfigurationClient.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2022 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.configuration.http; - -import io.dapr.client.DaprApiProtocol; -import io.dapr.client.DaprClient; -import io.dapr.client.DaprClientBuilder; -import io.dapr.client.domain.ConfigurationItem; -import io.dapr.client.domain.GetConfigurationRequest; -import io.dapr.client.domain.SubscribeConfigurationRequest; -import io.dapr.client.domain.SubscribeConfigurationResponse; -import io.dapr.config.Properties; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class ConfigurationClient { - - private static final String CONFIG_STORE_NAME = "configstore"; - private static String SUBSCRIPTION_ID; - - /** - * Executes various methods to check the different apis. - * @param args arguments - * @throws Exception throws Exception - */ - public static void main(String[] args) throws Exception { - System.getProperties().setProperty(Properties.API_PROTOCOL.getName(), DaprApiProtocol.HTTP.name()); - try (DaprClient client = (new DaprClientBuilder()).build()) { - System.out.println("Using Dapr client..."); - getConfigurations(client); - subscribeConfigurationRequest(client); - } - } - - /** - * Gets configurations for a list of keys. - * - * @param client DaprClient object - */ - public static void getConfigurations(DaprClient client) { - System.out.println("*******trying to retrieve configurations for a list of keys********"); - List keys = new ArrayList<>(); - keys.add("myconfig1"); - keys.add("myconfig2"); - keys.add("myconfig3"); - - Map hmap = new HashMap<>(); - hmap.put("meta_key","meta_value"); - GetConfigurationRequest req = new GetConfigurationRequest(CONFIG_STORE_NAME, keys); - req.setMetadata(hmap); - - try { - Mono> items = client.getConfiguration(req); - items.block().forEach((k,v) -> print(v, k)); - } catch (Exception ex) { - System.out.println(ex.getMessage()); - } - } - - /** - * Subscribe to a list of keys. - * - * @param client DaprClient object - */ - public static void subscribeConfigurationRequest(DaprClient client) throws InterruptedException { - System.out.println("Subscribing to key: myconfig2"); - SubscribeConfigurationRequest req = new SubscribeConfigurationRequest( - CONFIG_STORE_NAME, Collections.singletonList("myconfig2")); - Flux outFlux = client.subscribeConfiguration(req); - outFlux.subscribe( - cis -> { - SUBSCRIPTION_ID = cis.getSubscriptionId(); - }); - if (!SUBSCRIPTION_ID.isEmpty()) { - System.out.println("subscribing to myconfig2 is successful"); - } else { - System.out.println("error in subscribing to myconfig2"); - } - Thread.sleep(5000); - } - - private static void print(ConfigurationItem item, String key) { - System.out.println(item.getValue() + " : key ->" + key); - } -} diff --git a/examples/src/main/java/io/dapr/examples/configuration/http/ConfigurationHandler.java b/examples/src/main/java/io/dapr/examples/configuration/http/ConfigurationHandler.java deleted file mode 100644 index 3703185283..0000000000 --- a/examples/src/main/java/io/dapr/examples/configuration/http/ConfigurationHandler.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2022 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.configuration.http; - -import io.dapr.client.domain.SubscribeConfigurationResponse; -import io.dapr.examples.DaprApplication; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.Options; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -/** - * A sample springboot application to register and receive for updates on configuration items sent by Dapr. - * Users are free to write their own controllers to handle any specific route suited to the need. - * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.configuration.http.ConfigurationSubscriber -p 3009 - */ -@RestController -public class ConfigurationHandler { - - /** - * Receives a subscription change notification. - * @param configStore Path variables for post call - * @param key Key whose value has changed - * @param response Configuration response - */ - @PostMapping(path = "/configuration/{configStore}/{key}", produces = MediaType.ALL_VALUE) - public void handleConfigUpdate(@PathVariable("configStore") String configStore, - @PathVariable("key") String key, - @RequestBody SubscribeConfigurationResponse response) { - System.out.println("Configuration update received for store: " + configStore); - response.getItems().forEach((k,v) -> System.out.println("Key: " + k + " Value :" + v.getValue())); - } - - /** - * This is entry point for Configuration Subscriber service. - * @param args Arguments for main - * @throws Exception Throws Exception - */ - public static void main(String[] args) throws Exception { - Options options = new Options(); - options.addRequiredOption("p", "port", true, "The port this app will listen on"); - CommandLineParser parser = new DefaultParser(); - CommandLine cmd = parser.parse(options, args); - // If port string is not valid, it will throw an exception. - int port = Integer.parseInt(cmd.getOptionValue("port")); - DaprApplication.start(port); - } -} diff --git a/examples/src/main/java/io/dapr/examples/configuration/http/README.md b/examples/src/main/java/io/dapr/examples/configuration/http/README.md deleted file mode 100644 index 21bd50b60a..0000000000 --- a/examples/src/main/java/io/dapr/examples/configuration/http/README.md +++ /dev/null @@ -1,218 +0,0 @@ -## Retrieve Configurations via Configuration API - -This example provides the different capabilities provided by Dapr Java SDK for Configuration. For further information about Configuration APIs please refer to [this link](https://docs.dapr.io/developing-applications/building-blocks/configuration/) - -### Using the ConfigurationAPI - -The java SDK exposes several methods for this - -* `client.getConfiguration(...)` for getting a configuration for a single/multiple keys. -* `client.subscribeConfiguration(...)` for subscribing to a list of keys for any change. -* `client.unsubscribeConfiguration(...)` for unsubscribing to changes from subscribed items. - -## Pre-requisites - -* [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/). -* Java JDK 11 (or greater): - * [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11) - * [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) - * [OpenJDK 11](https://jdk.java.net/11/) -* [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 get into the examples directory: - -```sh -cd examples -``` - -### Initialize Dapr - -Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initialized. - -## Store few dummy configurations in configurationstore - - -```bash -docker exec dapr_redis redis-cli MSET myconfig1 "val1||1" myconfig2 "val2||1" myconfig3 "val3||1" -``` - - -### Running the example - -This example uses the Java SDK Dapr client in order to **Get, Subscribe and Unsubscribe** from configuration items and utilizes `Redis` as configuration store. -`ConfigurationClient.java` is the example class demonstrating all 3 features. -Check [DaprClient.java](https://github.com/dapr/java-sdk/blob/master/sdk/src/main/java/io/dapr/client/DaprClient.java) for detailed description of the supported APIs. - -```java -public class ConfigurationClient { - // ... - /** - * Executes various methods to check the different apis. - * @param args arguments - * @throws Exception throws Exception - */ - public static void main(String[] args) throws Exception { - System.getProperties().setProperty(Properties.API_PROTOCOL.getName(), DaprApiProtocol.HTTP.name()); - try (DaprClient client = (new DaprClientBuilder()).build()) { - System.out.println("Using Dapr client..."); - getConfigurations(client); - subscribeConfigurationRequest(client); - } - } - - /** - * Gets configurations for a list of keys. - * - * @param client DaprClient object - */ - public static void getConfigurations(DaprClient client) { - System.out.println("*******trying to retrieve configurations for a list of keys********"); - GetConfigurationRequest req = new GetConfigurationRequest(CONFIG_STORE_NAME, keys); - try { - Mono> items = client.getConfiguration(req); - // ... - } catch (Exception ex) { - // ... - } - } - - /** - * Subscribe to a list of keys. - * - * @param client DaprClient object - */ - public static void subscribeConfigurationRequest(DaprClient client) { - // ... - SubscribeConfigurationRequest req = new SubscribeConfigurationRequest( - CONFIG_STORE_NAME, Collections.singletonList("myconfig2")); - Runnable subscribeTask = () -> { - Flux outFlux = client.subscribeConfiguration(req); - // ... - }; - // .. - } -} -``` - -Get into the examples' directory: -```sh -cd examples -``` - -#### Running the configuration subscriber app: - -`DaprApplication.start()` Method will run a Spring Boot application containing a `ConfigurationHandler`, a contoller -to receive configuration change notifications. - -```java -@RestController -public class ConfigurationHandler { - //... - @PostMapping(path = "/configuration/{configStore}/{key}", produces = MediaType.ALL_VALUE) - public void handleConfigUpdate(@PathVariable("configStore") String configStore, - @PathVariable("key") String key, - @RequestBody SubscribeConfigurationResponse response) { - System.out.println("Configuration update received for store: " + configStore); - response.getItems().forEach((k,v) -> System.out.println("Key: " + k + " Value :" + v.getValue())); - } - //.... -} -``` -Execute the following script to run the ConfigSubscriber app: - - - -```bash -dapr run --app-id confighandler -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.configuration.http.ConfigurationHandler -p 3009 -``` - - -#### Running the ConfigurationClient app: - -Use the following command to run this example- - - - -```bash -dapr run --components-path ./components/configuration --app-id confighttp --log-level debug --app-port 3009 --dapr-http-port 3500 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.configuration.http.ConfigurationClient -``` - -#### Update myconfig2 key in configurationstore - - - - -```bash -docker exec dapr_redis redis-cli MSET myconfig2 "updated_val2||1" -``` - - -### Sample output -``` -== APP == Using Dapr client... -== APP == *******trying to retrieve configurations for a list of keys******** -== APP == val1 : key ->myconfig1 -== APP == val2 : key ->myconfig2 -== APP == val3 : key ->myconfig3 -== APP == Subscribing to key: myconfig2 -== APP == subscribing to myconfig2 is successful -``` -### Cleanup - -To stop the app, run (or press CTRL+C): - - - -```bash -dapr stop --app-id confighttp -dapr stop --app-id confighandler -``` - - diff --git a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java index 7eeb50fae9..8105a06641 100644 --- a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java +++ b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java @@ -13,17 +13,13 @@ package io.dapr.examples.invoke.grpc; +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; import io.dapr.examples.DaprExamplesProtos.HelloReply; import io.dapr.examples.DaprExamplesProtos.HelloRequest; import io.dapr.examples.HelloWorldGrpc; -import io.grpc.Grpc; -import io.grpc.InsecureChannelCredentials; -import io.grpc.ManagedChannel; -import io.grpc.Metadata; import io.grpc.StatusRuntimeException; -import io.grpc.stub.MetadataUtils; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,22 +41,11 @@ public class HelloWorldClient { * @param args Array of messages to be sent. */ public static void main(String[] args) throws Exception { - String user = "World"; - String target = "localhost:" + System.getenv("DAPR_GRPC_PORT"); - - ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) - .build(); - - try { - HelloWorldGrpc.HelloWorldBlockingStub blockingStub = HelloWorldGrpc.newBlockingStub(channel); - - Metadata headers = new Metadata(); - headers.put(Metadata.Key.of("dapr-app-id", Metadata.ASCII_STRING_MARSHALLER), - "hellogrpc"); - - // MetadataUtils.attachHeaders is deprecated. - blockingStub = blockingStub.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(headers)); + try (DaprClient client = new DaprClientBuilder().build()) { + HelloWorldGrpc.HelloWorldBlockingStub blockingStub = client.newGrpcStub( + "hellogrpc", + channel -> HelloWorldGrpc.newBlockingStub(channel)); logger.info("Will try to greet " + user + " ..."); try { @@ -70,10 +55,6 @@ public static void main(String[] args) throws Exception { } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); } - } finally { - // To prevent leaking resources like threads and TCP connections - // the channel should be shut down when it will no longer be used. - channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); } } } diff --git a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java index eb1cdd23ec..5da82d5364 100644 --- a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java +++ b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java @@ -55,7 +55,7 @@ static class HelloWorldImpl extends HelloWorldGrpc.HelloWorldImplBase { /** * Handling of the 'sayHello' method. * - * @param request Request to say something. + * @param req Request to say something. * @return Response with when it was said. */ @Override diff --git a/examples/src/main/java/io/dapr/examples/invoke/grpc/README.md b/examples/src/main/java/io/dapr/examples/invoke/grpc/README.md index 0dd26012ca..25588979c2 100644 --- a/examples/src/main/java/io/dapr/examples/invoke/grpc/README.md +++ b/examples/src/main/java/io/dapr/examples/invoke/grpc/README.md @@ -61,7 +61,7 @@ In the `HelloWorldService.java` file, you will find the `HelloWorldService` clas /** * Handling of the 'sayHello' method. * - * @param request Request to say something. + * @param req Request to say something. * @return Response with when it was said. */ @Override @@ -93,27 +93,17 @@ The `app-id` argument is used to identify this service in Dapr's runtime. The `a ### Running the example's client -The other component is the client. It will add user name to the grpc request and send it to the server. Open the `HelloWorldClient.java` file, it creates a new grpc channel and sends request directly to the dapr side car through this channel. +The other component is the client. It will add user name to the grpc request and send it to the server. Open the `HelloWorldClient.java` file, it uses the DaprClient's grpc channel and sends request directly to the dapr side car through it, including necessary headers. ```java private static class HelloWorldClient { ///... public static void main(String[] args) throws Exception { - String user = "World"; - // Access a service running on the local machine on port 50051 - String target = "localhost:50051"; - - ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) - .build(); - - try { - HelloWorldGrpc.HelloWorldBlockingStub blockingStub = HelloWorldGrpc.newBlockingStub(channel); - - Metadata headers = new Metadata(); - headers.put(Metadata.Key.of("dapr-app-id", Metadata.ASCII_STRING_MARSHALLER), - "hellogrpc"); - blockingStub = MetadataUtils.attachHeaders(blockingStub, headers); + try (DaprClient client = new DaprClientBuilder().build()) { + HelloWorldGrpc.HelloWorldBlockingStub blockingStub = HelloWorldGrpc.newBlockingStub(client.getGrpcChannel()); + // Adds Dapr interceptors to populate gRPC metadata automatically. + blockingStub = DaprClientGrpcInterceptors.intercept("hellogrpc", blockingStub); logger.info("Will try to greet " + user + " ..."); try { @@ -123,10 +113,6 @@ private static class HelloWorldClient { } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); } - } finally { - // To prevent leaking resources like threads and TCP connections - // the channel should be shut down when it will no longer be used. - channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); } } ///... diff --git a/examples/src/main/java/io/dapr/examples/lock/grpc/DistributedLockGrpcClient.java b/examples/src/main/java/io/dapr/examples/lock/DistributedLockGrpcClient.java similarity index 98% rename from examples/src/main/java/io/dapr/examples/lock/grpc/DistributedLockGrpcClient.java rename to examples/src/main/java/io/dapr/examples/lock/DistributedLockGrpcClient.java index b68b6e7e3c..6d3d8247d2 100644 --- a/examples/src/main/java/io/dapr/examples/lock/grpc/DistributedLockGrpcClient.java +++ b/examples/src/main/java/io/dapr/examples/lock/DistributedLockGrpcClient.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.examples.lock.grpc; +package io.dapr.examples.lock; import io.dapr.client.DaprClientBuilder; diff --git a/examples/src/main/java/io/dapr/examples/lock/grpc/README.md b/examples/src/main/java/io/dapr/examples/lock/README.md similarity index 91% rename from examples/src/main/java/io/dapr/examples/lock/grpc/README.md rename to examples/src/main/java/io/dapr/examples/lock/README.md index a98cf22453..430624b131 100644 --- a/examples/src/main/java/io/dapr/examples/lock/grpc/README.md +++ b/examples/src/main/java/io/dapr/examples/lock/README.md @@ -58,7 +58,7 @@ sleep: 5 --> ```bash -dapr run --components-path ./components/lock --app-id lockgrpc --log-level debug -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.lock.grpc.DistributedLockGrpcClient +dapr run --resources-path ./components/lock --app-id lockgrpc --log-level debug -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.lock.DistributedLockGrpcClient ``` diff --git a/examples/src/main/java/io/dapr/examples/lock/http/DistributedLockHttpClient.java b/examples/src/main/java/io/dapr/examples/lock/http/DistributedLockHttpClient.java deleted file mode 100644 index 9c8b936dee..0000000000 --- a/examples/src/main/java/io/dapr/examples/lock/http/DistributedLockHttpClient.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2022 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.lock.http; - - -import io.dapr.client.DaprClientBuilder; -import io.dapr.client.DaprPreviewClient; -import io.dapr.client.domain.LockRequest; -import io.dapr.client.domain.UnlockRequest; -import io.dapr.client.domain.UnlockResponseStatus; -import reactor.core.publisher.Mono; - -/** - * DistributedLockGrpcClient. - */ -public class DistributedLockHttpClient { - private static final String LOCK_STORE_NAME = "lockstore"; - - /** - * Executes various methods to check the different apis. - * - * @param args arguments - * @throws Exception throws Exception - */ - public static void main(String[] args) throws Exception { - try (DaprPreviewClient client = (new DaprClientBuilder()).buildPreviewClient()) { - System.out.println("Using preview client..."); - tryLock(client); - unlock(client); - } - } - - /** - * Trying to get lock. - * - * @param client DaprPreviewClient object - */ - public static void tryLock(DaprPreviewClient client) { - System.out.println("*******trying to get a free distributed lock********"); - try { - LockRequest lockRequest = new LockRequest(LOCK_STORE_NAME, "resouce1", "owner1", 5); - Mono result = client.tryLock(lockRequest); - System.out.println("Lock result -> " + (Boolean.TRUE.equals(result.block()) ? "SUCCESS" : "FAIL")); - } catch (Exception ex) { - System.out.println(ex.getMessage()); - } - } - - /** - * Unlock a lock. - * - * @param client DaprPreviewClient object - */ - public static void unlock(DaprPreviewClient client) { - System.out.println("*******unlock a distributed lock********"); - try { - UnlockRequest unlockRequest = new UnlockRequest(LOCK_STORE_NAME, "resouce1", "owner1"); - Mono result = client.unlock(unlockRequest); - System.out.println("Unlock result ->" + result.block().name()); - } catch (Exception ex) { - System.out.println(ex.getMessage()); - } - } -} diff --git a/examples/src/main/java/io/dapr/examples/lock/http/README.md b/examples/src/main/java/io/dapr/examples/lock/http/README.md deleted file mode 100644 index d0a9c944fa..0000000000 --- a/examples/src/main/java/io/dapr/examples/lock/http/README.md +++ /dev/null @@ -1,87 +0,0 @@ -## Distributed Lock API Example - -This example provides the different capabilities provided by Dapr Java SDK for Distributed Lock. For further information about Distributed Lock APIs please refer to [this link](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/) -**This API is available in Preview Mode**. - -### Using the Distributed Lock API - -The java SDK exposes several methods for this - -* `client.tryLock(...)` for getting a free distributed lock -* `client.unlock(...)` for unlocking a lock - -## Pre-requisites - -* [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/). -* Java JDK 11 (or greater): - * [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11) - * [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) - * [OpenJDK 11](https://jdk.java.net/11/) -* [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 -``` - - - -### Running the example - -Get into the examples' directory: -```sh -cd examples -``` - -Use the following command to run this example- - - - -```bash -dapr run --components-path ./components/lock --app-id lockhttp --log-level debug -- java -Ddapr.api.protocol=HTTP -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.lock.http.DistributedLockHttpClient -``` - - - -### Sample output -``` -== APP == Using preview client... -== APP == *******trying to get a free distributed lock******** -== APP == Lock result -> SUCCESS -== APP == *******unlock a distributed lock******** -== APP == Unlock result -> SUCCESS -``` -### Cleanup - -To stop the app, run (or press CTRL+C): - - - -```bash -dapr stop --app-id lockhttp -``` - - - diff --git a/examples/src/main/java/io/dapr/examples/pubsub/BulkPublisher.java b/examples/src/main/java/io/dapr/examples/pubsub/BulkPublisher.java index a6552f9cd4..4864a1d2a8 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/BulkPublisher.java +++ b/examples/src/main/java/io/dapr/examples/pubsub/BulkPublisher.java @@ -36,7 +36,7 @@ * mvn clean install * 2. cd [repo root]/examples * 3. Run the program: - * dapr run --components-path ./components/pubsub --app-id bulk-publisher -- \ + * dapr run --resources-path ./components/pubsub --app-id bulk-publisher -- \ * java -Ddapr.grpc.port="50010" -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.BulkPublisher */ public class BulkPublisher { diff --git a/examples/src/main/java/io/dapr/examples/pubsub/CloudEventBulkPublisher.java b/examples/src/main/java/io/dapr/examples/pubsub/CloudEventBulkPublisher.java index a409cc2eef..c30f6e684d 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/CloudEventBulkPublisher.java +++ b/examples/src/main/java/io/dapr/examples/pubsub/CloudEventBulkPublisher.java @@ -33,7 +33,7 @@ * mvn clean install * 2. cd [repo root]/examples * 3. Run the program: - * dapr run --components-path ./components/pubsub --app-id publisher -- \ + * dapr run --resources-path ./components/pubsub --app-id publisher -- \ * java -Ddapr.grpc.port="50010" \ * -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.CloudEventBulkPublisher */ diff --git a/examples/src/main/java/io/dapr/examples/pubsub/CloudEventPublisher.java b/examples/src/main/java/io/dapr/examples/pubsub/CloudEventPublisher.java index ffb487f9aa..62264e34f5 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/CloudEventPublisher.java +++ b/examples/src/main/java/io/dapr/examples/pubsub/CloudEventPublisher.java @@ -29,7 +29,7 @@ * mvn clean install * 2. cd [repo root]/examples * 3. Run the program: - * dapr run --components-path ./components/pubsub --app-id publisher -- \ + * dapr run --resources-path ./components/pubsub --app-id publisher -- \ * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.CloudEventPublisher */ public class CloudEventPublisher { diff --git a/examples/src/main/java/io/dapr/examples/pubsub/Publisher.java b/examples/src/main/java/io/dapr/examples/pubsub/Publisher.java index 679b3b4623..0f02729d22 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/Publisher.java +++ b/examples/src/main/java/io/dapr/examples/pubsub/Publisher.java @@ -25,7 +25,7 @@ * mvn clean install * 2. cd [repo root]/examples * 3. Run the program: - * dapr run --components-path ./components/pubsub --app-id publisher -- \ + * dapr run --resources-path ./components/pubsub --app-id publisher -- \ * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher */ public class Publisher { diff --git a/examples/src/main/java/io/dapr/examples/pubsub/PublisherWithTracing.java b/examples/src/main/java/io/dapr/examples/pubsub/PublisherWithTracing.java index cc8b3d1acd..fd79b076b3 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/PublisherWithTracing.java +++ b/examples/src/main/java/io/dapr/examples/pubsub/PublisherWithTracing.java @@ -30,7 +30,7 @@ * mvn clean install * 2. cd [repo root]/examples * 3. Run the program: - * dapr run --components-path ./components/pubsub --app-id publisher-tracing -- \ + * dapr run --resources-path ./components/pubsub --app-id publisher-tracing -- \ * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.PublisherWithTracing */ public class PublisherWithTracing { diff --git a/examples/src/main/java/io/dapr/examples/pubsub/README.md b/examples/src/main/java/io/dapr/examples/pubsub/README.md index 11bb5b5597..4fb16290ca 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/README.md +++ b/examples/src/main/java/io/dapr/examples/pubsub/README.md @@ -120,7 +120,7 @@ sleep: 15 --> ```bash -dapr run --components-path ./components/pubsub --app-id publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher +dapr run --resources-path ./components/pubsub --app-id publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher ``` @@ -322,7 +322,7 @@ background: true --> ```bash -dapr run --components-path ./components/pubsub --app-id bulk-publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.BulkPublisher +dapr run --resources-path ./components/pubsub --app-id bulk-publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.BulkPublisher ``` @@ -391,7 +391,7 @@ background: true --> ```bash -dapr run --components-path ./components/pubsub --app-id publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher testingtopicbulk +dapr run --resources-path ./components/pubsub --app-id publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher testingtopicbulk ``` @@ -520,7 +520,7 @@ sleep: 15 --> ```bash -dapr run --components-path ./components/pubsub --app-id subscriber --app-port 3000 --app-protocol http -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.http.Subscriber -p 3000 +dapr run --resources-path ./components/pubsub --app-id subscriber --app-port 3000 --app-protocol http -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.http.Subscriber -p 3000 ``` @@ -638,7 +638,7 @@ dapr stop --app-id subscriber // start a grpc subscriber -dapr run --components-path ./components/pubsub --app-id subscriber --app-port 3000 --app-protocol grpc -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.grpc.Subscriber -p 3000 +dapr run --resources-path ./components/pubsub --app-id subscriber --app-port 3000 --app-protocol grpc -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.grpc.Subscriber -p 3000 ``` @@ -654,7 +654,7 @@ sleep: 15 --> ```bash -dapr run --components-path ./components/pubsub --app-id publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher +dapr run --resources-path ./components/pubsub --app-id publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher ``` @@ -685,7 +685,7 @@ background: true --> ```bash -dapr run --components-path ./components/pubsub --app-id publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher testingtopicbulk +dapr run --resources-path ./components/pubsub --app-id publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher testingtopicbulk ``` @@ -754,7 +754,7 @@ sleep: 15 --> ```bash -dapr run --components-path ./components/pubsub --app-id publisher-tracing -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.PublisherWithTracing +dapr run --resources-path ./components/pubsub --app-id publisher-tracing -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.PublisherWithTracing ``` @@ -783,12 +783,12 @@ mvn install Run the publisher app: ```sh -dapr run --components-path ./components/pubsub --app-id publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher +dapr run --resources-path ./components/pubsub --app-id publisher -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Publisher ``` Wait until all 10 messages are published like before, then wait for a few more seconds and run the subscriber app: ```sh -dapr run --components-path ./components/pubsub --app-id subscriber --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Subscriber -p 3000 +dapr run --resources-path ./components/pubsub --app-id subscriber --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.Subscriber -p 3000 ``` No message is consumed by the subscriber app and warnings messages are emitted from Dapr sidecar: diff --git a/examples/src/main/java/io/dapr/examples/pubsub/grpc/Subscriber.java b/examples/src/main/java/io/dapr/examples/pubsub/grpc/Subscriber.java index 7893416b70..4c9cff939a 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/grpc/Subscriber.java +++ b/examples/src/main/java/io/dapr/examples/pubsub/grpc/Subscriber.java @@ -27,7 +27,7 @@ * mvn clean install * 2. cd [repo root]/examples * 3. Run the server: - * dapr run --components-path ./components/pubsub --app-id subscriber --app-port 3000 -- \ + * dapr run --resources-path ./components/pubsub --app-id subscriber --app-port 3000 -- \ * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.grpc.Subscriber -p 3000 */ public class Subscriber { diff --git a/examples/src/main/java/io/dapr/examples/pubsub/http/Subscriber.java b/examples/src/main/java/io/dapr/examples/pubsub/http/Subscriber.java index b5e9ab3c74..6b4edd5ad1 100644 --- a/examples/src/main/java/io/dapr/examples/pubsub/http/Subscriber.java +++ b/examples/src/main/java/io/dapr/examples/pubsub/http/Subscriber.java @@ -25,7 +25,7 @@ * mvn clean install * 2. cd [repo root]/examples * 3. Run the server: - * dapr run --components-path ./components/pubsub --app-id subscriber --app-port 3000 -- \ + * dapr run --resources-path ./components/pubsub --app-id subscriber --app-port 3000 -- \ * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.pubsub.http.Subscriber -p 3000 */ public class Subscriber { diff --git a/examples/src/main/java/io/dapr/examples/querystate/QuerySavedState.java b/examples/src/main/java/io/dapr/examples/querystate/QuerySavedState.java index 0b205a258b..320b4e35e0 100644 --- a/examples/src/main/java/io/dapr/examples/querystate/QuerySavedState.java +++ b/examples/src/main/java/io/dapr/examples/querystate/QuerySavedState.java @@ -34,7 +34,7 @@ * mvn clean install * 2. cd [repo root]/examples * 3. send a message to be saved as state: - * dapr run --components-path ./components/state -- \ + * dapr run --resources-path ./components/state -- \ * java -Ddapr.api.protocol=HTTP -jar target/dapr-java-sdk-examples-exec.jar \ * io.dapr.examples.querystate.QuerySavedState 'my message' */ diff --git a/examples/src/main/java/io/dapr/examples/querystate/README.md b/examples/src/main/java/io/dapr/examples/querystate/README.md index a7bae87d28..2b5320cd1c 100644 --- a/examples/src/main/java/io/dapr/examples/querystate/README.md +++ b/examples/src/main/java/io/dapr/examples/querystate/README.md @@ -36,6 +36,20 @@ cd examples Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initialized. +### Run MongoDB + + + +```bash +docker compose -f ./src/main/java/io/dapr/examples/querystate/docker-compose-single-mongo.yml up -d +``` + + + + ### Running the State Client 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: @@ -246,7 +260,7 @@ 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 +dapr run --resources-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 ``` @@ -282,5 +296,16 @@ name: Cleanup ```bash dapr stop --app-id query-state-example ``` + + +Then, stop MongoDB container. + + + +```bash +docker compose -f ./src/main/java/io/dapr/examples/querystate/docker-compose-single-mongo.yml down +``` diff --git a/examples/src/main/java/io/dapr/examples/querystate/docker-compose-single-mongo.yml b/examples/src/main/java/io/dapr/examples/querystate/docker-compose-single-mongo.yml new file mode 100644 index 0000000000..a75004c36a --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/querystate/docker-compose-single-mongo.yml @@ -0,0 +1,5 @@ +services: + mongo: + image: mongo + ports: + - "27017:27017" \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/secrets/README.md b/examples/src/main/java/io/dapr/examples/secrets/README.md index efc485d6b2..fc609d7f92 100644 --- a/examples/src/main/java/io/dapr/examples/secrets/README.md +++ b/examples/src/main/java/io/dapr/examples/secrets/README.md @@ -123,7 +123,7 @@ sleep: 5 --> ```bash -dapr run --components-path ./components/secrets --app-id secrets1 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.secrets.SecretClient localSecretStore redisPassword randomKey +dapr run --resources-path ./components/secrets --app-id secrets1 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.secrets.SecretClient localSecretStore redisPassword randomKey ``` @@ -176,7 +176,7 @@ sleep: 5 --> ```sh -dapr run --components-path ./components/secrets --config ./src/main/java/io/dapr/examples/secrets/config.yaml --app-id secrets2 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.secrets.SecretClient localSecretStore redisPassword randomKey +dapr run --resources-path ./components/secrets --config ./src/main/java/io/dapr/examples/secrets/config.yaml --app-id secrets2 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.secrets.SecretClient localSecretStore redisPassword randomKey ``` diff --git a/examples/src/main/java/io/dapr/examples/secrets/SecretClient.java b/examples/src/main/java/io/dapr/examples/secrets/SecretClient.java index 36256f05cd..a8c0862853 100644 --- a/examples/src/main/java/io/dapr/examples/secrets/SecretClient.java +++ b/examples/src/main/java/io/dapr/examples/secrets/SecretClient.java @@ -25,7 +25,7 @@ * 2. cd to [repo-root]/examples * 3. Creating a JSON secret file that contains two keys: redisPassword and randomKey locally: * 4. Read secret from example: - * dapr run --components-path ./components/secrets -- \ + * dapr run --resources-path ./components/secrets -- \ * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.secrets.SecretClient \ * localSecretStore redisPassword randomKey */ diff --git a/examples/src/main/java/io/dapr/examples/state/README.md b/examples/src/main/java/io/dapr/examples/state/README.md index ef42f34ca4..6038db8fb2 100644 --- a/examples/src/main/java/io/dapr/examples/state/README.md +++ b/examples/src/main/java/io/dapr/examples/state/README.md @@ -36,6 +36,19 @@ cd examples Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initialized. +### Run MongoDB + + + +```bash +docker compose -f ./src/main/java/io/dapr/examples/state/docker-compose-single-mongo.yml up -d +``` + + + ### Running the StateClient This example uses the Java SDK Dapr client in order to save, retrieve and delete a state, in this case, an instance of a class. Multiple state stores are supported since Dapr 0.4. See the code snippet bellow: @@ -166,7 +179,7 @@ sleep: 5 Run this example with the following command: ```bash -dapr run --components-path ./components/state --app-id state-example -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.state.StateClient 'my message' +dapr run --resources-path ./components/state --app-id state-example -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.state.StateClient 'my message' ``` @@ -206,3 +219,15 @@ dapr stop --app-id state-example ``` + +Then, stop MongoDB container. + + + +```bash +docker compose -f ./src/main/java/io/dapr/examples/state/docker-compose-single-mongo.yml down +``` + + diff --git a/examples/src/main/java/io/dapr/examples/state/StateClient.java b/examples/src/main/java/io/dapr/examples/state/StateClient.java index 4472492e00..2363f96c03 100644 --- a/examples/src/main/java/io/dapr/examples/state/StateClient.java +++ b/examples/src/main/java/io/dapr/examples/state/StateClient.java @@ -30,7 +30,7 @@ * mvn clean install * 2. cd [repo root]/examples * 3. send a message to be saved as state: - * dapr run --components-path ./components/state -- \ + * dapr run --resources-path ./components/state -- \ * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.state.StateClient 'my message' */ public class StateClient { diff --git a/examples/src/main/java/io/dapr/examples/state/docker-compose-single-mongo.yml b/examples/src/main/java/io/dapr/examples/state/docker-compose-single-mongo.yml new file mode 100644 index 0000000000..a75004c36a --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/state/docker-compose-single-mongo.yml @@ -0,0 +1,5 @@ +services: + mongo: + image: mongo + ports: + - "27017:27017" \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java b/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java index 810a365c34..1a190b4f84 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java @@ -16,7 +16,6 @@ import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.WorkflowInstanceStatus; -import java.time.Duration; import java.util.concurrent.TimeoutException; public class DemoChainClient { diff --git a/examples/src/main/java/io/dapr/examples/workflows/continueasnew/DemoContinueAsNewClient.java b/examples/src/main/java/io/dapr/examples/workflows/continueasnew/DemoContinueAsNewClient.java index dbafb2ebb7..a47edb6c32 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/continueasnew/DemoContinueAsNewClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/continueasnew/DemoContinueAsNewClient.java @@ -14,6 +14,7 @@ package io.dapr.examples.workflows.continueasnew; import io.dapr.workflows.client.DaprWorkflowClient; + import java.util.concurrent.TimeoutException; public class DemoContinueAsNewClient { diff --git a/examples/src/main/java/io/dapr/examples/workflows/externalevent/DemoExternalEventClient.java b/examples/src/main/java/io/dapr/examples/workflows/externalevent/DemoExternalEventClient.java index d7178cad70..adae11a997 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/externalevent/DemoExternalEventClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/externalevent/DemoExternalEventClient.java @@ -14,6 +14,7 @@ package io.dapr.examples.workflows.externalevent; import io.dapr.workflows.client.DaprWorkflowClient; + import java.util.concurrent.TimeoutException; public class DemoExternalEventClient { diff --git a/examples/src/main/java/io/dapr/examples/workflows/subworkflow/ReverseActivity.java b/examples/src/main/java/io/dapr/examples/workflows/subworkflow/ReverseActivity.java index 00d34382cd..e231a16eeb 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/subworkflow/ReverseActivity.java +++ b/examples/src/main/java/io/dapr/examples/workflows/subworkflow/ReverseActivity.java @@ -13,7 +13,6 @@ package io.dapr.examples.workflows.subworkflow; -import io.dapr.examples.workflows.chain.ToUpperCaseActivity; import io.dapr.workflows.runtime.WorkflowActivity; import io.dapr.workflows.runtime.WorkflowActivityContext; import org.slf4j.Logger; diff --git a/mvnw b/mvnw index 8d937f4c14..5e9618cac2 100755 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 +# Apache Maven Wrapper startup batch script, version 3.3.2 # # Required ENV vars: # ------------------ @@ -33,75 +33,84 @@ # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then +if [ -z "$MAVEN_SKIP_RC" ]; then - if [ -f /usr/local/etc/mavenrc ] ; then + if [ -f /usr/local/etc/mavenrc ]; then . /usr/local/etc/mavenrc fi - if [ -f /etc/mavenrc ] ; then + if [ -f /etc/mavenrc ]; then . /etc/mavenrc fi - if [ -f "$HOME/.mavenrc" ] ; then + if [ -f "$HOME/.mavenrc" ]; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; +cygwin=false +darwin=false mingw=false case "$(uname)" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)" + export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home" + export JAVA_HOME fi - ;; + fi + ;; esac -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then JAVA_HOME=$(java-config --jre-home) fi fi # For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --unix "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +if $cygwin; then + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && - JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +if $mingw; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ + && JAVA_HOME="$( + cd "$JAVA_HOME" || ( + echo "cannot cd into $JAVA_HOME." >&2 + exit 1 + ) + pwd + )" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="$(which javac)" - if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=$(which readlink) if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then - if $darwin ; then - javaHome="$(dirname "\"$javaExecutable\"")" - javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + if $darwin; then + javaHome="$(dirname "$javaExecutable")" + javaExecutable="$(cd "$javaHome" && pwd -P)/javac" else - javaExecutable="$(readlink -f "\"$javaExecutable\"")" + javaExecutable="$(readlink -f "$javaExecutable")" fi - javaHome="$(dirname "\"$javaExecutable\"")" + javaHome="$(dirname "$javaExecutable")" javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME @@ -109,52 +118,60 @@ if [ -z "$JAVA_HOME" ]; then fi fi -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then +if [ -z "$JAVACMD" ]; then + if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + JAVACMD="$( + \unset -f command 2>/dev/null + \command -v java + )" fi fi -if [ ! -x "$JAVACMD" ] ; then +if [ ! -x "$JAVACMD" ]; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." >&2 fi # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" >&2 return 1 fi basedir="$1" wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then - wdir=$(cd "$wdir/.." || exit 1; pwd) + wdir=$( + cd "$wdir/.." || exit 1 + pwd + ) fi # end of workaround done - printf '%s' "$(cd "$basedir" || exit 1; pwd)" + printf '%s' "$( + cd "$basedir" || exit 1 + pwd + )" } # concatenates all lines of a file @@ -165,7 +182,7 @@ concat_lines() { # enabled. Otherwise, we may read lines that are delimited with # \r\n and produce $'-Xarg\r' rather than -Xarg due to word # splitting rules. - tr -s '\r\n' ' ' < "$1" + tr -s '\r\n' ' ' <"$1" fi } @@ -177,10 +194,11 @@ log() { BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then - exit 1; + exit 1 fi -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +export MAVEN_PROJECTBASEDIR log "$MAVEN_PROJECTBASEDIR" ########################################################################################## @@ -189,63 +207,66 @@ log "$MAVEN_PROJECTBASEDIR" ########################################################################################## wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" if [ -r "$wrapperJarPath" ]; then - log "Found $wrapperJarPath" + log "Found $wrapperJarPath" else - log "Couldn't find $wrapperJarPath, downloading it ..." + log "Couldn't find $wrapperJarPath, downloading it ..." - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in wrapperUrl) + wrapperUrl="$safeValue" + break + ;; + esac + done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi - while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') - case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; - esac - done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" - log "Downloading from: $wrapperUrl" - + elif command -v curl >/dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac if $cygwin; then - wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") fi - - if command -v wget > /dev/null; then - log "Found wget ... using wget" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - log "Found curl ... using curl" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - fi - else - log "Falling back to using Java to download" - javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=$(cygpath --path --windows "$javaSource") - javaClass=$(cygpath --path --windows "$javaClass") - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - log " - Compiling MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - log " - Running MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" - fi - fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi fi + fi fi ########################################################################################## # End of extension @@ -254,22 +275,25 @@ fi # If specified, validate the SHA-256 sum of the Maven wrapper jar file wrapperSha256Sum="" while IFS="=" read -r key value; do - case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + case "$key" in wrapperSha256Sum) + wrapperSha256Sum=$value + break + ;; esac -done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" if [ -n "$wrapperSha256Sum" ]; then wrapperSha256Result=false - if command -v sha256sum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + if command -v sha256sum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then wrapperSha256Result=true fi - elif command -v shasum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + elif command -v shasum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then wrapperSha256Result=true fi else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." - echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi if [ $wrapperSha256Result = false ]; then @@ -284,12 +308,12 @@ MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --windows "$CLASSPATH") - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] \ + && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi # Provide a "standardized" way to retrieve the CLI args that will diff --git a/mvnw.cmd b/mvnw.cmd index f80fbad3e7..1204076a90 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -18,7 +18,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @@ -59,22 +59,22 @@ set ERROR_CODE=0 @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome -echo. +echo. >&2 echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 -echo. +echo. >&2 goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init -echo. +echo. >&2 echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 -echo. +echo. >&2 goto error @REM ==== END VALIDATION ==== @@ -119,7 +119,7 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B @@ -133,7 +133,7 @@ if exist %WRAPPER_JAR% ( ) ) else ( if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... @@ -160,11 +160,12 @@ FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapp ) IF NOT %WRAPPER_SHA_256_SUM%=="" ( powershell -Command "&{"^ + "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ " exit 1;"^ "}"^ "}" diff --git a/pom.xml b/pom.xml index 60e1922ffc..56e92a64dd 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ 4.0.0 @@ -17,7 +17,7 @@ 1.59.0 3.17.3 https://raw.githubusercontent.com/dapr/dapr/v1.13.0-rc.5/dapr/proto - 0.12.0-SNAPSHOT + 0.12.0-SNAPSHOT 1.6.2 3.1.1 1.8 @@ -38,6 +38,8 @@ 3.2.2 3.2.2 5.8.2 + 2.0 + 1.20.0 @@ -207,10 +209,10 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.3.1 + 3.4.0 checkstyle.xml - UTF-8 + .java_header true warning true @@ -233,7 +235,7 @@ com.puppycrawl.tools checkstyle - 8.37 + 10.17.0 @@ -329,8 +331,10 @@ sdk-actors sdk-workflows sdk-springboot - sdk-springboot3 + dapr-spring examples + + testcontainers-dapr io.dapr dapr-sdk @@ -44,56 +45,41 @@ dapr-sdk-actors ${project.version} - - org.mockito - mockito-core - test - - - com.github.gmazzo - okhttp-mock - 1.4.1 - test - - - org.junit.jupiter - junit-jupiter-api - test - - - org.springframework - spring-beans - compile - + + org.springframework spring-web - compile + true org.springframework spring-context - compile + true + + org.springframework.boot - spring-boot-autoconfigure + spring-boot-starter compile org.springframework.boot spring-boot-configuration-processor - compile true + + - org.junit.jupiter - junit-jupiter + org.springframework.boot + spring-boot-starter-test test - org.junit.jupiter - junit-jupiter-params + com.github.gmazzo.okhttp.mock + mock-client + 2.0.0 test @@ -103,7 +89,7 @@ org.apache.maven.plugins maven-source-plugin - 3.2.1 + 3.3.1 attach-sources @@ -117,7 +103,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + 3.7.0 attach-javadocs @@ -127,10 +113,11 @@ + org.jacoco jacoco-maven-plugin - 0.8.11 + 0.8.12 default-prepare-agent diff --git a/sdk-springboot/src/main/resources/META-INF/spring.factories b/sdk-springboot/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 58ff4b58d6..0000000000 --- a/sdk-springboot/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -io.dapr.springboot.DaprAutoConfiguration diff --git a/sdk-springboot3/pom.xml b/sdk-springboot3/pom.xml deleted file mode 100644 index d9fc926d68..0000000000 --- a/sdk-springboot3/pom.xml +++ /dev/null @@ -1,137 +0,0 @@ - - 4.0.0 - - - io.dapr - dapr-sdk-parent - 1.12.0-SNAPSHOT - - - dapr-sdk-springboot3 - jar - 1.12.0-SNAPSHOT - dapr-sdk-springboot3 - SDK extension for Springboot 3.0 - - - false - 3.0.13 - - - - - - org.springframework.boot - spring-boot-dependencies - ${springboot.version} - pom - import - - - - - - - io.dapr - dapr-sdk-springboot - ${project.version} - - - org.springframework - * - - - org.springframework.boot - * - - - - - org.mockito - mockito-core - test - - - com.github.gmazzo - okhttp-mock - 1.4.1 - test - - - org.junit.jupiter - junit-jupiter-api - test - - - org.springframework - spring-beans - compile - - - org.springframework - spring-web - compile - - - org.springframework - spring-context - compile - - - org.springframework.boot - spring-boot-autoconfigure - compile - - - org.springframework.boot - spring-boot-configuration-processor - compile - true - - - org.junit.jupiter - junit-jupiter - test - - - org.junit.jupiter - junit-jupiter-params - test - - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.2.1 - - - attach-sources - - jar-no-fork - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - - attach-javadocs - - jar - - - - - - - diff --git a/sdk-tests/deploy/local-test.yml b/sdk-tests/deploy/local-test.yml index edcfa2ea0e..989757a782 100644 --- a/sdk-tests/deploy/local-test.yml +++ b/sdk-tests/deploy/local-test.yml @@ -26,7 +26,3 @@ services: image: mongo ports: - "27017:27017" - redis: - image: redis - ports: - - "6379:6379" diff --git a/sdk-tests/pom.xml b/sdk-tests/pom.xml index 609d6d5d81..615c47822d 100644 --- a/sdk-tests/pom.xml +++ b/sdk-tests/pom.xml @@ -16,12 +16,17 @@ 17 true 1.12.0-SNAPSHOT + 0.12.0-SNAPSHOT ${project.build.directory}/generated-sources ${project.basedir}/proto 1.59.0 3.17.3 - 0.14.0 - 3.0.13 + 1.39.0 + 3.3.1 + 1.4.12 + 3.9.1 + 0.12.0-SNAPSHOT + 1.19.8 @@ -34,13 +39,6 @@ pom import - - org.junit - junit-bom - 5.7.2 - pom - import - @@ -103,7 +101,12 @@ io.opentelemetry opentelemetry-sdk-metrics - ${opentelemetry.version}-alpha + ${opentelemetry.version} + + + io.opentelemetry + opentelemetry-exporter-common + ${opentelemetry.version} io.opentelemetry @@ -115,6 +118,17 @@ opentelemetry-exporter-zipkin ${opentelemetry.version} + + + io.zipkin.reporter2 + zipkin-reporter + 3.4.0 + + + io.zipkin.reporter2 + zipkin-sender-okhttp3 + 3.4.0 + io.dapr dapr-sdk @@ -129,58 +143,125 @@ io.dapr - dapr-sdk-springboot3 + dapr-sdk-workflows + ${dapr.alpha.sdk.version} + test + + + io.dapr + dapr-sdk-springboot ${dapr.sdk.version} test - - - org.springframework - * - - - org.springframework.boot - * - - - - - org.junit.jupiter - junit-jupiter + + + io.dapr.spring + dapr-spring-core + ${dapr.alpha.sdk.version} + test + + + io.dapr.spring + dapr-spring-data + ${dapr.alpha.sdk.version} + test + + + io.dapr.spring + dapr-spring-messaging + ${dapr.alpha.sdk.version} + test + + + io.dapr.spring + dapr-spring-boot-autoconfigure + ${dapr.alpha.sdk.version} test - org.junit.jupiter - junit-jupiter-params + io.dapr.spring + dapr-spring-boot-starter + ${dapr.alpha.sdk.version} test + + io.dapr.spring + dapr-spring-boot-testcontainers + ${dapr.alpha.sdk.version} + test + + + org.springframework.data + spring-data-keyvalue + org.springframework.boot - spring-boot-starter-web + spring-boot-starter-test test org.springframework.boot - spring-boot-autoconfigure + spring-boot-testcontainers test - com.jayway.jsonpath - json-path - 2.9.0 + org.wiremock + wiremock-standalone + ${wiremock.version} + test + + + ch.qos.logback + logback-classic + ${logback-classic.version} + test + + + org.springframework.boot + spring-boot-starter-web + test + + + io.projectreactor + reactor-core + 3.6.7 test - - io.projectreactor - reactor-core - 3.5.0 - test - org.apache.commons commons-lang3 3.9 test + + io.dapr + testcontainers-dapr + ${testcontainers-dapr.version} + + + org.testcontainers + junit-jupiter + ${testcontainers-test.version} + test + + + org.testcontainers + postgresql + ${testcontainers-test.version} + test + + + org.testcontainers + mysql + ${testcontainers-test.version} + test + + + + com.mysql + mysql-connector-j + 8.2.0 + test + jakarta.annotation jakarta.annotation-api @@ -199,14 +280,12 @@ 2.1.7 - jakarta.servlet jakarta.servlet-api - 6.0.0 compile - + javax.servlet javax.servlet-api 4.0.1 diff --git a/sdk-tests/src/test/java/io/dapr/actors/runtime/DaprClientHttpUtils.java b/sdk-tests/src/test/java/io/dapr/actors/runtime/DaprClientHttpUtils.java index d04f6ffd62..c498d3c8df 100644 --- a/sdk-tests/src/test/java/io/dapr/actors/runtime/DaprClientHttpUtils.java +++ b/sdk-tests/src/test/java/io/dapr/actors/runtime/DaprClientHttpUtils.java @@ -12,7 +12,7 @@ */ package io.dapr.actors.runtime; -import io.dapr.client.DaprHttp; +import io.grpc.ManagedChannel; /** * Exposes useful methods for IT in DaprClientHttp. @@ -20,10 +20,10 @@ public class DaprClientHttpUtils { public static void unregisterActorReminder( - DaprHttp client, + ManagedChannel channel, String actorType, String actorId, - String reminderName) throws Exception { - new DaprHttpClient(client).unregisterReminder(actorType, actorId, reminderName).block(); + String reminderName) { + new DaprClientImpl(channel).unregisterReminder(actorType, actorId, reminderName).block(); } } diff --git a/sdk-tests/src/test/java/io/dapr/it/AppRun.java b/sdk-tests/src/test/java/io/dapr/it/AppRun.java index 5900f6a221..4ad886b841 100644 --- a/sdk-tests/src/test/java/io/dapr/it/AppRun.java +++ b/sdk-tests/src/test/java/io/dapr/it/AppRun.java @@ -13,7 +13,6 @@ package io.dapr.it; -import io.dapr.client.DaprApiProtocol; import io.dapr.config.Properties; import java.io.IOException; @@ -28,7 +27,7 @@ public class AppRun implements Stoppable { private static final String APP_COMMAND = - "mvn exec:java -B -D exec.mainClass=%s -D exec.classpathScope=test -D exec.args=\"%s\" -D %s=%s -D %s=%s"; + "mvn exec:java -B -D exec.mainClass=%s -D exec.classpathScope=test -D exec.args=\"%s\""; private final DaprPorts ports; @@ -39,11 +38,10 @@ public class AppRun implements Stoppable { AppRun(DaprPorts ports, String successMessage, Class serviceClass, - int maxWaitMilliseconds, - DaprApiProtocol protocol) { + int maxWaitMilliseconds) { this.command = new Command( successMessage, - buildCommand(serviceClass, ports, protocol), + buildCommand(serviceClass, ports), new HashMap<>() {{ put("DAPR_HTTP_PORT", ports.getHttpPort().toString()); put("DAPR_GRPC_PORT", ports.getGrpcPort().toString()); @@ -81,11 +79,9 @@ public void stop() throws InterruptedException { } } - private static String buildCommand(Class serviceClass, DaprPorts ports, DaprApiProtocol protocol) { + private static String buildCommand(Class serviceClass, DaprPorts ports) { return String.format(APP_COMMAND, serviceClass.getCanonicalName(), - ports.getAppPort() != null ? ports.getAppPort().toString() : "", - Properties.API_PROTOCOL.getName(), protocol, - Properties.API_METHOD_INVOCATION_PROTOCOL.getName(), protocol); + ports.getAppPort() != null ? ports.getAppPort().toString() : ""); } private static void assertListeningOnPort(int port) { @@ -101,4 +97,7 @@ private static void assertListeningOnPort(int port) { System.out.printf("Confirmed listening on port %d.\n", port); } + public enum AppProtocol { + HTTP, GRPC; + } } diff --git a/sdk-tests/src/test/java/io/dapr/it/BaseIT.java b/sdk-tests/src/test/java/io/dapr/it/BaseIT.java index c4f9d3cdea..abef394b79 100644 --- a/sdk-tests/src/test/java/io/dapr/it/BaseIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/BaseIT.java @@ -14,18 +14,19 @@ package io.dapr.it; import io.dapr.actors.client.ActorClient; -import io.dapr.client.DaprApiProtocol; import io.dapr.client.resiliency.ResiliencyOptions; import org.apache.commons.lang3.tuple.ImmutablePair; import org.junit.jupiter.api.AfterAll; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; -import static io.dapr.client.DaprApiProtocol.GRPC; -import static io.dapr.client.DaprApiProtocol.HTTP; +import static io.dapr.it.AppRun.AppProtocol.GRPC; +import static io.dapr.it.AppRun.AppProtocol.HTTP; public abstract class BaseIT { @@ -52,7 +53,7 @@ protected static DaprRun startDaprApp( String testName, String successMessage, Class serviceClass, - DaprApiProtocol appProtocol, + AppRun.AppProtocol appProtocol, int maxWaitMilliseconds) throws Exception { return startDaprApp(testName, successMessage, serviceClass, true, maxWaitMilliseconds, GRPC, appProtocol); } @@ -63,7 +64,7 @@ protected static DaprRun startDaprApp( Class serviceClass, Boolean useAppPort, int maxWaitMilliseconds, - DaprApiProtocol protocol) throws Exception { + AppRun.AppProtocol protocol) throws Exception { return startDaprApp( testName, successMessage, @@ -81,8 +82,8 @@ protected static DaprRun startDaprApp( Class serviceClass, Boolean useAppPort, int maxWaitMilliseconds, - DaprApiProtocol protocol, - DaprApiProtocol appProtocol) throws Exception { + AppRun.AppProtocol protocol, + AppRun.AppProtocol appProtocol) throws Exception { return startDaprApp( testName, successMessage, @@ -115,14 +116,13 @@ protected static DaprRun startDaprApp( Boolean useAppPort, Boolean useDaprPorts, int maxWaitMilliseconds, - DaprApiProtocol protocol, - DaprApiProtocol appProtocol) throws Exception { + AppRun.AppProtocol protocol, + AppRun.AppProtocol appProtocol) throws Exception { DaprRun.Builder builder = new DaprRun.Builder( testName, () -> DaprPorts.build(useAppPort, useDaprPorts, useDaprPorts), successMessage, maxWaitMilliseconds, - protocol, appProtocol).withServiceClass(serviceClass); DaprRun run = builder.build(); TO_BE_STOPPED.add(run); @@ -139,7 +139,7 @@ protected static ImmutablePair startSplitDaprAndApp( Boolean useAppPort, int maxWaitMilliseconds) throws Exception { return startSplitDaprAndApp( - testName, successMessage, serviceClass, useAppPort, maxWaitMilliseconds, DaprApiProtocol.GRPC); + testName, successMessage, serviceClass, useAppPort, maxWaitMilliseconds, AppRun.AppProtocol.GRPC); } protected static ImmutablePair startSplitDaprAndApp( @@ -148,7 +148,7 @@ protected static ImmutablePair startSplitDaprAndApp( Class serviceClass, Boolean useAppPort, int maxWaitMilliseconds, - DaprApiProtocol protocol) throws Exception { + AppRun.AppProtocol protocol) throws Exception { return startSplitDaprAndApp( testName, successMessage, @@ -165,14 +165,13 @@ protected static ImmutablePair startSplitDaprAndApp( Class serviceClass, Boolean useAppPort, int maxWaitMilliseconds, - DaprApiProtocol protocol, - DaprApiProtocol appProtocol) throws Exception { + AppRun.AppProtocol protocol, + AppRun.AppProtocol appProtocol) throws Exception { DaprRun.Builder builder = new DaprRun.Builder( testName, () -> DaprPorts.build(useAppPort, true, true), successMessage, maxWaitMilliseconds, - protocol, appProtocol).withServiceClass(serviceClass); ImmutablePair runs = builder.splitBuild(); TO_BE_STOPPED.add(runs.left); @@ -199,9 +198,21 @@ protected static ActorClient newActorClient() { return newActorClient(null); } - protected static ActorClient newActorClient(ResiliencyOptions resiliencyOptions) { - ActorClient client = new ActorClient(resiliencyOptions); - TO_BE_CLOSED.add(client); - return client; + protected static ActorClient newActorClient(ResiliencyOptions resiliencyOptions) throws RuntimeException { + try { + Constructor constructor = ActorClient.class.getDeclaredConstructor(ResiliencyOptions.class); + constructor.setAccessible(true); + ActorClient client = constructor.newInstance(resiliencyOptions); + TO_BE_CLOSED.add(client); + return client; + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } } } diff --git a/sdk-tests/src/test/java/io/dapr/it/DaprRun.java b/sdk-tests/src/test/java/io/dapr/it/DaprRun.java index c0181b1db2..9a7533a27b 100644 --- a/sdk-tests/src/test/java/io/dapr/it/DaprRun.java +++ b/sdk-tests/src/test/java/io/dapr/it/DaprRun.java @@ -14,7 +14,6 @@ package io.dapr.it; import com.google.protobuf.Empty; -import io.dapr.client.DaprApiProtocol; import io.dapr.config.Properties; import io.dapr.v1.AppCallbackHealthCheckGrpc; import io.grpc.ManagedChannel; @@ -42,13 +41,13 @@ public class DaprRun implements Stoppable { // the arg in -Dexec.args is the app's port private static final String DAPR_COMMAND = - " -- mvn exec:java -D exec.mainClass=%s -D exec.classpathScope=test -D exec.args=\"%s\" -D %s=%s -D %s=%s"; + " -- mvn exec:java -D exec.mainClass=%s -D exec.classpathScope=test -D exec.args=\"%s\""; private final DaprPorts ports; private final String appName; - private final DaprApiProtocol appProtocol; + private final AppRun.AppProtocol appProtocol; private final int maxWaitMilliseconds; @@ -67,15 +66,14 @@ private DaprRun(String testName, String successMessage, Class serviceClass, int maxWaitMilliseconds, - DaprApiProtocol protocol, - DaprApiProtocol appProtocol) { + AppRun.AppProtocol appProtocol) { // The app name needs to be deterministic since we depend on it to kill previous runs. this.appName = serviceClass == null ? testName.toLowerCase() : String.format("%s-%s", testName, serviceClass.getSimpleName()).toLowerCase(); this.appProtocol = appProtocol; this.startCommand = - new Command(successMessage, buildDaprCommand(this.appName, serviceClass, ports, protocol, appProtocol)); + new Command(successMessage, buildDaprCommand(this.appName, serviceClass, ports, appProtocol)); this.listCommand = new Command( this.appName, "dapr list"); @@ -145,29 +143,6 @@ public void stop() throws InterruptedException, IOException { public void use() { this.ports.use(); - System.getProperties().setProperty(Properties.API_PROTOCOL.getName(), DaprApiProtocol.GRPC.name()); - System.getProperties().setProperty( - Properties.API_METHOD_INVOCATION_PROTOCOL.getName(), - DaprApiProtocol.GRPC.name()); - } - - public void switchToGRPC() { - System.getProperties().setProperty(Properties.API_PROTOCOL.getName(), DaprApiProtocol.GRPC.name()); - System.getProperties().setProperty( - Properties.API_METHOD_INVOCATION_PROTOCOL.getName(), - DaprApiProtocol.GRPC.name()); - } - - public void switchToHTTP() { - System.getProperties().setProperty(Properties.API_PROTOCOL.getName(), DaprApiProtocol.HTTP.name()); - System.getProperties().setProperty( - Properties.API_METHOD_INVOCATION_PROTOCOL.getName(), - DaprApiProtocol.HTTP.name()); - } - - public void switchToProtocol(DaprApiProtocol protocol) { - System.getProperties().setProperty(Properties.API_PROTOCOL.getName(), protocol.name()); - System.getProperties().setProperty(Properties.API_METHOD_INVOCATION_PROTOCOL.getName(), protocol.name()); } public void waitForAppHealth(int maxWaitMilliseconds) throws InterruptedException { @@ -175,7 +150,7 @@ public void waitForAppHealth(int maxWaitMilliseconds) throws InterruptedExceptio return; } - if (DaprApiProtocol.GRPC.equals(this.appProtocol)) { + if (AppRun.AppProtocol.GRPC.equals(this.appProtocol)) { ManagedChannel channel = ManagedChannelBuilder.forAddress("127.0.0.1", this.getAppPort()) .usePlaintext() .build(); @@ -263,7 +238,7 @@ public void checkRunState(long timeout, boolean shouldBeRunning) throws Interrup } private static String buildDaprCommand( - String appName, Class serviceClass, DaprPorts ports, DaprApiProtocol protocol, DaprApiProtocol appProtocol) { + String appName, Class serviceClass, DaprPorts ports, AppRun.AppProtocol appProtocol) { StringBuilder stringBuilder = new StringBuilder(String.format(DAPR_RUN, appName, appProtocol.toString().toLowerCase())) .append(ports.getAppPort() != null ? " --app-port " + ports.getAppPort() : "") @@ -273,9 +248,7 @@ private static String buildDaprCommand( " --enable-app-health-check --app-health-probe-interval=1" : "") .append(serviceClass == null ? "" : String.format(DAPR_COMMAND, serviceClass.getCanonicalName(), - ports.getAppPort() != null ? ports.getAppPort().toString() : "", - Properties.API_PROTOCOL.getName(), protocol, - Properties.API_METHOD_INVOCATION_PROTOCOL.getName(), protocol)); + ports.getAppPort() != null ? ports.getAppPort().toString() : "")); return stringBuilder.toString(); } @@ -315,22 +288,18 @@ static class Builder { private Class serviceClass; - private DaprApiProtocol protocol; - - private DaprApiProtocol appProtocol; + private AppRun.AppProtocol appProtocol; Builder( String testName, Supplier portsSupplier, String successMessage, int maxWaitMilliseconds, - DaprApiProtocol protocol, - DaprApiProtocol appProtocol) { + AppRun.AppProtocol appProtocol) { this.testName = testName; this.portsSupplier = portsSupplier; this.successMessage = successMessage; this.maxWaitMilliseconds = maxWaitMilliseconds; - this.protocol = protocol; this.appProtocol = appProtocol; } @@ -346,7 +315,6 @@ DaprRun build() { this.successMessage, this.serviceClass, this.maxWaitMilliseconds, - this.protocol, this.appProtocol); } @@ -360,8 +328,7 @@ ImmutablePair splitBuild() { ports, this.successMessage, this.serviceClass, - this.maxWaitMilliseconds, - this.protocol); + this.maxWaitMilliseconds); DaprRun daprRun = new DaprRun( this.testName, @@ -369,7 +336,6 @@ ImmutablePair splitBuild() { DAPR_SUCCESS_MESSAGE, null, this.maxWaitMilliseconds, - this.protocol, this.appProtocol); return new ImmutablePair<>(appRun, daprRun); diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/ActorReminderRecoveryIT.java b/sdk-tests/src/test/java/io/dapr/it/actors/ActorReminderRecoveryIT.java index d0f0570d74..73f0eb3820 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/ActorReminderRecoveryIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/ActorReminderRecoveryIT.java @@ -22,22 +22,24 @@ import io.dapr.it.actors.app.ActorReminderDataParam; import io.dapr.it.actors.app.MyActorService; import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.Before; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.runners.Parameterized; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import java.util.stream.Stream; import static io.dapr.it.Retry.callWithRetry; -import static io.dapr.it.actors.MyActorTestUtils.*; +import static io.dapr.it.actors.MyActorTestUtils.countMethodCalls; +import static io.dapr.it.actors.MyActorTestUtils.fetchMethodCallLogs; +import static io.dapr.it.actors.MyActorTestUtils.validateMessageContent; +import static io.dapr.it.actors.MyActorTestUtils.validateMethodCalls; public class ActorReminderRecoveryIT extends BaseIT { diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/ActorSdkResiliencytIT.java b/sdk-tests/src/test/java/io/dapr/it/actors/ActorSdkResiliencyIT.java similarity index 94% rename from sdk-tests/src/test/java/io/dapr/it/actors/ActorSdkResiliencytIT.java rename to sdk-tests/src/test/java/io/dapr/it/actors/ActorSdkResiliencyIT.java index ae9f8c6760..e8140d68c9 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/ActorSdkResiliencytIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/ActorSdkResiliencyIT.java @@ -38,7 +38,7 @@ /** * Test SDK resiliency. */ -public class ActorSdkResiliencytIT extends BaseIT { +public class ActorSdkResiliencyIT extends BaseIT { private static final ActorId ACTOR_ID = new ActorId(UUID.randomUUID().toString()); @@ -69,16 +69,12 @@ public class ActorSdkResiliencytIT extends BaseIT { @BeforeAll public static void init() throws Exception { daprRun = startDaprApp( - ActorSdkResiliencytIT.class.getSimpleName(), + ActorSdkResiliencyIT.class.getSimpleName(), DemoActorService.SUCCESS_MESSAGE, DemoActorService.class, true, 60000); - ActorId actorId = new ActorId(UUID.randomUUID().toString()); - - // HTTP client is deprecated, so SDK resiliency is for gRPC client only. - daprRun.switchToGRPC(); demoActor = buildDemoActorProxy(null); daprClient = new DaprClientBuilder().build(); diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java b/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java index 90a160ef8d..534fd481d2 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java @@ -16,7 +16,7 @@ import io.dapr.actors.ActorId; import io.dapr.actors.client.ActorProxy; import io.dapr.actors.client.ActorProxyBuilder; -import io.dapr.client.DaprApiProtocol; +import io.dapr.it.AppRun; import io.dapr.it.BaseIT; import io.dapr.it.DaprRun; import io.dapr.it.actors.services.springboot.StatefulActor; @@ -45,16 +45,14 @@ public class ActorStateIT extends BaseIT { */ public static Stream data() { return Stream.of( - Arguments.of(DaprApiProtocol.HTTP, DaprApiProtocol.HTTP ), - Arguments.of(DaprApiProtocol.HTTP, DaprApiProtocol.GRPC ), - Arguments.of(DaprApiProtocol.GRPC, DaprApiProtocol.HTTP ), - Arguments.of(DaprApiProtocol.GRPC, DaprApiProtocol.GRPC ) + Arguments.of(AppRun.AppProtocol.HTTP ), + Arguments.of(AppRun.AppProtocol.GRPC ) ); } @ParameterizedTest @MethodSource("data") - public void writeReadState(DaprApiProtocol daprClientProtocol, DaprApiProtocol serviceAppProtocol) throws Exception { + public void writeReadState(AppRun.AppProtocol serviceAppProtocol) throws Exception { logger.debug("Starting actor runtime ..."); // The call below will fail if service cannot start successfully. DaprRun runtime = startDaprApp( @@ -65,13 +63,11 @@ public void writeReadState(DaprApiProtocol daprClientProtocol, DaprApiProtocol s 60000, serviceAppProtocol); - runtime.switchToProtocol(daprClientProtocol); - String message = "This is a message to be saved and retrieved."; String name = "Jon Doe"; byte[] bytes = new byte[] { 0x1 }; ActorId actorId = new ActorId( - String.format("%d-%b-%b", System.currentTimeMillis(), daprClientProtocol, serviceAppProtocol)); + String.format("%d-%b", System.currentTimeMillis(), serviceAppProtocol)); String actorType = "StatefulActorTest"; logger.debug("Building proxy ..."); ActorProxyBuilder proxyBuilder = @@ -161,8 +157,6 @@ public void writeReadState(DaprApiProtocol daprClientProtocol, DaprApiProtocol s 60000, serviceAppProtocol); - runtime.switchToProtocol(daprClientProtocol); - // Need new proxy builder because the proxy builder holds the channel. proxyBuilder = new ActorProxyBuilder(actorType, ActorProxy.class, newActorClient()); ActorProxy newProxy = proxyBuilder.build(actorId); diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/ActorTurnBasedConcurrencyIT.java b/sdk-tests/src/test/java/io/dapr/it/actors/ActorTurnBasedConcurrencyIT.java index 38faec9827..adc7b36c3b 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/ActorTurnBasedConcurrencyIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/ActorTurnBasedConcurrencyIT.java @@ -17,10 +17,12 @@ import io.dapr.actors.client.ActorProxy; import io.dapr.actors.client.ActorProxyBuilder; import io.dapr.actors.runtime.DaprClientHttpUtils; -import io.dapr.client.DaprHttp; -import io.dapr.client.DaprHttpBuilder; +import io.dapr.config.Properties; import io.dapr.it.BaseIT; import io.dapr.it.actors.app.MyActorService; +import io.dapr.utils.Version; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -54,14 +56,15 @@ public class ActorTurnBasedConcurrencyIT extends BaseIT { @AfterEach public void cleanUpTestCase() { // Delete the reminder in case the test failed, otherwise it may interfere with future tests since it is persisted. - DaprHttp client = new DaprHttpBuilder().build(); - System.out.println("Invoking during cleanup"); + var channel = buildManagedChannel(); try { - DaprClientHttpUtils.unregisterActorReminder(client, ACTOR_TYPE, ACTOR_ID, REMINDER_NAME); - } catch(Exception e) { + System.out.println("Invoking during cleanup"); + DaprClientHttpUtils.unregisterActorReminder(channel, ACTOR_TYPE, ACTOR_ID, REMINDER_NAME); + } catch (Exception e) { e.printStackTrace(); + } finally { + channel.shutdown(); } - } /** @@ -225,4 +228,16 @@ void validateEventNotObserved(List logs, String startingPoin } } } + + private static ManagedChannel buildManagedChannel() { + int port = Properties.GRPC_PORT.get(); + if (port <= 0) { + throw new IllegalStateException("Invalid port."); + } + + return ManagedChannelBuilder.forAddress(Properties.SIDECAR_IP.get(), port) + .usePlaintext() + .userAgent(Version.getSdkVersion()) + .build(); + } } diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/MyActorTestUtils.java b/sdk-tests/src/test/java/io/dapr/it/actors/MyActorTestUtils.java index d1d5cffd6a..ae50aa6ab5 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/MyActorTestUtils.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/MyActorTestUtils.java @@ -20,7 +20,9 @@ import java.util.List; import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * Utility class for tests that use MyActor class. diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorBase.java b/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorBase.java index 46aebbbc70..b05dc3b2bf 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorBase.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorBase.java @@ -24,7 +24,13 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.Duration; -import java.util.*; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.TimeZone; import java.util.function.Function; public abstract class MyActorBase extends AbstractActor implements MyActor, Remindable { diff --git a/sdk-tests/src/test/java/io/dapr/it/api/ApiIT.java b/sdk-tests/src/test/java/io/dapr/it/api/ApiIT.java index 4fd9999c7e..462d22927d 100644 --- a/sdk-tests/src/test/java/io/dapr/it/api/ApiIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/api/ApiIT.java @@ -4,34 +4,19 @@ import io.dapr.client.DaprClientBuilder; import io.dapr.it.BaseIT; import io.dapr.it.DaprRun; -import io.dapr.it.actors.ActorReminderRecoveryIT; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.Collection; - public class ApiIT extends BaseIT { private static final Logger logger = LoggerFactory.getLogger(ApiIT.class); private static final int DEFAULT_TIMEOUT = 60000; - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testShutdownAPI(boolean useGrpc) throws Exception { + @Test + public void testShutdownAPI() throws Exception { DaprRun run = startDaprApp(this.getClass().getSimpleName(), DEFAULT_TIMEOUT); - if (useGrpc) { - run.switchToGRPC(); - } else { - run.switchToHTTP(); - } - // TODO(artursouza): change this to wait for the sidecar to be healthy (new method needed in DaprClient). Thread.sleep(3000); try (DaprClient client = new DaprClientBuilder().build()) { diff --git a/sdk-tests/src/test/java/io/dapr/it/binding/http/BindingIT.java b/sdk-tests/src/test/java/io/dapr/it/binding/http/BindingIT.java index 8d66c26aa3..cc83b9b628 100644 --- a/sdk-tests/src/test/java/io/dapr/it/binding/http/BindingIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/binding/http/BindingIT.java @@ -20,8 +20,6 @@ import io.dapr.it.BaseIT; import io.dapr.it.DaprRun; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import java.util.Collections; import java.util.List; @@ -46,24 +44,14 @@ public MyClass() { public String message; } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void inputOutputBinding(boolean useGrpc) throws Exception { - System.out.println("Working Directory = " + System.getProperty("user.dir")); - String serviceNameVariant = useGrpc ? "-grpc" : "-http"; - + @Test + public void inputOutputBinding() throws Exception { DaprRun daprRun = startDaprApp( - this.getClass().getSimpleName() + serviceNameVariant, + this.getClass().getSimpleName() + "-grpc", InputBindingService.SUCCESS_MESSAGE, InputBindingService.class, true, 60000); - // At this point, it is guaranteed that the service above is running and all ports being listened to. - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } try(DaprClient client = new DaprClientBuilder().build()) { callWithRetry(() -> { diff --git a/sdk-tests/src/test/java/io/dapr/it/configuration/grpc/ConfigurationClientIT.java b/sdk-tests/src/test/java/io/dapr/it/configuration/ConfigurationClientIT.java similarity index 94% rename from sdk-tests/src/test/java/io/dapr/it/configuration/grpc/ConfigurationClientIT.java rename to sdk-tests/src/test/java/io/dapr/it/configuration/ConfigurationClientIT.java index b667158426..02c5798de1 100644 --- a/sdk-tests/src/test/java/io/dapr/it/configuration/grpc/ConfigurationClientIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/configuration/ConfigurationClientIT.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.it.configuration.grpc; +package io.dapr.it.configuration; import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; @@ -20,7 +20,6 @@ import io.dapr.client.domain.UnsubscribeConfigurationResponse; import io.dapr.it.BaseIT; import io.dapr.it.DaprRun; - import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -29,10 +28,14 @@ import reactor.core.publisher.Flux; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ConfigurationClientIT extends BaseIT { @@ -65,7 +68,6 @@ public class ConfigurationClientIT extends BaseIT { @BeforeAll public static void init() throws Exception { daprRun = startDaprApp(ConfigurationClientIT.class.getSimpleName(), 5000); - daprRun.switchToGRPC(); daprClient = new DaprClientBuilder().build(); } @@ -198,6 +200,9 @@ private static void executeDockerCommand(String[] command) { try { process = processBuilder.start(); process.waitFor(); + if (process.exitValue() != 0) { + throw new RuntimeException("Not zero exit code for Redis command: " + process.exitValue()); + } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { diff --git a/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigSubscriberController.java b/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigSubscriberController.java deleted file mode 100644 index c3323bde65..0000000000 --- a/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigSubscriberController.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 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.it.configuration.http; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.dapr.client.domain.ConfigurationItem; -import io.dapr.client.domain.SubscribeConfigurationResponse; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Mono; - -import java.util.Iterator; -import java.util.Map; - -/** - * Spring boot Controller class for api endpoints. - */ -@RestController -public class ConfigSubscriberController { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - /** - * Api mapping for subscribe configuration. - * @param pathVarsMap Path variables for post call - * @param obj request Body - * @return Returns void - */ - @PostMapping(path = "/configuration/{configStore}/{key}", produces = MediaType.APPLICATION_JSON_VALUE) - public Mono handleMessage( - @PathVariable Map pathVarsMap, - @RequestBody SubscribeConfigurationResponse obj) { - return Mono.fromRunnable( - () -> { - try { - Map items = obj.getItems(); - for (Map.Entry entry : items.entrySet()) { - System.out.println(entry.getValue().getValue() + " : key ->" + entry.getKey()); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - ); - } - - @GetMapping(path = "/health") - public void health() { - } -} diff --git a/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigurationIT.java b/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigurationIT.java deleted file mode 100644 index 93e3816a21..0000000000 --- a/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigurationIT.java +++ /dev/null @@ -1,83 +0,0 @@ -package io.dapr.it.configuration.http; - -import io.dapr.client.DaprClient; -import io.dapr.client.DaprClientBuilder; -import io.dapr.client.domain.ConfigurationItem; -import io.dapr.it.BaseIT; -import io.dapr.it.DaprRun; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.*; - -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class ConfigurationIT extends BaseIT { - private static final String CONFIG_STORE_NAME = "redisconfigstore"; - - private static DaprRun daprRun; - - private static DaprClient daprClient; - - private static String key = "myconfig1"; - - private static List keys = new ArrayList<>(Arrays.asList("myconfig1", "myconfig2", "myconfig3")); - - private static String[] insertCmd = new String[] { - "docker", "exec", "dapr_redis", "redis-cli", - "MSET", - "myconfigkey1", "myconfigvalue1||1", - "myconfigkey2", "myconfigvalue2||1", - "myconfigkey3", "myconfigvalue3||1" - }; - - @BeforeAll - public static void init() throws Exception { - daprRun = startDaprApp(ConfigurationIT.class.getSimpleName(), 5000); - daprRun.switchToHTTP(); - daprClient = new DaprClientBuilder().build(); - } - - @AfterAll - public static void tearDown() throws Exception { - daprClient.close(); - } - - @BeforeEach - public void setupConfigStore() { - executeDockerCommand(insertCmd); - } - - @Test - public void getConfiguration() { - ConfigurationItem ci = daprClient.getConfiguration(CONFIG_STORE_NAME, "myconfigkey1").block(); - assertEquals(ci.getKey(), "myconfigkey1"); - assertEquals(ci.getValue(), "myconfigvalue1"); - } - - @Test - public void getConfigurations() { - Map cis = daprClient.getConfiguration(CONFIG_STORE_NAME, "myconfigkey1", "myconfigkey2").block(); - assertTrue(cis.size() == 2); - assertTrue(cis.containsKey("myconfigkey1")); - assertTrue(cis.containsKey("myconfigkey2")); - } - - private static void executeDockerCommand(String[] command) { - ProcessBuilder processBuilder = new ProcessBuilder(command); - Process process = null; - try { - process = processBuilder.start(); - process.waitFor(); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } -} diff --git a/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigurationSubscribeIT.java b/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigurationSubscribeIT.java deleted file mode 100644 index d2ebb50cdc..0000000000 --- a/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigurationSubscribeIT.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.dapr.it.configuration.http; - -import io.dapr.client.DaprClient; -import io.dapr.client.DaprClientBuilder; -import io.dapr.client.domain.SubscribeConfigurationResponse; -import io.dapr.client.domain.UnsubscribeConfigurationResponse; -import io.dapr.it.BaseIT; -import io.dapr.it.DaprRun; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -import java.io.IOException; -import java.util.*; -import java.util.concurrent.atomic.AtomicReference; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class ConfigurationSubscribeIT extends BaseIT { - private static final String CONFIG_STORE_NAME = "redisconfigstore"; - - private static DaprRun daprRun; - - private static DaprClient daprClient; - - private static String key = "myconfig1"; - - private static List keys = new ArrayList<>(Arrays.asList("myconfig1", "myconfig2", "myconfig3")); - - private static String[] insertCmd = new String[] { - "docker", "exec", "dapr_redis", "redis-cli", - "MSET", - "myconfigkey1", "myconfigvalue1||1", - "myconfigkey2", "myconfigvalue2||1", - "myconfigkey3", "myconfigvalue3||1" - }; - - @BeforeAll - public static void init() throws Exception { - daprRun = startDaprApp( - ConfigurationIT.class.getSimpleName(), - ConfigurationSubscriberService.SUCCESS_MESSAGE, - ConfigurationSubscriberService.class, - true, - 60000); - daprRun.switchToHTTP(); - daprClient = new DaprClientBuilder().build(); - } - - @AfterAll - public static void tearDown() throws Exception { - daprClient.close(); - } - - @BeforeEach - public void setupConfigStore() { - executeDockerCommand(insertCmd); - } - - @Test - public void subscribeAndUnsubscribeConfiguration() { - AtomicReference subId= new AtomicReference<>(""); - Flux outFlux = daprClient - .subscribeConfiguration(CONFIG_STORE_NAME, "myconfigkey1", "myconfigkey2"); - outFlux.subscribe(items -> { - subId.set(items.getSubscriptionId()); - }); - assertTrue(subId.get().length() > 0); - - UnsubscribeConfigurationResponse res = daprClient.unsubscribeConfiguration( - subId.get(), - CONFIG_STORE_NAME - ).block(); - assertTrue(res.getIsUnsubscribed()); - } - - private static void executeDockerCommand(String[] command) { - ProcessBuilder processBuilder = new ProcessBuilder(command); - Process process = null; - try { - process = processBuilder.start(); - process.waitFor(); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } -} diff --git a/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigurationSubscriberService.java b/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigurationSubscriberService.java deleted file mode 100644 index 1589c562e4..0000000000 --- a/sdk-tests/src/test/java/io/dapr/it/configuration/http/ConfigurationSubscriberService.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2022 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.it.configuration.http; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * Service for ConfigurationSubscriber. - * dapr run --components-path ./components/configuration --app-id configsubscriber --app-port 3000 -- \ - * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.configuration.http.ConfigurationSubscriber -p 3000 - */ -@SpringBootApplication -public class ConfigurationSubscriberService { - - public static final String SUCCESS_MESSAGE = "dapr initialized. Status: Running"; - - /** - * This is entry point for Configuration Subscriber service. - * @param args Arguments for main - * @throws Exception Throws Exception - */ - public static void main(String[] args) throws Exception { - int port = Integer.parseInt(args[0]); - - System.out.printf("Service starting on port %d ...\n", port); - - // Start Dapr's callback endpoint. - start(port); - } - - /** - * Starts Dapr's callback in a given port. - * - * @param port Port to listen to. - */ - private static void start(int port) { - SpringApplication app = new SpringApplication(ConfigurationSubscriberService.class); - app.run(String.format("--server.port=%d", port)); - } -} diff --git a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeIT.java b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeIT.java index 4d01dc05c6..5eac1b2a7f 100644 --- a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeIT.java @@ -1,12 +1,14 @@ package io.dapr.it.methodinvoke.grpc; -import io.dapr.client.DaprApiProtocol; import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; -import io.dapr.client.domain.HttpExtension; -import io.dapr.exceptions.DaprException; +import io.dapr.client.resiliency.ResiliencyOptions; +import io.dapr.it.AppRun; import io.dapr.it.BaseIT; import io.dapr.it.DaprRun; +import io.dapr.it.MethodInvokeServiceGrpc; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,7 +17,6 @@ import static io.dapr.it.MethodInvokeServiceProtos.DeleteMessageRequest; import static io.dapr.it.MethodInvokeServiceProtos.GetMessagesRequest; -import static io.dapr.it.MethodInvokeServiceProtos.GetMessagesResponse; import static io.dapr.it.MethodInvokeServiceProtos.PostMessageRequest; import static io.dapr.it.MethodInvokeServiceProtos.SleepRequest; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -26,6 +27,10 @@ public class MethodInvokeIT extends BaseIT { //Number of messages to be sent: 10 private static final int NUM_MESSAGES = 10; + private static final int TIMEOUT_MS = 100; + private static final ResiliencyOptions RESILIENCY_OPTIONS = new ResiliencyOptions() + .setTimeout(Duration.ofMillis(TIMEOUT_MS)); + private static final String EXCEPTION_MARKER = "DEADLINE_EXCEEDED: deadline exceeded after"; /** * Run of a Dapr application. @@ -38,9 +43,8 @@ public void init() throws Exception { MethodInvokeIT.class.getSimpleName() + "grpc", MethodInvokeService.SUCCESS_MESSAGE, MethodInvokeService.class, - DaprApiProtocol.GRPC, // appProtocol + AppRun.AppProtocol.GRPC, // appProtocol 60000); - daprRun.switchToGRPC(); daprRun.waitForAppHealth(40000); } @@ -49,64 +53,46 @@ public void testInvoke() throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { client.waitForSidecar(10000).block(); daprRun.waitForAppHealth(10000); + + MethodInvokeServiceGrpc.MethodInvokeServiceBlockingStub stub = createGrpcStub(client); for (int i = 0; i < NUM_MESSAGES; i++) { String message = String.format("This is message #%d", i); - PostMessageRequest req = PostMessageRequest.newBuilder().setId(i).setMessage(message).build(); - client.invokeMethod(daprRun.getAppName(), "postMessage", req, HttpExtension.POST).block(); + + stub.postMessage(req); + System.out.println("Invoke method messages : " + message); } - Map messages = client.invokeMethod( - daprRun.getAppName(), - "getMessages", - GetMessagesRequest.newBuilder().build(), - HttpExtension.POST, GetMessagesResponse.class).block().getMessagesMap(); - assertEquals(10, messages.size()); + Map messages = stub.getMessages(GetMessagesRequest.newBuilder().build()).getMessagesMap(); + assertEquals(NUM_MESSAGES, messages.size()); // Delete one message. - client.invokeMethod( - daprRun.getAppName(), - "deleteMessage", - DeleteMessageRequest.newBuilder().setId(1).build(), - HttpExtension.POST).block(); - messages = client.invokeMethod( - daprRun.getAppName(), - "getMessages", - GetMessagesRequest.newBuilder().build(), - HttpExtension.POST, GetMessagesResponse.class).block().getMessagesMap(); - assertEquals(9, messages.size()); + stub.deleteMessage(DeleteMessageRequest.newBuilder().setId(1).build()); + messages = stub.getMessages(GetMessagesRequest.newBuilder().build()).getMessagesMap(); + assertEquals(NUM_MESSAGES - 1, messages.size()); // Now update one message. - client.invokeMethod( - daprRun.getAppName(), - "postMessage", - PostMessageRequest.newBuilder().setId(2).setMessage("updated message").build(), - HttpExtension.POST).block(); - messages = client.invokeMethod( - daprRun.getAppName(), - "getMessages", - GetMessagesRequest.newBuilder().build(), - HttpExtension.POST, GetMessagesResponse.class).block().getMessagesMap(); + stub.postMessage(PostMessageRequest.newBuilder().setId(2).setMessage("updated message").build()); + messages = stub.getMessages(GetMessagesRequest.newBuilder().build()).getMessagesMap(); assertEquals("updated message", messages.get(2)); } } @Test public void testInvokeTimeout() throws Exception { - try (DaprClient client = new DaprClientBuilder().build()) { + try (DaprClient client = new DaprClientBuilder().withResiliencyOptions(RESILIENCY_OPTIONS).build()) { client.waitForSidecar(10000).block(); daprRun.waitForAppHealth(10000); + MethodInvokeServiceGrpc.MethodInvokeServiceBlockingStub stub = createGrpcStub(client); long started = System.currentTimeMillis(); SleepRequest req = SleepRequest.newBuilder().setSeconds(1).build(); - String message = assertThrows(IllegalStateException.class, () -> - client.invokeMethod(daprRun.getAppName(), "sleep", req.toByteArray(), HttpExtension.POST) - .block(Duration.ofMillis(10))).getMessage(); + String message = assertThrows(StatusRuntimeException.class, () -> stub.sleep(req)).getMessage(); long delay = System.currentTimeMillis() - started; - assertTrue(delay <= 500); // 500 ms is a reasonable delay if the request timed out. - assertEquals("Timeout on blocking read for 10000000 NANOSECONDS", message); + assertTrue(delay >= TIMEOUT_MS, "Delay: " + delay + " is not greater than timeout: " + TIMEOUT_MS); + assertTrue(message.startsWith(EXCEPTION_MARKER), "Message: " + message + " does not start with: " + EXCEPTION_MARKER); } } @@ -115,17 +101,22 @@ public void testInvokeException() throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { client.waitForSidecar(10000).block(); daprRun.waitForAppHealth(10000); - + + MethodInvokeServiceGrpc.MethodInvokeServiceBlockingStub stub = createGrpcStub(client); + SleepRequest req = SleepRequest.newBuilder().setSeconds(-9).build(); - DaprException exception = assertThrows(DaprException.class, () -> - client.invokeMethod(daprRun.getAppName(), "sleep", req.toByteArray(), HttpExtension.POST).block()); + StatusRuntimeException exception = assertThrows(StatusRuntimeException.class, () -> stub.sleep(req)); // The error messages should be improved once runtime has standardized error serialization in the API. // This message is not ideal but last time it was improved, there was side effects reported by users. // If this test fails, there might be a regression in runtime (like we had in 1.10.0). // The expectations below are as per 1.9 release and (later on) hotfixed in 1.10. - assertEquals("UNKNOWN", exception.getErrorCode()); - assertEquals("UNKNOWN: ", exception.getMessage()); + assertEquals(Status.UNKNOWN.getCode(), exception.getStatus().getCode()); + assertEquals("", exception.getStatus().getDescription()); } } + + private MethodInvokeServiceGrpc.MethodInvokeServiceBlockingStub createGrpcStub(DaprClient client) { + return client.newGrpcStub(daprRun.getAppName(), MethodInvokeServiceGrpc::newBlockingStub); + } } diff --git a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeService.java b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeService.java index 45648075f1..ac7b157af6 100644 --- a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeService.java +++ b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeService.java @@ -13,26 +13,18 @@ package io.dapr.it.methodinvoke.grpc; -import com.google.protobuf.Any; import io.dapr.grpc.GrpcHealthCheckService; import io.dapr.it.DaprRunConfig; -import io.dapr.v1.AppCallbackGrpc; -import io.dapr.v1.CommonProtos; +import io.dapr.it.MethodInvokeServiceGrpc; import io.grpc.Server; import io.grpc.ServerBuilder; -import io.grpc.stub.StreamObserver; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import static io.dapr.it.MethodInvokeServiceProtos.DeleteMessageRequest; -import static io.dapr.it.MethodInvokeServiceProtos.DeleteMessageResponse; -import static io.dapr.it.MethodInvokeServiceProtos.GetMessagesRequest; import static io.dapr.it.MethodInvokeServiceProtos.GetMessagesResponse; -import static io.dapr.it.MethodInvokeServiceProtos.PostMessageRequest; -import static io.dapr.it.MethodInvokeServiceProtos.PostMessageResponse; import static io.dapr.it.MethodInvokeServiceProtos.SleepRequest; import static io.dapr.it.MethodInvokeServiceProtos.SleepResponse; @@ -48,7 +40,7 @@ public class MethodInvokeService { /** * Server mode: class that encapsulates all server-side logic for Grpc. */ - private static class MyDaprService extends AppCallbackGrpc.AppCallbackImplBase { + private static class MyDaprService extends MethodInvokeServiceGrpc.MethodInvokeServiceImplBase { private final Map messages = Collections.synchronizedMap(new HashMap<>()); @@ -92,57 +84,55 @@ private void awaitTermination() throws InterruptedException { } /** - * Server mode: this is the Dapr method to receive Invoke operations via Grpc. - * - * @param request Dapr envelope request, - * @param responseObserver Dapr envelope response. + * {@inheritDoc} */ - @Override - public void onInvoke(CommonProtos.InvokeRequest request, - StreamObserver responseObserver) { - System.out.println("Server: received " + request.getMethod() + " ..."); - try { - if ("postMessage".equals(request.getMethod())) { - PostMessageRequest req = PostMessageRequest.parseFrom(request.getData().getValue().toByteArray()); - - this.messages.put(req.getId(), req.getMessage()); - - CommonProtos.InvokeResponse.Builder responseBuilder = CommonProtos.InvokeResponse.newBuilder(); - responseBuilder.setData(Any.pack(PostMessageResponse.newBuilder().build())); - responseObserver.onNext(responseBuilder.build()); - } - if ("deleteMessage".equals(request.getMethod())) { - DeleteMessageRequest req = DeleteMessageRequest.parseFrom(request.getData().getValue().toByteArray()); - - this.messages.remove(req.getId()); - - CommonProtos.InvokeResponse.Builder responseBuilder = CommonProtos.InvokeResponse.newBuilder(); - responseBuilder.setData(Any.pack(DeleteMessageResponse.newBuilder().build())); - responseObserver.onNext(responseBuilder.build()); - } - if ("getMessages".equals(request.getMethod())) { - GetMessagesRequest.parseFrom(request.getData().getValue().toByteArray()); - - GetMessagesResponse res = GetMessagesResponse.newBuilder().putAllMessages(this.messages).build(); - - CommonProtos.InvokeResponse.Builder responseBuilder = CommonProtos.InvokeResponse.newBuilder(); - responseBuilder.setData(Any.pack(res)); - responseObserver.onNext(responseBuilder.build()); - } - if ("sleep".equals(request.getMethod())) { - SleepRequest req = SleepRequest.parseFrom(request.getData().getValue().toByteArray()); - - SleepResponse res = this.sleep(req); - - CommonProtos.InvokeResponse.Builder responseBuilder = CommonProtos.InvokeResponse.newBuilder(); - responseBuilder.setData(Any.pack(res)); - responseObserver.onNext(responseBuilder.build()); - } - } catch (Exception e) { - responseObserver.onError(e); - } finally { - responseObserver.onCompleted(); - } + public void postMessage(io.dapr.it.MethodInvokeServiceProtos.PostMessageRequest request, + io.grpc.stub.StreamObserver responseObserver) { + this.messages.put(request.getId(), request.getMessage()); + + io.dapr.it.MethodInvokeServiceProtos.PostMessageResponse.Builder responseBuilder = + io.dapr.it.MethodInvokeServiceProtos.PostMessageResponse.newBuilder(); + responseObserver.onNext(responseBuilder.build()); + responseObserver.onCompleted(); + } + + /** + * {@inheritDoc} + */ + public void deleteMessage(io.dapr.it.MethodInvokeServiceProtos.DeleteMessageRequest request, + io.grpc.stub.StreamObserver responseObserver) { + this.messages.remove(request.getId()); + + io.dapr.it.MethodInvokeServiceProtos.DeleteMessageResponse.Builder responseBuilder = + io.dapr.it.MethodInvokeServiceProtos.DeleteMessageResponse.newBuilder(); + responseObserver.onNext(responseBuilder.build()); + responseObserver.onCompleted(); + } + + /** + * {@inheritDoc} + */ + public void getMessages(io.dapr.it.MethodInvokeServiceProtos.GetMessagesRequest request, + io.grpc.stub.StreamObserver responseObserver) { + GetMessagesResponse res = GetMessagesResponse.newBuilder().putAllMessages(this.messages).build(); + + io.dapr.it.MethodInvokeServiceProtos.GetMessagesResponse.Builder responseBuilder + = io.dapr.it.MethodInvokeServiceProtos.GetMessagesResponse.newBuilder(); + responseObserver.onNext(res); + responseObserver.onCompleted(); + } + + /** + * {@inheritDoc} + */ + public void sleep(io.dapr.it.MethodInvokeServiceProtos.SleepRequest request, + io.grpc.stub.StreamObserver responseObserver) { + SleepResponse res = this.sleep(request); + + io.dapr.it.MethodInvokeServiceProtos.SleepResponse.Builder responseBuilder = + io.dapr.it.MethodInvokeServiceProtos.SleepResponse.newBuilder(); + responseObserver.onNext(responseBuilder.build()); + responseObserver.onCompleted(); } public SleepResponse sleep(SleepRequest request) { diff --git a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeController.java b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeController.java index f75b009846..8ff77985b7 100644 --- a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeController.java +++ b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeController.java @@ -33,12 +33,12 @@ public void postMessages(@RequestBody String message){ } @PutMapping(path = "/messages/{messageId}") - public void putMessages(@PathVariable Integer messageId, @RequestBody String message){ + public void putMessages(@PathVariable("messageId") Integer messageId, @RequestBody String message){ messagesReceived.put(messageId,message); } @DeleteMapping(path = "/messages/{messageId}") - public void deleteMessages(@PathVariable Integer messageId){ + public void deleteMessages(@PathVariable("messageId") Integer messageId){ messagesReceived.remove(messageId); } @@ -58,7 +58,7 @@ public void postPerson(@RequestBody Person person){ } @PutMapping(path = "/persons/{personId}") - public void putPerson(@PathVariable Integer personId, @RequestBody Person person){ + public void putPerson(@PathVariable("personId") Integer personId, @RequestBody Person person){ final Optional auxPerson = persons.stream().filter(person1 -> person1.getId() == personId).findFirst(); if(auxPerson.isPresent()){ auxPerson.get().setName(person.getName()); @@ -68,7 +68,7 @@ public void putPerson(@PathVariable Integer personId, @RequestBody Person person } @DeleteMapping(path = "/persons/{personId}") - public void deletePerson(@PathVariable Integer personId){ + public void deletePerson(@PathVariable("personId") Integer personId){ final Optional auxPerson = persons.stream().filter(person1 -> person1.getId() == personId).findFirst(); if(auxPerson.isPresent()) { persons.remove(auxPerson.get()); diff --git a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java index 0cf9e42f52..6068db2f02 100644 --- a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java @@ -40,7 +40,6 @@ public void init() throws Exception { MethodInvokeService.class, true, 30000); - daprRun.switchToHTTP(); daprRun.waitForAppHealth(20000); } @@ -135,7 +134,7 @@ public void testInvokeException() throws Exception { // TODO(artursouza): change this to INTERNAL once runtime is fixed. assertEquals("UNKNOWN", exception.getErrorCode()); assertNotNull(exception.getMessage()); - assertTrue(exception.getMessage().contains("Internal Server Error")); + assertTrue(exception.getMessage().contains("HTTP status code: 500")); assertTrue(new String(exception.getPayload()).contains("Internal Server Error")); } } diff --git a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java b/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java index 77b917450c..347bd64dec 100644 --- a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java @@ -34,8 +34,7 @@ import io.dapr.utils.TypeRef; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; @@ -99,49 +98,26 @@ public void tearDown() throws Exception { } } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void publishPubSubNotFound(boolean useGrpc) throws Exception { + @Test + public void publishPubSubNotFound() throws Exception { DaprRun daprRun = closeLater(startDaprApp( this.getClass().getSimpleName(), 60000)); - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } try (DaprClient client = new DaprClientBuilder().build()) { - - if (useGrpc) { - assertThrowsDaprExceptionWithReason( - "INVALID_ARGUMENT", - "INVALID_ARGUMENT: pubsub unknown pubsub is not found", - "DAPR_PUBSUB_NOT_FOUND", - () -> client.publishEvent("unknown pubsub", "mytopic", "payload").block()); - } else { - assertThrowsDaprExceptionWithReason( - "ERR_PUBSUB_NOT_FOUND", - "ERR_PUBSUB_NOT_FOUND: pubsub unknown pubsub is not found", - "DAPR_PUBSUB_NOT_FOUND", - () -> client.publishEvent("unknown pubsub", "mytopic", "payload").block()); - } + assertThrowsDaprExceptionWithReason( + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: pubsub unknown pubsub is not found", + "DAPR_PUBSUB_NOT_FOUND", + () -> client.publishEvent("unknown pubsub", "mytopic", "payload").block()); } } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testBulkPublishPubSubNotFound(boolean useGrpc) throws Exception { + @Test + public void testBulkPublishPubSubNotFound() throws Exception { DaprRun daprRun = closeLater(startDaprApp( this.getClass().getSimpleName(), 60000)); - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - // No HTTP implementation for bulk publish - System.out.println("no HTTP impl for bulkPublish"); - return; - } try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) { assertThrowsDaprException( @@ -151,22 +127,14 @@ public void testBulkPublishPubSubNotFound(boolean useGrpc) throws Exception { } } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testBulkPublish(boolean useGrpc) throws Exception { + @Test + public void testBulkPublish() throws Exception { final DaprRun daprRun = closeLater(startDaprApp( this.getClass().getSimpleName(), SubscriberService.SUCCESS_MESSAGE, SubscriberService.class, true, 60000)); - // At this point, it is guaranteed that the service above is running and all ports being listened to. - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - System.out.println("HTTP BulkPublish is not implemented. So skipping tests"); - return; - } DaprObjectSerializer serializer = new DaprObjectSerializer() { @Override public byte[] serialize(Object o) throws JsonProcessingException { @@ -287,21 +255,14 @@ public String getContentType() { } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testPubSub(boolean useGrpc) throws Exception { + @Test + public void testPubSub() throws Exception { final DaprRun daprRun = closeLater(startDaprApp( this.getClass().getSimpleName(), SubscriberService.SUCCESS_MESSAGE, SubscriberService.class, true, 60000)); - // At this point, it is guaranteed that the service above is running and all ports being listened to. - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } DaprObjectSerializer serializer = new DaprObjectSerializer() { @Override @@ -508,21 +469,14 @@ public String getContentType() { } } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testPubSubBinary(boolean useGrpc) throws Exception { + @Test + public void testPubSubBinary() throws Exception { final DaprRun daprRun = closeLater(startDaprApp( this.getClass().getSimpleName(), SubscriberService.SUCCESS_MESSAGE, SubscriberService.class, true, 60000)); - // At this point, it is guaranteed that the service above is running and all ports being listened to. - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } DaprObjectSerializer serializer = new DaprObjectSerializer() { @Override @@ -565,17 +519,11 @@ public String getContentType() { } } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testPubSubTTLMetadata(boolean useGrpc) throws Exception { + @Test + public void testPubSubTTLMetadata() throws Exception { DaprRun daprRun = closeLater(startDaprApp( this.getClass().getSimpleName(), 60000)); - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } // Send a batch of messages on one topic, all to be expired in 1 second. try (DaprClient client = new DaprClientBuilder().build()) { @@ -602,11 +550,6 @@ public void testPubSubTTLMetadata(boolean useGrpc) throws Exception { SubscriberService.class, true, 60000)); - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } // Sleeps for five seconds to give subscriber a chance to receive messages. Thread.sleep(5000); @@ -623,20 +566,14 @@ public void testPubSubTTLMetadata(boolean useGrpc) throws Exception { daprRun.stop(); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testPubSubBulkSubscribe(boolean useGrpc) throws Exception { + @Test + public void testPubSubBulkSubscribe() throws Exception { DaprRun daprRun = closeLater(startDaprApp( this.getClass().getSimpleName(), SubscriberService.SUCCESS_MESSAGE, SubscriberService.class, true, 60000)); - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } // Send a batch of messages on one topic. try (DaprClient client = new DaprClientBuilder().build()) { @@ -686,21 +623,14 @@ public void testPubSubBulkSubscribe(boolean useGrpc) throws Exception { daprRun.stop(); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testLongValues(boolean useGrpc) throws Exception { + @Test + public void testLongValues() throws Exception { final DaprRun daprRun = closeLater(startDaprApp( this.getClass().getSimpleName(), SubscriberService.SUCCESS_MESSAGE, SubscriberService.class, true, 60000)); - // At this point, it is guaranteed that the service above is running and all ports being listened to. - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } Random random = new Random(590518626939830271L); Set values = new HashSet<>(); diff --git a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/SubscriberController.java b/sdk-tests/src/test/java/io/dapr/it/pubsub/http/SubscriberController.java index d98d1bd5cc..9fc5df3ee2 100644 --- a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/SubscriberController.java +++ b/sdk-tests/src/test/java/io/dapr/it/pubsub/http/SubscriberController.java @@ -15,7 +15,12 @@ import io.dapr.Rule; import io.dapr.Topic; -import io.dapr.client.domain.*; +import io.dapr.client.domain.BulkSubscribeAppResponse; +import io.dapr.client.domain.BulkSubscribeAppResponseEntry; +import io.dapr.client.domain.BulkSubscribeAppResponseStatus; +import io.dapr.client.domain.BulkSubscribeMessage; +import io.dapr.client.domain.BulkSubscribeMessageEntry; +import io.dapr.client.domain.CloudEvent; import io.dapr.springboot.annotations.BulkSubscribe; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/sdk-tests/src/test/java/io/dapr/it/resiliency/SdkResiliencytIT.java b/sdk-tests/src/test/java/io/dapr/it/resiliency/SdkResiliencyIT.java similarity index 74% rename from sdk-tests/src/test/java/io/dapr/it/resiliency/SdkResiliencytIT.java rename to sdk-tests/src/test/java/io/dapr/it/resiliency/SdkResiliencyIT.java index 49ead52e06..c3bdc71953 100644 --- a/sdk-tests/src/test/java/io/dapr/it/resiliency/SdkResiliencytIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/resiliency/SdkResiliencyIT.java @@ -15,7 +15,7 @@ import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; -import io.dapr.client.DaprClientGrpc; +import io.dapr.client.DaprClientImpl; import io.dapr.client.resiliency.ResiliencyOptions; import io.dapr.it.BaseIT; import io.dapr.it.DaprRun; @@ -36,20 +36,18 @@ /** * Test SDK resiliency. */ -public class SdkResiliencytIT extends BaseIT { +public class SdkResiliencyIT extends BaseIT { private static final int NUM_ITERATIONS = 35; private static final Duration TIMEOUT = Duration.ofMillis(100); - private static final Duration LATENCY = TIMEOUT.dividedBy(2); + private static final Duration LATENCY = TIMEOUT.dividedBy(3); - private static final Duration JITTER = TIMEOUT.multipliedBy(2); + private static final Duration JITTER = TIMEOUT.multipliedBy(3); private static final int MAX_RETRIES = -1; // Infinity - private static DaprRun daprRun; - private static DaprClient daprClient; private static ToxiProxyRun toxiProxyRun; @@ -64,9 +62,7 @@ public class SdkResiliencytIT extends BaseIT { @BeforeAll public static void init() throws Exception { - daprRun = startDaprApp(SdkResiliencytIT.class.getSimpleName(), 5000); - // HTTP client is deprecated, so SDK resiliency is for gRPC client only. - daprRun.switchToGRPC(); + DaprRun daprRun = startDaprApp(SdkResiliencyIT.class.getSimpleName(), 5000); daprClient = new DaprClientBuilder().build(); daprClient.waitForSidecar(8000).block(); @@ -87,10 +83,10 @@ public static void init() throws Exception { new ResiliencyOptions().setTimeout(TIMEOUT).setMaxRetries(1)) .build(); - assertTrue(daprClient instanceof DaprClientGrpc); - assertTrue(daprToxiClient instanceof DaprClientGrpc); - assertTrue(daprResilientClient instanceof DaprClientGrpc); - assertTrue(daprRetriesOnceClient instanceof DaprClientGrpc); + assertTrue(daprClient instanceof DaprClientImpl); + assertTrue(daprToxiClient instanceof DaprClientImpl); + assertTrue(daprResilientClient instanceof DaprClientImpl); + assertTrue(daprRetriesOnceClient instanceof DaprClientImpl); } @AfterAll @@ -115,7 +111,7 @@ public static void tearDown() throws Exception { @Test public void retryAndTimeout() { AtomicInteger toxiClientErrorCount = new AtomicInteger(); - AtomicInteger retryOneClientErrorCount = new AtomicInteger(); + AtomicInteger retryOnceClientErrorCount = new AtomicInteger(); while (true){ for (int i = 0; i < NUM_ITERATIONS; i++) { @@ -131,7 +127,7 @@ public void retryAndTimeout() { daprRetriesOnceClient.saveState(STATE_STORE_NAME, key, value).block(); } catch (Exception e) { // This call should fail sometimes. So, we count. - retryOneClientErrorCount.incrementAndGet(); + retryOnceClientErrorCount.incrementAndGet(); } // We retry forever so that the call below should always work. @@ -142,17 +138,29 @@ public void retryAndTimeout() { } // We should have at least one success per client, otherwise retry. - if(toxiClientErrorCount.get() < NUM_ITERATIONS && retryOneClientErrorCount.get() < NUM_ITERATIONS){ + if(toxiClientErrorCount.get() < NUM_ITERATIONS && retryOnceClientErrorCount.get() < NUM_ITERATIONS){ // This assertion makes sure that toxicity is on - assertTrue(toxiClientErrorCount.get() > 0); - assertTrue(retryOneClientErrorCount.get() > 0); + assertTrue(toxiClientErrorCount.get() > 0, "Toxi client error count is 0"); + assertTrue(retryOnceClientErrorCount.get() > 0, "Retry once client error count is 0"); // A client without retries should have more errors than a client with one retry. - assertTrue(toxiClientErrorCount.get() > retryOneClientErrorCount.get()); + + String failureMessage = formatFailureMessage(toxiClientErrorCount, retryOnceClientErrorCount); + assertTrue(toxiClientErrorCount.get() > retryOnceClientErrorCount.get(), failureMessage); break; } toxiClientErrorCount.set(0); - retryOneClientErrorCount.set(0); + retryOnceClientErrorCount.set(0); } + } + private static String formatFailureMessage( + AtomicInteger toxiClientErrorCount, + AtomicInteger retryOnceClientErrorCount + ) { + return String.format( + "Toxi client error count: %d, Retry once client error count: %d", + toxiClientErrorCount.get(), + retryOnceClientErrorCount.get() + ); } } diff --git a/sdk-tests/src/test/java/io/dapr/it/secrets/SecretsClientIT.java b/sdk-tests/src/test/java/io/dapr/it/secrets/SecretsClientIT.java index 022f0bfcda..fc6260a680 100644 --- a/sdk-tests/src/test/java/io/dapr/it/secrets/SecretsClientIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/secrets/SecretsClientIT.java @@ -19,23 +19,14 @@ import io.dapr.it.BaseIT; import io.dapr.it.DaprRun; import org.apache.commons.io.IOUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -82,13 +73,8 @@ public static void init() throws Exception { daprRun = startDaprApp(SecretsClientIT.class.getSimpleName(), 5000); } - public void setup(boolean useGrpc) { - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } - + @BeforeEach + public void setup() { this.daprClient = new DaprClientBuilder().build(); } @@ -98,22 +84,16 @@ public void tearDown() throws Exception { clearSecretFile(); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void getSecret(boolean useGrpc) throws Exception { - setup(useGrpc); - + @Test + public void getSecret() throws Exception { Map data = daprClient.getSecret(SECRETS_STORE_NAME, KEY1).block(); assertEquals(2, data.size()); assertEquals("The Metrics IV", data.get("title")); assertEquals("2020", data.get("year")); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void getBulkSecret(boolean useGrpc) throws Exception { - setup(useGrpc); - + @Test + public void getBulkSecret() throws Exception { Map> data = daprClient.getBulkSecret(SECRETS_STORE_NAME).block(); // There can be other keys from other runs or test cases, so we are good with at least two. assertTrue(data.size() >= 2); @@ -124,19 +104,13 @@ public void getBulkSecret(boolean useGrpc) throws Exception { assertEquals("Jon Doe", data.get(KYE2).get("name")); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void getSecretKeyNotFound(boolean useGrpc) { - setup(useGrpc); - + @Test + public void getSecretKeyNotFound() { assertThrows(RuntimeException.class, () -> daprClient.getSecret(SECRETS_STORE_NAME, "unknownKey").block()); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void getSecretStoreNotFound(boolean useGrpc) throws Exception { - setup(useGrpc); - + @Test + public void getSecretStoreNotFound() { assertThrows(RuntimeException.class, () -> daprClient.getSecret("unknownStore", "unknownKey").block()); } diff --git a/sdk-tests/src/test/java/io/dapr/it/state/GRPCStateClientIT.java b/sdk-tests/src/test/java/io/dapr/it/state/GRPCStateClientIT.java index a9dd05a4e2..1c77d93d20 100644 --- a/sdk-tests/src/test/java/io/dapr/it/state/GRPCStateClientIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/state/GRPCStateClientIT.java @@ -37,7 +37,6 @@ public class GRPCStateClientIT extends AbstractStateClientIT { @BeforeAll public static void init() throws Exception { daprRun = startDaprApp(GRPCStateClientIT.class.getSimpleName(), 5000); - daprRun.switchToGRPC(); daprClient = new DaprClientBuilder().build(); } diff --git a/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldClientIT.java b/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldClientIT.java index 577d8b72d1..1cf8db98e4 100644 --- a/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldClientIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldClientIT.java @@ -13,9 +13,9 @@ package io.dapr.it.state; +import io.dapr.config.Properties; import io.dapr.it.BaseIT; import io.dapr.it.DaprRun; -import io.dapr.config.Properties; import io.dapr.v1.DaprGrpc; import io.dapr.v1.DaprProtos; import io.grpc.ManagedChannel; diff --git a/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldGrpcStateService.java b/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldGrpcStateService.java index d3446a27b7..a5b92eb8d9 100644 --- a/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldGrpcStateService.java +++ b/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldGrpcStateService.java @@ -27,7 +27,7 @@ * Simple example. * To run manually, from repo root: * 1. mvn clean install - * 2. dapr run --components-path ./components --dapr-grpc-port 50001 -- mvn exec:java -Dexec.mainClass=io.dapr.it.state.HelloWorldGrpcStateService -Dexec.classpathScope="test" -pl=sdk + * 2. dapr run --resources-path ./components --dapr-grpc-port 50001 -- mvn exec:java -Dexec.mainClass=io.dapr.it.state.HelloWorldGrpcStateService -Dexec.classpathScope="test" -pl=sdk */ public class HelloWorldGrpcStateService { diff --git a/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java b/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java deleted file mode 100644 index 9a281bd026..0000000000 --- a/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.it.state; - -import io.dapr.client.DaprClient; -import io.dapr.client.DaprClientBuilder; -import io.dapr.client.DaprClientHttp; -import io.dapr.client.domain.State; -import io.dapr.it.DaprRun; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.util.Collections; - -import static io.dapr.it.TestUtils.assertThrowsDaprException; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Test State HTTP DAPR capabilities using a DAPR instance with an empty service running - */ -public class HttpStateClientIT extends AbstractStateClientIT { - - private static DaprRun daprRun; - - private static DaprClient daprClient; - - @BeforeAll - public static void init() throws Exception { - daprRun = startDaprApp(HttpStateClientIT.class.getSimpleName(), 5000); - daprRun.switchToHTTP(); - daprClient = new DaprClientBuilder().build(); - assertTrue(daprClient instanceof DaprClientHttp); - } - - @AfterAll - public static void tearDown() throws Exception { - daprClient.close(); - } - - @Override - protected DaprClient buildDaprClient() { - return daprClient; - } - - /** Tests where HTTP and GRPC behavior differ in Dapr runtime. **/ - - @Test - public void getStateStoreNotFound() { - final String stateKey = "key"; - - DaprClient daprClient = buildDaprClient(); - - // DaprException is guaranteed in the Dapr SDK but getCause() is null in HTTP while present in GRPC implementation. - assertThrowsDaprException( - "ERR_STATE_STORE_NOT_FOUND", - "ERR_STATE_STORE_NOT_FOUND: state store unknown state store is not found", - () -> daprClient.getState("unknown state store", new State(stateKey), byte[].class).block()); - } - - @Test - public void getStatesStoreNotFound() { - final String stateKey = "key"; - - DaprClient daprClient = buildDaprClient(); - - // DaprException is guaranteed in the Dapr SDK but getCause() is null in HTTP while present in GRPC implementation. - assertThrowsDaprException( - "ERR_STATE_STORE_NOT_FOUND", - "ERR_STATE_STORE_NOT_FOUND: state store unknown state store is not found", - () -> daprClient.getBulkState( - "unknown state store", - Collections.singletonList(stateKey), - byte[].class).block()); - } - - @Test - public void publishPubSubNotFound() { - DaprClient daprClient = buildDaprClient(); - - - } -} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/AbstractDaprSpringBoootBaseIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/AbstractDaprSpringBoootBaseIT.java new file mode 100644 index 0000000000..b453ba6908 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/AbstractDaprSpringBoootBaseIT.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.Collections; + +@Testcontainers +public abstract class AbstractDaprSpringBoootBaseIT { + + public static Network DAPR_NETWORK = Network.newNetwork(); + + @Container + public static DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.13.2") + .withAppName("local-dapr-app") + .withNetwork(DAPR_NETWORK) + .withComponent(new Component("pubsub", "pubsub.in-memory", "v1", Collections.emptyMap())) + .withAppPort(8080) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) + .withAppChannelAddress("host.testcontainers.internal"); + + @BeforeAll + static void beforeAll() { + org.testcontainers.Testcontainers.exposeHostPorts(8080); + + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprContainerTest.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprContainerTest.java new file mode 100644 index 0000000000..50c9c69dde --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprContainerTest.java @@ -0,0 +1,167 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.domain.Metadata; +import io.dapr.client.domain.State; + +import io.dapr.testcontainers.DaprContainer; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.IOException; +import java.util.Map; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.configureFor; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static java.util.Collections.singletonMap; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +public class DaprContainerTest { + + // Time-to-live for messages published. + private static final String MESSAGE_TTL_IN_SECONDS = "1000"; + private static final String STATE_STORE_NAME = "kvstore"; + private static final String KEY = "my-key"; + private static final String PUBSUB_NAME = "pubsub"; + private static final String PUBSUB_TOPIC_NAME = "topic"; + + @RegisterExtension + public static WireMockExtension wireMockExtension = WireMockExtension.newInstance() + .options(wireMockConfig().port(8081)) + .build(); + + @Container + public static DaprContainer daprContainer = new DaprContainer("daprio/daprd") + .withAppName("dapr-app") + .withAppPort(8081) + .withAppChannelAddress("host.testcontainers.internal"); + + /** + * Sets the Dapr properties for the test. + */ + @BeforeAll + public static void setDaprProperties() { + configStub(); + org.testcontainers.Testcontainers.exposeHostPorts(8081); + System.setProperty("dapr.grpc.port", Integer.toString(daprContainer.getGrpcPort())); + System.setProperty("dapr.http.port", Integer.toString(daprContainer.getHttpPort())); + } + + private static void configStub() { + + stubFor(any(urlMatching("/dapr/subscribe")) + .willReturn(aResponse().withBody("[]").withStatus(200))); + + stubFor(get(urlMatching("/dapr/config")) + .willReturn(aResponse().withBody("[]").withStatus(200))); + + stubFor(any(urlMatching("/([a-z1-9]*)")) + .willReturn(aResponse().withBody("[]").withStatus(200))); + + // create a stub + stubFor(post(urlEqualTo("/events")) + .willReturn(aResponse().withBody("event received!").withStatus(200))); + + configureFor("localhost", 8081); + } + + @Test + public void testDaprContainerDefaults() { + assertEquals(2, + daprContainer.getComponents().size(), + "The pubsub and kvstore component should be configured by default" + ); + assertEquals( + 1, + daprContainer.getSubscriptions().size(), + "A subscription should be configured by default if none is provided" + ); + } + + @Test + public void testStateStore() throws Exception { + try (DaprClient client = (new DaprClientBuilder()).build()) { + client.waitForSidecar(5000).block(); + + String value = "value"; + // Save state + client.saveState(STATE_STORE_NAME, KEY, value).block(); + + // Get the state back from the state store + State retrievedState = client.getState(STATE_STORE_NAME, KEY, String.class).block(); + + assertEquals("The value retrieved should be the same as the one stored", value, retrievedState.getValue()); + } + } + + @Test + public void testPlacement() throws Exception { + // Here we are just waiting for Dapr to be ready + try (DaprClient client = (new DaprClientBuilder()).build()) { + client.waitForSidecar(5000).block(); + } + + OkHttpClient client = new OkHttpClient.Builder().build(); + + String url = "http://" + daprContainer.getHost() + ":" + daprContainer.getMappedPort(3500); + Request request = new Request.Builder().url(url + "/v1.0/metadata").build(); + + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful()) { + assertTrue(response.body().string().contains("placement: connected")); + + } else { + throw new IOException("Unexpected response: " + response.code()); + } + } + + } + + @Test + public void testPubSub() throws Exception { + try (DaprClient client = (new DaprClientBuilder()).build()) { + client.waitForSidecar(5000).block(); + + String message = "message content"; + Map metadata = singletonMap(Metadata.TTL_IN_SECONDS, MESSAGE_TTL_IN_SECONDS); + client.publishEvent(PUBSUB_NAME, PUBSUB_TOPIC_NAME, message, metadata).block(); + } + + verify(getRequestedFor(urlMatching("/dapr/config"))); + verify(postRequestedFor(urlEqualTo("/events")).withHeader("Content-Type", equalTo("application/cloudevents+json"))); + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprModuleTests.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprModuleTests.java new file mode 100644 index 0000000000..8cc9aa6adb --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprModuleTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowInstanceStatus; +import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.time.Duration; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest(classes = MyTestWithWorkflowsApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT) +@Testcontainers +public class DaprModuleTests { + + @Autowired + private SubscriptionsRestController subscriptionsRestController; + + private DaprWorkflowClient workflowClient; + + /** + * Initializes the test. + */ + @BeforeEach + public void init() { + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(TestWorkflow.class); + builder.registerActivity(FirstActivity.class); + builder.registerActivity(SecondActivity.class); + + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("Start workflow runtime"); + runtime.start(false); + } + } + + @Test + public void myWorkflowTest() throws Exception { + workflowClient = new DaprWorkflowClient(); + + TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>()); + String instanceId = workflowClient.scheduleNewWorkflow(TestWorkflow.class, payload); + + workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false); + + workflowClient.raiseEvent(instanceId, "MoveForward", payload); + + WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, + Duration.ofSeconds(10), + true); + + // The workflow completed before 10 seconds + assertNotNull(workflowStatus); + + String workflowPlayloadJson = workflowStatus.getSerializedOutput(); + + ObjectMapper mapper = new ObjectMapper(); + TestWorkflowPayload workflowOutput = mapper.readValue(workflowPlayloadJson, TestWorkflowPayload.class); + + assertEquals(2, workflowOutput.getPayloads().size()); + assertEquals("First Activity", workflowOutput.getPayloads().get(0)); + assertEquals("Second Activity", workflowOutput.getPayloads().get(1)); + assertEquals(instanceId, workflowOutput.getWorkflowId()); + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprPlacementContainerTest.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprPlacementContainerTest.java new file mode 100644 index 0000000000..c43af2561b --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprPlacementContainerTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import io.dapr.testcontainers.DaprPlacementContainer; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Testcontainers +public class DaprPlacementContainerTest { + + @Container + public static DaprPlacementContainer placement = new DaprPlacementContainer("daprio/placement"); + + @Test + public void testDaprPlacementContainerDefaults() { + assertEquals(50005, placement.getPort(), "The default port is not set"); + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprPubSubIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprPubSubIT.java new file mode 100644 index 0000000000..d7e73cf39e --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprPubSubIT.java @@ -0,0 +1,64 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import io.dapr.client.DaprClient; +import io.dapr.client.domain.CloudEvent; +import io.dapr.it.testcontainers.AbstractDaprSpringBoootBaseIT; +import io.dapr.spring.messaging.DaprMessagingTemplate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, + classes = TestApplication.class, + properties = {"dapr.pubsub.name=pubsub"} +) +public class DaprPubSubIT extends AbstractDaprSpringBoootBaseIT { + + private static final Logger logger = LoggerFactory.getLogger(DaprPubSubIT.class); + + private static final String TOPIC = "mockTopic"; + + @Autowired + private DaprMessagingTemplate messagingTemplate; + + @Autowired + private TestRestController testRestController; + + + @Test + public void testDaprMessagingTemplate() throws InterruptedException { + for (int i = 0; i < 10; i++) { + var msg = "ProduceAndReadWithPrimitiveMessageType:" + i; + messagingTemplate.send(TOPIC, msg); + logger.info("++++++PRODUCE {}------", msg); + } + + // Wait for the messages to arrive + Thread.sleep(1000); + + List> events = testRestController.getEvents(); + assertThat(events.size()).isEqualTo(10); + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/FirstActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/FirstActivity.java new file mode 100644 index 0000000000..d3bda7df64 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/FirstActivity.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import io.dapr.workflows.runtime.WorkflowActivity; +import io.dapr.workflows.runtime.WorkflowActivityContext; + +public class FirstActivity implements WorkflowActivity { + + @Override + public Object run(WorkflowActivityContext ctx) { + TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class); + workflowPayload.getPayloads().add("First Activity"); + return workflowPayload; + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/MyTestWithWorkflowsApplication.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/MyTestWithWorkflowsApplication.java new file mode 100644 index 0000000000..c17c55b6df --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/MyTestWithWorkflowsApplication.java @@ -0,0 +1,33 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import io.dapr.testcontainers.DaprModule; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.testcontainers.context.ImportTestcontainers; + +@SpringBootApplication +public class MyTestWithWorkflowsApplication { + + public static void main(String[] args) { + SpringApplication.run(MyTestWithWorkflowsApplication.class, args); + } + + @ImportTestcontainers(DaprModule.class) + static class DaprTestConfiguration { + + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/SecondActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/SecondActivity.java new file mode 100644 index 0000000000..e3a83c293e --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/SecondActivity.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import io.dapr.workflows.runtime.WorkflowActivity; +import io.dapr.workflows.runtime.WorkflowActivityContext; + +public class SecondActivity implements WorkflowActivity { + + @Override + public Object run(WorkflowActivityContext ctx) { + TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class); + workflowPayload.getPayloads().add("Second Activity"); + return workflowPayload; + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/SubscriptionsRestController.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/SubscriptionsRestController.java new file mode 100644 index 0000000000..2d41de6aae --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/SubscriptionsRestController.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import io.dapr.client.domain.CloudEvent; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +@RestController +public class SubscriptionsRestController { + + private final List> events = new ArrayList<>(); + + @PostMapping(path = "/events", consumes = "application/cloudevents+json") + public void receiveEvents(@RequestBody CloudEvent event) { + events.add(event); + } + + @GetMapping(path = "/events", produces = "application/cloudevents+json") + public List> getAllEvents() { + return events; + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestApplication.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestApplication.java new file mode 100644 index 0000000000..2df0d10eb0 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestApplication.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import io.dapr.client.DaprClientBuilder; +import io.dapr.spring.boot.autoconfigure.pubsub.DaprPubSubProperties; +import io.dapr.spring.messaging.DaprMessagingTemplate; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@SpringBootApplication +public class TestApplication { + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } + + @Configuration + @EnableConfigurationProperties(DaprPubSubProperties.class) + class PubSubConfiguration { + @Bean + public DaprMessagingTemplate messagingTemplate(DaprClientBuilder daprClientBuilder, + DaprPubSubProperties daprPubSubProperties) { + return new DaprMessagingTemplate<>(daprClientBuilder.build(), daprPubSubProperties.getName()); + } + + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestRestController.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestRestController.java new file mode 100644 index 0000000000..7e6a277344 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestRestController.java @@ -0,0 +1,52 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import io.dapr.Topic; +import io.dapr.client.domain.CloudEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +@RestController +public class TestRestController { + + public static final String pubSubName = "pubsub"; + public static final String topicName = "mockTopic"; + private static final Logger LOG = LoggerFactory.getLogger(TestRestController.class); + private final List> events = new ArrayList<>(); + + @GetMapping("/") + public String ok() { + return "OK"; + } + + @Topic(name = topicName, pubsubName = pubSubName) + @PostMapping("/subscribe") + public void handleMessages(@RequestBody CloudEvent event) { + LOG.info("++++++CONSUME {}------", event); + events.add(event); + } + + public List> getEvents() { + return events; + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestType.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestType.java new file mode 100644 index 0000000000..6b69675d30 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestType.java @@ -0,0 +1,19 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import org.springframework.data.annotation.Id; + +public record TestType(@Id Integer id, String content) { +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestTypeRepository.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestTypeRepository.java new file mode 100644 index 0000000000..34f8d464be --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestTypeRepository.java @@ -0,0 +1,21 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TestTypeRepository extends CrudRepository { +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestWorkflow.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestWorkflow.java new file mode 100644 index 0000000000..1011c7b6a8 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestWorkflow.java @@ -0,0 +1,48 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import org.slf4j.Logger; + +import java.time.Duration; + +public class TestWorkflow extends Workflow { + + @Override + public WorkflowStub create() { + return ctx -> { + Logger logger = ctx.getLogger(); + String instanceId = ctx.getInstanceId(); + logger.info("Starting Workflow: " + ctx.getName()); + logger.info("Instance ID: " + instanceId); + logger.info("Current Orchestration Time: " + ctx.getCurrentInstant()); + + TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class); + workflowPayload.setWorkflowId(instanceId); + + TestWorkflowPayload payloadAfterFirst = + ctx.callActivity(FirstActivity.class.getName(), workflowPayload, TestWorkflowPayload.class).await(); + + ctx.waitForExternalEvent("MoveForward", Duration.ofSeconds(3), TestWorkflowPayload.class).await(); + + TestWorkflowPayload payloadAfterSecond = + ctx.callActivity(SecondActivity.class.getName(), payloadAfterFirst, TestWorkflowPayload.class).await(); + + ctx.complete(payloadAfterSecond); + }; + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestWorkflowPayload.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestWorkflowPayload.java new file mode 100644 index 0000000000..38e9e7f96f --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestWorkflowPayload.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 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.it.testcontainers; + +import java.util.List; + +public class TestWorkflowPayload { + private List payloads; + private String workflowId; + + public TestWorkflowPayload() { + } + + public TestWorkflowPayload(List payloads, String workflowId) { + this.payloads = payloads; + this.workflowId = workflowId; + } + + public TestWorkflowPayload(List payloads) { + this.payloads = payloads; + } + + public List getPayloads() { + return payloads; + } + + public void setPayloads(List payloads) { + this.payloads = payloads; + } + + public String getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(String workflowId) { + this.workflowId = workflowId; + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/tracing/OpenTelemetry.java b/sdk-tests/src/test/java/io/dapr/it/tracing/OpenTelemetry.java index bae97e7a61..08a6ea88f7 100644 --- a/sdk-tests/src/test/java/io/dapr/it/tracing/OpenTelemetry.java +++ b/sdk-tests/src/test/java/io/dapr/it/tracing/OpenTelemetry.java @@ -14,11 +14,10 @@ package io.dapr.it.tracing; import io.dapr.utils.NetworkUtils; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -41,11 +40,7 @@ public class OpenTelemetry { public static io.opentelemetry.api.OpenTelemetry createOpenTelemetry(String serviceName) throws InterruptedException { waitForZipkin(); String httpUrl = String.format("http://localhost:%d", ZIPKIN_PORT); - ZipkinSpanExporter zipkinExporter = - ZipkinSpanExporter.builder() - .setEndpoint(httpUrl + ENDPOINT_V2_SPANS) - .setServiceName(serviceName) - .build(); + ZipkinSpanExporter zipkinExporter = ZipkinSpanExporter.builder().setEndpoint(httpUrl + ENDPOINT_V2_SPANS).build(); SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() .addSpanProcessor(SimpleSpanProcessor.create(zipkinExporter)) @@ -54,15 +49,15 @@ public static io.opentelemetry.api.OpenTelemetry createOpenTelemetry(String serv return OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProvider) .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .buildAndRegisterGlobal(); + .build(); } /** * Converts current OpenTelemetry's context into Reactor's context. * @return Reactor's context. */ - public static reactor.util.context.Context getReactorContext() { - return getReactorContext(Context.current()); + public static reactor.util.context.Context getReactorContext(io.opentelemetry.api.OpenTelemetry openTelemetry) { + return getReactorContext(openTelemetry, Context.current()); } /** @@ -70,12 +65,12 @@ public static reactor.util.context.Context getReactorContext() { * @param context OpenTelemetry's context. * @return Reactor's context. */ - public static reactor.util.context.Context getReactorContext(Context context) { + public static reactor.util.context.Context getReactorContext(io.opentelemetry.api.OpenTelemetry openTelemetry, + Context context) { Map map = new HashMap<>(); - TextMapPropagator.Setter> setter = - (carrier, key, value) -> map.put(key, value); + TextMapSetter> setter = (carrier, key, value) -> map.put(key, value); - GlobalOpenTelemetry.getPropagators().getTextMapPropagator().inject(context, map, setter); + openTelemetry.getPropagators().getTextMapPropagator().inject(context, map, setter); reactor.util.context.Context reactorContext = reactor.util.context.Context.empty(); for (Map.Entry entry : map.entrySet()) { reactorContext = reactorContext.put(entry.getKey(), entry.getValue()); diff --git a/sdk-tests/src/test/java/io/dapr/it/tracing/grpc/TracingIT.java b/sdk-tests/src/test/java/io/dapr/it/tracing/grpc/TracingIT.java index e82aa248c6..76df400442 100644 --- a/sdk-tests/src/test/java/io/dapr/it/tracing/grpc/TracingIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/tracing/grpc/TracingIT.java @@ -1,19 +1,19 @@ package io.dapr.it.tracing.grpc; -import io.dapr.client.DaprApiProtocol; import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; import io.dapr.client.domain.HttpExtension; +import io.dapr.it.AppRun; import io.dapr.it.BaseIT; import io.dapr.it.DaprRun; import io.dapr.it.tracing.Validation; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.util.UUID; @@ -28,45 +28,36 @@ public class TracingIT extends BaseIT { */ private DaprRun daprRun = null; - public void setup(boolean useGrpc) throws Exception { + @BeforeEach + public void setup() throws Exception { daprRun = startDaprApp( TracingIT.class.getSimpleName() + "grpc", Service.SUCCESS_MESSAGE, Service.class, - DaprApiProtocol.GRPC, // appProtocol + AppRun.AppProtocol.GRPC, // appProtocol 60000); - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } - daprRun.waitForAppHealth(10000); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testInvoke(boolean useGrpc) throws Exception { - setup(useGrpc); - - final OpenTelemetry openTelemetry = createOpenTelemetry("service over grpc"); - final Tracer tracer = openTelemetry.getTracer("grpc integration test tracer"); - - final String spanName = UUID.randomUUID().toString(); - Span span = tracer.spanBuilder(spanName).setSpanKind(Span.Kind.CLIENT).startSpan(); + @Test + public void testInvoke() throws Exception { + OpenTelemetry openTelemetry = createOpenTelemetry("service over grpc"); + Tracer tracer = openTelemetry.getTracer("grpc integration test tracer"); + String spanName = UUID.randomUUID().toString(); + Span span = tracer.spanBuilder(spanName).setSpanKind(SpanKind.CLIENT).startSpan(); try (DaprClient client = new DaprClientBuilder().build()) { client.waitForSidecar(10000).block(); try (Scope scope = span.makeCurrent()) { SleepRequest req = SleepRequest.newBuilder().setSeconds(1).build(); client.invokeMethod(daprRun.getAppName(), "sleepOverGRPC", req.toByteArray(), HttpExtension.POST) - .contextWrite(getReactorContext()) + .contextWrite(getReactorContext(openTelemetry)) .block(); } } + span.end(); - OpenTelemetrySdk.getGlobalTracerManagement().shutdown(); Validation.validate(spanName, "calllocal/tracingitgrpc-service/sleepovergrpc"); } diff --git a/sdk-tests/src/test/java/io/dapr/it/tracing/http/OpenTelemetryInterceptor.java b/sdk-tests/src/test/java/io/dapr/it/tracing/http/OpenTelemetryInterceptor.java index 91bedea536..960dce888e 100644 --- a/sdk-tests/src/test/java/io/dapr/it/tracing/http/OpenTelemetryInterceptor.java +++ b/sdk-tests/src/test/java/io/dapr/it/tracing/http/OpenTelemetryInterceptor.java @@ -15,15 +15,16 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; -import jakarta.servlet.DispatcherType; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import java.util.Collections; @Component @@ -32,9 +33,9 @@ public class OpenTelemetryInterceptor implements HandlerInterceptor { @Autowired private OpenTelemetry openTelemetry; - // implmentation for springboot 3.0, which uses jakarta.servlet instead of javax.servlet - private static final TextMapPropagator.Getter JAKARTA_HTTP_SERVLET_REQUEST_GETTER = - new TextMapPropagator.Getter<>() { + // Implementation for springboot 3.0, which uses jakarta.servlet instead of javax.servlet + private static final TextMapGetter JAKARTA_HTTP_SERVLET_REQUEST_GETTER = + new TextMapGetter<>() { @Override public Iterable keys(HttpServletRequest carrier) { return Collections.list(carrier.getHeaderNames()); @@ -67,9 +68,9 @@ public void postHandle( } - // implmentation for springboot 3.0, which uses jakarta.servlet instead of javax.servlet - private static final TextMapPropagator.Getter JAVA_HTTP_SERVLET_REQUEST_GETTER = - new TextMapPropagator.Getter<>() { + // Implementation for springboot 3.0, which uses jakarta.servlet instead of javax.servlet + private static final TextMapGetter JAVA_HTTP_SERVLET_REQUEST_GETTER = + new TextMapGetter<>() { @Override public Iterable keys(javax.servlet.http.HttpServletRequest carrier) { return Collections.list(carrier.getHeaderNames()); diff --git a/sdk-tests/src/test/java/io/dapr/it/tracing/http/OpenTelemetryInterceptorConfig.java b/sdk-tests/src/test/java/io/dapr/it/tracing/http/OpenTelemetryInterceptorConfig.java index ce23430139..fe2913649e 100644 --- a/sdk-tests/src/test/java/io/dapr/it/tracing/http/OpenTelemetryInterceptorConfig.java +++ b/sdk-tests/src/test/java/io/dapr/it/tracing/http/OpenTelemetryInterceptorConfig.java @@ -28,4 +28,5 @@ public class OpenTelemetryInterceptorConfig extends WebMvcConfigurationSupport { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor); } -} \ No newline at end of file + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/tracing/http/TracingIT.java b/sdk-tests/src/test/java/io/dapr/it/tracing/http/TracingIT.java index 96b669ef78..a4c75d1403 100644 --- a/sdk-tests/src/test/java/io/dapr/it/tracing/http/TracingIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/tracing/http/TracingIT.java @@ -8,11 +8,11 @@ import io.dapr.it.tracing.Validation; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.util.UUID; @@ -26,7 +26,8 @@ public class TracingIT extends BaseIT { */ private DaprRun daprRun = null; - public void setup(boolean useGrpc) throws Exception { + @BeforeEach + public void setup() throws Exception { daprRun = startDaprApp( TracingIT.class.getSimpleName() + "http", Service.SUCCESS_MESSAGE, @@ -34,37 +35,27 @@ public void setup(boolean useGrpc) throws Exception { true, 30000); - if (useGrpc) { - daprRun.switchToGRPC(); - } else { - daprRun.switchToHTTP(); - } - // Wait since service might be ready even after port is available. Thread.sleep(2000); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testInvoke(boolean useGrpc) throws Exception { - setup(useGrpc); - - final OpenTelemetry openTelemetry = createOpenTelemetry(OpenTelemetryConfig.SERVICE_NAME); - final Tracer tracer = openTelemetry.getTracer(OpenTelemetryConfig.TRACER_NAME); - - final String spanName = UUID.randomUUID().toString(); - Span span = tracer.spanBuilder(spanName).setSpanKind(Span.Kind.CLIENT).startSpan(); + @Test + public void testInvoke() throws Exception { + OpenTelemetry openTelemetry = createOpenTelemetry(OpenTelemetryConfig.SERVICE_NAME); + Tracer tracer = openTelemetry.getTracer(OpenTelemetryConfig.TRACER_NAME); + String spanName = UUID.randomUUID().toString(); + Span span = tracer.spanBuilder(spanName).setSpanKind(SpanKind.CLIENT).startSpan(); try (DaprClient client = new DaprClientBuilder().build()) { client.waitForSidecar(10000).block(); try (Scope scope = span.makeCurrent()) { client.invokeMethod(daprRun.getAppName(), "sleep", 1, HttpExtension.POST) - .contextWrite(getReactorContext()) + .contextWrite(getReactorContext(openTelemetry)) .block(); } } + span.end(); - OpenTelemetrySdk.getGlobalTracerManagement().shutdown(); Validation.validate(spanName, "calllocal/tracingithttp-service/sleep"); } diff --git a/sdk-tests/src/test/resources/application.yml b/sdk-tests/src/test/resources/application.yml new file mode 100644 index 0000000000..a3e7b72706 --- /dev/null +++ b/sdk-tests/src/test/resources/application.yml @@ -0,0 +1 @@ +debug: true \ No newline at end of file diff --git a/sdk-tests/src/test/resources/query.json b/sdk-tests/src/test/resources/query.json new file mode 100644 index 0000000000..0a92e82cd6 --- /dev/null +++ b/sdk-tests/src/test/resources/query.json @@ -0,0 +1,11 @@ +{ + "filter": { + "EQ": { + "content": "test" + } + }, + "page": { + "limit": 0, + "token": "0" + } +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowInstanceStatus.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowInstanceStatus.java index dc61312ce2..85d80e9985 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowInstanceStatus.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowInstanceStatus.java @@ -149,8 +149,7 @@ public boolean isRunning() { * {@link WorkflowRuntimeStatus#FAILED}, or * {@link WorkflowRuntimeStatus#TERMINATED}. * - * @return {@code true} if the workflow was in a terminal state; otherwise - * {@code false} + * @return {@code true} if the workflow was in a terminal state; otherwise {@code false} */ public boolean isCompleted() { return orchestrationMetadata.isCompleted(); diff --git a/sdk/pom.xml b/sdk/pom.xml index 5f876e8367..7944954e5b 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -134,16 +134,6 @@ 1.7 test - - io.projectreactor - reactor-test - test - - - io.grpc - grpc-testing - test - io.grpc grpc-inprocess diff --git a/sdk/src/main/java/io/dapr/client/AbstractDaprClient.java b/sdk/src/main/java/io/dapr/client/AbstractDaprClient.java index 95d7ef37fd..61a7ad5c1e 100644 --- a/sdk/src/main/java/io/dapr/client/AbstractDaprClient.java +++ b/sdk/src/main/java/io/dapr/client/AbstractDaprClient.java @@ -59,7 +59,7 @@ * Abstract class with convenient methods common between client implementations. * * @see io.dapr.client.DaprClient - * @see io.dapr.client.DaprClientGrpc + * @see DaprClientImpl * @see io.dapr.client.DaprClientHttp */ abstract class AbstractDaprClient implements DaprClient, DaprPreviewClient { @@ -493,7 +493,11 @@ public Mono saveState(String storeName, String key, Object value) { */ @Override public Mono saveState(String storeName, String key, String etag, Object value, StateOptions options) { - State state = new State<>(key, value, etag, options); + Map meta = null; + if (value != null) { + meta = Collections.singletonMap("contentType", stateSerializer.getContentType()); + } + State state = new State<>(key, value, etag, meta, options); return this.saveBulkState(storeName, Collections.singletonList(state)); } diff --git a/sdk/src/main/java/io/dapr/client/DaprClient.java b/sdk/src/main/java/io/dapr/client/DaprClient.java index f625aee765..9b713f7c7e 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClient.java +++ b/sdk/src/main/java/io/dapr/client/DaprClient.java @@ -14,6 +14,7 @@ package io.dapr.client; import io.dapr.client.domain.ConfigurationItem; +import io.dapr.client.domain.DaprMetadata; import io.dapr.client.domain.DeleteStateRequest; import io.dapr.client.domain.ExecuteStateTransactionRequest; import io.dapr.client.domain.GetBulkSecretRequest; @@ -34,11 +35,14 @@ import io.dapr.client.domain.UnsubscribeConfigurationRequest; import io.dapr.client.domain.UnsubscribeConfigurationResponse; import io.dapr.utils.TypeRef; +import io.grpc.Channel; +import io.grpc.stub.AbstractStub; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; import java.util.Map; +import java.util.function.Function; /** * Generic Client Adapter to be used regardless of the GRPC or the HTTP Client implementation required. @@ -659,6 +663,22 @@ Flux subscribeConfiguration(String storeName, Li */ Mono unsubscribeConfiguration(UnsubscribeConfigurationRequest request); + /** + * Returns a newly created gRPC stub with proper interceptors and channel for gRPC proxy invocation. + * @param appId appId to be included in all gRPC calls for service invocation. + * @param stubBuilder user-provided callback method to construct a new stub given the channel. + * @return the gRPC stub with proper interceptors and channel. + * @param the generic type of the service to be invoked. + */ + > T newGrpcStub(String appId, Function stubBuilder); + + /** + * Fetches Dapr Metadata from the metadata endpoint. + * + * @return DaprMetadata containing Dapr Metadata from the metadata endpoint. + */ + Mono getMetadata(); + /** * Gracefully shutdown the dapr runtime. * diff --git a/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java b/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java index dc67d019c8..f57f122d2b 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java @@ -14,14 +14,11 @@ package io.dapr.client; import io.dapr.client.resiliency.ResiliencyOptions; -import io.dapr.config.Properties; import io.dapr.serializer.DaprObjectSerializer; import io.dapr.serializer.DefaultObjectSerializer; import io.dapr.utils.NetworkUtils; import io.dapr.v1.DaprGrpc; import io.grpc.ManagedChannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * A builder for the DaprClient, @@ -29,18 +26,6 @@ */ public class DaprClientBuilder { - private static final Logger LOGGER = LoggerFactory.getLogger(DaprClientBuilder.class); - - /** - * Determine if this builder will create GRPC clients instead of HTTP clients. - */ - private final DaprApiProtocol apiProtocol; - - /** - * Determine if this builder will use HTTP client for service method invocation APIs. - */ - private final DaprApiProtocol methodInvocationApiProtocol; - /** * Builder for Dapr's HTTP Client. */ @@ -70,8 +55,6 @@ public class DaprClientBuilder { public DaprClientBuilder() { this.objectSerializer = new DefaultObjectSerializer(); this.stateSerializer = new DefaultObjectSerializer(); - this.apiProtocol = Properties.API_PROTOCOL.get(); - this.methodInvocationApiProtocol = Properties.API_METHOD_INVOCATION_PROTOCOL.get(); this.daprHttpBuilder = new DaprHttpBuilder(); } @@ -129,15 +112,7 @@ public DaprClientBuilder withResiliencyOptions(ResiliencyOptions options) { * @throws java.lang.IllegalStateException if any required field is missing */ public DaprClient build() { - if (this.apiProtocol == DaprApiProtocol.HTTP) { - LOGGER.warn("HTTP client protocol is deprecated and will be removed in Dapr's Java SDK version 1.10."); - } - - if (this.apiProtocol != this.methodInvocationApiProtocol) { - return new DaprClientProxy(buildDaprClient(this.apiProtocol), buildDaprClient(this.methodInvocationApiProtocol)); - } - - return buildDaprClient(this.apiProtocol); + return buildDaprClient(); } /** @@ -147,26 +122,7 @@ public DaprClient build() { * @throws IllegalStateException if any required field is missing */ public DaprPreviewClient buildPreviewClient() { - return (DaprPreviewClient) buildDaprClient(this.apiProtocol); - } - - /** - * Creates an instance of a Dapr Client based on the chosen protocol. - * - * @param protocol Dapr API's protocol. - * @return the GRPC Client. - * @throws java.lang.IllegalStateException if either host is missing or if port is missing or a negative number. - */ - private DaprClient buildDaprClient(DaprApiProtocol protocol) { - if (protocol == null) { - throw new IllegalStateException("Protocol is required."); - } - - switch (protocol) { - case GRPC: return buildDaprClientGrpc(); - case HTTP: return buildDaprClientHttp(); - default: throw new IllegalStateException("Unsupported protocol: " + protocol.name()); - } + return buildDaprClient(); } /** @@ -175,25 +131,17 @@ private DaprClient buildDaprClient(DaprApiProtocol protocol) { * @return the GRPC Client. * @throws java.lang.IllegalStateException if either host is missing or if port is missing or a negative number. */ - private DaprClient buildDaprClientGrpc() { + private DaprClientImpl buildDaprClient() { final ManagedChannel channel = NetworkUtils.buildGrpcManagedChannel(); - final GrpcChannelFacade channelFacade = new GrpcChannelFacade(channel, this.daprHttpBuilder.build()); + final DaprHttp daprHttp = this.daprHttpBuilder.build(); + final GrpcChannelFacade channelFacade = new GrpcChannelFacade(channel); DaprGrpc.DaprStub asyncStub = DaprGrpc.newStub(channel); - return new DaprClientGrpc( + return new DaprClientImpl( channelFacade, asyncStub, + daprHttp, this.objectSerializer, this.stateSerializer, this.resiliencyOptions); } - - /** - * Creates and instance of DaprClient over HTTP. - * - * @return DaprClient over HTTP. - */ - private DaprClient buildDaprClientHttp() { - return new DaprClientHttp(this.daprHttpBuilder.build(), this.objectSerializer, this.stateSerializer); - } - } diff --git a/sdk/src/main/java/io/dapr/client/DaprClientHttp.java b/sdk/src/main/java/io/dapr/client/DaprClientHttp.java deleted file mode 100644 index f9a01f9845..0000000000 --- a/sdk/src/main/java/io/dapr/client/DaprClientHttp.java +++ /dev/null @@ -1,1069 +0,0 @@ -/* - * Copyright 2021 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ - -package io.dapr.client; - -import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.base.Strings; -import io.dapr.client.domain.BulkPublishRequest; -import io.dapr.client.domain.BulkPublishResponse; -import io.dapr.client.domain.ConfigurationItem; -import io.dapr.client.domain.DeleteStateRequest; -import io.dapr.client.domain.ExecuteStateTransactionRequest; -import io.dapr.client.domain.GetBulkSecretRequest; -import io.dapr.client.domain.GetBulkStateRequest; -import io.dapr.client.domain.GetConfigurationRequest; -import io.dapr.client.domain.GetSecretRequest; -import io.dapr.client.domain.GetStateRequest; -import io.dapr.client.domain.HttpExtension; -import io.dapr.client.domain.InvokeBindingRequest; -import io.dapr.client.domain.InvokeMethodRequest; -import io.dapr.client.domain.LockRequest; -import io.dapr.client.domain.Metadata; -import io.dapr.client.domain.PublishEventRequest; -import io.dapr.client.domain.QueryStateItem; -import io.dapr.client.domain.QueryStateRequest; -import io.dapr.client.domain.QueryStateResponse; -import io.dapr.client.domain.SaveStateRequest; -import io.dapr.client.domain.State; -import io.dapr.client.domain.StateOptions; -import io.dapr.client.domain.SubscribeConfigurationRequest; -import io.dapr.client.domain.SubscribeConfigurationResponse; -import io.dapr.client.domain.TransactionalStateOperation; -import io.dapr.client.domain.TransactionalStateRequest; -import io.dapr.client.domain.UnlockRequest; -import io.dapr.client.domain.UnlockResponseStatus; -import io.dapr.client.domain.UnsubscribeConfigurationRequest; -import io.dapr.client.domain.UnsubscribeConfigurationResponse; -import io.dapr.config.Properties; -import io.dapr.exceptions.DaprException; -import io.dapr.serializer.DaprObjectSerializer; -import io.dapr.serializer.DefaultObjectSerializer; -import io.dapr.utils.NetworkUtils; -import io.dapr.utils.TypeRef; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.util.retry.Retry; - -import java.io.IOException; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - - -/** - * An adapter for the HTTP Client. - * @deprecated This class will be deleted at SDK release version 1.10. - * @see io.dapr.client.DaprHttp - * @see io.dapr.client.DaprClient - */ -@Deprecated -public class DaprClientHttp extends AbstractDaprClient { - /** - * Header for the conditional operation. - */ - private static final String HEADER_HTTP_ETAG_ID = "If-Match"; - - /** - * Metadata prefix in query params. - */ - private static final String METADATA_PREFIX = "metadata."; - - /** - * Serializer for internal objects. - */ - private static final ObjectSerializer INTERNAL_SERIALIZER = new ObjectSerializer(); - - /** - * The HTTP client to be used. - * - * @see io.dapr.client.DaprHttp - */ - private final DaprHttp client; - - /** - * Flag determining if object serializer's input and output is Dapr's default instead of user provided. - */ - private final boolean isObjectSerializerDefault; - - /** - * Flag determining if state serializer is the default serializer instead of user provided. - */ - private final boolean isStateSerializerDefault; - - /** - * Default access level constructor, in order to create an instance of this class use io.dapr.client.DaprClientBuilder - * - * @param client Dapr's http client. - * @param objectSerializer Dapr's serializer for transient request/response objects. - * @param stateSerializer Dapr's serializer for state objects. - * @see DaprClientBuilder - * @see DefaultObjectSerializer - */ - DaprClientHttp(DaprHttp client, DaprObjectSerializer objectSerializer, DaprObjectSerializer stateSerializer) { - super(objectSerializer, stateSerializer); - this.client = client; - this.isObjectSerializerDefault = objectSerializer.getClass() == DefaultObjectSerializer.class; - this.isStateSerializerDefault = stateSerializer.getClass() == DefaultObjectSerializer.class; - } - - /** - * Constructor useful for tests. - * - * @param client Dapr's http client. - * @see io.dapr.client.DaprClientBuilder - * @see DefaultObjectSerializer - */ - DaprClientHttp(DaprHttp client) { - this(client, new DefaultObjectSerializer(), new DefaultObjectSerializer()); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono waitForSidecar(int timeoutInMilliseconds) { - return Mono.defer(() -> { - String[] pathSegments = new String[] { DaprHttp.API_VERSION, "healthz", "outbound"}; - int maxRetries = 5; - - Retry retrySpec = Retry - .fixedDelay(maxRetries, Duration.ofMillis(500)) - .doBeforeRetry(retrySignal -> { - System.out.println("Retrying component health check..."); - }); - - Mono responseMono = this.client.invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, - null, "", null, null); - - return responseMono - .retryWhen(retrySpec) - .timeout(Duration.ofMillis(timeoutInMilliseconds)) - .onErrorResume(DaprException.class, e -> - Mono.error(new RuntimeException(e))) - .switchIfEmpty(DaprException.wrapMono(new RuntimeException("Health check timed out"))) - .then(); - }); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono publishEvent(PublishEventRequest request) { - try { - String pubsubName = request.getPubsubName(); - String topic = request.getTopic(); - - if (pubsubName == null || pubsubName.trim().isEmpty()) { - throw new IllegalArgumentException("Pubsub name cannot be null or empty."); - } - - if (topic == null || topic.trim().isEmpty()) { - throw new IllegalArgumentException("Topic name cannot be null or empty."); - } - - Object data = request.getData(); - Map metadata = request.getMetadata(); - - byte[] serializedEvent = objectSerializer.serialize(data); - // Content-type can be overwritten on a per-request basis. - // It allows CloudEvents to be handled differently, for example. - String contentType = request.getContentType(); - if (contentType == null || contentType.isEmpty()) { - contentType = objectSerializer.getContentType(); - } - Map headers = Collections.singletonMap("content-type", contentType); - - String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "publish", pubsubName, topic }; - - Map> queryArgs = metadataToQueryArgs(metadata); - return Mono.deferContextual( - context -> this.client.invokeApi( - DaprHttp.HttpMethods.POST.name(), pathSegments, queryArgs, serializedEvent, headers, context - ) - ).then(); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> publishEvents(BulkPublishRequest request) { - return DaprException.wrapMono(new UnsupportedOperationException()); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(InvokeMethodRequest invokeMethodRequest, TypeRef type) { - try { - final String appId = invokeMethodRequest.getAppId(); - final String method = invokeMethodRequest.getMethod(); - final Object request = invokeMethodRequest.getBody(); - final HttpExtension httpExtension = invokeMethodRequest.getHttpExtension(); - final String contentType = invokeMethodRequest.getContentType(); - final Map metadata = invokeMethodRequest.getMetadata(); - - if (httpExtension == null) { - throw new IllegalArgumentException("HttpExtension cannot be null. Use HttpExtension.NONE instead."); - } - // If the httpExtension is not null, then the method will not be null based on checks in constructor - final String httpMethod = httpExtension.getMethod().toString(); - if (appId == null || appId.trim().isEmpty()) { - throw new IllegalArgumentException("App Id cannot be null or empty."); - } - if (method == null || method.trim().isEmpty()) { - throw new IllegalArgumentException("Method name cannot be null or empty."); - } - - - String[] methodSegments = method.split("/"); - - List pathSegments = new ArrayList<>(Arrays.asList(DaprHttp.API_VERSION, "invoke", appId, "method")); - pathSegments.addAll(Arrays.asList(methodSegments)); - - final Map headers = new HashMap<>(); - headers.putAll(httpExtension.getHeaders()); - if (metadata != null) { - headers.putAll(metadata); - } - byte[] serializedRequestBody = objectSerializer.serialize(request); - if (contentType != null && !contentType.isEmpty()) { - headers.put(Metadata.CONTENT_TYPE, contentType); - } else { - headers.put(Metadata.CONTENT_TYPE, objectSerializer.getContentType()); - } - Mono response = Mono.deferContextual( - context -> this.client.invokeApi(httpMethod, pathSegments.toArray(new String[0]), - httpExtension.getQueryParams(), serializedRequestBody, headers, context) - ); - return response.flatMap(r -> getMono(type, r)); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - private Mono getMono(TypeRef type, DaprHttp.Response r) { - try { - T object = objectSerializer.deserialize(r.getBody(), type); - if (object == null) { - return Mono.empty(); - } - - return Mono.just(object); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeBinding(InvokeBindingRequest request, TypeRef type) { - try { - final String name = request.getName(); - final String operation = request.getOperation(); - final Object data = request.getData(); - final Map metadata = request.getMetadata(); - if (name == null || name.trim().isEmpty()) { - throw new IllegalArgumentException("Binding name cannot be null or empty."); - } - - if (operation == null || operation.trim().isEmpty()) { - throw new IllegalArgumentException("Binding operation cannot be null or empty."); - } - - Map jsonMap = new HashMap<>(); - jsonMap.put("operation", operation); - if (metadata != null) { - jsonMap.put("metadata", metadata); - } - - if (data != null) { - if (this.isObjectSerializerDefault) { - // If we are using Dapr's default serializer, we pass the object directly and skip objectSerializer. - // This allows binding to receive JSON directly without having to extract it from a quoted string. - // Example of output binding vs body in the input binding: - // This logic DOES this: - // Output Binding: { "data" : { "mykey": "myvalue" } } - // Input Binding: { "mykey": "myvalue" } - // This logic AVOIDS this: - // Output Binding: { "data" : "{ \"mykey\": \"myvalue\" }" } - // Input Binding: "{ \"mykey\": \"myvalue\" }" - jsonMap.put("data", data); - } else { - // When customer provides a custom serializer, he will get a Base64 encoded String back - always. - // Example of body in the input binding resulting from this logic: - // { "data" : "eyJrZXkiOiAidmFsdWUifQ==" } - jsonMap.put("data", objectSerializer.serialize(data)); - } - } - - byte[] payload = INTERNAL_SERIALIZER.serialize(jsonMap); - String httpMethod = DaprHttp.HttpMethods.POST.name(); - - String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "bindings", name }; - - Mono response = Mono.deferContextual( - context -> this.client.invokeApi( - httpMethod, pathSegments, null, payload, null, context) - ); - return response.flatMap(r -> getMono(type, r)); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Mono>> getBulkState(GetBulkStateRequest request, TypeRef type) { - try { - final String stateStoreName = request.getStoreName(); - final List keys = request.getKeys(); - final int parallelism = request.getParallelism(); - final Map metadata = request.getMetadata(); - if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("State store name cannot be null or empty."); - } - if (keys == null || keys.isEmpty()) { - throw new IllegalArgumentException("Key cannot be null or empty."); - } - - if (parallelism < 0) { - throw new IllegalArgumentException("Parallelism cannot be negative."); - } - - Map jsonMap = new HashMap<>(); - jsonMap.put("keys", keys); - jsonMap.put("parallelism", parallelism); - - byte[] requestBody = INTERNAL_SERIALIZER.serialize(jsonMap); - - String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, "bulk" }; - - Map> queryArgs = metadataToQueryArgs(metadata); - return Mono.deferContextual( - context -> this.client - .invokeApi(DaprHttp.HttpMethods.POST.name(), pathSegments, queryArgs, requestBody, null, context) - ).flatMap(s -> { - try { - return Mono.just(buildStates(s, type)); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - }); - - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public Mono> getState(GetStateRequest request, TypeRef type) { - try { - final String stateStoreName = request.getStoreName(); - final String key = request.getKey(); - final StateOptions options = request.getStateOptions(); - final Map metadata = request.getMetadata(); - - if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("State store name cannot be null or empty."); - } - if ((key == null) || (key.trim().isEmpty())) { - throw new IllegalArgumentException("Key cannot be null or empty."); - } - Map optionsMap = Optional.ofNullable(options) - .map(o -> o.getStateOptionsAsMap()) - .orElse(Collections.emptyMap()); - - final Map> queryParams = new HashMap<>(); - queryParams.putAll(metadataToQueryArgs(metadata)); - queryParams.putAll(optionsMap.entrySet().stream().collect( - Collectors.toMap(kv -> kv.getKey(), kv -> Collections.singletonList(kv.getValue())))); - - String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, key }; - - return Mono.deferContextual( - context -> this.client - .invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, queryParams, null, context) - ).flatMap(s -> { - try { - return Mono.justOrEmpty(buildState(s, key, options, type)); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - }); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Mono executeStateTransaction(ExecuteStateTransactionRequest request) { - try { - final String stateStoreName = request.getStateStoreName(); - final List> operations = request.getOperations(); - final Map metadata = request.getMetadata(); - if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("State store name cannot be null or empty."); - } - if (operations == null || operations.isEmpty()) { - return Mono.empty(); - } - - List> internalOperationObjects = new ArrayList<>(operations.size()); - for (TransactionalStateOperation operation : operations) { - if (operation == null) { - continue; - } - State state = operation.getRequest(); - if (state == null) { - continue; - } - if (this.isStateSerializerDefault) { - // If default serializer is being used, we just pass the object through to be serialized directly. - // This avoids a JSON object from being quoted inside a string. - // We WANT this: { "value" : { "myField" : 123 } } - // We DON't WANT this: { "value" : "{ \"myField\" : 123 }" } - internalOperationObjects.add(operation); - continue; - } - byte[] data = this.stateSerializer.serialize(state.getValue()); - // Custom serializer, so everything is byte[]. - internalOperationObjects.add(new TransactionalStateOperation<>(operation.getOperation(), - new State<>(state.getKey(), data, state.getEtag(), state.getMetadata(), state.getOptions()))); - } - TransactionalStateRequest req = new TransactionalStateRequest<>(internalOperationObjects, metadata); - byte[] serializedOperationBody = INTERNAL_SERIALIZER.serialize(req); - - String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, "transaction" }; - - return Mono.deferContextual( - context -> this.client.invokeApi( - DaprHttp.HttpMethods.POST.name(), pathSegments, null, serializedOperationBody, null, context - ) - ).then(); - } catch (Exception e) { - return DaprException.wrapMono(e); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Mono saveBulkState(SaveStateRequest request) { - try { - final String stateStoreName = request.getStoreName(); - final List> states = request.getStates(); - if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("State store name cannot be null or empty."); - } - if (states == null || states.isEmpty()) { - return Mono.empty(); - } - - List> internalStateObjects = new ArrayList<>(states.size()); - for (State state : states) { - if (state == null) { - continue; - } - if (this.isStateSerializerDefault) { - // If default serializer is being used, we just pass the object through to be serialized directly. - // This avoids a JSON object from being quoted inside a string. - // We WANT this: { "value" : { "myField" : 123 } } - // We DON't WANT this: { "value" : "{ \"myField\" : 123 }" } - internalStateObjects.add(state); - continue; - } - - byte[] data = this.stateSerializer.serialize(state.getValue()); - // Custom serializer, so everything is byte[]. - internalStateObjects.add(new State<>(state.getKey(), data, state.getEtag(), state.getMetadata(), - state.getOptions())); - } - byte[] serializedStateBody = INTERNAL_SERIALIZER.serialize(internalStateObjects); - - String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName }; - - return Mono.deferContextual( - context -> this.client.invokeApi( - DaprHttp.HttpMethods.POST.name(), pathSegments, null, serializedStateBody, null, context) - ).then(); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Mono deleteState(DeleteStateRequest request) { - try { - final String stateStoreName = request.getStateStoreName(); - final String key = request.getKey(); - final StateOptions options = request.getStateOptions(); - final String etag = request.getEtag(); - final Map metadata = request.getMetadata(); - - if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("State store name cannot be null or empty."); - } - if ((key == null) || (key.trim().isEmpty())) { - throw new IllegalArgumentException("Key cannot be null or empty."); - } - Map headers = new HashMap<>(); - if (etag != null && !etag.trim().isEmpty()) { - headers.put(HEADER_HTTP_ETAG_ID, etag); - } - - Map optionsMap = Optional.ofNullable(options) - .map(o -> o.getStateOptionsAsMap()) - .orElse(Collections.emptyMap()); - - final Map> queryParams = new HashMap<>(); - queryParams.putAll(metadataToQueryArgs(metadata)); - queryParams.putAll(optionsMap.entrySet().stream().collect( - Collectors.toMap(kv -> kv.getKey(), kv -> Collections.singletonList(kv.getValue())))); - - String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "state", stateStoreName, key }; - - return Mono.deferContextual( - context -> this.client.invokeApi( - DaprHttp.HttpMethods.DELETE.name(), pathSegments, queryParams, headers, context) - ).then(); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - /** - * Builds a State object based on the Response. - * - * @param response The response of the HTTP Call - * @param requestedKey The Key Requested. - * @param type The Class of the Value of the state - * @param The Type of the Value of the state - * @return A State instance - * @throws IOException If there's a issue deserializing the response. - */ - private State buildState( - DaprHttp.Response response, String requestedKey, StateOptions stateOptions, TypeRef type) throws IOException { - // The state is in the body directly, so we use the state serializer here. - T value = stateSerializer.deserialize(response.getBody(), type); - String etag = null; - if (response.getHeaders() != null && response.getHeaders().containsKey("Etag")) { - etag = response.getHeaders().get("Etag"); - } - return new State<>(requestedKey, value, etag, Collections.emptyMap(), stateOptions); - } - - /** - * Builds a State object based on the Response. - * - * @param response The response of the HTTP Call - * @param type The Class of the Value of the state - * @param The Type of the Value of the state - * @return A list of states. - * @throws IOException If there's a issue deserializing the response. - */ - private List> buildStates( - DaprHttp.Response response, TypeRef type) throws IOException { - JsonNode root = INTERNAL_SERIALIZER.parseNode(response.getBody()); - List> result = new ArrayList<>(); - for (Iterator it = root.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 State<>(key, 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 State<>(key, value, etag)); - } - - return result; - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getSecret(GetSecretRequest request) { - String secretStoreName = request.getStoreName(); - String key = request.getKey(); - Map metadata = request.getMetadata(); - try { - if ((secretStoreName == null) || (secretStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("Secret store name cannot be null or empty."); - } - if ((key == null) || (key.trim().isEmpty())) { - throw new IllegalArgumentException("Secret key cannot be null or empty."); - } - } catch (Exception e) { - return DaprException.wrapMono(e); - } - - Map> queryArgs = metadataToQueryArgs(metadata); - String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "secrets", secretStoreName, key }; - - return Mono.deferContextual( - context -> this.client - .invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, queryArgs, (String) null, null, context) - ).flatMap(response -> { - try { - Map m = INTERNAL_SERIALIZER.deserialize(response.getBody(), Map.class); - if (m == null) { - return Mono.just(Collections.EMPTY_MAP); - } - - return Mono.just(m); - } catch (IOException e) { - return DaprException.wrapMono(e); - } - }) - .map(m -> (Map) m); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono>> getBulkSecret(GetBulkSecretRequest request) { - String secretStoreName = request.getStoreName(); - Map metadata = request.getMetadata(); - try { - if ((secretStoreName == null) || (secretStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("Secret store name cannot be null or empty."); - } - } catch (Exception e) { - return DaprException.wrapMono(e); - } - - Map> queryArgs = metadataToQueryArgs(metadata); - String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "secrets", secretStoreName, "bulk" }; - - return Mono.deferContextual( - context -> this.client - .invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, queryArgs, (String) null, null, context) - ).flatMap(response -> { - try { - Map m = INTERNAL_SERIALIZER.deserialize(response.getBody(), Map.class); - if (m == null) { - return Mono.just(Collections.EMPTY_MAP); - } - - return Mono.just(m); - } catch (IOException e) { - return DaprException.wrapMono(e); - } - }) - .map(m -> (Map>) m); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono tryLock(LockRequest request) { - try { - final String stateStoreName = request.getStoreName(); - final String resourceId = request.getResourceId(); - final String lockOwner = request.getLockOwner(); - final Integer expiryInSeconds = request.getExpiryInSeconds(); - - if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("State store name cannot be null or empty."); - } - if (resourceId == null || resourceId.isEmpty()) { - throw new IllegalArgumentException("ResourceId cannot be null or empty."); - } - if (lockOwner == null || lockOwner.isEmpty()) { - throw new IllegalArgumentException("LockOwner cannot be null or empty."); - } - if (expiryInSeconds < 0) { - throw new IllegalArgumentException("ExpiryInSeconds cannot be negative."); - } - - byte[] requestBody = INTERNAL_SERIALIZER.serialize(request); - - String[] pathSegments = new String[]{DaprHttp.ALPHA_1_API_VERSION, "lock", stateStoreName}; - - return Mono.deferContextual( - context -> this.client - .invokeApi(DaprHttp.HttpMethods.POST.name(), pathSegments, null, requestBody, null, context) - ).flatMap(response -> { - try { - Map m = INTERNAL_SERIALIZER.deserialize(response.getBody(), Map.class); - if (m == null) { - return Mono.just(Boolean.FALSE); - } - return Mono.just((Boolean) m.get("success")); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - }); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Mono unlock(UnlockRequest request) { - try { - final String stateStoreName = request.getStoreName(); - final String resourceId = request.getResourceId(); - final String lockOwner = request.getLockOwner(); - - if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("State store name cannot be null or empty."); - } - if (resourceId == null || resourceId.isEmpty()) { - throw new IllegalArgumentException("ResourceId cannot be null or empty."); - } - if (lockOwner == null || lockOwner.isEmpty()) { - throw new IllegalArgumentException("LockOwner cannot be null or empty."); - } - - byte[] requestBody = INTERNAL_SERIALIZER.serialize(request); - - String[] pathSegments = new String[]{DaprHttp.ALPHA_1_API_VERSION, "unlock", stateStoreName}; - - return Mono.deferContextual( - context -> this.client - .invokeApi(DaprHttp.HttpMethods.POST.name(), pathSegments, null, requestBody, null, context) - ).flatMap(response -> { - try { - Map m = INTERNAL_SERIALIZER.deserialize(response.getBody(), Map.class); - if (m == null) { - return Mono.just(UnlockResponseStatus.INTERNAL_ERROR); - } - - Integer statusCode = (Integer) m.get("status"); - - return Mono.just(UnlockResponseStatus.valueOf(statusCode)); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - }); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> queryState(QueryStateRequest request, TypeRef type) { - try { - if (request == null) { - throw new IllegalArgumentException("Query state request cannot be null."); - } - String stateStoreName = request.getStoreName(); - Map metadata = request.getMetadata(); - if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("State store name cannot be null or empty."); - } - Map> 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.deferContextual( - 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} - */ - @Override - public void close() { - client.close(); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono shutdown() { - String[] pathSegments = new String[]{ DaprHttp.API_VERSION, "shutdown" }; - return Mono.deferContextual( - context -> client.invokeApi(DaprHttp.HttpMethods.POST.name(), pathSegments, - null, null, context)) - .then(); - } - - private QueryStateResponse buildQueryStateResponse(DaprHttp.Response response, - TypeRef 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 metadata = new HashMap<>(); - if (root.has("metadata")) { - for (Iterator> it = root.get("metadata").fields(); it.hasNext(); ) { - Map.Entry entry = it.next(); - metadata.put(entry.getKey(), entry.getValue().asText()); - } - } - List> result = new ArrayList<>(); - for (Iterator 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} - */ - @Override - public Mono> getConfiguration(GetConfigurationRequest request) { - try { - final String configurationStoreName = request.getStoreName(); - final List keys = request.getKeys(); - final Map metadata = request.getMetadata(); - - if ((configurationStoreName == null) || (configurationStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("Configuration Store Name cannot be null or empty."); - } - - Map> queryParams = new HashMap<>(); - if (!keys.isEmpty()) { - queryParams.put("key", Collections.unmodifiableList(keys)); - } - - // Appending passed metadata too into queryparams - Map> queryArgs = metadataToQueryArgs(metadata); - queryParams.putAll(queryArgs); - - String[] pathSegments = new String[] {DaprHttp.API_VERSION, "configuration", configurationStoreName }; - return Mono.deferContextual( - context -> this.client - .invokeApi( - DaprHttp.HttpMethods.GET.name(), - pathSegments, queryParams, - (String) null, null, context) - ).map( - response -> { - try { - Map m = INTERNAL_SERIALIZER.deserialize(response.getBody(), Map.class); - Set set = m.keySet(); - JsonNode root = INTERNAL_SERIALIZER.parseNode(response.getBody()); - Iterator itr = set.iterator(); - Map result = new HashMap<>(); - while (itr.hasNext()) { - String key = itr.next(); - String value = root.get(key).path("value").asText(); - String version = root.get(key).path("version").asText(); - result.put(key, new ConfigurationItem( - key, - value, - version, - new HashMap<>() - )); - } - return Collections.unmodifiableMap(result); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - ); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Flux subscribeConfiguration(SubscribeConfigurationRequest request) { - try { - final String configurationStoreName = request.getStoreName(); - final List keys = request.getKeys(); - final Map metadata = request.getMetadata(); - - if (configurationStoreName == null || (configurationStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("Configuration Store Name can not be null or empty."); - } - - Map> queryParams = new HashMap<>(); - if (!keys.isEmpty()) { - queryParams.put("key", Collections.unmodifiableList(keys)); - } - - // Appending passed metadata too into queryparams - Map> queryArgs = metadataToQueryArgs(metadata); - queryParams.putAll(queryArgs); - - String[] pathSegments = - new String[] { DaprHttp.API_VERSION, "configuration", configurationStoreName, "subscribe" }; - SubscribeConfigurationResponse res = Mono.deferContextual( - context -> this.client.invokeApi( - DaprHttp.HttpMethods.GET.name(), - pathSegments, queryParams, - (String) null, null, context - ) - ).map(response -> { - try { - JsonNode root = INTERNAL_SERIALIZER.parseNode(response.getBody()); - String subscriptionId = root.path("id").asText(); - return new SubscribeConfigurationResponse(subscriptionId, new HashMap<>()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }).block(); - if (res != null) { - return Flux.just(res); - } - return Flux.empty(); - } catch (Exception ex) { - return DaprException.wrapFlux(ex); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Mono unsubscribeConfiguration(UnsubscribeConfigurationRequest request) { - try { - final String id = request.getSubscriptionId(); - final String configStoreName = request.getStoreName(); - if (configStoreName == null || (configStoreName.trim().isEmpty())) { - throw new IllegalArgumentException("Configuration Store Name can not be null or empty."); - } - if (id.isEmpty()) { - throw new IllegalArgumentException("Subscription id can not be null or empty."); - } - - String[] pathSegments = new String[] - { DaprHttp.API_VERSION, "configuration", configStoreName, id, "unsubscribe" }; - - return Mono.deferContextual( - context -> this.client - .invokeApi( - DaprHttp.HttpMethods.GET.name(), - pathSegments, null, - (String) null, null, context) - ).map( - response -> { - JsonNode root = null; - try { - root = INTERNAL_SERIALIZER.parseNode(response.getBody()); - } catch (IOException e) { - throw new RuntimeException(e); - } - boolean ok = root.path("ok").asBoolean(); - String message = root.path("message").asText(); - return new UnsubscribeConfigurationResponse(ok, message); - } - ); - } catch (Exception ex) { - return DaprException.wrapMono(ex); - } - } - - /** - * Converts metadata map into Query params. - * - * @param metadata metadata map - * @return Query params - */ - private static Map> metadataToQueryArgs(Map metadata) { - if (metadata == null) { - return Collections.emptyMap(); - } - - return metadata - .entrySet() - .stream() - .filter(e -> e.getKey() != null) - .collect(Collectors.toMap(e -> METADATA_PREFIX + e.getKey(), e -> Collections.singletonList(e.getValue()))); - } -} diff --git a/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java b/sdk/src/main/java/io/dapr/client/DaprClientImpl.java similarity index 76% rename from sdk/src/main/java/io/dapr/client/DaprClientGrpc.java rename to sdk/src/main/java/io/dapr/client/DaprClientImpl.java index eb9c045fc7..aabc07a60f 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientImpl.java @@ -14,14 +14,18 @@ package io.dapr.client; import com.google.common.base.Strings; -import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.Empty; +import io.dapr.client.domain.ActorMetadata; +import io.dapr.client.domain.AppConnectionPropertiesHealthMetadata; +import io.dapr.client.domain.AppConnectionPropertiesMetadata; import io.dapr.client.domain.BulkPublishEntry; import io.dapr.client.domain.BulkPublishRequest; import io.dapr.client.domain.BulkPublishResponse; import io.dapr.client.domain.BulkPublishResponseFailedEntry; +import io.dapr.client.domain.ComponentMetadata; import io.dapr.client.domain.ConfigurationItem; +import io.dapr.client.domain.DaprMetadata; import io.dapr.client.domain.DeleteStateRequest; import io.dapr.client.domain.ExecuteStateTransactionRequest; import io.dapr.client.domain.GetBulkSecretRequest; @@ -29,6 +33,7 @@ import io.dapr.client.domain.GetConfigurationRequest; import io.dapr.client.domain.GetSecretRequest; import io.dapr.client.domain.GetStateRequest; +import io.dapr.client.domain.HttpEndpointMetadata; import io.dapr.client.domain.HttpExtension; import io.dapr.client.domain.InvokeBindingRequest; import io.dapr.client.domain.InvokeMethodRequest; @@ -37,20 +42,21 @@ import io.dapr.client.domain.QueryStateItem; import io.dapr.client.domain.QueryStateRequest; import io.dapr.client.domain.QueryStateResponse; +import io.dapr.client.domain.RuleMetadata; import io.dapr.client.domain.SaveStateRequest; import io.dapr.client.domain.State; import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.SubscribeConfigurationRequest; import io.dapr.client.domain.SubscribeConfigurationResponse; +import io.dapr.client.domain.SubscriptionMetadata; import io.dapr.client.domain.TransactionalStateOperation; import io.dapr.client.domain.UnlockRequest; import io.dapr.client.domain.UnlockResponseStatus; import io.dapr.client.domain.UnsubscribeConfigurationRequest; import io.dapr.client.domain.UnsubscribeConfigurationResponse; import io.dapr.client.resiliency.ResiliencyOptions; -import io.dapr.config.Properties; import io.dapr.exceptions.DaprException; -import io.dapr.internal.opencensus.GrpcWrapper; +import io.dapr.internal.grpc.DaprClientGrpcInterceptors; import io.dapr.internal.resiliency.RetryPolicy; import io.dapr.internal.resiliency.TimeoutPolicy; import io.dapr.serializer.DaprObjectSerializer; @@ -60,22 +66,29 @@ import io.dapr.v1.CommonProtos; import io.dapr.v1.DaprGrpc; import io.dapr.v1.DaprProtos; +import io.dapr.v1.DaprProtos.ActiveActorsCount; +import io.dapr.v1.DaprProtos.ActorRuntime; +import io.dapr.v1.DaprProtos.AppConnectionHealthProperties; +import io.dapr.v1.DaprProtos.AppConnectionProperties; +import io.dapr.v1.DaprProtos.MetadataHTTPEndpoint; +import io.dapr.v1.DaprProtos.PubsubSubscription; +import io.dapr.v1.DaprProtos.PubsubSubscriptionRule; +import io.dapr.v1.DaprProtos.RegisteredComponents; import io.grpc.CallOptions; import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; -import io.grpc.ForwardingClientCall; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; +import io.grpc.stub.AbstractStub; import io.grpc.stub.StreamObserver; import reactor.core.publisher.Flux; import reactor.core.publisher.FluxSink; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoSink; import reactor.util.context.ContextView; +import reactor.util.retry.Retry; import java.io.IOException; +import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -83,15 +96,16 @@ import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; /** - * An adapter for the GRPC Client. + * Implementation of the Dapr client combining gRPC and HTTP (when applicable). * * @see io.dapr.v1.DaprGrpc * @see io.dapr.client.DaprClient */ -public class DaprClientGrpc extends AbstractDaprClient { +public class DaprClientImpl extends AbstractDaprClient { /** * The GRPC managed channel to be used. @@ -114,7 +128,15 @@ public class DaprClientGrpc extends AbstractDaprClient { private final DaprGrpc.DaprStub asyncStub; /** - * Default access level constructor, in order to create an instance of this class use io.dapr.client.DaprClientBuilder + * The HTTP client to be used for healthz and HTTP service invocation only. + * + * @see io.dapr.client.DaprHttp + */ + private final DaprHttp httpClient; + + /** + * Default access level constructor, in order to create an instance of this + * class use io.dapr.client.DaprClientBuilder * * @param channel Facade for the managed GRPC channel * @param asyncStub async gRPC stub @@ -122,12 +144,13 @@ public class DaprClientGrpc extends AbstractDaprClient { * @param stateSerializer Serializer for state objects. * @see DaprClientBuilder */ - DaprClientGrpc( + DaprClientImpl( GrpcChannelFacade channel, DaprGrpc.DaprStub asyncStub, + DaprHttp httpClient, DaprObjectSerializer objectSerializer, DaprObjectSerializer stateSerializer) { - this(channel, asyncStub, objectSerializer, stateSerializer, null); + this(channel, asyncStub, httpClient, objectSerializer, stateSerializer, null); } /** @@ -135,20 +158,23 @@ public class DaprClientGrpc extends AbstractDaprClient { * * @param channel Facade for the managed GRPC channel * @param asyncStub async gRPC stub + * @param httpClient client for http service invocation * @param objectSerializer Serializer for transient request/response objects. * @param stateSerializer Serializer for state objects. * @param resiliencyOptions Client-level override for resiliency options. * @see DaprClientBuilder */ - DaprClientGrpc( + DaprClientImpl( GrpcChannelFacade channel, DaprGrpc.DaprStub asyncStub, + DaprHttp httpClient, DaprObjectSerializer objectSerializer, DaprObjectSerializer stateSerializer, ResiliencyOptions resiliencyOptions) { super(objectSerializer, stateSerializer); this.channel = channel; - this.asyncStub = intercept(asyncStub); + this.asyncStub = asyncStub; + this.httpClient = httpClient; this.timeoutPolicy = new TimeoutPolicy( resiliencyOptions == null ? null : resiliencyOptions.getTimeout()); this.retryPolicy = new RetryPolicy( @@ -177,12 +203,72 @@ private CommonProtos.StateOptions.StateConcurrency getGrpcStateConcurrency(State } } + /** + * {@inheritDoc} + */ + public > T newGrpcStub(String appId, Function stubBuilder) { + // Adds Dapr interceptors to populate gRPC metadata automatically. + return DaprClientGrpcInterceptors.intercept(appId, stubBuilder.apply(this.channel.getGrpcChannel()), timeoutPolicy); + } + /** * {@inheritDoc} */ @Override public Mono waitForSidecar(int timeoutInMilliseconds) { - return this.channel.waitForChannelReady(timeoutInMilliseconds); + String[] pathSegments = new String[] { DaprHttp.API_VERSION, "healthz", "outbound"}; + int maxRetries = 5; + + Retry retrySpec = Retry + .fixedDelay(maxRetries, Duration.ofMillis(500)) + .doBeforeRetry(retrySignal -> { + System.out.println("Retrying component health check..."); + }); + + /* + NOTE: (Cassie) Uncomment this once it actually gets implemented: + https://github.com/grpc/grpc-java/issues/4359 + + int maxChannelStateRetries = 5; + + // Retry logic for checking the channel state + Retry channelStateRetrySpec = Retry + .fixedDelay(maxChannelStateRetries, Duration.ofMillis(500)) + .doBeforeRetry(retrySignal -> { + System.out.println("Retrying channel state check..."); + }); + */ + + // Do the Dapr Http endpoint check to have parity with Dotnet + Mono responseMono = this.httpClient.invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, + null, "", null, null); + + return responseMono + .retryWhen(retrySpec) + /* + NOTE: (Cassie) Uncomment this once it actually gets implemented: + https://github.com/grpc/grpc-java/issues/4359 + .flatMap(response -> { + // Check the status code + int statusCode = response.getStatusCode(); + + // Check if the channel's state is READY + return Mono.defer(() -> { + if (this.channel.getState(true) == ConnectivityState.READY) { + // Return true if the status code is in the 2xx range + if (statusCode >= 200 && statusCode < 300) { + return Mono.empty(); // Continue with the flow + } + } + return Mono.error(new RuntimeException("Health check failed")); + }).retryWhen(channelStateRetrySpec); + }) + */ + .timeout(Duration.ofMillis(timeoutInMilliseconds)) + .onErrorResume(DaprException.class, e -> + Mono.error(new RuntimeException(e))) + .switchIfEmpty(DaprException.wrapMono(new RuntimeException("Health check timed out"))) + .then(); } /** @@ -309,38 +395,63 @@ public Mono> publishEvents(BulkPublishRequest requ } } - /** - * {@inheritDoc} - */ @Override public Mono invokeMethod(InvokeMethodRequest invokeMethodRequest, TypeRef type) { try { - String appId = invokeMethodRequest.getAppId(); - String method = invokeMethodRequest.getMethod(); - Object body = invokeMethodRequest.getBody(); - HttpExtension httpExtension = invokeMethodRequest.getHttpExtension(); - DaprProtos.InvokeServiceRequest envelope = buildInvokeServiceRequest( - httpExtension, - appId, - method, - body); - // Regarding missing metadata in method invocation for gRPC: - // gRPC to gRPC does not handle metadata in Dapr runtime proto. - // gRPC to HTTP does not map correctly in Dapr runtime as per https://github.com/dapr/dapr/issues/2342 + final String appId = invokeMethodRequest.getAppId(); + final String method = invokeMethodRequest.getMethod(); + final Object request = invokeMethodRequest.getBody(); + final HttpExtension httpExtension = invokeMethodRequest.getHttpExtension(); + final String contentType = invokeMethodRequest.getContentType(); + final Map metadata = invokeMethodRequest.getMetadata(); + + if (httpExtension == null) { + throw new IllegalArgumentException("HttpExtension cannot be null. Use HttpExtension.NONE instead."); + } + // If the httpExtension is not null, then the method will not be null based on checks in constructor + final String httpMethod = httpExtension.getMethod().toString(); + if (appId == null || appId.trim().isEmpty()) { + throw new IllegalArgumentException("App Id cannot be null or empty."); + } + if (method == null || method.trim().isEmpty()) { + throw new IllegalArgumentException("Method name cannot be null or empty."); + } - return Mono.deferContextual( - context -> this.createMono( - it -> intercept(context, asyncStub).invokeService(envelope, it) - ) - ).flatMap( - it -> { - try { - return Mono.justOrEmpty(objectSerializer.deserialize(it.getData().getValue().toByteArray(), type)); - } catch (IOException e) { - throw DaprException.propagate(e); - } - } + + String[] methodSegments = method.split("/"); + + List pathSegments = new ArrayList<>(Arrays.asList(DaprHttp.API_VERSION, "invoke", appId, "method")); + pathSegments.addAll(Arrays.asList(methodSegments)); + + final Map headers = new HashMap<>(); + headers.putAll(httpExtension.getHeaders()); + if (metadata != null) { + headers.putAll(metadata); + } + byte[] serializedRequestBody = objectSerializer.serialize(request); + if (contentType != null && !contentType.isEmpty()) { + headers.put(io.dapr.client.domain.Metadata.CONTENT_TYPE, contentType); + } else { + headers.put(io.dapr.client.domain.Metadata.CONTENT_TYPE, objectSerializer.getContentType()); + } + Mono response = Mono.deferContextual( + context -> this.httpClient.invokeApi(httpMethod, pathSegments.toArray(new String[0]), + httpExtension.getQueryParams(), serializedRequestBody, headers, context) ); + return response.flatMap(r -> getMonoForHttpResponse(type, r)); + } catch (Exception ex) { + return DaprException.wrapMono(ex); + } + } + + private Mono getMonoForHttpResponse(TypeRef type, DaprHttp.Response r) { + try { + T object = objectSerializer.deserialize(r.getBody(), type); + if (object == null) { + return Mono.empty(); + } + + return Mono.just(object); } catch (Exception ex) { return DaprException.wrapMono(ex); } @@ -673,48 +784,6 @@ public Mono deleteState(DeleteStateRequest request) { } } - /** - * Builds the object io.dapr.{@link DaprProtos.InvokeServiceRequest} to be send based on the parameters. - * - * @param httpExtension Object for HttpExtension - * @param appId The application id to be invoked - * @param method The application method to be invoked - * @param body The body of the request to be send as part of the invocation - * @param The Type of the Body - * @return The object to be sent as part of the invocation. - * @throws IOException If there's an issue serializing the request. - */ - private DaprProtos.InvokeServiceRequest buildInvokeServiceRequest( - HttpExtension httpExtension, - String appId, - String method, - K body) throws IOException { - if (httpExtension == null) { - throw new IllegalArgumentException("HttpExtension cannot be null. Use HttpExtension.NONE instead."); - } - CommonProtos.InvokeRequest.Builder requestBuilder = CommonProtos.InvokeRequest.newBuilder(); - requestBuilder.setMethod(method); - if (body != null) { - byte[] byteRequest = objectSerializer.serialize(body); - Any data = Any.newBuilder().setValue(ByteString.copyFrom(byteRequest)).build(); - requestBuilder.setData(data); - } else { - requestBuilder.setData(Any.newBuilder().build()); - } - CommonProtos.HTTPExtension.Builder httpExtensionBuilder = CommonProtos.HTTPExtension.newBuilder(); - - httpExtensionBuilder.setVerb(CommonProtos.HTTPExtension.Verb.valueOf(httpExtension.getMethod().toString())) - .setQuerystring(httpExtension.encodeQueryString()); - requestBuilder.setHttpExtension(httpExtensionBuilder.build()); - - requestBuilder.setContentType(objectSerializer.getContentType()); - - DaprProtos.InvokeServiceRequest.Builder envelopeBuilder = DaprProtos.InvokeServiceRequest.newBuilder() - .setId(appId) - .setMessage(requestBuilder.build()); - return envelopeBuilder.build(); - } - /** * {@inheritDoc} */ @@ -963,12 +1032,15 @@ private QueryStateItem buildQueryStateKeyValue( */ @Override public void close() throws Exception { - if (channel != null) { - DaprException.wrap(() -> { + DaprException.wrap(() -> { + if (channel != null) { channel.close(); - return true; - }).call(); - } + } + if (httpClient != null) { + httpClient.close(); + } + return true; + }).call(); } /** @@ -1054,7 +1126,7 @@ public Flux subscribeConfiguration(SubscribeConf DaprProtos.SubscribeConfigurationRequest envelope = builder.build(); return this.createFlux( - it -> intercept(asyncStub).subscribeConfiguration(envelope, it) + it -> intercept(null, asyncStub).subscribeConfiguration(envelope, it) ).map( it -> { Map configMap = new HashMap<>(); @@ -1094,7 +1166,7 @@ public Mono unsubscribeConfiguration(Unsubscri DaprProtos.UnsubscribeConfigurationRequest envelope = builder.build(); return this.createMono( - it -> intercept(asyncStub).unsubscribeConfiguration(envelope, it) + it -> intercept(null, asyncStub).unsubscribeConfiguration(envelope, it) ).map( it -> new UnsubscribeConfigurationResponse(it.getOk(), it.getMessage()) ); @@ -1115,37 +1187,7 @@ private ConfigurationItem buildConfigurationItem( key, configurationItem.getValue(), configurationItem.getVersion(), - configurationItem.getMetadataMap() - ); - } - - /** - * Populates GRPC client with interceptors. - * - * @param client GRPC client for Dapr. - * @return Client after adding interceptors. - */ - private DaprGrpc.DaprStub intercept(DaprGrpc.DaprStub client) { - ClientInterceptor interceptor = new ClientInterceptor() { - @Override - public ClientCall interceptCall( - MethodDescriptor methodDescriptor, - CallOptions options, - Channel channel) { - ClientCall clientCall = channel.newCall(methodDescriptor, timeoutPolicy.apply(options)); - return new ForwardingClientCall.SimpleForwardingClientCall(clientCall) { - @Override - public void start(final Listener responseListener, final Metadata metadata) { - String daprApiToken = Properties.API_TOKEN.get(); - if (daprApiToken != null) { - metadata.put(Metadata.Key.of(Headers.DAPR_API_TOKEN, Metadata.ASCII_STRING_MARSHALLER), daprApiToken); - } - super.start(responseListener, metadata); - } - }; - } - }; - return client.withInterceptors(interceptor); + configurationItem.getMetadataMap()); } /** @@ -1155,8 +1197,8 @@ public void start(final Listener responseListener, final Metadata metadat * @param client GRPC client for Dapr. * @return Client after adding interceptors. */ - private static DaprGrpc.DaprStub intercept(ContextView context, DaprGrpc.DaprStub client) { - return GrpcWrapper.intercept(context, client); + private DaprGrpc.DaprStub intercept(ContextView context, DaprGrpc.DaprStub client) { + return DaprClientGrpcInterceptors.intercept(client, this.timeoutPolicy, context); } private Mono createMono(Consumer> consumer) { @@ -1206,4 +1248,114 @@ public void onCompleted() { } }; } + + @Override + public Mono getMetadata() { + DaprProtos.GetMetadataRequest metadataRequest = DaprProtos.GetMetadataRequest.newBuilder().build(); + return Mono.deferContextual( + context -> this.createMono( + it -> intercept(context, asyncStub).getMetadata(metadataRequest, it))) + .map( + it -> { + try { + return buildDaprMetadata(it); + } catch (IOException ex) { + throw DaprException.propagate(ex); + } + }); + } + + private DaprMetadata buildDaprMetadata(DaprProtos.GetMetadataResponse response) throws IOException { + String id = response.getId(); + String runtimeVersion = response.getRuntimeVersion(); + List enabledFeatures = response.getEnabledFeaturesList(); + List actors = getActors(response); + Map attributes = response.getExtendedMetadataMap(); + List components = getComponents(response); + List httpEndpoints = getHttpEndpoints(response); + List subscriptions = getSubscriptions(response); + AppConnectionPropertiesMetadata appConnectionProperties = getAppConnectionProperties(response); + + return new DaprMetadata(id, runtimeVersion, enabledFeatures, actors, attributes, components, httpEndpoints, + subscriptions, appConnectionProperties); + } + + private List getActors(DaprProtos.GetMetadataResponse response) { + ActorRuntime actorRuntime = response.getActorRuntime(); + List activeActorsList = actorRuntime.getActiveActorsList(); + + List actors = new ArrayList<>(); + for (ActiveActorsCount aac : activeActorsList) { + actors.add(new ActorMetadata(aac.getType(), aac.getCount())); + } + + return actors; + } + + private List getComponents(DaprProtos.GetMetadataResponse response) { + List registeredComponentsList = response.getRegisteredComponentsList(); + + List components = new ArrayList<>(); + for (RegisteredComponents rc : registeredComponentsList) { + components.add(new ComponentMetadata(rc.getName(), rc.getType(), rc.getVersion(), rc.getCapabilitiesList())); + } + + return components; + } + + private List getSubscriptions(DaprProtos.GetMetadataResponse response) { + List subscriptionsList = response.getSubscriptionsList(); + + List subscriptions = new ArrayList<>(); + for (PubsubSubscription s : subscriptionsList) { + List rulesList = s.getRules().getRulesList(); + List rules = new ArrayList<>(); + for (PubsubSubscriptionRule r : rulesList) { + rules.add(new RuleMetadata(r.getMatch(), r.getPath())); + } + subscriptions.add(new SubscriptionMetadata(s.getPubsubName(), s.getTopic(), s.getMetadataMap(), rules, + s.getDeadLetterTopic())); + } + + return subscriptions; + } + + private List getHttpEndpoints(DaprProtos.GetMetadataResponse response) { + List httpEndpointsList = response.getHttpEndpointsList(); + + List httpEndpoints = new ArrayList<>(); + for (MetadataHTTPEndpoint m : httpEndpointsList) { + httpEndpoints.add(new HttpEndpointMetadata(m.getName())); + } + + return httpEndpoints; + } + + private AppConnectionPropertiesMetadata getAppConnectionProperties(DaprProtos.GetMetadataResponse response) { + AppConnectionProperties appConnectionProperties = response.getAppConnectionProperties(); + int port = appConnectionProperties.getPort(); + String protocol = appConnectionProperties.getProtocol(); + String channelAddress = appConnectionProperties.getChannelAddress(); + int maxConcurrency = appConnectionProperties.getMaxConcurrency(); + AppConnectionPropertiesHealthMetadata health = getAppConnectionPropertiesHealth(appConnectionProperties); + + return new AppConnectionPropertiesMetadata(port, protocol, channelAddress, maxConcurrency, health); + } + + private AppConnectionPropertiesHealthMetadata getAppConnectionPropertiesHealth( + AppConnectionProperties appConnectionProperties) { + if (!appConnectionProperties.hasHealth()) { + return null; + } + + AppConnectionHealthProperties health = appConnectionProperties.getHealth(); + String healthCheckPath = health.getHealthCheckPath(); + String healthProbeInterval = health.getHealthProbeInterval(); + String healthProbeTimeout = health.getHealthProbeTimeout(); + int healthThreshold = health.getHealthThreshold(); + + return new AppConnectionPropertiesHealthMetadata(healthCheckPath, healthProbeInterval, healthProbeTimeout, + healthThreshold); + } + } diff --git a/sdk/src/main/java/io/dapr/client/DaprClientProxy.java b/sdk/src/main/java/io/dapr/client/DaprClientProxy.java deleted file mode 100644 index 370091a96e..0000000000 --- a/sdk/src/main/java/io/dapr/client/DaprClientProxy.java +++ /dev/null @@ -1,608 +0,0 @@ -/* - * Copyright 2021 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ - -package io.dapr.client; - -import io.dapr.client.domain.ConfigurationItem; -import io.dapr.client.domain.DeleteStateRequest; -import io.dapr.client.domain.ExecuteStateTransactionRequest; -import io.dapr.client.domain.GetBulkSecretRequest; -import io.dapr.client.domain.GetBulkStateRequest; -import io.dapr.client.domain.GetConfigurationRequest; -import io.dapr.client.domain.GetSecretRequest; -import io.dapr.client.domain.GetStateRequest; -import io.dapr.client.domain.HttpExtension; -import io.dapr.client.domain.InvokeBindingRequest; -import io.dapr.client.domain.InvokeMethodRequest; -import io.dapr.client.domain.PublishEventRequest; -import io.dapr.client.domain.SaveStateRequest; -import io.dapr.client.domain.State; -import io.dapr.client.domain.StateOptions; -import io.dapr.client.domain.SubscribeConfigurationRequest; -import io.dapr.client.domain.SubscribeConfigurationResponse; -import io.dapr.client.domain.TransactionalStateOperation; -import io.dapr.client.domain.UnsubscribeConfigurationRequest; -import io.dapr.client.domain.UnsubscribeConfigurationResponse; -import io.dapr.utils.TypeRef; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.List; -import java.util.Map; - -/** - * Class that delegates to other implementations. - * @deprecated This class will be deleted at SDK release version 1.10. - * @see DaprClient - * @see DaprClientGrpc - * @see DaprClientHttp - */ -@Deprecated -class DaprClientProxy implements DaprClient { - - /** - * Client for all API invocations. - */ - private final DaprClient client; - - /** - * Client to override Dapr's service invocation APIs. - */ - private final DaprClient methodInvocationOverrideClient; - - /** - * Constructor with delegate client. - * - * @param client Client for all API invocations. - * @see DaprClientBuilder - */ - DaprClientProxy(DaprClient client) { - this(client, client); - } - - /** - * Constructor with delegate client and override client for Dapr's method invocation APIs. - * - * @param client Client for all API invocations, except override below. - * @param methodInvocationOverrideClient Client to override Dapr's service invocation APIs. - * @see DaprClientBuilder - */ - DaprClientProxy( - DaprClient client, - DaprClient methodInvocationOverrideClient) { - this.client = client; - this.methodInvocationOverrideClient = methodInvocationOverrideClient; - } - - /** - * {@inheritDoc} - */ - @Override - public Mono waitForSidecar(int timeoutInMilliseconds) { - return client.waitForSidecar(timeoutInMilliseconds); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono publishEvent(String pubsubName, String topicName, Object data) { - return client.publishEvent(pubsubName, topicName, data); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono publishEvent(String pubsubName, String topicName, Object data, Map metadata) { - return client.publishEvent(pubsubName, topicName, data, metadata); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono publishEvent(PublishEventRequest request) { - return client.publishEvent(request); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(String appId, - String methodName, - Object data, - HttpExtension httpExtension, - Map metadata, - TypeRef type) { - return methodInvocationOverrideClient.invokeMethod(appId, methodName, data, httpExtension, metadata, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(String appId, - String methodName, - Object request, - HttpExtension httpExtension, - Map metadata, - Class clazz) { - return methodInvocationOverrideClient.invokeMethod(appId, methodName, request, httpExtension, metadata, clazz); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(String appId, - String methodName, - Object request, - HttpExtension httpExtension, - TypeRef type) { - return methodInvocationOverrideClient.invokeMethod(appId, methodName, request, httpExtension, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(String appId, - String methodName, - Object request, - HttpExtension httpExtension, - Class clazz) { - return methodInvocationOverrideClient.invokeMethod(appId, methodName, request, httpExtension, clazz); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(String appId, - String methodName, - HttpExtension httpExtension, - Map metadata, - TypeRef type) { - return methodInvocationOverrideClient.invokeMethod(appId, methodName, httpExtension, metadata, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(String appId, - String methodName, - HttpExtension httpExtension, - Map metadata, - Class clazz) { - return methodInvocationOverrideClient.invokeMethod(appId, methodName, httpExtension, metadata, clazz); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(String appId, - String methodName, - Object request, - HttpExtension httpExtension, - Map metadata) { - return methodInvocationOverrideClient.invokeMethod(appId, methodName, request, httpExtension, metadata); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(String appId, String methodName, Object request, HttpExtension httpExtension) { - return methodInvocationOverrideClient.invokeMethod(appId, methodName, request, httpExtension); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(String appId, - String methodName, - HttpExtension httpExtension, - Map metadata) { - return methodInvocationOverrideClient.invokeMethod(appId, methodName, httpExtension, metadata); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(String appId, - String methodName, - byte[] request, - HttpExtension httpExtension, - Map metadata) { - return methodInvocationOverrideClient.invokeMethod(appId, methodName, request, httpExtension, metadata); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeMethod(InvokeMethodRequest invokeMethodRequest, TypeRef type) { - return methodInvocationOverrideClient.invokeMethod(invokeMethodRequest, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeBinding(String bindingName, String operation, Object data) { - return client.invokeBinding(bindingName, operation, data); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeBinding(String bindingName, String operation, byte[] data, Map metadata) { - return client.invokeBinding(bindingName, operation, data, metadata); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeBinding(String bindingName, String operation, Object data, TypeRef type) { - return client.invokeBinding(bindingName, operation, data, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeBinding(String bindingName, String operation, Object data, Class clazz) { - return client.invokeBinding(bindingName, operation, data, clazz); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeBinding(String bindingName, - String operation, - Object data, - Map metadata, - TypeRef type) { - return client.invokeBinding(bindingName, operation, data, metadata, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeBinding(String bindingName, - String operation, - Object data, - Map metadata, - Class clazz) { - return client.invokeBinding(bindingName, operation, data, metadata, clazz); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono invokeBinding(InvokeBindingRequest request, TypeRef type) { - return client.invokeBinding(request, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getState(String storeName, State state, TypeRef type) { - return client.getState(storeName, state, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getState(String storeName, State state, Class clazz) { - return client.getState(storeName, state, clazz); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getState(String storeName, String key, TypeRef type) { - return client.getState(storeName, key, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getState(String storeName, String key, Class clazz) { - return client.getState(storeName, key, clazz); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getState(String storeName, String key, StateOptions options, TypeRef type) { - return client.getState(storeName, key, options, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getState(String storeName, String key, StateOptions options, Class clazz) { - return client.getState(storeName, key, options, clazz); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getState(GetStateRequest request, TypeRef type) { - return client.getState(request, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono>> getBulkState(String storeName, List keys, TypeRef type) { - return client.getBulkState(storeName, keys, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono>> getBulkState(String storeName, List keys, Class clazz) { - return client.getBulkState(storeName, keys, clazz); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono>> getBulkState(GetBulkStateRequest request, TypeRef type) { - return client.getBulkState(request, type); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono executeStateTransaction(String storeName, List> operations) { - return client.executeStateTransaction(storeName, operations); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono executeStateTransaction(ExecuteStateTransactionRequest request) { - return client.executeStateTransaction(request); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono saveBulkState(String storeName, List> states) { - return client.saveBulkState(storeName, states); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono saveBulkState(SaveStateRequest request) { - return client.saveBulkState(request); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono saveState(String storeName, String key, Object value) { - return client.saveState(storeName, key, value); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono saveState(String storeName, String key, String etag, Object value, StateOptions options) { - return client.saveState(storeName, key, etag, value, options); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono deleteState(String storeName, String key) { - return client.deleteState(storeName, key); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono deleteState(String storeName, String key, String etag, StateOptions options) { - return client.deleteState(storeName, key, etag, options); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono deleteState(DeleteStateRequest request) { - return client.deleteState(request); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getSecret(String storeName, String secretName, Map metadata) { - return client.getSecret(storeName, secretName, metadata); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getSecret(String storeName, String secretName) { - return client.getSecret(storeName, secretName); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getSecret(GetSecretRequest request) { - return client.getSecret(request); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono>> getBulkSecret(String storeName) { - return client.getBulkSecret(storeName); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono>> getBulkSecret(String storeName, Map metadata) { - return client.getBulkSecret(storeName, metadata); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono>> getBulkSecret(GetBulkSecretRequest request) { - return client.getBulkSecret(request); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono getConfiguration(String storeName, String key) { - return client.getConfiguration(storeName, key); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono getConfiguration(String storeName, String key, Map metadata) { - return client.getConfiguration(storeName, key, metadata); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getConfiguration(String storeName, String... keys) { - return client.getConfiguration(storeName, keys); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getConfiguration(String storeName, List keys, - Map metadata) { - return client.getConfiguration(storeName, keys, metadata); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono> getConfiguration(GetConfigurationRequest request) { - return client.getConfiguration(request); - } - - /** - * {@inheritDoc} - */ - @Override - public Flux subscribeConfiguration(String storeName, String... keys) { - return client.subscribeConfiguration(storeName, keys); - } - - /** - * {@inheritDoc} - */ - @Override - public Flux subscribeConfiguration(String storeName, List keys, - Map metadata) { - return client.subscribeConfiguration(storeName, keys, metadata); - } - - /** - * {@inheritDoc} - */ - @Override - public Flux subscribeConfiguration(SubscribeConfigurationRequest request) { - return client.subscribeConfiguration(request); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono unsubscribeConfiguration(String id, String storeName) { - return client.unsubscribeConfiguration(id, storeName); - } - - /** - * {@inheritDoc} - */ - @Override - public Mono unsubscribeConfiguration(UnsubscribeConfigurationRequest request) { - return client.unsubscribeConfiguration(request); - } - - /** - * {@inheritDoc} - */ - @Override - public void close() throws Exception { - client.close(); - if (client != methodInvocationOverrideClient) { - methodInvocationOverrideClient.close(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Mono shutdown() { - return client.shutdown(); - } -} diff --git a/sdk/src/main/java/io/dapr/client/DaprHttp.java b/sdk/src/main/java/io/dapr/client/DaprHttp.java index ed2b7e996a..05da6efd5d 100644 --- a/sdk/src/main/java/io/dapr/client/DaprHttp.java +++ b/sdk/src/main/java/io/dapr/client/DaprHttp.java @@ -349,7 +349,8 @@ private static DaprError parseDaprError(byte[] json) { try { return DAPR_ERROR_DETAILS_OBJECT_MAPPER.readValue(json, DaprError.class); } catch (IOException e) { - throw new DaprException("UNKNOWN", new String(json, StandardCharsets.UTF_8), json); + // Could not parse DaprError. Return null. + return null; } } @@ -384,17 +385,13 @@ public void onResponse(@NotNull Call call, @NotNull okhttp3.Response response) t try { byte[] payload = getBodyBytesOrEmptyArray(response); DaprError error = parseDaprError(payload); - if ((error != null) && (error.getErrorCode() != null)) { - if (error.getMessage() != null) { - future.completeExceptionally(new DaprException(error, payload)); - } else { - future.completeExceptionally( - new DaprException(error.getErrorCode(), "HTTP status code: " + response.code(), payload)); - } + if (error != null) { + future.completeExceptionally(new DaprException(error, payload, response.code())); return; } - future.completeExceptionally(new DaprException("UNKNOWN", "HTTP status code: " + response.code(), payload)); + future.completeExceptionally( + new DaprException("UNKNOWN", "", payload, response.code())); return; } catch (DaprException e) { future.completeExceptionally(e); diff --git a/sdk/src/main/java/io/dapr/client/DaprHttpBuilder.java b/sdk/src/main/java/io/dapr/client/DaprHttpBuilder.java index 73842b5467..e33b2e6980 100644 --- a/sdk/src/main/java/io/dapr/client/DaprHttpBuilder.java +++ b/sdk/src/main/java/io/dapr/client/DaprHttpBuilder.java @@ -23,9 +23,7 @@ /** * A builder for the DaprHttp. - * @deprecated Use {@link DaprClientBuilder} instead, this will be removed in a future release. */ -@Deprecated public class DaprHttpBuilder { /** diff --git a/sdk/src/main/java/io/dapr/client/DaprTracingInterceptor.java b/sdk/src/main/java/io/dapr/client/DaprTracingInterceptor.java new file mode 100644 index 0000000000..20f6d1c063 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/DaprTracingInterceptor.java @@ -0,0 +1,48 @@ +/* + * Copyright 2024 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.client; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.MethodDescriptor; +import reactor.util.context.ContextView; + +/** + * Injects tracing headers to gRPC metadata. + */ +public class DaprTracingInterceptor implements ClientInterceptor { + + private final io.dapr.internal.grpc.interceptors.DaprTracingInterceptor delegate; + + /** + * Creates an instance of the injector for gRPC context from Reactor's context. + * @param context Reactor's context + */ + public DaprTracingInterceptor(ContextView context) { + this.delegate = new io.dapr.internal.grpc.interceptors.DaprTracingInterceptor(context); + } + + /** + * {@inheritDoc} + */ + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, + CallOptions callOptions, + Channel channel) { + return this.delegate.interceptCall(methodDescriptor, callOptions, channel); + } +} diff --git a/sdk/src/main/java/io/dapr/client/GrpcChannelFacade.java b/sdk/src/main/java/io/dapr/client/GrpcChannelFacade.java index b0e13e033a..5647f5294c 100644 --- a/sdk/src/main/java/io/dapr/client/GrpcChannelFacade.java +++ b/sdk/src/main/java/io/dapr/client/GrpcChannelFacade.java @@ -13,18 +13,11 @@ package io.dapr.client; -import io.dapr.config.Properties; -import io.dapr.exceptions.DaprException; import io.dapr.v1.DaprGrpc; -import io.grpc.ConnectivityState; import io.grpc.ManagedChannel; -import okhttp3.OkHttpClient; -import reactor.core.publisher.Mono; -import reactor.util.retry.Retry; import java.io.Closeable; import java.io.IOException; -import java.time.Duration; /** * Facade for common operations on gRPC channel. @@ -39,11 +32,6 @@ class GrpcChannelFacade implements Closeable { */ private final ManagedChannel channel; - /** - * The reference to the DaprHttp client. - */ - private final DaprHttp daprHttp; - /** * Default access level constructor, in order to create an instance of this class use io.dapr.client.DaprClientBuilder @@ -51,75 +39,23 @@ class GrpcChannelFacade implements Closeable { * @param channel A Managed GRPC channel * @see DaprClientBuilder */ - GrpcChannelFacade(ManagedChannel channel, DaprHttp daprHttp) { + GrpcChannelFacade(ManagedChannel channel) { this.channel = channel; - this.daprHttp = daprHttp; + } + + /** + * Returns the gRPC channel to the sidecar. + * @return Sidecar's gRPC channel. + */ + ManagedChannel getGrpcChannel() { + return this.channel; } @Override public void close() throws IOException { - if (daprHttp != null) { - daprHttp.close(); - } - if (channel != null && !channel.isShutdown()) { channel.shutdown(); } } - public Mono waitForChannelReady(int timeoutInMilliseconds) { - String[] pathSegments = new String[] { DaprHttp.API_VERSION, "healthz", "outbound"}; - int maxRetries = 5; - - Retry retrySpec = Retry - .fixedDelay(maxRetries, Duration.ofMillis(500)) - .doBeforeRetry(retrySignal -> { - System.out.println("Retrying component health check..."); - }); - - /* - NOTE: (Cassie) Uncomment this once it actually gets implemented: - https://github.com/grpc/grpc-java/issues/4359 - - int maxChannelStateRetries = 5; - - // Retry logic for checking the channel state - Retry channelStateRetrySpec = Retry - .fixedDelay(maxChannelStateRetries, Duration.ofMillis(500)) - .doBeforeRetry(retrySignal -> { - System.out.println("Retrying channel state check..."); - }); - */ - - // Do the Dapr Http endpoint check to have parity with Dotnet - Mono responseMono = this.daprHttp.invokeApi(DaprHttp.HttpMethods.GET.name(), pathSegments, - null, "", null, null); - - return responseMono - .retryWhen(retrySpec) - /* - NOTE: (Cassie) Uncomment this once it actually gets implemented: - https://github.com/grpc/grpc-java/issues/4359 - .flatMap(response -> { - // Check the status code - int statusCode = response.getStatusCode(); - - // Check if the channel's state is READY - return Mono.defer(() -> { - if (this.channel.getState(true) == ConnectivityState.READY) { - // Return true if the status code is in the 2xx range - if (statusCode >= 200 && statusCode < 300) { - return Mono.empty(); // Continue with the flow - } - } - return Mono.error(new RuntimeException("Health check failed")); - }).retryWhen(channelStateRetrySpec); - }) - */ - .timeout(Duration.ofMillis(timeoutInMilliseconds)) - .onErrorResume(DaprException.class, e -> - Mono.error(new RuntimeException(e))) - .switchIfEmpty(DaprException.wrapMono(new RuntimeException("Health check timed out"))) - .then(); - } } diff --git a/sdk/src/main/java/io/dapr/client/domain/ActorMetadata.java b/sdk/src/main/java/io/dapr/client/domain/ActorMetadata.java new file mode 100644 index 0000000000..a0420d34cf --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ActorMetadata.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 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; + +/** + * ActorMetadata describes a registered Dapr Actor. + */ +public final class ActorMetadata { + private final String type; + private final int count; + + /** + * Constructor for a ActorMetadata. + * + * @param type of the actor + * @param count number of actors of a particular type + */ + public ActorMetadata(String type, int count) { + this.type = type; + this.count = count; + } + + public String getType() { + return type; + } + + public int getCount() { + return count; + } +} diff --git a/sdk/src/main/java/io/dapr/client/domain/AppConnectionPropertiesHealthMetadata.java b/sdk/src/main/java/io/dapr/client/domain/AppConnectionPropertiesHealthMetadata.java new file mode 100644 index 0000000000..b8aa1f65f8 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/AppConnectionPropertiesHealthMetadata.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 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; + +/** + * AppConnectionPropertiesHealthMetadata describes the application health properties. + */ +public final class AppConnectionPropertiesHealthMetadata { + + private final String healthCheckPath; + private final String healthProbeInterval; + private final String healthProbeTimeout; + private final int healthThreshold; + + /** + * Constructor for a AppConnectionPropertiesHealthMetadata. + * + * @param healthCheckPath of the application + * @param healthProbeInterval time interval between health probes + * @param healthProbeTimeout timeout for each health probe + * @param healthThreshold max number of failed health probes + */ + public AppConnectionPropertiesHealthMetadata(String healthCheckPath, String healthProbeInterval, + String healthProbeTimeout, int healthThreshold) { + this.healthCheckPath = healthCheckPath; + this.healthProbeInterval = healthProbeInterval; + this.healthProbeTimeout = healthProbeTimeout; + this.healthThreshold = healthThreshold; + } + + public String getHealthCheckPath() { + return healthCheckPath; + } + + public String getHealthProbeInterval() { + return healthProbeInterval; + } + + public String getHealthProbeTimeout() { + return healthProbeTimeout; + } + + public int getHealthThreshold() { + return healthThreshold; + } + +} diff --git a/sdk/src/main/java/io/dapr/client/domain/AppConnectionPropertiesMetadata.java b/sdk/src/main/java/io/dapr/client/domain/AppConnectionPropertiesMetadata.java new file mode 100644 index 0000000000..0b256e47a3 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/AppConnectionPropertiesMetadata.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024 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; + +/** + * AppConnectionPropertiesMetadata describes the application connection properties. + */ +public final class AppConnectionPropertiesMetadata { + + private final int port; + private final String protocol; + private final String channelAddress; + private final int maxConcurrency; + private final AppConnectionPropertiesHealthMetadata health; + + /** + * Constructor for a AppConnectionPropertiesMetadata. + * + * @param port of the application + * @param protocol of the application + * @param channelAddress host address of the application + * @param maxConcurrency number of concurrent requests the app can handle + * @param health health check details of the application + */ + public AppConnectionPropertiesMetadata(int port, String protocol, String channelAddress, int maxConcurrency, + AppConnectionPropertiesHealthMetadata health) { + this.port = port; + this.protocol = protocol; + this.channelAddress = channelAddress; + this.maxConcurrency = maxConcurrency; + this.health = health; + } + + public int getPort() { + return port; + } + + public String getProtocol() { + return protocol; + } + + public String getChannelAddress() { + return channelAddress; + } + + public int getMaxConcurrency() { + return maxConcurrency; + } + + public AppConnectionPropertiesHealthMetadata getHealth() { + return health; + } + +} diff --git a/sdk/src/main/java/io/dapr/client/domain/BulkSubscribeAppResponse.java b/sdk/src/main/java/io/dapr/client/domain/BulkSubscribeAppResponse.java index 511e3468fb..cccddc1172 100644 --- a/sdk/src/main/java/io/dapr/client/domain/BulkSubscribeAppResponse.java +++ b/sdk/src/main/java/io/dapr/client/domain/BulkSubscribeAppResponse.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/sdk/src/main/java/io/dapr/client/domain/ComponentMetadata.java b/sdk/src/main/java/io/dapr/client/domain/ComponentMetadata.java new file mode 100644 index 0000000000..b5813944e7 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/ComponentMetadata.java @@ -0,0 +1,60 @@ +/* + * Copyright 2024 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; + +/** + * ComponentMetadata describes a Dapr Component. + */ +public final class ComponentMetadata { + + private final String name; + private final String type; + private final String version; + private final List capabilities; + + /** + * Constructor for a ComponentMetadata. + * + * @param name of the component + * @param type component type + * @param version version of the component + * @param capabilities capabilities of the component + */ + public ComponentMetadata(String name, String type, String version, List capabilities) { + this.name = name; + this.type = type; + this.version = version; + this.capabilities = capabilities == null ? Collections.emptyList() : Collections.unmodifiableList(capabilities); + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public String getVersion() { + return version; + } + + public List getCapabilities() { + return capabilities; + } + +} diff --git a/sdk/src/main/java/io/dapr/client/domain/DaprMetadata.java b/sdk/src/main/java/io/dapr/client/domain/DaprMetadata.java new file mode 100644 index 0000000000..037365b737 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/DaprMetadata.java @@ -0,0 +1,99 @@ +/* + * Copyright 2024 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; + +/** + * DaprMetadata describes the Dapr Metadata. + */ +public final class DaprMetadata { + + private final String id; + private final String runtimeVersion; + private final List enabledFeatures; + private final List actors; + private final Map attributes; + private final List components; + private final List httpEndpoints; + private final List subscriptions; + private final AppConnectionPropertiesMetadata appConnectionProperties; + + /** + * Constructor for a DaprMetadata. + * + * @param id of the application + * @param runtimeVersion Dapr version + * @param enabledFeatures list of enabled features + * @param actors list of registered features + * @param attributes map of extended attributes + * @param components list of registered components + * @param httpEndpoints list of registered http endpoints + * @param subscriptions list of registered subscription + * @param appConnectionProperties connection properties of the application + */ + public DaprMetadata(String id, String runtimeVersion, List enabledFeatures, List actors, + Map attributes, List components, List httpEndpoints, + List subscriptions, AppConnectionPropertiesMetadata appConnectionProperties) { + this.id = id; + this.runtimeVersion = runtimeVersion; + this.enabledFeatures = enabledFeatures == null ? Collections.emptyList() : + Collections.unmodifiableList(enabledFeatures); + this.actors = actors == null ? Collections.emptyList() : Collections.unmodifiableList(actors); + this.attributes = attributes == null ? Collections.emptyMap() : Collections.unmodifiableMap(attributes); + this.components = components == null ? Collections.emptyList() : Collections.unmodifiableList(components); + this.httpEndpoints = httpEndpoints == null ? Collections.emptyList() : Collections.unmodifiableList(httpEndpoints); + this.subscriptions = subscriptions == null ? Collections.emptyList() : Collections.unmodifiableList(subscriptions); + this.appConnectionProperties = appConnectionProperties; + } + + public String getId() { + return id; + } + + public String getRuntimeVersion() { + return runtimeVersion; + } + + public List getEnabledFeatures() { + return enabledFeatures; + } + + public List getActors() { + return actors; + } + + public Map getAttributes() { + return attributes; + } + + public List getComponents() { + return components; + } + + public List getHttpEndpoints() { + return httpEndpoints; + } + + public List getSubscriptions() { + return subscriptions; + } + + public AppConnectionPropertiesMetadata getAppConnectionProperties() { + return appConnectionProperties; + } + +} diff --git a/sdk/src/main/java/io/dapr/client/domain/HttpEndpointMetadata.java b/sdk/src/main/java/io/dapr/client/domain/HttpEndpointMetadata.java new file mode 100644 index 0000000000..c0327859b0 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/HttpEndpointMetadata.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 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; + +/** + * HttpEndpointMetadata describes a registered Dapr HTTP endpoint. + */ +public final class HttpEndpointMetadata { + + private final String name; + + /** + * Constructor for a HttpEndpointMetadata. + * + * @param name of the HTTP endpoint + */ + public HttpEndpointMetadata(String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/sdk/src/main/java/io/dapr/client/domain/PublishEventRequest.java b/sdk/src/main/java/io/dapr/client/domain/PublishEventRequest.java index a2e65955ae..61249bb15f 100644 --- a/sdk/src/main/java/io/dapr/client/domain/PublishEventRequest.java +++ b/sdk/src/main/java/io/dapr/client/domain/PublishEventRequest.java @@ -14,7 +14,6 @@ package io.dapr.client.domain; import java.util.Collections; -import java.util.HashMap; import java.util.Map; /** diff --git a/sdk/src/main/java/io/dapr/client/domain/RuleMetadata.java b/sdk/src/main/java/io/dapr/client/domain/RuleMetadata.java new file mode 100644 index 0000000000..f3499e2fd5 --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/RuleMetadata.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 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; + +/** + * RuleMetadata describes the Subscription Rule's Metadata. + */ +public final class RuleMetadata { + + private final String match; + private final String path; + + /** + * Constructor for a RuleMetadata. + * + * @param match CEL expression to match the message + * @param path path to route the message + */ + public RuleMetadata(String match, String path) { + this.match = match; + this.path = path; + } + + public String getMatch() { + return match; + } + + public String getPath() { + return path; + } + +} diff --git a/sdk/src/main/java/io/dapr/client/domain/SubscriptionMetadata.java b/sdk/src/main/java/io/dapr/client/domain/SubscriptionMetadata.java new file mode 100644 index 0000000000..76d8dce77a --- /dev/null +++ b/sdk/src/main/java/io/dapr/client/domain/SubscriptionMetadata.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 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; + +/** + * SubscriptionMetadata describes the Subscription Metadata. + */ +public final class SubscriptionMetadata { + + private final String pubsubname; + private final String topic; + private final Map metadata; + private final List rules; + private final String deadLetterTopic; + + /** + * Constructor for a SubscriptionMetadata. + * + * @param pubsubname component name + * @param topic of the pubsub component + * @param metadata of the pubsub component + * @param rules subscription path rules + * @param deadLetterTopic dead letter topic + */ + public SubscriptionMetadata(String pubsubname, String topic, Map metadata, List rules, + String deadLetterTopic) { + this.pubsubname = pubsubname; + this.topic = topic; + this.metadata = metadata == null ? Collections.emptyMap() : Collections.unmodifiableMap(metadata); + this.rules = rules == null ? Collections.emptyList() : Collections.unmodifiableList(rules); + this.deadLetterTopic = deadLetterTopic; + } + + public String getPubsubname() { + return pubsubname; + } + + public String getTopic() { + return topic; + } + + public Map getMetadata() { + return metadata; + } + + public List getRules() { + return rules; + } + + public String getDeadLetterTopic() { + return deadLetterTopic; + } + +} diff --git a/sdk/src/main/java/io/dapr/config/Properties.java b/sdk/src/main/java/io/dapr/config/Properties.java index 2ceeffb4a4..27769c6ccf 100644 --- a/sdk/src/main/java/io/dapr/config/Properties.java +++ b/sdk/src/main/java/io/dapr/config/Properties.java @@ -13,7 +13,6 @@ package io.dapr.config; -import io.dapr.client.DaprApiProtocol; import io.dapr.utils.NetworkUtils; import java.nio.charset.Charset; @@ -50,16 +49,6 @@ public class Properties { */ private static final Duration DEFAULT_API_TIMEOUT = Duration.ofMillis(0L); - /** - * Dapr's default use of gRPC or HTTP. - */ - private static final DaprApiProtocol DEFAULT_API_PROTOCOL = DaprApiProtocol.GRPC; - - /** - * Dapr's default use of gRPC or HTTP for Dapr's method invocation APIs. - */ - private static final DaprApiProtocol DEFAULT_API_METHOD_INVOCATION_PROTOCOL = DaprApiProtocol.HTTP; - /** * Dapr's default String encoding: UTF-8. */ @@ -143,28 +132,6 @@ public class Properties { "DAPR_API_TIMEOUT_MILLISECONDS", DEFAULT_API_TIMEOUT); - /** - * Determines if Dapr client will use gRPC or HTTP to talk to Dapr's side car. - * @deprecated This attribute will be deleted at SDK version 1.10. - */ - @Deprecated - public static final Property API_PROTOCOL = new GenericProperty<>( - "dapr.api.protocol", - "DAPR_API_PROTOCOL", - DEFAULT_API_PROTOCOL, - (s) -> DaprApiProtocol.valueOf(s.toUpperCase())); - - /** - * Determines if Dapr client should use gRPC or HTTP for Dapr's service method invocation APIs. - * @deprecated This attribute will be deleted at SDK version 1.10. - */ - @Deprecated - public static final Property API_METHOD_INVOCATION_PROTOCOL = new GenericProperty<>( - "dapr.api.methodInvocation.protocol", - "DAPR_API_METHOD_INVOCATION_PROTOCOL", - DEFAULT_API_METHOD_INVOCATION_PROTOCOL, - (s) -> DaprApiProtocol.valueOf(s.toUpperCase())); - /** * API token for authentication between App and Dapr's side car. */ diff --git a/sdk/src/main/java/io/dapr/exceptions/DaprException.java b/sdk/src/main/java/io/dapr/exceptions/DaprException.java index 40d48530f5..0801069e85 100644 --- a/sdk/src/main/java/io/dapr/exceptions/DaprException.java +++ b/sdk/src/main/java/io/dapr/exceptions/DaprException.java @@ -13,7 +13,9 @@ package io.dapr.exceptions; +import com.google.rpc.Status; import io.grpc.StatusRuntimeException; +import io.grpc.protobuf.StatusProto; import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -30,26 +32,32 @@ public class DaprException extends RuntimeException { /** * Dapr's error code for this exception. */ - private String errorCode; + private final String errorCode; /** * The status details for the error. */ - private DaprErrorDetails errorDetails; + private final DaprErrorDetails errorDetails; /** * Optional payload, if the exception came from a response body. */ - private byte[] payload; + private final byte[] payload; + + /** + * Optional HTTP status code, if error happened for an HTTP call (0 if not set). + */ + private final int httpStatusCode; /** * New exception from a server-side generated error code and message. * * @param daprError Server-side error. - * @param payload Payload containing the error. + * @param payload Optional payload containing the error. + * @param httpStatusCode Optional http Status Code (0 if not set). */ - public DaprException(DaprError daprError, byte[] payload) { - this(daprError.getErrorCode(), daprError.getMessage(), daprError.getDetails(), payload); + public DaprException(DaprError daprError, byte[] payload, int httpStatusCode) { + this(daprError.getErrorCode(), daprError.getMessage(), daprError.getDetails(), payload, httpStatusCode); } /** @@ -77,10 +85,11 @@ public DaprException(Throwable exception) { * * @param errorCode Client-side error code. * @param message Client-side error message. - * @param payload Error's raw payload. + * @param payload Optional payload containing the error. + * @param httpStatusCode Optional http Status Code (0 if not set). */ - public DaprException(String errorCode, String message, byte[] payload) { - this(errorCode, message, DaprErrorDetails.EMPTY_INSTANCE, payload); + public DaprException(String errorCode, String message, byte[] payload, int httpStatusCode) { + this(errorCode, message, DaprErrorDetails.EMPTY_INSTANCE, payload, httpStatusCode); } /** @@ -89,10 +98,12 @@ public DaprException(String errorCode, String message, byte[] payload) { * @param errorCode Client-side error code. * @param message Client-side error message. * @param errorDetails Details of the error from runtime. - * @param payload Payload containing the error. + * @param payload Optional payload containing the error. + * @param httpStatusCode Optional http Status Code (0 if not set). */ - public DaprException(String errorCode, String message, List> errorDetails, byte[] payload) { - this(errorCode, message, new DaprErrorDetails(errorDetails), payload); + public DaprException( + String errorCode, String message, List> errorDetails, byte[] payload, int httpStatusCode) { + this(errorCode, message, new DaprErrorDetails(errorDetails), payload, httpStatusCode); } /** @@ -101,10 +112,29 @@ public DaprException(String errorCode, String message, List> * @param errorCode Client-side error code. * @param message Client-side error message. * @param errorDetails Details of the error from runtime. - * @param payload Payload containing the error. + * @param payload Optional payload containing the error. */ public DaprException(String errorCode, String message, DaprErrorDetails errorDetails, byte[] payload) { - super(String.format("%s: %s", errorCode, message)); + this(errorCode, message, errorDetails, payload, 0); + } + + /** + * New Exception from a client-side generated error code and message. + * + * @param errorCode Client-side error code. + * @param message Client-side error message. + * @param errorDetails Details of the error from runtime. + * @param payload Optional payload containing the error. + * @param httpStatusCode Optional http Status Code (0 if not set). + */ + public DaprException( + String errorCode, + String message, + DaprErrorDetails errorDetails, + byte[] payload, + int httpStatusCode) { + super(buildErrorMessage(errorCode, httpStatusCode, message)); + this.httpStatusCode = httpStatusCode; this.errorCode = errorCode; this.errorDetails = errorDetails; this.payload = payload; @@ -120,8 +150,11 @@ public DaprException(String errorCode, String message, DaprErrorDetails errorDet * unknown.) */ public DaprException(String errorCode, String message, Throwable cause) { - super(String.format("%s: %s", errorCode, emptyIfNull(message)), cause); + super(buildErrorMessage(errorCode, 0, message), cause); + this.httpStatusCode = 0; this.errorCode = errorCode; + this.errorDetails = DaprErrorDetails.EMPTY_INSTANCE; + this.payload = null; } /** @@ -137,7 +170,8 @@ public DaprException(String errorCode, String message, Throwable cause) { */ public DaprException( String errorCode, String message, Throwable cause, DaprErrorDetails errorDetails, byte[] payload) { - super(String.format("%s: %s", errorCode, emptyIfNull(message)), cause); + super(buildErrorMessage(errorCode, 0, message), cause); + this.httpStatusCode = 0; this.errorCode = errorCode; this.errorDetails = errorDetails == null ? DaprErrorDetails.EMPTY_INSTANCE : errorDetails; this.payload = payload; @@ -170,6 +204,15 @@ public byte[] getPayload() { return this.payload == null ? null : this.payload.clone(); } + /** + * Returns the exception's http status code, 0 if not applicable. + * + * @return Http status code (0 if not applicable). + */ + public int getHttpStatusCode() { + return this.httpStatusCode; + } + /** * Wraps an exception into DaprException (if not already DaprException). * @@ -266,7 +309,7 @@ public static RuntimeException propagate(Throwable exception) { while (e != null) { if (e instanceof StatusRuntimeException) { StatusRuntimeException statusRuntimeException = (StatusRuntimeException) e; - com.google.rpc.Status status = io.grpc.protobuf.StatusProto.fromThrowable(statusRuntimeException); + Status status = StatusProto.fromThrowable(statusRuntimeException); DaprErrorDetails errorDetails = new DaprErrorDetails(status); @@ -289,11 +332,18 @@ public static RuntimeException propagate(Throwable exception) { return new DaprException(exception); } - private static String emptyIfNull(String str) { - if (str == null) { - return ""; + private static String buildErrorMessage(String errorCode, int httpStatusCode, String message) { + String result = ((errorCode == null) || errorCode.isEmpty()) ? "UNKNOWN: " : errorCode + ": "; + if ((message == null) || message.isEmpty()) { + if (httpStatusCode > 0) { + return result + "HTTP status code: " + httpStatusCode; + } + return result; } - return str; + if (httpStatusCode > 0) { + return result + message + " (HTTP status code: " + httpStatusCode + ")"; + } + return result + message; } } diff --git a/sdk/src/main/java/io/dapr/internal/grpc/DaprClientGrpcInterceptors.java b/sdk/src/main/java/io/dapr/internal/grpc/DaprClientGrpcInterceptors.java new file mode 100644 index 0000000000..9a47d1aeb8 --- /dev/null +++ b/sdk/src/main/java/io/dapr/internal/grpc/DaprClientGrpcInterceptors.java @@ -0,0 +1,139 @@ +/* + * Copyright 2024 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.internal.grpc; + +import io.dapr.internal.grpc.interceptors.DaprApiTokenInterceptor; +import io.dapr.internal.grpc.interceptors.DaprAppIdInterceptor; +import io.dapr.internal.grpc.interceptors.DaprTimeoutInterceptor; +import io.dapr.internal.grpc.interceptors.DaprTracingInterceptor; +import io.dapr.internal.resiliency.TimeoutPolicy; +import io.grpc.stub.AbstractStub; +import reactor.util.context.ContextView; + +/** + * Class to be used as part of your service's client stub interceptor. + * Usage: myClientStub = DaprClientGrpcInterceptors.intercept(myClientStub); + */ +public class DaprClientGrpcInterceptors { + + /** + * Adds all Dapr interceptors to a gRPC async stub. + * @param appId the appId to be invoked + * @param client gRPC client + * @param async client type + * @return async client instance with interceptors + */ + public static > T intercept(final String appId, final T client) { + return intercept(appId, client, null, null); + } + + /** + * Adds all Dapr interceptors to a gRPC async stub. + * @param client gRPC client + * @param async client type + * @return async client instance with interceptors + */ + public static > T intercept(final T client) { + return intercept(null, client, null, null); + } + + /** + * Adds all Dapr interceptors to a gRPC async stub. + * @param appId the appId to be invoked + * @param client gRPC client + * @param timeoutPolicy timeout policy for gRPC call + * @param async client type + * @return async client instance with interceptors + */ + public static > T intercept( + final String appId, final T client, final TimeoutPolicy timeoutPolicy) { + return intercept(appId, client, timeoutPolicy, null); + } + + /** + * Adds all Dapr interceptors to a gRPC async stub. + * @param client gRPC client + * @param timeoutPolicy timeout policy for gRPC call + * @param async client type + * @return async client instance with interceptors + */ + public static > T intercept(final T client, final TimeoutPolicy timeoutPolicy) { + return intercept(null, client, timeoutPolicy, null); + } + + /** + * Adds all Dapr interceptors to a gRPC async stub. + * @param appId the appId to be invoked + * @param client gRPC client + * @param context Reactor context for tracing + * @param async client type + * @return async client instance with interceptors + */ + public static > T intercept( + final String appId, final T client, final ContextView context) { + return intercept(appId, client, null, context); + } + + /** + * Adds all Dapr interceptors to a gRPC async stub. + * @param client gRPC client + * @param context Reactor context for tracing + * @param async client type + * @return async client instance with interceptors + */ + public static > T intercept(final T client, final ContextView context) { + return intercept(null, client, null, context); + } + + /** + * Adds all Dapr interceptors to a gRPC async stub. + * @param client gRPC client + * @param timeoutPolicy timeout policy for gRPC call + * @param context Reactor context for tracing + * @param async client type + * @return async client instance with interceptors + */ + public static > T intercept( + final T client, + final TimeoutPolicy timeoutPolicy, + final ContextView context) { + return intercept(null, client, timeoutPolicy, context); + } + + /** + * Adds all Dapr interceptors to a gRPC async stub. + * @param appId the appId to be invoked + * @param client gRPC client + * @param timeoutPolicy timeout policy for gRPC call + * @param context Reactor context for tracing + * @param async client type + * @return async client instance with interceptors + */ + public static > T intercept( + final String appId, + final T client, + final TimeoutPolicy timeoutPolicy, + final ContextView context) { + if (client == null) { + throw new IllegalArgumentException("client cannot be null"); + } + + return client.withInterceptors( + new DaprAppIdInterceptor(appId), + new DaprApiTokenInterceptor(), + new DaprTimeoutInterceptor(timeoutPolicy), + new DaprTracingInterceptor(context)); + } + +} diff --git a/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprApiTokenInterceptor.java b/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprApiTokenInterceptor.java new file mode 100644 index 0000000000..cda6e896b9 --- /dev/null +++ b/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprApiTokenInterceptor.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 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.internal.grpc.interceptors; + +import io.dapr.client.Headers; +import io.dapr.config.Properties; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; + +/** + * Class to be used as part of your service's client stub interceptor to include Dapr tokens. + */ +public class DaprApiTokenInterceptor implements ClientInterceptor { + + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, + CallOptions options, + Channel channel) { + ClientCall clientCall = channel.newCall(methodDescriptor, options); + return new ForwardingClientCall.SimpleForwardingClientCall<>(clientCall) { + @Override + public void start(final Listener responseListener, final Metadata metadata) { + String daprApiToken = Properties.API_TOKEN.get(); + if (daprApiToken != null) { + metadata.put(Metadata.Key.of(Headers.DAPR_API_TOKEN, Metadata.ASCII_STRING_MARSHALLER), daprApiToken); + } + super.start(responseListener, metadata); + } + }; + } + +} diff --git a/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprAppIdInterceptor.java b/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprAppIdInterceptor.java new file mode 100644 index 0000000000..33715e73d2 --- /dev/null +++ b/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprAppIdInterceptor.java @@ -0,0 +1,63 @@ +/* + * Copyright 2024 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.internal.grpc.interceptors; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; + +/** + * Class to be used as part of your service's client stub interceptor to include Dapr App Id metadata. + */ +public class DaprAppIdInterceptor implements ClientInterceptor { + + private final Metadata extraHeaders; + + public DaprAppIdInterceptor(String appId) { + this.extraHeaders = buildMetadata(appId); + } + + private static final Metadata buildMetadata(String appId) { + if (appId == null) { + return null; + } + + Metadata headers = new Metadata(); + headers.put(Metadata.Key.of("dapr-app-id", Metadata.ASCII_STRING_MARSHALLER), appId); + return headers; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, + CallOptions options, + Channel channel) { + ClientCall clientCall = channel.newCall(methodDescriptor, options); + final Metadata extraHeaders = this.extraHeaders; + return new ForwardingClientCall.SimpleForwardingClientCall<>(clientCall) { + @Override + public void start(ClientCall.Listener responseListener, Metadata headers) { + if (extraHeaders != null) { + headers.merge(extraHeaders); + } + super.start(responseListener, headers); + } + }; + } + +} diff --git a/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprTimeoutInterceptor.java b/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprTimeoutInterceptor.java new file mode 100644 index 0000000000..5ab17ba816 --- /dev/null +++ b/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprTimeoutInterceptor.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024 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.internal.grpc.interceptors; + +import io.dapr.internal.resiliency.TimeoutPolicy; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.MethodDescriptor; + +/** + * Class to be used as part of your service's client stub interceptor to include timeout. + */ +public class DaprTimeoutInterceptor implements ClientInterceptor { + + private final TimeoutPolicy timeoutPolicy; + + public DaprTimeoutInterceptor(TimeoutPolicy timeoutPolicy) { + this.timeoutPolicy = timeoutPolicy; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, + CallOptions options, + Channel channel) { + if (timeoutPolicy == null) { + return channel.newCall(methodDescriptor, options); + } + + return channel.newCall(methodDescriptor, timeoutPolicy.apply(options)); + } + +} diff --git a/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprTracingInterceptor.java b/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprTracingInterceptor.java new file mode 100644 index 0000000000..d93838de9d --- /dev/null +++ b/sdk/src/main/java/io/dapr/internal/grpc/interceptors/DaprTracingInterceptor.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 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.internal.grpc.interceptors; + +import io.dapr.internal.opencensus.GrpcHelper; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import reactor.util.context.ContextView; + +/** + * Injects tracing headers to gRPC metadata. + */ +public class DaprTracingInterceptor implements ClientInterceptor { + + private final ContextView context; + + /** + * Creates an instance of the injector for gRPC context from Reactor's context. + * @param context Reactor's context + */ + public DaprTracingInterceptor(ContextView context) { + this.context = context; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, + CallOptions callOptions, + Channel channel) { + ClientCall clientCall = channel.newCall(methodDescriptor, callOptions); + return new ForwardingClientCall.SimpleForwardingClientCall<>(clientCall) { + @Override + public void start(final Listener responseListener, final Metadata metadata) { + if (context != null) { + GrpcHelper.populateMetadata(context, metadata); + } + super.start(responseListener, metadata); + } + }; + } + +} diff --git a/sdk/src/main/java/io/dapr/internal/opencensus/GrpcHelper.java b/sdk/src/main/java/io/dapr/internal/opencensus/GrpcHelper.java new file mode 100644 index 0000000000..5845f9f9ba --- /dev/null +++ b/sdk/src/main/java/io/dapr/internal/opencensus/GrpcHelper.java @@ -0,0 +1,96 @@ +/* + * 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.internal.opencensus; + +import io.grpc.Metadata; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Helper to extract tracing information for gRPC calls. + */ +public final class GrpcHelper { + + private static final Logger LOGGER = Logger.getLogger(GrpcHelper.class.getName()); + + /** + * Binary formatter to generate grpc-trace-bin. + */ + private static final BinaryFormatImpl OPENCENSUS_BINARY_FORMAT = new BinaryFormatImpl(); + + private static final Metadata.Key GRPC_TRACE_BIN_KEY = + Metadata.Key.of("grpc-trace-bin", Metadata.BINARY_BYTE_MARSHALLER); + + private static final Metadata.Key TRACEPARENT_KEY = + Metadata.Key.of("traceparent", Metadata.ASCII_STRING_MARSHALLER); + + private static final Metadata.Key TRACESTATE_KEY = + Metadata.Key.of("tracestate", Metadata.ASCII_STRING_MARSHALLER); + + private GrpcHelper() { + } + + /** + * Populates GRPC client's metadata with tracing headers. + * + * @param context Reactor's context. + * @param metadata GRPC client metadata to be populated. + */ + public static void populateMetadata(final ContextView context, final Metadata metadata) { + Map map = (context == null ? Context.empty() : context) + .stream() + .filter(e -> (e.getKey() != null) && (e.getValue() != null)) + .collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue())); + if (map.containsKey(GRPC_TRACE_BIN_KEY.name())) { + byte[] value = (byte[]) map.get(GRPC_TRACE_BIN_KEY.name()); + metadata.put(GRPC_TRACE_BIN_KEY, value); + } + if (map.containsKey(TRACEPARENT_KEY.name())) { + String value = map.get(TRACEPARENT_KEY.name()).toString(); + metadata.put(TRACEPARENT_KEY, value); + } + if (map.containsKey(TRACESTATE_KEY.name())) { + String value = map.get(TRACESTATE_KEY.name()).toString(); + metadata.put(TRACESTATE_KEY, value); + } + + // Dapr only supports "grpc-trace-bin" for GRPC and OpenTelemetry SDK does not support that yet: + // https://github.com/open-telemetry/opentelemetry-specification/issues/639 + // This should be the only use of OpenCensus SDK: populate "grpc-trace-bin". + SpanContext opencensusSpanContext = extractOpenCensusSpanContext(metadata); + if (opencensusSpanContext != null) { + byte[] grpcTraceBin = OPENCENSUS_BINARY_FORMAT.toByteArray(opencensusSpanContext); + metadata.put(GRPC_TRACE_BIN_KEY, grpcTraceBin); + } + } + + private static SpanContext extractOpenCensusSpanContext(Metadata metadata) { + if (!metadata.keys().contains(TRACEPARENT_KEY.name())) { + // Trying to extract context without this key will throw an "expected" exception, so we avoid it here. + return null; + } + + try { + return TraceContextFormat.extract(metadata); + } catch (RuntimeException e) { + LOGGER.log(Level.FINE, "Could not extract span context.", e); + return null; + } + } +} diff --git a/sdk/src/main/java/io/dapr/internal/opencensus/GrpcWrapper.java b/sdk/src/main/java/io/dapr/internal/opencensus/GrpcWrapper.java deleted file mode 100644 index 61db83627b..0000000000 --- a/sdk/src/main/java/io/dapr/internal/opencensus/GrpcWrapper.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.internal.opencensus; - -import io.dapr.config.Property; -import io.dapr.v1.DaprGrpc; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; -import io.grpc.ForwardingClientCall; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import reactor.util.context.Context; -import reactor.util.context.ContextView; - -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -/** - * Wraps a Dapr gRPC stub with telemetry interceptor. - */ -public final class GrpcWrapper { - - private static final Logger LOGGER = Logger.getLogger(Property.class.getName()); - - /** - * Binary formatter to generate grpc-trace-bin. - */ - private static final BinaryFormatImpl OPENCENSUS_BINARY_FORMAT = new BinaryFormatImpl(); - - private static final Metadata.Key GRPC_TRACE_BIN_KEY = - Metadata.Key.of("grpc-trace-bin", Metadata.BINARY_BYTE_MARSHALLER); - - private static final Metadata.Key TRACEPARENT_KEY = - Metadata.Key.of("traceparent", Metadata.ASCII_STRING_MARSHALLER); - - private static final Metadata.Key TRACESTATE_KEY = - Metadata.Key.of("tracestate", Metadata.ASCII_STRING_MARSHALLER); - - private GrpcWrapper() { - } - - /** - * Populates GRPC client with interceptors. - * - * @param context Reactor's context. - * @param client GRPC client for Dapr. - * @return Client after adding interceptors. - */ - public static DaprGrpc.DaprStub intercept(final ContextView context, DaprGrpc.DaprStub client) { - ClientInterceptor interceptor = new ClientInterceptor() { - @Override - public ClientCall interceptCall( - MethodDescriptor methodDescriptor, - CallOptions callOptions, - Channel channel) { - ClientCall clientCall = channel.newCall(methodDescriptor, callOptions); - return new ForwardingClientCall.SimpleForwardingClientCall(clientCall) { - @Override - public void start(final Listener responseListener, final Metadata metadata) { - Map map = (context == null ? Context.empty() : context) - .stream() - .filter(e -> (e.getKey() != null) && (e.getValue() != null)) - .collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue())); - if (map.containsKey(GRPC_TRACE_BIN_KEY.name())) { - byte[] value = (byte[]) map.get(GRPC_TRACE_BIN_KEY.name()); - metadata.put(GRPC_TRACE_BIN_KEY, value); - } - if (map.containsKey(TRACEPARENT_KEY.name())) { - String value = map.get(TRACEPARENT_KEY.name()).toString(); - metadata.put(TRACEPARENT_KEY, value); - } - if (map.containsKey(TRACESTATE_KEY.name())) { - String value = map.get(TRACESTATE_KEY.name()).toString(); - metadata.put(TRACESTATE_KEY, value); - } - - // Dapr only supports "grpc-trace-bin" for GRPC and OpenTelemetry SDK does not support that yet: - // https://github.com/open-telemetry/opentelemetry-specification/issues/639 - // This should be the only use of OpenCensus SDK: populate "grpc-trace-bin". - SpanContext opencensusSpanContext = extractOpenCensusSpanContext(metadata); - if (opencensusSpanContext != null) { - byte[] grpcTraceBin = OPENCENSUS_BINARY_FORMAT.toByteArray(opencensusSpanContext); - metadata.put(GRPC_TRACE_BIN_KEY, grpcTraceBin); - } - - super.start(responseListener, metadata); - } - }; - } - }; - return client.withInterceptors(interceptor); - } - - private static SpanContext extractOpenCensusSpanContext(Metadata metadata) { - if (!metadata.keys().contains(TRACEPARENT_KEY.name())) { - // Trying to extract context without this key will throw an "expected" exception, so we avoid it here. - return null; - } - - try { - return TraceContextFormat.extract(metadata); - } catch (RuntimeException e) { - LOGGER.log(Level.FINE, "Could not extract span context.", e); - return null; - } - } -} diff --git a/sdk/src/main/java/io/dapr/utils/TypeRef.java b/sdk/src/main/java/io/dapr/utils/TypeRef.java index c606d6da7f..983d748fd3 100644 --- a/sdk/src/main/java/io/dapr/utils/TypeRef.java +++ b/sdk/src/main/java/io/dapr/utils/TypeRef.java @@ -13,8 +13,6 @@ package io.dapr.utils; -import com.fasterxml.jackson.databind.JavaType; - import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; diff --git a/sdk/src/test/java/io/dapr/client/CloudEventCustom.java b/sdk/src/test/java/io/dapr/client/CloudEventCustom.java index 4c581d819a..bc055f3ee1 100644 --- a/sdk/src/test/java/io/dapr/client/CloudEventCustom.java +++ b/sdk/src/test/java/io/dapr/client/CloudEventCustom.java @@ -15,11 +15,8 @@ import io.dapr.client.domain.CloudEvent; -import java.util.Arrays; import java.util.Objects; -import java.io.IOException; - public class CloudEventCustom extends CloudEvent { diff --git a/sdk/src/test/java/io/dapr/client/CloudEventCustomTest.java b/sdk/src/test/java/io/dapr/client/CloudEventCustomTest.java index 3d62fcfbd2..803203a34c 100644 --- a/sdk/src/test/java/io/dapr/client/CloudEventCustomTest.java +++ b/sdk/src/test/java/io/dapr/client/CloudEventCustomTest.java @@ -13,16 +13,15 @@ package io.dapr.client; +import io.dapr.serializer.DaprObjectSerializer; +import io.dapr.serializer.DefaultObjectSerializer; +import io.dapr.utils.TypeRef; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import io.dapr.serializer.DaprObjectSerializer; -import io.dapr.serializer.DefaultObjectSerializer; -import io.dapr.utils.TypeRef; - public class CloudEventCustomTest { private DaprObjectSerializer serializer = new DefaultObjectSerializer(); diff --git a/sdk/src/test/java/io/dapr/client/DaprClientBuilderTest.java b/sdk/src/test/java/io/dapr/client/DaprClientBuilderTest.java index 2b31a8879f..bd94043bcf 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientBuilderTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientBuilderTest.java @@ -16,7 +16,6 @@ import io.dapr.serializer.DaprObjectSerializer; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; diff --git a/sdk/src/test/java/io/dapr/client/DaprClientGrpcTelemetryTest.java b/sdk/src/test/java/io/dapr/client/DaprClientGrpcTelemetryTest.java index ae62a59234..4b8b80317d 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientGrpcTelemetryTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientGrpcTelemetryTest.java @@ -15,10 +15,10 @@ import io.dapr.client.domain.HttpExtension; import io.dapr.client.domain.InvokeMethodRequest; -import io.dapr.serializer.DefaultObjectSerializer; -import io.dapr.utils.TypeRef; +import io.dapr.internal.grpc.DaprClientGrpcInterceptors; import io.dapr.v1.CommonProtos; import io.dapr.v1.DaprGrpc; +import io.dapr.v1.DaprProtos; import io.grpc.ManagedChannel; import io.grpc.Metadata; import io.grpc.ServerCall; @@ -28,6 +28,7 @@ import io.grpc.ServerServiceDefinition; import io.grpc.inprocess.InProcessChannelBuilder; import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; import org.junit.Rule; import org.junit.jupiter.api.AfterEach; @@ -36,15 +37,16 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mockito; import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoSink; import reactor.util.context.Context; +import reactor.util.context.ContextView; import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; import java.util.stream.Stream; -import reactor.util.context.ContextView; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -67,6 +69,8 @@ public class DaprClientGrpcTelemetryTest { @Rule public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); + private DaprGrpc.DaprStub daprStub; + private DaprClient client; @AfterEach @@ -144,10 +148,7 @@ public ServerCall.Listener interceptCall(ServerCall ServerCall.Listener interceptCall(ServerCall result = this.client.invokeMethod(req, TypeRef.get(Void.class)) + Mono result = this.invoke() .contextWrite(it -> it.putAll(contextCopy)); result.block(); } @@ -233,8 +227,42 @@ public void invokeServiceVoidWithTracingTestAndEmptyContext() throws IOException InvokeMethodRequest req = new InvokeMethodRequest("appId", "method") .setBody("request") .setHttpExtension(HttpExtension.NONE); - Mono result = this.client.invokeMethod(req, TypeRef.get(Void.class)) + Mono result = this.invoke() .contextWrite(it -> it.putAll(contextCopy == null ? (ContextView) Context.empty() : contextCopy)); result.block(); } + + private Mono invoke() { + DaprProtos.InvokeServiceRequest req = + DaprProtos.InvokeServiceRequest.newBuilder() + .build(); + return Mono.deferContextual( + context -> this.createMono( + it -> DaprClientGrpcInterceptors.intercept(daprStub, context).invokeService(req, it) + ) + ).then(); + } + + private Mono createMono(Consumer> consumer) { + return Mono.create(sink -> consumer.accept(createStreamObserver(sink))); + } + + private StreamObserver createStreamObserver(MonoSink sink) { + return new StreamObserver() { + @Override + public void onNext(T value) { + sink.success(value); + } + + @Override + public void onError(Throwable t) { + sink.error(new ExecutionException(t)); + } + + @Override + public void onCompleted() { + sink.success(); + } + }; + } } diff --git a/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java b/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java index 33be4e32aa..1081220a9f 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java @@ -16,16 +16,21 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.Empty; +import io.dapr.client.domain.AppConnectionPropertiesHealthMetadata; +import io.dapr.client.domain.AppConnectionPropertiesMetadata; +import io.dapr.client.domain.ComponentMetadata; import io.dapr.client.domain.ConfigurationItem; +import io.dapr.client.domain.DaprMetadata; import io.dapr.client.domain.DeleteStateRequest; import io.dapr.client.domain.ExecuteStateTransactionRequest; import io.dapr.client.domain.GetBulkStateRequest; import io.dapr.client.domain.GetStateRequest; -import io.dapr.client.domain.HttpExtension; import io.dapr.client.domain.PublishEventRequest; +import io.dapr.client.domain.RuleMetadata; import io.dapr.client.domain.State; import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.SubscribeConfigurationResponse; +import io.dapr.client.domain.SubscriptionMetadata; import io.dapr.client.domain.TransactionalStateOperation; import io.dapr.client.domain.UnsubscribeConfigurationRequest; import io.dapr.client.domain.UnsubscribeConfigurationResponse; @@ -35,6 +40,14 @@ import io.dapr.v1.CommonProtos; import io.dapr.v1.DaprGrpc; import io.dapr.v1.DaprProtos; +import io.dapr.v1.DaprProtos.ActiveActorsCount; +import io.dapr.v1.DaprProtos.ActorRuntime; +import io.dapr.v1.DaprProtos.AppConnectionHealthProperties; +import io.dapr.v1.DaprProtos.AppConnectionProperties; +import io.dapr.v1.DaprProtos.MetadataHTTPEndpoint; +import io.dapr.v1.DaprProtos.PubsubSubscription; +import io.dapr.v1.DaprProtos.PubsubSubscriptionRules; +import io.dapr.v1.DaprProtos.RegisteredComponents; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.protobuf.StatusProto; @@ -45,7 +58,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; import org.mockito.stubbing.Answer; import reactor.core.publisher.Mono; @@ -63,7 +75,6 @@ import java.util.stream.Collectors; import static io.dapr.utils.TestUtils.assertThrowsDaprException; -import static io.dapr.utils.TestUtils.findFreePort; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -72,7 +83,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.argThat; +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.when; public class DaprClientGrpcTest { @@ -84,6 +100,7 @@ public class DaprClientGrpcTest { private GrpcChannelFacade channel; private DaprGrpc.DaprStub daprStub; + private DaprHttp daprHttp; private DaprClient client; private ObjectSerializer serializer; @@ -91,10 +108,10 @@ public class DaprClientGrpcTest { public void setup() throws IOException { channel = mock(GrpcChannelFacade.class); daprStub = mock(DaprGrpc.DaprStub.class); + daprHttp = mock(DaprHttp.class); when(daprStub.withInterceptors(any())).thenReturn(daprStub); - DaprClient grpcClient = new DaprClientGrpc( - channel, daprStub, new DefaultObjectSerializer(), new DefaultObjectSerializer()); - client = new DaprClientProxy(grpcClient); + client = new DaprClientImpl( + channel, daprStub, daprHttp, new DefaultObjectSerializer(), new DefaultObjectSerializer()); serializer = new ObjectSerializer(); doNothing().when(channel).close(); } @@ -105,18 +122,6 @@ public void tearDown() throws Exception { verify(channel).close(); } - @Test - public void waitForSidecarTimeout() { - Mockito.doReturn(Mono.error(new RuntimeException())).when(channel).waitForChannelReady(1); - assertThrows(RuntimeException.class, () -> client.waitForSidecar(1).block()); - } - - @Test - public void waitForSidecarOK() { - Mockito.doReturn(Mono.empty()).when(channel).waitForChannelReady(10000); - client.waitForSidecar(10000).block(); - } - @Test public void publishEventExceptionThrownTest() { doAnswer((Answer) invocation -> { @@ -150,7 +155,7 @@ public void publishEventCallbackExceptionThrownTest() { @Test public void publishEventSerializeException() throws IOException { DaprObjectSerializer mockSerializer = mock(DaprObjectSerializer.class); - client = new DaprClientGrpc(channel, daprStub, mockSerializer, new DefaultObjectSerializer()); + client = new DaprClientImpl(channel, daprStub, daprHttp, mockSerializer, new DefaultObjectSerializer()); doAnswer((Answer) invocation -> { StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; observer.onNext(Empty.getDefaultInstance()); @@ -268,7 +273,7 @@ public void invokeBindingIllegalArgumentExceptionTest() { @Test public void invokeBindingSerializeException() throws IOException { DaprObjectSerializer mockSerializer = mock(DaprObjectSerializer.class); - client = new DaprClientGrpc(channel, daprStub, mockSerializer, new DefaultObjectSerializer()); + client = new DaprClientImpl(channel, daprStub, daprHttp, mockSerializer, new DefaultObjectSerializer()); doAnswer((Answer) invocation -> { StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; observer.onNext(Empty.getDefaultInstance()); @@ -419,440 +424,6 @@ public void invokeBindingObjectNoHotMono() throws IOException { assertFalse(called.get()); } - @Test - public void invokeServiceVoidExceptionThrownTest() { - doAnswer((Answer) invocation -> { - throw new RuntimeException(); - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", "request", HttpExtension.NONE); - - assertThrowsDaprException( - RuntimeException.class, - "UNKNOWN", - "UNKNOWN: ", - () -> result.block()); - } - - @Test - public void invokeServiceIllegalArgumentExceptionThrownTest() { - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny("Value")).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - // HttpExtension cannot be null - Mono result = client.invokeMethod("appId", "method", "request", null); - - assertThrows(IllegalArgumentException.class, () -> result.block()); - } - - @Test - public void invokeServiceEmptyRequestVoidExceptionThrownTest() { - doAnswer((Answer) invocation -> { - throw new RuntimeException(); - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", HttpExtension.NONE, (Map)null); - - assertThrowsDaprException( - RuntimeException.class, - "UNKNOWN", - "UNKNOWN: ", - () -> result.block()); - } - - @Test - public void invokeServiceVoidCallbackExceptionThrownTest() { - RuntimeException ex = new RuntimeException("An Exception"); - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onError(ex); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", "request", HttpExtension.NONE); - - assertThrowsDaprException( - ExecutionException.class, - "UNKNOWN", - "UNKNOWN: java.lang.RuntimeException: An Exception", - () -> result.block()); - } - - @Test - public void invokeServiceVoidTest() { - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny("Value")).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", "request", HttpExtension.NONE); - result.block(); - } - - @Test - public void invokeServiceVoidObjectTest() { - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny("Value")).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - MyObject request = new MyObject(1, "Event"); - Mono result = client.invokeMethod("appId", "method", request, HttpExtension.NONE); - result.block(); - } - - @Test - public void invokeServiceExceptionThrownTest() { - doAnswer((Answer) invocation -> { - throw new RuntimeException(); - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", "request", HttpExtension.NONE, null, String.class); - - assertThrowsDaprException( - RuntimeException.class, - "UNKNOWN", - "UNKNOWN: ", - () -> result.block()); - } - - @Test - public void invokeServiceNoRequestClassExceptionThrownTest() { - doAnswer((Answer) invocation -> { - throw new RuntimeException(); - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", HttpExtension.NONE, (Map)null, String.class); - - assertThrowsDaprException( - RuntimeException.class, - "UNKNOWN", - "UNKNOWN: ", - () -> result.block()); - } - - @Test - public void invokeServiceNoRequestTypeRefExceptionThrownTest() { - doAnswer((Answer) invocation -> { - throw new RuntimeException(); - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", HttpExtension.NONE, (Map)null, TypeRef.STRING); - - assertThrowsDaprException( - RuntimeException.class, - "UNKNOWN", - "UNKNOWN: ", - () -> result.block()); - } - - @Test - public void invokeServiceCallbackExceptionThrownTest() { - RuntimeException ex = new RuntimeException("An Exception"); - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onError(ex); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", "request", HttpExtension.NONE, null, String.class); - - assertThrowsDaprException( - ExecutionException.class, - "UNKNOWN", - "UNKNOWN: java.lang.RuntimeException: An Exception", - () -> result.block()); - } - - @Test - public void invokeServiceWithHttpExtensionTest() throws IOException { - HttpExtension httpExtension = new HttpExtension( - DaprHttp.HttpMethods.GET, Collections.singletonMap("test", Arrays.asList("1", "ab/c")), null); - CommonProtos.InvokeRequest message = CommonProtos.InvokeRequest.newBuilder() - .setMethod("method") - .setData(getAny("request")) - .setContentType("application/json") - .setHttpExtension(CommonProtos.HTTPExtension.newBuilder() - .setVerb(CommonProtos.HTTPExtension.Verb.GET) - .setQuerystring("test=1&test=ab%2Fc").build()) - .build(); - DaprProtos.InvokeServiceRequest request = DaprProtos.InvokeServiceRequest.newBuilder() - .setId("appId") - .setMessage(message) - .build(); - String expected = "Value"; - - doAnswer(invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny(expected)).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(eq(request), any()); - - Mono result = client.invokeMethod("appId", "method", "request", httpExtension, null, String.class); - String strOutput = result.block(); - assertEquals(expected, strOutput); - } - - @Test - public void invokeServiceTest() { - String expected = "Value"; - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny(expected)).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", "request", HttpExtension.NONE, null, String.class); - String strOutput = result.block(); - - assertEquals(expected, strOutput); - } - - @Test - public void invokeServiceObjectTest() throws Exception { - MyObject object = new MyObject(1, "Value"); - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny(object)).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", "request", HttpExtension.NONE, null, MyObject.class); - MyObject resultObject = result.block(); - - assertEquals(object.id, resultObject.id); - assertEquals(object.value, resultObject.value); - } - - @Test - public void invokeServiceNoRequestBodyExceptionThrownTest() { - doAnswer((Answer) invocation -> { - throw new RuntimeException(); - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", (Object)null, HttpExtension.NONE, String.class); - - assertThrowsDaprException( - RuntimeException.class, - "UNKNOWN", - "UNKNOWN: ", - () -> result.block()); - } - - @Test - public void invokeServiceNoRequestCallbackExceptionThrownTest() { - RuntimeException ex = new RuntimeException("An Exception"); - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onError(ex); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", (Object)null, HttpExtension.NONE, String.class); - - assertThrowsDaprException( - ExecutionException.class, - "UNKNOWN", - "UNKNOWN: java.lang.RuntimeException: An Exception", - () -> result.block()); - } - - @Test - public void invokeServiceNoRequestBodyTest() throws Exception { - String expected = "Value"; - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny(expected)).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", (Object)null, HttpExtension.NONE, String.class); - String strOutput = result.block(); - - assertEquals(expected, strOutput); - } - - @Test - public void invokeServiceNoRequestBodyObjectTest() throws Exception { - MyObject object = new MyObject(1, "Value"); - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny(object)).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", (Object)null, HttpExtension.NONE, MyObject.class); - MyObject resultObject = result.block(); - - assertEquals(object.id, resultObject.id); - assertEquals(object.value, resultObject.value); - } - - @Test - public void invokeServiceByteRequestExceptionThrownTest() throws IOException { - doAnswer((Answer) invocation -> { - throw new RuntimeException(); - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - String request = "Request"; - byte[] byteRequest = serializer.serialize(request); - - Mono result = client.invokeMethod("appId", "method", byteRequest, HttpExtension.NONE, byte[].class); - - assertThrowsDaprException( - RuntimeException.class, - "UNKNOWN", - "UNKNOWN: ", - () -> result.block()); - } - - @Test - public void invokeServiceByteRequestCallbackExceptionThrownTest() throws IOException { - RuntimeException ex = new RuntimeException("An Exception"); - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onError(ex); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - String request = "Request"; - byte[] byteRequest = serializer.serialize(request); - - Mono result = - client.invokeMethod("appId", "method", byteRequest, HttpExtension.NONE,(HashMap) null); - - assertThrowsDaprException( - ExecutionException.class, - "UNKNOWN", - "UNKNOWN: java.lang.RuntimeException: An Exception", - () -> result.block()); - } - - @Test - public void invokeByteRequestServiceTest() throws Exception { - String expected = "Value"; - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny(expected)).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - String request = "Request"; - byte[] byteRequest = serializer.serialize(request); - - Mono result = client.invokeMethod( - "appId", "method", byteRequest, HttpExtension.NONE, (HashMap) null); - byte[] byteOutput = result.block(); - - String strOutput = serializer.deserialize(byteOutput, String.class); - assertEquals(expected, strOutput); - } - - @Test - public void invokeServiceByteRequestObjectTest() throws Exception { - MyObject resultObj = new MyObject(1, "Value"); - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny(resultObj)).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - String request = "Request"; - byte[] byteRequest = serializer.serialize(request); - Mono result = client.invokeMethod("appId", "method", byteRequest, HttpExtension.NONE, byte[].class); - byte[] byteOutput = result.block(); - - assertEquals(resultObj, serializer.deserialize(byteOutput, MyObject.class)); - } - - @Test - public void invokeServiceNoRequestNoClassBodyExceptionThrownTest() { - doAnswer((Answer) invocation -> { - throw new RuntimeException(); - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - Mono result = client.invokeMethod("appId", "method", (Object)null, HttpExtension.NONE); - - assertThrowsDaprException( - RuntimeException.class, - "UNKNOWN", - "UNKNOWN: ", - () -> result.block()); - } - - @Test - public void invokeServiceNoRequestNoClassCallbackExceptionThrownTest() { - RuntimeException ex = new RuntimeException("An Exception"); - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onError(ex); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", (Object)null, HttpExtension.NONE); - - assertThrowsDaprException( - ExecutionException.class, - "UNKNOWN", - "UNKNOWN: java.lang.RuntimeException: An Exception", - () -> result.block()); - } - - @Test - public void invokeServiceNoRequestNoClassBodyTest() { - String expected = "Value"; - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny(expected)).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", (Object)null, HttpExtension.NONE); - result.block(); - } - - @Test - public void invokeServiceNoRequestNoHotMono() { - AtomicBoolean called = new AtomicBoolean(false); - String expected = "Value"; - doAnswer((Answer) invocation -> { - called.set(true); - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny(expected)).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - client.invokeMethod("appId", "method", (Object)null, HttpExtension.NONE); - // Do not call block() on mono above, so nothing should happen. - assertFalse(called.get()); - } - - @Test - public void invokeServiceNoRequestNoClassBodyObjectTest() throws Exception { - MyObject resultObj = new MyObject(1, "Value"); - doAnswer((Answer) invocation -> { - StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; - observer.onNext(CommonProtos.InvokeResponse.newBuilder().setData(getAny(resultObj)).build()); - observer.onCompleted(); - return null; - }).when(daprStub).invokeService(any(DaprProtos.InvokeServiceRequest.class), any()); - - Mono result = client.invokeMethod("appId", "method", (Object)null, HttpExtension.NONE); - result.block(); - } - @Test public void getStateIllegalArgumentExceptionTest() { State key = buildStateKey(null, "Key1", "ETag1", null); @@ -1429,7 +1000,7 @@ public void executeTransactionIllegalArgumentExceptionTest() { @Test public void executeTransactionSerializerExceptionTest() throws IOException { DaprObjectSerializer mockSerializer = mock(DaprObjectSerializer.class); - client = new DaprClientGrpc(channel, daprStub, mockSerializer, mockSerializer); + client = new DaprClientImpl(channel, daprStub, daprHttp, mockSerializer, mockSerializer); String etag = "ETag1"; String key = "key1"; String data = "my data"; @@ -2521,4 +2092,148 @@ public static StatusRuntimeException newStatusRuntimeException(String statusCode return StatusProto.toStatusRuntimeException(status); } + + @Test + public void getMetadataTest() { + ActiveActorsCount activeActorsCount = DaprProtos.ActiveActorsCount.newBuilder() + .setType("actor") + .setCount(1) + .build(); + + ActorRuntime actorRuntime = DaprProtos.ActorRuntime.newBuilder() + .addActiveActors(activeActorsCount) + .build(); + + RegisteredComponents registeredComponents = DaprProtos.RegisteredComponents.newBuilder() + .setName("statestore") + .setType("state.redis") + .setVersion("v1") + .build(); + + DaprProtos.MetadataHTTPEndpoint httpEndpoint = DaprProtos.MetadataHTTPEndpoint.newBuilder() + .setName("httpEndpoint") + .build(); + + PubsubSubscriptionRules pubsubSubscriptionRules = DaprProtos.PubsubSubscriptionRules.newBuilder() + .addRules(DaprProtos.PubsubSubscriptionRule.newBuilder().setPath("/events").build()) + .build(); + + PubsubSubscription pubsubSubscription = DaprProtos.PubsubSubscription.newBuilder() + .setDeadLetterTopic("") + .setPubsubName("pubsub") + .setTopic("topic") + .setRules(pubsubSubscriptionRules) + .build(); + + AppConnectionHealthProperties healthProperties = DaprProtos.AppConnectionHealthProperties.newBuilder() + .setHealthCheckPath("/health") + .setHealthProbeInterval("10s") + .setHealthProbeTimeout("5s") + .setHealthThreshold(1) + .build(); + + AppConnectionProperties appConnectionProperties = DaprProtos.AppConnectionProperties.newBuilder() + .setPort(8080) + .setProtocol("http") + .setChannelAddress("localhost") + .setMaxConcurrency(1) + .setHealth(healthProperties) + .build(); + + DaprProtos.GetMetadataResponse responseEnvelope = DaprProtos.GetMetadataResponse.newBuilder() + .setId("app") + .setRuntimeVersion("1.1x.x") + .addAllEnabledFeatures(Collections.emptyList()) + .setActorRuntime(actorRuntime) + .putAllExtendedMetadata(Collections.emptyMap()) + .addAllRegisteredComponents(Collections.singletonList(registeredComponents)) + .addAllHttpEndpoints(Collections.singletonList(httpEndpoint)) + .addAllSubscriptions(Collections.singletonList(pubsubSubscription)) + .setAppConnectionProperties(appConnectionProperties) + .build(); + + doAnswer((Answer) invocation -> { + StreamObserver observer = (StreamObserver) invocation + .getArguments()[1]; + observer.onNext(responseEnvelope); + observer.onCompleted(); + return null; + }).when(daprStub).getMetadata(any(DaprProtos.GetMetadataRequest.class), any()); + + Mono result = client.getMetadata(); + DaprMetadata metadata = result.block(); + + assertNotNull(metadata); + assertEquals("app", metadata.getId()); + assertEquals("1.1x.x", metadata.getRuntimeVersion()); + assertEquals(0, metadata.getEnabledFeatures().size()); + assertEquals(0, metadata.getAttributes().size()); + + // Actors + assertEquals(1, metadata.getActors().size()); + assertEquals(activeActorsCount.getType(), metadata.getActors().get(0).getType()); + assertEquals(activeActorsCount.getCount(), metadata.getActors().get(0).getCount()); + + // Components + assertEquals(1, metadata.getComponents().size()); + + ComponentMetadata componentMetadata = metadata.getComponents().get(0); + + assertEquals(registeredComponents.getName(), componentMetadata.getName()); + assertEquals(registeredComponents.getVersion(), componentMetadata.getVersion()); + assertEquals(registeredComponents.getType(), componentMetadata.getType()); + assertEquals(registeredComponents.getCapabilitiesList(), componentMetadata.getCapabilities()); + + // Subscriptions + assertEquals(1, metadata.getSubscriptions().size()); + + SubscriptionMetadata subscriptionMetadata = metadata.getSubscriptions().get(0); + + assertEquals(pubsubSubscription.getPubsubName(), subscriptionMetadata.getPubsubname()); + assertEquals(pubsubSubscription.getTopic(), subscriptionMetadata.getTopic()); + assertEquals(pubsubSubscription.getDeadLetterTopic(), subscriptionMetadata.getDeadLetterTopic()); + + // Subscription Rules + assertEquals(1, subscriptionMetadata.getRules().size()); + + RuleMetadata ruleMetadata = subscriptionMetadata.getRules().get(0); + + assertEquals(pubsubSubscription.getRules().getRules(0).getMatch(), ruleMetadata.getMatch()); + assertEquals(pubsubSubscription.getRules().getRules(0).getPath(), ruleMetadata.getPath()); + + // HTTP Endpoints + assertEquals(1, metadata.getHttpEndpoints().size()); + assertEquals(httpEndpoint.getName(), metadata.getHttpEndpoints().get(0).getName()); + + // App Connection Properties + AppConnectionPropertiesMetadata appConnectionPropertiesMetadata = metadata.getAppConnectionProperties(); + + assertEquals(appConnectionProperties.getPort(), appConnectionPropertiesMetadata.getPort()); + assertEquals(appConnectionProperties.getProtocol(), appConnectionPropertiesMetadata.getProtocol()); + assertEquals(appConnectionProperties.getChannelAddress(), appConnectionPropertiesMetadata.getChannelAddress()); + assertEquals(appConnectionProperties.getMaxConcurrency(), appConnectionPropertiesMetadata.getMaxConcurrency()); + + // App Connection Health Properties + AppConnectionPropertiesHealthMetadata healthMetadata = appConnectionPropertiesMetadata.getHealth(); + + assertEquals(healthProperties.getHealthCheckPath(), healthMetadata.getHealthCheckPath()); + assertEquals(healthProperties.getHealthProbeInterval(), healthMetadata.getHealthProbeInterval()); + assertEquals(healthProperties.getHealthProbeTimeout(), healthMetadata.getHealthProbeTimeout()); + assertEquals(healthProperties.getHealthThreshold(), healthMetadata.getHealthThreshold()); + } + + @Test + public void getMetadataExceptionTest() { + doAnswer((Answer) invocation -> { + throw new RuntimeException(); + }).when(daprStub).getMetadata(any(DaprProtos.GetMetadataRequest.class), any()); + + Mono result = client.getMetadata(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); + } } diff --git a/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java b/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java index 9592377a2f..48b7cd0acc 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java @@ -15,76 +15,52 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import io.dapr.client.domain.ConfigurationItem; -import io.dapr.client.domain.DeleteStateRequest; -import io.dapr.client.domain.GetBulkStateRequest; -import io.dapr.client.domain.GetStateRequest; import io.dapr.client.domain.HttpExtension; import io.dapr.client.domain.InvokeMethodRequest; -import io.dapr.client.domain.PublishEventRequest; -import io.dapr.client.domain.State; -import io.dapr.client.domain.StateOptions; -import io.dapr.client.domain.SubscribeConfigurationResponse; -import io.dapr.client.domain.TransactionalStateOperation; -import io.dapr.client.domain.UnsubscribeConfigurationRequest; -import io.dapr.client.domain.UnsubscribeConfigurationResponse; import io.dapr.config.Properties; -import io.dapr.exceptions.DaprError; import io.dapr.exceptions.DaprException; import io.dapr.serializer.DaprObjectSerializer; +import io.dapr.serializer.DefaultObjectSerializer; import io.dapr.utils.TypeRef; +import io.dapr.v1.DaprGrpc; import okhttp3.MediaType; import okhttp3.OkHttpClient; -import okhttp3.Request; import okhttp3.ResponseBody; import okhttp3.mock.Behavior; import okhttp3.mock.MediaTypes; import okhttp3.mock.MockInterceptor; -import okhttp3.mock.matchers.Matcher; -import okio.Buffer; -import okio.BufferedSink; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import reactor.test.scheduler.VirtualTimeScheduler; import reactor.util.context.Context; +import reactor.util.context.ContextView; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.time.Duration; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Base64; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; - -import reactor.util.context.ContextView; -import uk.org.webcompere.systemstubs.stream.SystemOut; import static io.dapr.utils.TestUtils.assertThrowsDaprException; import static io.dapr.utils.TestUtils.findFreePort; import static io.dapr.utils.TestUtils.formatIpAddress; - -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class DaprClientHttpTest { - private static final String STATE_STORE_NAME = "MyStateStore"; - - private static final String CONFIG_STORE_NAME = "MyConfigStore"; - - private static final String SECRET_STORE_NAME = "MySecretStore"; - private final String EXPECTED_RESULT = "{\"data\":\"ewoJCSJwcm9wZXJ0eUEiOiAidmFsdWVBIiwKCQkicHJvcGVydHlCIjogInZhbHVlQiIKCX0=\"}"; @@ -92,8 +68,6 @@ public class DaprClientHttpTest { private DaprClient daprClientHttp; - private DaprClient daprClientHttpXML; - private DaprHttp daprHttp; private OkHttpClient okHttpClient; @@ -106,14 +80,25 @@ public void setUp() { mockInterceptor = new MockInterceptor(Behavior.UNORDERED); okHttpClient = new OkHttpClient.Builder().addInterceptor(mockInterceptor).build(); daprHttp = new DaprHttp(sidecarIp, 3000, okHttpClient); - daprClientHttp = new DaprClientProxy(new DaprClientHttp(daprHttp)); - daprClientHttpXML = new DaprClientProxy(new DaprClientHttp(daprHttp, new XmlSerializer(), new XmlSerializer())); + daprClientHttp = buildDaprClient(daprHttp); + } + + private static DaprClient buildDaprClient(DaprHttp daprHttp) { + GrpcChannelFacade channel = mock(GrpcChannelFacade.class); + DaprGrpc.DaprStub daprStub = mock(DaprGrpc.DaprStub.class); + when(daprStub.withInterceptors(any())).thenReturn(daprStub); + try { + doNothing().when(channel).close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return new DaprClientImpl(channel, daprStub, daprHttp, new DefaultObjectSerializer(), new DefaultObjectSerializer()); } @Test public void waitForSidecarTimeOutHealthCheck() throws Exception { daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); - DaprClientHttp daprClientHttp = new DaprClientHttp(daprHttp); + DaprClient daprClientHttp = buildDaprClient(daprHttp); mockInterceptor.addRule() .get() @@ -138,7 +123,7 @@ public void waitForSidecarBadHealthCheck() throws Exception { int port = findFreePort(); System.setProperty(Properties.HTTP_PORT.getName(), Integer.toString(port)); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), port, okHttpClient); - DaprClientHttp daprClientHttp = new DaprClientHttp(daprHttp); + DaprClient daprClientHttp = buildDaprClient(daprHttp); mockInterceptor.addRule() .get() @@ -163,7 +148,7 @@ public void waitForSidecarSlowSuccessfulHealthCheck() throws Exception { int port = findFreePort(); System.setProperty(Properties.HTTP_PORT.getName(), Integer.toString(port)); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), port, okHttpClient); - DaprClientHttp daprClientHttp = new DaprClientHttp(daprHttp); + DaprClient daprClientHttp = buildDaprClient(daprHttp); // Simulate a slow response mockInterceptor.addRule() @@ -192,7 +177,7 @@ public void waitForSidecarOK() throws Exception { int port = findFreePort(); System.setProperty(Properties.HTTP_PORT.getName(), Integer.toString(port)); daprHttp = new DaprHttp(sidecarIp, port, okHttpClient); - DaprClientHttp daprClientHttp = new DaprClientHttp(daprHttp); + DaprClient daprClientHttp = buildDaprClient(daprHttp); mockInterceptor.addRule() .get() @@ -223,96 +208,11 @@ public void waitForSidecarTimeoutOK() throws Exception { }); t.start(); daprHttp = new DaprHttp(sidecarIp, port, okHttpClient); - DaprClientHttp daprClientHttp = new DaprClientHttp(daprHttp); + DaprClient daprClientHttp = buildDaprClient(daprHttp); daprClientHttp.waitForSidecar(10000).block(); } } - @Test - public void publishEventInvocation() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/publish/mypubsubname/A") - .respond(EXPECTED_RESULT); - String event = "{ \"message\": \"This is a test\" }"; - daprHttp = new DaprHttp(sidecarIp, 3000, okHttpClient); - DaprClientHttp daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.publishEvent("mypubsubname", "A", event, null); - assertNull(mono.block()); - } - - @Test - public void publishEventInvocationIPv6() { - String prevSidecarIp = sidecarIp; - System.setProperty(Properties.SIDECAR_IP.getName(), "2001:db8:3333:4444:5555:6666:7777:8888"); - sidecarIp = formatIpAddress(Properties.SIDECAR_IP.get()); - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/publish/mypubsubname/A") - .respond(EXPECTED_RESULT); - String event = "{ \"message\": \"This is a test\" }"; - daprHttp = new DaprHttp(sidecarIp, 3000, okHttpClient); - daprClientHttp = new DaprClientHttp(daprHttp); - System.setProperty(Properties.SIDECAR_IP.getName(), prevSidecarIp); - Mono mono = daprClientHttp.publishEvent("mypubsubname", "A", event, null); - assertNull(mono.block()); - } - - @Test - public void publishEvent() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/publish/mypubsubname/A") - .header("content-type", "application/json") - .respond(EXPECTED_RESULT); - String event = "{ \"message\": \"This is a test\" }"; - - Mono mono = daprClientHttp.publishEvent("mypubsubname","A", event); - assertNull(mono.block()); - } - - @Test - public void publishEventContentTypeOverride() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/publish/mypubsubname/A") - .header("content-type", "text/plain") - .respond(EXPECTED_RESULT); - String event = "{ \"message\": \"This is a test\" }"; - - Mono mono = daprClientHttp.publishEvent( - new PublishEventRequest("mypubsubname","A", event) - .setContentType("text/plain")); - assertNull(mono.block()); - } - - @Test - public void publishEventIfTopicIsNullOrEmpty() { - String event = "{ \"message\": \"This is a test\" }"; - - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.publishEvent("mypubsubname", null, event).block()); - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.publishEvent("mypubsubname", "", event).block()); - } - - @Test - public void publishEventIfPubsubIsNullOrEmpty() { - String event = "{ \"message\": \"This is a test\" }"; - - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.publishEvent(null, "A", event).block()); - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.publishEvent("", "A", event).block()); - } - - @Test - public void publishEventNoHotMono() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/publish/mypubsubname/A") - .respond(EXPECTED_RESULT); - String event = "{ \"message\": \"This is a test\" }"; - - daprClientHttp.publishEvent("mypubsubname", "", event); - // Should not throw exception because did not call block() on mono above. - } - @Test public void invokeServiceVerbNull() { mockInterceptor.addRule() @@ -374,7 +274,8 @@ public void invokeServiceDaprError() { }); assertEquals("MYCODE", exception.getErrorCode()); - assertEquals("MYCODE: My Message", exception.getMessage()); + assertEquals("MYCODE: My Message (HTTP status code: 500)", exception.getMessage()); + assertEquals(500, exception.getHttpStatusCode()); } @Test @@ -408,7 +309,7 @@ public void invokeServiceDaprErrorUnknownJSON() { }); assertEquals("UNKNOWN", exception.getErrorCode()); - assertEquals("UNKNOWN: { \"anything\": 7 }", exception.getMessage()); + assertEquals("UNKNOWN: HTTP status code: 500", exception.getMessage()); assertEquals("{ \"anything\": 7 }", new String(exception.getPayload())); } @@ -553,1125 +454,42 @@ public void invokeServiceWithContext() { } @Test - public void invokeBinding() { - Map map = new HashMap<>(); - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond(""); - - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", ""); - assertNull(mono.block()); - } - - @Test - public void invokeBindingNullData() { - Map map = new HashMap<>(); - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond(""); - - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", null); - assertNull(mono.block()); - } - - @Test - public void invokeBindingErrors() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond("NOT VALID JSON"); - - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.invokeBinding(null, "myoperation", "").block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.invokeBinding("", "myoperation", "").block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.invokeBinding("topic", null, "").block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.invokeBinding("topic", "", "").block(); - }); - assertThrowsDaprException(JsonParseException.class, () -> { - daprClientHttp.invokeBinding("sample-topic", "op", "data", String.class).block(); - }); - } - - @Test - public void invokeBindingResponseNull() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond(new byte[0]); - - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, String.class); - assertNull(mono.block()); - } - - @Test - public void invokeBindingResponseObject() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond("\"OK\""); - - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, String.class); - assertEquals("OK", mono.block()); - } - - @Test - public void invokeBindingResponseDouble() { - Map map = new HashMap<>(); - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond("1.5"); - - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", map, double.class); - assertEquals(1.5, mono.block(), 0.0001); - } - - @Test - public void invokeBindingResponseFloat() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond("1.5"); - - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, float.class); - assertEquals(1.5, mono.block(), 0.0001); - } - - @Test - public void invokeBindingResponseChar() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond("\"a\""); - - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, char.class); - assertEquals('a', (char)mono.block()); - } - - @Test - public void invokeBindingResponseByte() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond("\"2\""); - - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, byte.class); - assertEquals((byte)0x2, (byte)mono.block()); - } - - @Test - public void invokeBindingResponseLong() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond("1"); - - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, long.class); - assertEquals(1, (long)mono.block()); - } - - @Test - public void invokeBindingResponseInt() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond("1"); - - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, int.class); - assertEquals(1, (int)mono.block()); - } - - @Test - public void invokeBindingNullName() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond(EXPECTED_RESULT); - - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.invokeBinding(null, "myoperation", "").block()); - } - - @Test - public void invokeBindingNullOpName() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond(EXPECTED_RESULT); - - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.invokeBinding("sample-topic", null, "").block()); - } - - @Test - public void bindingNoHotMono() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/bindings/sample-topic") - .respond(EXPECTED_RESULT); - - daprClientHttp.invokeBinding(null, "", ""); - // No exception is thrown because did not call block() on mono above. - } - - @Test - public void getStatesErrors() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/bulk") - .respond("NOT VALID JSON"); - - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getBulkState(STATE_STORE_NAME, null, String.class).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getBulkState(STATE_STORE_NAME, new ArrayList<>(), String.class).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getBulkState(null, Arrays.asList("100", "200"), String.class).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getBulkState("", Arrays.asList("100", "200"), String.class).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getBulkState( - new GetBulkStateRequest(STATE_STORE_NAME, Collections.singletonList("100")).setParallelism(-1), - TypeRef.get(String.class)).block(); - }); - assertThrowsDaprException(JsonParseException.class, () -> { - daprClientHttp.getBulkState( - new GetBulkStateRequest(STATE_STORE_NAME, Collections.singletonList("100")), - TypeRef.get(String.class)).block(); - }); - } - - @Test - public void getStatesString() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/bulk") - .respond("[{\"key\": \"100\", \"data\": \"hello world\", \"etag\": \"1\"}," + - "{\"key\": \"200\", \"error\": \"not found\"}]"); - - List> result = - daprClientHttp.getBulkState(STATE_STORE_NAME, Arrays.asList("100", "200"), String.class).block(); - assertEquals(2, result.size()); - assertEquals("100", result.stream().findFirst().get().getKey()); - assertEquals("hello world", result.stream().findFirst().get().getValue()); - assertEquals("1", result.stream().findFirst().get().getEtag()); - assertNull(result.stream().findFirst().get().getError()); - assertEquals("200", result.stream().skip(1).findFirst().get().getKey()); - assertNull(result.stream().skip(1).findFirst().get().getValue()); - assertNull(result.stream().skip(1).findFirst().get().getEtag()); - assertEquals("not found", result.stream().skip(1).findFirst().get().getError()); - } - - @Test - public void getStatesInteger() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/bulk") - .respond("[{\"key\": \"100\", \"data\": 1234, \"etag\": \"1\"}," + - "{\"key\": \"200\", \"error\": \"not found\"}]"); - - List> result = - daprClientHttp.getBulkState(STATE_STORE_NAME, Arrays.asList("100", "200"), int.class).block(); - assertEquals(2, result.size()); - assertEquals("100", result.stream().findFirst().get().getKey()); - assertEquals(1234, (int)result.stream().findFirst().get().getValue()); - assertEquals("1", result.stream().findFirst().get().getEtag()); - assertNull(result.stream().findFirst().get().getError()); - assertEquals("200", result.stream().skip(1).findFirst().get().getKey()); - assertNull(result.stream().skip(1).findFirst().get().getValue()); - assertNull(result.stream().skip(1).findFirst().get().getEtag()); - assertEquals("not found", result.stream().skip(1).findFirst().get().getError()); - } - - @SuppressWarnings("OptionalGetWithoutIsPresent") - @Test - public void getStatesBoolean() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/bulk") - .respond("[{\"key\": \"100\", \"data\": true, \"etag\": \"1\"}," + - "{\"key\": \"200\", \"error\": \"not found\"}]"); - - List> result = - daprClientHttp.getBulkState(STATE_STORE_NAME, Arrays.asList("100", "200"), boolean.class).block(); - assertNotNull(result); - assertEquals(2, result.size()); - assertEquals("100", result.stream().findFirst().get().getKey()); - assertTrue((boolean) result.stream().findFirst().get().getValue()); - assertEquals("1", result.stream().findFirst().get().getEtag()); - assertNull(result.stream().findFirst().get().getError()); - assertEquals("200", result.stream().skip(1).findFirst().get().getKey()); - assertNull(result.stream().skip(1).findFirst().get().getValue()); - assertNull(result.stream().skip(1).findFirst().get().getEtag()); - assertEquals("not found", result.stream().skip(1).findFirst().get().getError()); - } - - @Test - public void getStatesByteArray() { - byte[] value = new byte[]{1, 2, 3}; - String base64Value = Base64.getEncoder().encodeToString(value); - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/bulk") - .respond("[{\"key\": \"100\", \"data\": \"" + base64Value + "\", \"etag\": \"1\"}," + - "{\"key\": \"200\", \"error\": \"not found\"}]"); - - // JSON cannot differentiate if data returned is String or byte[], it is ambiguous. So we get base64 encoded back. - // So, users should use String instead of byte[]. - List> result = - daprClientHttp.getBulkState(STATE_STORE_NAME, Arrays.asList("100", "200"), String.class).block(); - assertEquals(2, result.size()); - assertEquals("100", result.stream().findFirst().get().getKey()); - assertEquals(base64Value, result.stream().findFirst().get().getValue()); - assertEquals("1", result.stream().findFirst().get().getEtag()); - assertNull(result.stream().findFirst().get().getError()); - assertEquals("200", result.stream().skip(1).findFirst().get().getKey()); - assertNull(result.stream().skip(1).findFirst().get().getValue()); - assertNull(result.stream().skip(1).findFirst().get().getEtag()); - assertEquals("not found", result.stream().skip(1).findFirst().get().getError()); - } - - @Test - public void getStatesObject() { - MyObject object = new MyObject(1, "Event"); - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/bulk") - .respond("[{\"key\": \"100\", \"data\": " + - "{ \"id\": \"" + object.id + "\", \"value\": \"" + object.value + "\"}, \"etag\": \"1\"}," + - "{\"key\": \"200\", \"error\": \"not found\"}]"); - - // JSON cannot differentiate if data returned is String or byte[], it is ambiguous. So we get base64 encoded back. - // So, users should use String instead of byte[]. - List> result = - daprClientHttp.getBulkState(STATE_STORE_NAME, Arrays.asList("100", "200"), MyObject.class).block(); - assertEquals(2, result.size()); - assertEquals("100", result.stream().findFirst().get().getKey()); - assertEquals(object, result.stream().findFirst().get().getValue()); - assertEquals("1", result.stream().findFirst().get().getEtag()); - assertNull(result.stream().findFirst().get().getError()); - assertEquals("200", result.stream().skip(1).findFirst().get().getKey()); - assertNull(result.stream().skip(1).findFirst().get().getValue()); - assertNull(result.stream().skip(1).findFirst().get().getEtag()); - assertEquals("not found", result.stream().skip(1).findFirst().get().getError()); - } - - @Test - public void getState() { - StateOptions stateOptions = mock(StateOptions.class); - State stateKeyValue = new State<>("key", "value", "etag", stateOptions); - State stateKeyNull = new State<>(null, "value", "etag", stateOptions); - State stateKeyEmpty = new State<>("", "value", "etag", stateOptions); - State stateKeyBadPayload = new State<>("keyBadPayload", "value", "etag", stateOptions); - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key") - .respond("\"" + EXPECTED_RESULT + "\""); - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/keyBadPayload") - .respond("NOT VALID"); - - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getState(STATE_STORE_NAME, stateKeyNull, String.class).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getState(STATE_STORE_NAME, stateKeyEmpty, String.class).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getState(null, stateKeyValue, String.class).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getState("", stateKeyValue, String.class).block(); - }); - assertThrowsDaprException(JsonParseException.class, () -> { - daprClientHttp.getState(STATE_STORE_NAME, stateKeyBadPayload, String.class).block(); - }); - Mono> mono = daprClientHttp.getState(STATE_STORE_NAME, stateKeyValue, String.class); - State result = mono.block(); - assertNotNull(result); - assertEquals(result.getKey(), "key"); - } - - @Test - public void getStatesEmptyEtag() { - State stateEmptyEtag = new State<>("key", "value", "", null); - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key") - .respond("\"" + EXPECTED_RESULT + "\""); - - State monoEmptyEtag = daprClientHttp.getState(STATE_STORE_NAME, stateEmptyEtag, String.class).block(); - assertEquals(monoEmptyEtag.getKey(), "key"); - assertNull(monoEmptyEtag.getEtag()); - } - - @Test - public void getStateWithMetadata() { - Map metadata = new HashMap<>(); - metadata.put("key_1", "val_1"); - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key?metadata.key_1=val_1") - .respond("\"" + EXPECTED_RESULT + "\""); - - GetStateRequest request = new GetStateRequest(STATE_STORE_NAME, "key"); - request.setMetadata(metadata); - Mono> monoMetadata = daprClientHttp.getState(request, TypeRef.get(String.class)); - assertEquals(monoMetadata.block().getKey(), "key"); - } - - @Test - public void getStateWithStateOptions() { - StateOptions stateOptions = new StateOptions(StateOptions.Consistency.STRONG, StateOptions.Concurrency.FIRST_WRITE); - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key?consistency=strong&concurrency=first-write") - .respond("\"" + EXPECTED_RESULT + "\""); + public void closeException() { + DaprHttp daprHttp = Mockito.mock(DaprHttp.class); + Mockito.doThrow(new RuntimeException()).when(daprHttp).close(); - GetStateRequest request = new GetStateRequest(STATE_STORE_NAME, "key"); - request.setStateOptions(stateOptions); - Mono> monoOptions = daprClientHttp.getState(request, TypeRef.get(String.class)); - assertEquals(monoOptions.block().getKey(), "key"); + // This method does not throw DaprException because it already throws RuntimeException and does not call Dapr. + daprClientHttp = buildDaprClient(daprHttp); + assertThrows(RuntimeException.class, () -> daprClientHttp.close()); } @Test - public void getStatesNullEtag() { - State stateNullEtag = new State<>("key", "value", null, null); - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key") - .respond("\"" + EXPECTED_RESULT + "\""); + public void close() throws Exception { + DaprHttp daprHttp = Mockito.mock(DaprHttp.class); + Mockito.doNothing().when(daprHttp).close(); - State monoNullEtag = daprClientHttp.getState(STATE_STORE_NAME, stateNullEtag, String.class).block(); - assertEquals(monoNullEtag.getKey(), "key"); - assertNull(monoNullEtag.getEtag()); + // This method does not throw DaprException because IOException is expected by the Closeable interface. + daprClientHttp = buildDaprClient(daprHttp); + daprClientHttp.close(); } - @Test - public void getStatesNoHotMono() { - State stateNullEtag = new State<>("key", "value", null, null); - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key") - .respond(500); - - daprClientHttp.getState(STATE_STORE_NAME, stateNullEtag, String.class); - // No exception should be thrown since did not call block() on mono above. - } + private static class XmlSerializer implements DaprObjectSerializer { - @Test - public void saveStates() { - State stateKeyValue = new State<>("key", "value", "etag", null); - List> stateKeyValueList = Collections.singletonList(stateKeyValue); - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore") - .respond(EXPECTED_RESULT); + private static final XmlMapper XML_MAPPER = new XmlMapper(); - Mono mono = daprClientHttp.saveBulkState(STATE_STORE_NAME, stateKeyValueList); - assertNull(mono.block()); - } + @Override + public byte[] serialize(Object o) throws IOException { + return XML_MAPPER.writeValueAsBytes(o); + } - @Test - public void saveStatesErrors() { - - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.saveBulkState(null, null).block()); - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.saveBulkState("", null).block()); - } - - @Test - public void saveStatesNull() { - List> stateKeyValueList = new ArrayList<>(); - - Mono mono = daprClientHttp.saveBulkState(STATE_STORE_NAME, null); - assertNull(mono.block()); - Mono mono1 = daprClientHttp.saveBulkState(STATE_STORE_NAME, stateKeyValueList); - assertNull(mono1.block()); - } - - @Test - public void saveStatesNullState() { - List> stateKeyValueList = new ArrayList<>(); - stateKeyValueList.add(null); - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore") - .respond(EXPECTED_RESULT); - - Mono mono1 = daprClientHttp.saveBulkState(STATE_STORE_NAME, stateKeyValueList); - assertNull(mono1.block()); - } - - @Test - public void saveStatesEtagNull() { - State stateKeyValue = new State<>("key", "value", null, null); - List> stateKeyValueList = Collections.singletonList(stateKeyValue); - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore") - .respond(EXPECTED_RESULT); - - Mono mono = daprClientHttp.saveBulkState(STATE_STORE_NAME, stateKeyValueList); - assertNull(mono.block()); - } - - @Test - public void saveStatesEtagEmpty() { - State stateKeyValue = new State<>("key", "value", "", null); - List> stateKeyValueList = Collections.singletonList(stateKeyValue); - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore") - .respond(EXPECTED_RESULT); - - Mono mono = daprClientHttp.saveBulkState(STATE_STORE_NAME, stateKeyValueList); - assertNull(mono.block()); - } - - @Test - public void simpleSaveStates() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore") - .respond(EXPECTED_RESULT); - StateOptions stateOptions = mock(StateOptions.class); - - Mono mono = daprClientHttp.saveState(STATE_STORE_NAME, "key", "etag", "value", stateOptions); - assertNull(mono.block()); - } - - @Test - public void saveStatesNoHotMono() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore") - .respond(500); - StateOptions stateOptions = mock(StateOptions.class); - - daprClientHttp.saveState(STATE_STORE_NAME, "key", "etag", "value", stateOptions); - // No exception should be thrown because we did not call block() on the mono above. - } - - @Test - public void simpleExecuteTransaction() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/transaction") - .matches(new BodyMatcher( - "{\"operations\":[{\"operation\":\"upsert\",\"request\":{\"value\":\"my data\",\"key\":\"key1\"," + - "\"etag\":\"ETag1\",\"options\":{}}},{\"operation\":\"delete\",\"request\":{\"key\":\"deleteKey\"}}]}" - )) - .respond(EXPECTED_RESULT); - String etag = "ETag1"; - String key = "key1"; - String data = "my data"; - StateOptions stateOptions = mock(StateOptions.class); - - - State stateKey = new State<>(key, data, etag, stateOptions); - TransactionalStateOperation upsertOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.UPSERT, - stateKey); - TransactionalStateOperation deleteOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.DELETE, - new State<>("deleteKey")); - Mono mono = daprClientHttp.executeStateTransaction(STATE_STORE_NAME, Arrays.asList(upsertOperation, - deleteOperation)); - assertNull(mono.block()); - } - - @Test - public void simpleExecuteTransactionXMLData() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/transaction") - .matches(new BodyMatcher("{\"operations\":[{\"operation\":\"upsert\"," + - "\"request\":{\"value\":\"PFN0cmluZz5teSBkYXRhPC9TdHJpbmc+\",\"key\":\"key1\",\"etag\":\"ETag1\"," + - "\"options\":{}}},{\"operation\":\"delete\",\"request\":{\"value\":\"PG51bGwvPg==\"," + - "\"key\":\"deleteKey\"}}]}")) - .respond(EXPECTED_RESULT); - String etag = "ETag1"; - String key = "key1"; - String data = "my data"; - StateOptions stateOptions = mock(StateOptions.class); - - - State stateKey = new State<>(key, data, etag, stateOptions); - TransactionalStateOperation upsertOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.UPSERT, - stateKey); - TransactionalStateOperation deleteOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.DELETE, - new State<>("deleteKey")); - Mono mono = daprClientHttpXML.executeStateTransaction(STATE_STORE_NAME, Arrays.asList(upsertOperation, - deleteOperation)); - assertNull(mono.block()); - } - - @Test - public void simpleExecuteTransactionNullEtag() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/transaction") - .respond(EXPECTED_RESULT); - String etag = null; - String key = "key1"; - String data = "my data"; - StateOptions stateOptions = mock(StateOptions.class); - - - State stateKey = new State<>(key, data, etag, stateOptions); - TransactionalStateOperation upsertOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.UPSERT, - stateKey); - TransactionalStateOperation deleteOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.DELETE, - new State<>("deleteKey")); - Mono mono = daprClientHttp.executeStateTransaction(STATE_STORE_NAME, Arrays.asList(upsertOperation, - deleteOperation)); - assertNull(mono.block()); - } - - @Test - public void simpleExecuteTransactionEmptyEtag() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/transaction") - .respond(EXPECTED_RESULT); - String etag = "empty"; - String key = "key1"; - String data = "my data"; - StateOptions stateOptions = mock(StateOptions.class); - - - State stateKey = new State<>(key, data, etag, stateOptions); - TransactionalStateOperation upsertOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.UPSERT, - stateKey); - TransactionalStateOperation deleteOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.DELETE, - new State<>("deleteKey")); - Mono mono = daprClientHttp.executeStateTransaction(STATE_STORE_NAME, Arrays.asList(upsertOperation, - deleteOperation)); - assertNull(mono.block()); - } - - @Test - public void simpleExecuteTransactionNullOperationAndNullState() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/transaction") - .respond(EXPECTED_RESULT); - String etag = null; - String key = "key1"; - String data = "my data"; - StateOptions stateOptions = mock(StateOptions.class); - - - State stateKey = new State<>(key, data, etag, stateOptions); - TransactionalStateOperation upsertOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.UPSERT, - stateKey); - TransactionalStateOperation deleteOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.DELETE, - new State<>("deleteKey")); - TransactionalStateOperation nullStateOperation = new TransactionalStateOperation<>( - TransactionalStateOperation.OperationType.DELETE, - null); - Mono mono = daprClientHttp.executeStateTransaction(STATE_STORE_NAME, Arrays.asList( - null, - nullStateOperation, - upsertOperation, - deleteOperation)); - assertNull(mono.block()); - } - - @Test - public void executeTransactionErrors() { - - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.executeStateTransaction(null, null).block()); - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.executeStateTransaction("", null).block()); - } - - @Test - public void simpleExecuteTransactionNull() { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/transaction") - .respond(EXPECTED_RESULT); - - Mono mono = daprClientHttp.executeStateTransaction(STATE_STORE_NAME, null); - assertNull(mono.block()); - mono = daprClientHttp.executeStateTransaction(STATE_STORE_NAME, Collections.emptyList()); - assertNull(mono.block()); - } - - @Test - public void deleteState() { - StateOptions stateOptions = mock(StateOptions.class); - State stateKeyValue = new State<>("key", "value", "etag", stateOptions); - mockInterceptor.addRule() - .delete("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key") - .respond(EXPECTED_RESULT); - - Mono mono = daprClientHttp.deleteState(STATE_STORE_NAME, stateKeyValue.getKey(), stateKeyValue.getEtag(), stateOptions); - assertNull(mono.block()); - } - - @Test - public void deleteStateWithMetadata() { - Map metadata = new HashMap<>(); - metadata.put("key_1", "val_1"); - StateOptions stateOptions = mock(StateOptions.class); - State stateKeyValue = new State<>("key", "value", "etag", stateOptions); - mockInterceptor.addRule() - .delete("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key?metadata.key_1=val_1") - .respond(EXPECTED_RESULT); - - DeleteStateRequest request = new DeleteStateRequest(STATE_STORE_NAME, stateKeyValue.getKey()); - request.setMetadata(metadata) - .setEtag(stateKeyValue.getEtag()) - .setStateOptions(stateOptions); - Mono monoMetadata = daprClientHttp.deleteState(request); - assertNull(monoMetadata.block()); - } - - @Test - public void deleteStateNoHotMono() { - StateOptions stateOptions = mock(StateOptions.class); - State stateKeyValue = new State<>("key", "value", "etag", stateOptions); - mockInterceptor.addRule() - .delete("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key") - .respond(500); - - daprClientHttp.deleteState(STATE_STORE_NAME, stateKeyValue.getKey(), stateKeyValue.getEtag(), stateOptions); - // No exception should be thrown because we did not call block() on the mono above. - } - - @Test - public void deleteStateNullEtag() { - State stateKeyValue = new State<>("key", "value", null, null); - mockInterceptor.addRule() - .delete("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key") - .respond(EXPECTED_RESULT); - - Mono mono = daprClientHttp.deleteState(STATE_STORE_NAME, stateKeyValue.getKey(), stateKeyValue.getEtag(), null); - assertNull(mono.block()); - } - - @Test - public void deleteStateEmptyEtag() { - State stateKeyValue = new State<>("key", "value", "", null); - mockInterceptor.addRule() - .delete("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key") - .respond(EXPECTED_RESULT); - - Mono mono = daprClientHttp.deleteState(STATE_STORE_NAME, stateKeyValue.getKey(), stateKeyValue.getEtag(), null); - assertNull(mono.block()); - } - - @Test - public void deleteStateIllegalArgumentException() { - State stateKeyValueNull = new State<>(null, "value", "etag", null); - State stateKeyValueEmpty = new State<>("", "value", "etag", null); - mockInterceptor.addRule() - .delete("http://" + sidecarIp + ":3000/v1.0/state/MyStateStore/key") - .respond(EXPECTED_RESULT); - - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.deleteState(STATE_STORE_NAME, null, null, null).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.deleteState(STATE_STORE_NAME, "", null, null).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.deleteState(STATE_STORE_NAME, " ", null, null).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.deleteState(null, "key", null, null).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.deleteState("", "key", null, null).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.deleteState(" ", "key", null, null).block(); - }); - } - - @Test - public void getSecrets() { - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/secrets/MySecretStore/key") - .respond("{ \"mysecretkey\": \"mysecretvalue\"}"); - - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getSecret(SECRET_STORE_NAME, null).block(); - }); - Map secret = daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block(); - - assertEquals(1, secret.size()); - assertEquals("mysecretvalue", secret.get("mysecretkey")); - } - - @Test - public void getSecretsSpecialCharsInKey() { - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/secrets/MySecretStore/key%2Fone") - .respond("{ \"mysecretkey\": \"mysecretvalue\"}"); - - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getSecret(SECRET_STORE_NAME, null).block(); - }); - Map secret = daprClientHttp.getSecret(SECRET_STORE_NAME, "key/one").block(); - - assertEquals(1, secret.size()); - assertEquals("mysecretvalue", secret.get("mysecretkey")); - } - - @Test - public void getSecretsEmpty() { - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/secrets/MySecretStore/key") - .respond(""); - - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getSecret(SECRET_STORE_NAME, null).block(); - }); - Map secret = daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block(); - - assertTrue(secret.isEmpty()); - } - - @Test - public void getSecrets404() { - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/secrets/MySecretStore/key") - .respond(404); - - assertThrowsDaprException("UNKNOWN", () -> - daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block() - ); - } - - @Test - public void getSecrets404WithErrorCode() { - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/secrets/MySecretStore/key") - .respond(404, - ResponseBody.create("" + - "{\"errorCode\":\"ERR_SECRET_STORE_NOT_FOUND\"," + - "\"message\":\"error message\"}", MediaTypes.MEDIATYPE_JSON)); - - assertThrowsDaprException("ERR_SECRET_STORE_NOT_FOUND", "ERR_SECRET_STORE_NOT_FOUND: error message", () -> - daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block() - ); - } - - @Test - public void getSecretsErrors() { - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/secrets/MySecretStore/key") - .respond("INVALID JSON"); - - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.getSecret(null, "key").block()); - assertThrows(IllegalArgumentException.class, () -> - daprClientHttp.getSecret("", "key").block()); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getSecret(SECRET_STORE_NAME, null).block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getSecret(SECRET_STORE_NAME, "").block(); - }); - assertThrowsDaprException(JsonParseException.class, () -> { - daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block(); - }); - } - - @Test - public void getSecretsWithMetadata() { - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/secrets/MySecretStore/key") - .respond("{ \"mysecretkey\": \"mysecretvalue\"}"); - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/secrets/MySecretStore/key?metadata.metakey=metavalue") - .respond("{ \"mysecretkey2\": \"mysecretvalue2\"}"); - - { - Map secret = daprClientHttp.getSecret( - SECRET_STORE_NAME, - "key", - null).block(); - - assertEquals(1, secret.size()); - assertEquals("mysecretvalue", secret.get("mysecretkey")); - } - - { - Map secret = daprClientHttp.getSecret( - SECRET_STORE_NAME, - "key", - Collections.singletonMap("metakey", "metavalue")).block(); - - assertEquals(1, secret.size()); - assertEquals("mysecretvalue2", secret.get("mysecretkey2")); - } - } - - @Test - public void getBulkSecrets() { - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/secrets/MySecretStore/bulk") - .respond("{ \"one\": { \"mysecretkey\": \"mysecretvalue\"}, \"two\": { \"a\": \"1\", \"b\": \"2\"}}"); - - Map> secrets = daprClientHttp.getBulkSecret(SECRET_STORE_NAME).block(); - - assertEquals(2, secrets.size()); - assertEquals(1, secrets.get("one").size()); - assertEquals("mysecretvalue", secrets.get("one").get("mysecretkey")); - assertEquals(2, secrets.get("two").size()); - assertEquals("1", secrets.get("two").get("a")); - assertEquals("2", secrets.get("two").get("b")); - } - - @Test - public void getBulkSecretsWithMetadata() { - mockInterceptor.addRule() - .get("http://" + sidecarIp + ":3000/v1.0/secrets/MySecretStore/bulk?metadata.metakey=metavalue") - .respond("{ \"one\": { \"mysecretkey\": \"mysecretvalue\"}, \"two\": { \"a\": \"1\", \"b\": \"2\"}}"); - - Map> secrets = - daprClientHttp.getBulkSecret(SECRET_STORE_NAME, Collections.singletonMap("metakey", "metavalue")).block(); - - assertEquals(2, secrets.size()); - assertEquals(1, secrets.get("one").size()); - assertEquals("mysecretvalue", secrets.get("one").get("mysecretkey")); - assertEquals(2, secrets.get("two").size()); - assertEquals("1", secrets.get("two").get("a")); - assertEquals("2", secrets.get("two").get("b")); - } - - @Test - public void getConfigurationTestErrorScenario() { - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getConfiguration("", "key").block(); - }); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getConfiguration(" ", "key").block(); - }); - } - - @Test - public void getConfigurationTest() { - mockInterceptor.addRule() - .get() - .path("/v1.0/configuration/MyConfigStore") - .param("key","configkey1") - .respond("{\"configkey1\" : {\"value\" : \"configvalue1\",\"version\" : \"1\"}}"); - - ConfigurationItem ci = daprClientHttp.getConfiguration(CONFIG_STORE_NAME, "configkey1").block(); - assertNotNull(ci); - assertEquals("configkey1", ci.getKey()); - assertEquals("configvalue1", ci.getValue()); - assertEquals("1", ci.getVersion()); - } - - @Test - public void getAllConfigurationTest() { - mockInterceptor.addRule() - .get() - .path("/v1.0/configuration/MyConfigStore") - .respond("{\"configkey1\" : {\"value\" : \"configvalue1\",\"version\" : \"1\"}}"); - - ConfigurationItem ci = daprClientHttp.getConfiguration(CONFIG_STORE_NAME, "configkey1").block(); - assertNotNull(ci); - assertEquals("configkey1", ci.getKey()); - assertEquals("configvalue1", ci.getValue()); - assertEquals("1", ci.getVersion()); - } - - @Test - public void subscribeConfigurationTest() { - mockInterceptor.addRule() - .get() - .path("/v1.0/configuration/MyConfigStore/subscribe") - .param("key", "configkey1") - .respond("{\"id\":\"1234\"}"); - - Iterator itr = daprClientHttp.subscribeConfiguration(CONFIG_STORE_NAME, "configkey1").toIterable().iterator(); - assertTrue(itr.hasNext()); - SubscribeConfigurationResponse res = itr.next(); - assertEquals("1234", res.getSubscriptionId()); - assertFalse(itr.hasNext()); - } - - @Test - public void subscribeAllConfigurationTest() { - mockInterceptor.addRule() - .get() - .path("/v1.0/configuration/MyConfigStore/subscribe") - .respond("{\"id\":\"1234\"}"); - - Iterator itr = daprClientHttp.subscribeConfiguration(CONFIG_STORE_NAME, "configkey1").toIterable().iterator(); - assertTrue(itr.hasNext()); - SubscribeConfigurationResponse res = itr.next(); - assertEquals("1234", res.getSubscriptionId()); - assertFalse(itr.hasNext()); - } - - @Test - public void unsubscribeConfigurationTest() { - mockInterceptor.addRule() - .get() - .path("/v1.0/configuration/MyConfigStore/1234/unsubscribe") - .respond("{\"ok\": true}"); - - UnsubscribeConfigurationResponse res = daprClientHttp.unsubscribeConfiguration("1234", CONFIG_STORE_NAME).block(); - assertTrue(res.getIsUnsubscribed()); - } - - @Test - public void unsubscribeConfigurationTestWithError() { - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.unsubscribeConfiguration("", CONFIG_STORE_NAME).block(); - }); - - UnsubscribeConfigurationRequest req = new UnsubscribeConfigurationRequest("subscription_id", ""); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.unsubscribeConfiguration(req).block(); - }); - - mockInterceptor.addRule() - .get() - .path("/v1.0/configuration/MyConfigStore/1234/unsubscribe") - .respond("{\"ok\": false, \"message\": \"some error while unsubscribing\"}"); - UnsubscribeConfigurationResponse res = daprClientHttp.unsubscribeConfiguration("1234", CONFIG_STORE_NAME).block(); - assertFalse(res.getIsUnsubscribed()); - } - - @Test - public void subscribeConfigurationTestWithError() { - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.subscribeConfiguration("", "key1").blockFirst(); - }); - - mockInterceptor.addRule() - .get() - .path("/v1.0/configuration/MyConfigStore/subscribe") - .param("key", "configkey1") - .respond(500); - assertThrows(DaprException.class, () -> { - daprClientHttp.subscribeConfiguration(CONFIG_STORE_NAME, "configkey1").blockFirst(); - }); - } - - @Test - public void closeException() { - DaprHttp daprHttp = Mockito.mock(DaprHttp.class); - Mockito.doThrow(new RuntimeException()).when(daprHttp).close(); - - // This method does not throw DaprException because it already throws RuntimeException and does not call Dapr. - daprClientHttp = new DaprClientHttp(daprHttp); - assertThrows(RuntimeException.class, () -> daprClientHttp.close()); - } - - @Test - public void close() throws Exception { - DaprHttp daprHttp = Mockito.mock(DaprHttp.class); - Mockito.doNothing().when(daprHttp).close(); - - // This method does not throw DaprException because IOException is expected by the Closeable interface. - daprClientHttp = new DaprClientHttp(daprHttp); - daprClientHttp.close(); - } - - @Test - public void shutdown() throws Exception { - mockInterceptor.addRule() - .post("http://" + sidecarIp + ":3000/v1.0/shutdown") - .respond(204); - - final Mono mono = daprClientHttp.shutdown(); - assertNull(mono.block()); - } - - private static final class BodyMatcher implements Matcher { - - private final String expected; - - private BodyMatcher(String expected) { - this.expected = expected; - } - - @Override - public boolean matches(Request request) { - BufferedSink sink = new Buffer(); - try { - request.body().writeTo(sink); - } catch (IOException e) { - return false; - } - String body = sink.getBuffer().readByteString().utf8(); - return expected.equals(body); - } - - @Override - public String failReason(Request request) { - BufferedSink sink = new Buffer(); - try { - request.body().writeTo(sink); - } catch (IOException e) { - throw new RuntimeException(e); - } - String body = sink.getBuffer().readByteString().utf8(); - return String.format("Body does not match expected:\n%s\nvs actual\n%s", expected, body); - } - } - - private static class XmlSerializer implements DaprObjectSerializer { - - private static final XmlMapper XML_MAPPER = new XmlMapper(); - - @Override - public byte[] serialize(Object o) throws IOException { - return XML_MAPPER.writeValueAsBytes(o); - } - - @Override - public T deserialize(byte[] data, TypeRef type) throws IOException { - return XML_MAPPER.readValue(data, new TypeReference() {}); - } + @Override + public T deserialize(byte[] data, TypeRef type) throws IOException { + return XML_MAPPER.readValue(data, new TypeReference() {}); + } @Override public String getContentType() { return "application/xml"; } } - - public static class MyObject { - private Integer id; - private String value; - - public MyObject() { - } - - public MyObject(Integer id, String value) { - this.id = id; - this.value = value; - } - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof MyObject)) return false; - - MyObject myObject = (MyObject) o; - - if (!getId().equals(myObject.getId())) return false; - if (getValue() != null ? !getValue().equals(myObject.getValue()) : myObject.getValue() != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = getId().hashCode(); - result = 31 * result + (getValue() != null ? getValue().hashCode() : 0); - return result; - } - } } \ No newline at end of file diff --git a/sdk/src/test/java/io/dapr/client/DaprClientProxyTest.java b/sdk/src/test/java/io/dapr/client/DaprClientProxyTest.java deleted file mode 100644 index 375e8b0831..0000000000 --- a/sdk/src/test/java/io/dapr/client/DaprClientProxyTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2021 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ -package io.dapr.client; - -import io.dapr.client.domain.HttpExtension; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import reactor.core.publisher.Mono; - -import static org.mockito.Mockito.times; - -public class DaprClientProxyTest { - - @Test - public void stateAPI() { - DaprClient client1 = Mockito.mock(DaprClient.class); - DaprClient client2 = Mockito.mock(DaprClient.class); - - Mockito.when(client1.saveState("state", "key", "value")).thenReturn(Mono.empty()); - Mockito.when(client2.saveState("state", "key", "value")).thenReturn(Mono.empty()); - - DaprClient proxy = new DaprClientProxy(client1, client2); - proxy.saveState("state", "key", "value").block(); - - Mockito.verify(client1, times(1)).saveState("state", "key", "value"); - Mockito.verify(client2, times(0)).saveState("state", "key", "value"); - } - - @Test - public void methodInvocationAPI() { - DaprClient client1 = Mockito.mock(DaprClient.class); - DaprClient client2 = Mockito.mock(DaprClient.class); - - Mockito.when(client1.invokeMethod("appId", "methodName", "body", HttpExtension.POST)) - .thenReturn(Mono.empty()); - Mockito.when(client2.invokeMethod("appId", "methodName", "body", HttpExtension.POST)) - .thenReturn(Mono.empty()); - - DaprClient proxy = new DaprClientProxy(client1, client2); - proxy.invokeMethod("appId", "methodName", "body", HttpExtension.POST).block(); - - Mockito.verify(client1, times(0)) - .invokeMethod("appId", "methodName", "body", HttpExtension.POST); - Mockito.verify(client2, times(1)) - .invokeMethod("appId", "methodName", "body", HttpExtension.POST); - } - - @Test - public void closeAllClients() throws Exception { - DaprClient client1 = Mockito.mock(DaprClient.class); - DaprClient client2 = Mockito.mock(DaprClient.class); - - DaprClient proxy = new DaprClientProxy(client1, client2); - proxy.close(); - - Mockito.verify(client1, times(1)).close(); - Mockito.verify(client2, times(1)).close(); - } - - @Test - public void closeSingleClient() throws Exception { - DaprClient client1 = Mockito.mock(DaprClient.class); - - DaprClient proxy = new DaprClientProxy(client1); - proxy.close(); - - Mockito.verify(client1, times(1)).close(); - } - -} diff --git a/sdk/src/test/java/io/dapr/client/DaprClientTestBuilder.java b/sdk/src/test/java/io/dapr/client/DaprClientTestBuilder.java index b0984bac4e..ef9e9788f9 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientTestBuilder.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientTestBuilder.java @@ -13,17 +13,19 @@ package io.dapr.client; +import io.dapr.serializer.DefaultObjectSerializer; + /** * Builder for DaprClient used in tests only. */ public class DaprClientTestBuilder { /** - * Builds a DaprClient. + * Builds a DaprClient only for HTTP calls. * @param client DaprHttp used for http calls (can be mocked or stubbed) * @return New instance of DaprClient. */ - public static DaprClient buildHttpClient(DaprHttp client) { - return new DaprClientHttp(client); + public static DaprClient buildClientForHttpOnly(DaprHttp client) { + return new DaprClientImpl(null, null, client, new DefaultObjectSerializer(), new DefaultObjectSerializer()); } } diff --git a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java index 7310480d8b..4abb10186e 100644 --- a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java @@ -40,16 +40,17 @@ public class DaprExceptionTest { private GrpcChannelFacade channel; private DaprGrpc.DaprStub daprStub; + private DaprHttp daprHttp; private DaprClient client; @BeforeEach public void setup() throws IOException { channel = mock(GrpcChannelFacade.class); daprStub = mock(DaprGrpc.DaprStub.class); + daprHttp = mock(DaprHttp.class); when(daprStub.withInterceptors(any())).thenReturn(daprStub); - DaprClient grpcClient = new DaprClientGrpc( - channel, daprStub, new DefaultObjectSerializer(), new DefaultObjectSerializer()); - client = new DaprClientProxy(grpcClient); + client = new DaprClientImpl( + channel, daprStub, daprHttp, new DefaultObjectSerializer(), new DefaultObjectSerializer()); doNothing().when(channel).close(); } diff --git a/sdk/src/test/java/io/dapr/client/DaprHttpTest.java b/sdk/src/test/java/io/dapr/client/DaprHttpTest.java index ceac555878..e976e41b44 100644 --- a/sdk/src/test/java/io/dapr/client/DaprHttpTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprHttpTest.java @@ -16,17 +16,17 @@ import io.dapr.exceptions.DaprErrorDetails; import io.dapr.exceptions.DaprException; import io.dapr.utils.TypeRef; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.test.StepVerifier; -import reactor.util.context.Context; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.ResponseBody; import okhttp3.mock.Behavior; import okhttp3.mock.MockInterceptor; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; +import reactor.util.context.Context; import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; import uk.org.webcompere.systemstubs.jupiter.SystemStub; import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; diff --git a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java index 1292de68c4..200b4cae6d 100644 --- a/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprPreviewClientGrpcTest.java @@ -17,8 +17,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.protobuf.ByteString; -import io.dapr.client.domain.BulkPublishRequest; import io.dapr.client.domain.BulkPublishEntry; +import io.dapr.client.domain.BulkPublishRequest; import io.dapr.client.domain.BulkPublishResponse; import io.dapr.client.domain.QueryStateItem; import io.dapr.client.domain.QueryStateRequest; @@ -74,15 +74,17 @@ public class DaprPreviewClientGrpcTest { private GrpcChannelFacade channel; private DaprGrpc.DaprStub daprStub; + private DaprHttp daprHttp; private DaprPreviewClient previewClient; @BeforeEach public void setup() throws IOException { channel = mock(GrpcChannelFacade.class); daprStub = mock(DaprGrpc.DaprStub.class); + daprHttp = mock(DaprHttp.class); when(daprStub.withInterceptors(any())).thenReturn(daprStub); - previewClient = new DaprClientGrpc( - channel, daprStub, new DefaultObjectSerializer(), new DefaultObjectSerializer()); + previewClient = new DaprClientImpl( + channel, daprStub, daprHttp, new DefaultObjectSerializer(), new DefaultObjectSerializer()); doNothing().when(channel).close(); } @@ -147,7 +149,7 @@ public void publishEventsContentTypeMismatchException() throws IOException { @Test public void publishEventsSerializeException() throws IOException { DaprObjectSerializer mockSerializer = mock(DaprObjectSerializer.class); - previewClient = new DaprClientGrpc(channel, daprStub, mockSerializer, new DefaultObjectSerializer()); + previewClient = new DaprClientImpl(channel, daprStub, daprHttp, mockSerializer, new DefaultObjectSerializer()); doAnswer((Answer) invocation -> { StreamObserver observer = (StreamObserver) invocation.getArguments()[1]; diff --git a/sdk/src/test/java/io/dapr/client/DaprPreviewClientHttpTest.java b/sdk/src/test/java/io/dapr/client/DaprPreviewClientHttpTest.java deleted file mode 100644 index c96e8260db..0000000000 --- a/sdk/src/test/java/io/dapr/client/DaprPreviewClientHttpTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2021 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ - -package io.dapr.client; - -import io.dapr.client.domain.LockRequest; -import io.dapr.client.domain.QueryStateRequest; -import io.dapr.client.domain.QueryStateResponse; -import io.dapr.client.domain.UnlockRequest; -import io.dapr.client.domain.UnlockResponseStatus; -import io.dapr.client.domain.query.Query; -import io.dapr.config.Properties; -import io.dapr.utils.TypeRef; -import okhttp3.OkHttpClient; -import okhttp3.mock.Behavior; -import okhttp3.mock.MockInterceptor; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class DaprPreviewClientHttpTest { - - private static final String LOCK_STORE_NAME = "MyLockStore"; - - private DaprPreviewClient daprPreviewClientHttp; - - private DaprHttp daprHttp; - - private OkHttpClient okHttpClient; - - private MockInterceptor mockInterceptor; - - @BeforeEach - public void setUp() { - mockInterceptor = new MockInterceptor(Behavior.UNORDERED); - okHttpClient = new OkHttpClient.Builder().addInterceptor(mockInterceptor).build(); - daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); - daprPreviewClientHttp = new DaprClientHttp(daprHttp); - } - - @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 response = daprPreviewClientHttp.queryState("testStore", "query", String.class).block(); - assertNotNull(response); - assertEquals(1, response.getResults().size(), "result size must be 1"); - assertEquals( "1", response.getResults().get(0).getKey(), "result must be same"); - assertEquals("testData", response.getResults().get(0).getValue(), "result must be same"); - assertEquals( "6f54ad94-dfb9-46f0-a371-e42d550adb7d", response.getResults().get(0).getEtag(), "result must be same"); - } - - @Test - public void tryLock() { - mockInterceptor.addRule() - .post("http://127.0.0.1:3000/v1.0-alpha1/lock/MyLockStore") - .respond("{ \"success\": true}"); - - LockRequest lockRequest = new LockRequest(LOCK_STORE_NAME,"1","owner",10); - - Mono mono = daprPreviewClientHttp.tryLock(lockRequest); - assertEquals(Boolean.TRUE, mono.block()); - } - - @Test - public void unLock() { - mockInterceptor.addRule() - .post("http://127.0.0.1:3000/v1.0-alpha1/unlock/MyLockStore") - .respond("{ \"status\": 0}"); - - UnlockRequest unLockRequest = new UnlockRequest(LOCK_STORE_NAME,"1","owner"); - - Mono mono = daprPreviewClientHttp.unlock(unLockRequest); - assertEquals(UnlockResponseStatus.SUCCESS, mono.block()); - } -} diff --git a/sdk/src/test/java/io/dapr/client/GrpcChannelFacadeTest.java b/sdk/src/test/java/io/dapr/client/GrpcChannelFacadeTest.java deleted file mode 100644 index 206c0bc5df..0000000000 --- a/sdk/src/test/java/io/dapr/client/GrpcChannelFacadeTest.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2023 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ - -package io.dapr.client; - -import io.dapr.config.Properties; -import io.dapr.utils.NetworkUtils; -import io.dapr.v1.DaprGrpc; -import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; -import io.grpc.Server; -import io.grpc.ServerBuilder; -import io.grpc.inprocess.InProcessChannelBuilder; -import io.grpc.inprocess.InProcessServerBuilder; -import io.grpc.testing.GrpcCleanupRule; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.ResponseBody; -import okhttp3.mock.Behavior; -import okhttp3.mock.MockInterceptor; -import org.junit.Before; -import org.junit.Rule; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.test.StepVerifier; -import reactor.test.scheduler.VirtualTimeScheduler; - -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import static io.dapr.utils.TestUtils.findFreePort; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class GrpcChannelFacadeTest { - - private static int port; - - public static Server server; - - private MockInterceptor mockInterceptor; - - private OkHttpClient okHttpClient; - - private static DaprHttp daprHttp; - - @Rule - public static final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); - - /** - * Enable the waitForSidecar to allow the gRPC to check the http endpoint for the health check - */ - @BeforeEach - public void setUp() { - mockInterceptor = new MockInterceptor(Behavior.UNORDERED); - okHttpClient = new OkHttpClient.Builder().addInterceptor(mockInterceptor).build(); - } - - @BeforeAll - public static void setup() throws IOException { - port = findFreePort(); - - // Create a server, add service, start, and register for automatic graceful shutdown. - grpcCleanup.register(ServerBuilder.forPort(port) - .addService(new DaprGrpc.DaprImplBase() { - }) - .build().start()); - } - - @AfterAll - public static void teardown() throws InterruptedException { - if (daprHttp != null) { - daprHttp.close(); - } - } - - @Test - public void waitForSidecarTimeoutHealthCheck() throws Exception { - OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(mockInterceptor).build(); - DaprHttp daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3500, okHttpClient); - - ManagedChannel channel = InProcessChannelBuilder.forName("waitForSidecarTimeoutHealthCheck").build(); - grpcCleanup.register(channel); - final GrpcChannelFacade channelFacade = new GrpcChannelFacade(channel, daprHttp); - - mockInterceptor.addRule() - .get() - .path("/v1.0/healthz/outbound") - .times(6) - .respond(404, ResponseBody.create("Not Found", MediaType.get("application/json"))); - - StepVerifier.create(channelFacade.waitForChannelReady(1000)) - .expectSubscription() - .expectError(TimeoutException.class) - .verify(Duration.ofSeconds(20)); - } - - @Test - public void waitForSidecarOK() { - OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(mockInterceptor).build(); - - DaprHttp daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3500, okHttpClient); - - ManagedChannel channel = ManagedChannelBuilder.forAddress("127.0.0.1", port) - .usePlaintext().build(); - - grpcCleanup.register(channel); - - final GrpcChannelFacade channelFacade = new GrpcChannelFacade(channel, daprHttp); - - // added since this is doing a check against the http health check endpoint - // for parity with dotnet - mockInterceptor.addRule() - .get() - .path("/v1.0/healthz/outbound") - .respond(204); - - StepVerifier.create(channelFacade.waitForChannelReady(10000)) - .expectSubscription() - .expectComplete() - .verify(); - } -} diff --git a/sdk/src/test/java/io/dapr/client/domain/BulkPublishRequestTest.java b/sdk/src/test/java/io/dapr/client/domain/BulkPublishRequestTest.java index e4932c4e9e..5211cec483 100644 --- a/sdk/src/test/java/io/dapr/client/domain/BulkPublishRequestTest.java +++ b/sdk/src/test/java/io/dapr/client/domain/BulkPublishRequestTest.java @@ -16,10 +16,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/sdk/src/test/java/io/dapr/client/domain/DeleteStateRequestTest.java b/sdk/src/test/java/io/dapr/client/domain/DeleteStateRequestTest.java index a22b510055..d90494a8d5 100644 --- a/sdk/src/test/java/io/dapr/client/domain/DeleteStateRequestTest.java +++ b/sdk/src/test/java/io/dapr/client/domain/DeleteStateRequestTest.java @@ -3,7 +3,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.Collections; import java.util.HashMap; import java.util.Map; diff --git a/sdk/src/test/java/io/dapr/client/domain/GetBulkStateRequestTest.java b/sdk/src/test/java/io/dapr/client/domain/GetBulkStateRequestTest.java index 55e7d1ea3d..f7ccb51f8f 100644 --- a/sdk/src/test/java/io/dapr/client/domain/GetBulkStateRequestTest.java +++ b/sdk/src/test/java/io/dapr/client/domain/GetBulkStateRequestTest.java @@ -7,10 +7,8 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; public class GetBulkStateRequestTest { diff --git a/sdk/src/test/java/io/dapr/client/domain/GetSecretRequestTest.java b/sdk/src/test/java/io/dapr/client/domain/GetSecretRequestTest.java index e376403443..9280ffd3d9 100644 --- a/sdk/src/test/java/io/dapr/client/domain/GetSecretRequestTest.java +++ b/sdk/src/test/java/io/dapr/client/domain/GetSecretRequestTest.java @@ -2,14 +2,11 @@ import org.junit.jupiter.api.Test; -import java.util.Collections; import java.util.HashMap; import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; public class GetSecretRequestTest { diff --git a/sdk/src/test/java/io/dapr/client/domain/query/QueryTest.java b/sdk/src/test/java/io/dapr/client/domain/query/QueryTest.java index 957fea9292..688785e3c3 100644 --- a/sdk/src/test/java/io/dapr/client/domain/query/QueryTest.java +++ b/sdk/src/test/java/io/dapr/client/domain/query/QueryTest.java @@ -2,7 +2,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import io.dapr.client.domain.query.filters.*; +import io.dapr.client.domain.query.filters.AndFilter; +import io.dapr.client.domain.query.filters.EqFilter; +import io.dapr.client.domain.query.filters.Filter; +import io.dapr.client.domain.query.filters.InFilter; +import io.dapr.client.domain.query.filters.OrFilter; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/sdk/src/test/java/io/dapr/runtime/DaprRuntimeTest.java b/sdk/src/test/java/io/dapr/runtime/DaprRuntimeTest.java index 8ae30b4126..2343ca0591 100644 --- a/sdk/src/test/java/io/dapr/runtime/DaprRuntimeTest.java +++ b/sdk/src/test/java/io/dapr/runtime/DaprRuntimeTest.java @@ -127,24 +127,8 @@ public void pubSubHappyCase() throws Exception { generateSingleMetadata()) }; - DaprHttpStub daprHttp = mock(DaprHttpStub.class); - DaprClient client = DaprClientTestBuilder.buildHttpClient(daprHttp); - DaprObjectSerializer serializer = new DefaultObjectSerializer(); - for (Message message : messages) { - when(daprHttp.invokeApi( - eq("POST"), - eq((PUBLISH_PATH + "/" + PUBSUB_NAME + "/" + TOPIC_NAME).split("/")), - any(), - eq(serializer.serialize(message.data)), - any(), - any())) - .thenAnswer(invocationOnMock -> this.daprRuntime.handleInvocation( - TOPIC_NAME, - this.serialize(message), - message.metadata).then()); - - client.publishEvent(PUBSUB_NAME, TOPIC_NAME, message.data).block(); + this.daprRuntime.handleInvocation(TOPIC_NAME, this.serialize(message), message.metadata); CloudEvent envelope = new CloudEvent( message.id, @@ -212,7 +196,7 @@ public void invokeHappyCase() throws Exception { }; DaprHttpStub daprHttp = mock(DaprHttpStub.class); - DaprClient client = DaprClientTestBuilder.buildHttpClient(daprHttp); + DaprClient client = DaprClientTestBuilder.buildClientForHttpOnly(daprHttp); DaprObjectSerializer serializer = new DefaultObjectSerializer(); for (Message message : messages) { diff --git a/sdk/src/test/java/io/dapr/utils/DurationUtilsTest.java b/sdk/src/test/java/io/dapr/utils/DurationUtilsTest.java index dd75781f78..02aa5b1a70 100644 --- a/sdk/src/test/java/io/dapr/utils/DurationUtilsTest.java +++ b/sdk/src/test/java/io/dapr/utils/DurationUtilsTest.java @@ -14,7 +14,6 @@ package io.dapr.utils; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.time.Duration; diff --git a/testcontainers-dapr/pom.xml b/testcontainers-dapr/pom.xml new file mode 100644 index 0000000000..5ffa152cc2 --- /dev/null +++ b/testcontainers-dapr/pom.xml @@ -0,0 +1,111 @@ + + + 4.0.0 + + io.dapr + dapr-sdk-parent + 1.12.0-SNAPSHOT + + + testcontainers-dapr + testcontainers-dapr + Testcontainers Dapr Module + 0.12.0-SNAPSHOT + jar + + + + org.yaml + snakeyaml + ${snakeyaml.version} + + + org.testcontainers + testcontainers + ${testcontainers.version} + + + io.dapr + dapr-sdk + ${project.parent.version} + test + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + + attach-javadocs + + jar + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + default-prepare-agent + + prepare-agent + + + + report + test + + report + + + target/jacoco-report/ + + + + check + + check + + + + + BUNDLE + + + LINE + COVEREDRATIO + 80% + + + + + + + + + + + + diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/Component.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/Component.java new file mode 100644 index 0000000000..6d70b1ed49 --- /dev/null +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/Component.java @@ -0,0 +1,83 @@ +/* + * Copyright 2024 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.testcontainers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Represents a Dapr component. + */ +public class Component { + private String name; + private String type; + private String version; + private List metadata; + + /** + * Creates a new component. + * + * @param name Component name. + * @param type Component type. + * @param version Component version. + * @param metadata Metadata. + */ + public Component(String name, String type, String version, Map metadata) { + this.name = name; + this.type = type; + this.version = version; + + List entries = new ArrayList<>(); + if (!metadata.isEmpty()) { + for (Map.Entry entry : metadata.entrySet()) { + entries.add(new MetadataEntry(entry.getKey(), entry.getValue())); + } + } + this.metadata = Collections.unmodifiableList(entries); + } + + /** + * Creates a new component. + * + * @param name Component name. + * @param type Component type. + * @param version Component version. + * @param metadataEntries Component metadata entries. + */ + public Component(String name, String type, String version, List metadataEntries) { + this.name = name; + this.type = type; + this.version = version; + metadata = Objects.requireNonNull(metadataEntries); + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public List getMetadata() { + return metadata; + } + + public String getVersion() { + return version; + } +} diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java new file mode 100644 index 0000000000..e0623d01f5 --- /dev/null +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java @@ -0,0 +1,341 @@ +/* + * Copyright 2024 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.testcontainers; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.utility.DockerImageName; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.representer.Representer; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class DaprContainer extends GenericContainer { + + private static final int DAPRD_DEFAULT_HTTP_PORT = 3500; + private static final int DAPRD_DEFAULT_GRPC_PORT = 50001; + private final Set components = new HashSet<>(); + private final Set subscriptions = new HashSet<>(); + private DaprProtocol protocol = DaprProtocol.HTTP; + private String appName; + private Integer appPort = null; + private DaprLogLevel daprLogLevel = DaprLogLevel.INFO; + private String appChannelAddress = "localhost"; + private String placementService = "placement"; + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("daprio/daprd"); + private static final Yaml yaml = getYamlMapper(); + private DaprPlacementContainer placementContainer; + private String placementDockerImageName = "daprio/placement"; + private boolean shouldReusePlacement; + + /** + * Creates a new Dapr container. + * @param dockerImageName Docker image name. + */ + public DaprContainer(DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + // For susbcriptions the container needs to access the app channel + withAccessToHost(true); + // Here we don't want to wait for the Dapr sidecar to be ready, as the sidecar + // needs to + // connect with the application for susbcriptions + + withExposedPorts(DAPRD_DEFAULT_HTTP_PORT, DAPRD_DEFAULT_GRPC_PORT); + + } + + private static Yaml getYamlMapper() { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setPrettyFlow(true); + Representer representer = new Representer(options); + representer.addClassTag(MetadataEntry.class, Tag.MAP); + return new Yaml(representer); + } + + /** + * Creates a new Dapr container. + * @param image Docker image name. + */ + public DaprContainer(String image) { + this(DockerImageName.parse(image)); + } + + public Set getComponents() { + return components; + } + + public Set getSubscriptions() { + return subscriptions; + } + + public DaprContainer withAppPort(Integer port) { + this.appPort = port; + return this; + } + + public DaprContainer withPlacementService(String placementService) { + this.placementService = placementService; + return this; + } + + public DaprContainer withAppName(String appName) { + this.appName = appName; + return this; + } + + public DaprContainer withDaprLogLevel(DaprLogLevel daprLogLevel) { + this.daprLogLevel = daprLogLevel; + return this; + } + + public DaprContainer withSubscription(Subscription subscription) { + subscriptions.add(subscription); + return this; + } + + public DaprContainer withComponent(Component component) { + components.add(component); + return this; + } + + + + /** + * Adds a Dapr component from a YAML file. + * @param path Path to the YAML file. + * @return This container. + */ + public DaprContainer withComponent(Path path) { + try { + Map component = this.yaml.loadAs(Files.newInputStream(path), Map.class); + + String type = (String) component.get("type"); + Map metadata = (Map) component.get("metadata"); + String name = (String) metadata.get("name"); + + Map spec = (Map) component.get("spec"); + String version = (String) spec.get("version"); + List> specMetadata = + (List>) spec.getOrDefault("metadata", Collections.emptyMap()); + + ArrayList metadataEntries = new ArrayList<>(); + + for (Map specMetadataItem : specMetadata) { + for (Map.Entry metadataItem : specMetadataItem.entrySet()) { + metadataEntries.add(new MetadataEntry(metadataItem.getKey(), metadataItem.getValue())); + } + } + + return withComponent(new Component(name, type, version, metadataEntries)); + } catch (IOException e) { + logger().warn("Error while reading component from {}", path.toAbsolutePath()); + } + return this; + } + + public int getHttpPort() { + return getMappedPort(DAPRD_DEFAULT_HTTP_PORT); + } + + public String getHttpEndpoint() { + return "http://" + getHost() + ":" + getMappedPort(DAPRD_DEFAULT_HTTP_PORT); + } + + public int getGrpcPort() { + return getMappedPort(DAPRD_DEFAULT_GRPC_PORT); + } + + public DaprContainer withAppChannelAddress(String appChannelAddress) { + this.appChannelAddress = appChannelAddress; + return this; + } + + /** + * Get a map of Dapr component details. + * @param component A Dapr Component. + * @return Map of component details. + */ + public Map componentToMap(Component component) { + Map componentProps = new HashMap<>(); + componentProps.put("apiVersion", "dapr.io/v1alpha1"); + componentProps.put("kind", "Component"); + + Map componentMetadata = new LinkedHashMap<>(); + componentMetadata.put("name", component.getName()); + componentProps.put("metadata", componentMetadata); + + Map componentSpec = new HashMap<>(); + componentSpec.put("type", component.getType()); + componentSpec.put("version", component.getVersion()); + + if (!component.getMetadata().isEmpty()) { + componentSpec.put("metadata", component.getMetadata()); + } + componentProps.put("spec", componentSpec); + return Collections.unmodifiableMap(componentProps); + } + + /** + * Get a map of Dapr subscription details. + * @param subscription A Dapr Subscription. + * @return Map of subscription details. + */ + public Map subscriptionToMap(Subscription subscription) { + Map subscriptionProps = new HashMap<>(); + subscriptionProps.put("apiVersion", "dapr.io/v1alpha1"); + subscriptionProps.put("kind", "Subscription"); + + Map subscriptionMetadata = new LinkedHashMap<>(); + subscriptionMetadata.put("name", subscription.getName()); + subscriptionProps.put("metadata", subscriptionMetadata); + + Map subscriptionSpec = new HashMap<>(); + subscriptionSpec.put("pubsubname", subscription.getPubsubName()); + subscriptionSpec.put("topic", subscription.getTopic()); + subscriptionSpec.put("route", subscription.getRoute()); + + subscriptionProps.put("spec", subscriptionSpec); + return Collections.unmodifiableMap(subscriptionProps); + } + + @Override + protected void configure() { + super.configure(); + + if (getNetwork() == null) { + withNetwork(Network.newNetwork()); + } + if (this.placementContainer == null) { + this.placementContainer = new DaprPlacementContainer(this.placementDockerImageName) + .withNetwork(getNetwork()) + .withNetworkAliases(placementService) + .withReuse(this.shouldReusePlacement); + this.placementContainer.start(); + } + + List cmds = new ArrayList<>(); + cmds.add("./daprd"); + cmds.add("-app-id"); + cmds.add(appName); + cmds.add("--dapr-listen-addresses=0.0.0.0"); + cmds.add("--app-protocol"); + cmds.add(protocol.getName()); + cmds.add("-placement-host-address"); + cmds.add(placementService + ":50005"); + + if (appChannelAddress != null && !appChannelAddress.isEmpty()) { + cmds.add("--app-channel-address"); + cmds.add(appChannelAddress); + } + if (appPort != null) { + cmds.add("--app-port"); + cmds.add(Integer.toString(appPort)); + } + cmds.add("--log-level"); + cmds.add(daprLogLevel.toString()); + cmds.add("-components-path"); + cmds.add("/dapr-resources"); + withCommand(cmds.toArray(new String[]{})); + + if (components.isEmpty()) { + components.add(new Component("kvstore", "state.in-memory", "v1", Collections.emptyMap())); + components.add(new Component("pubsub", "pubsub.in-memory", "v1", Collections.emptyMap())); + } + + if (subscriptions.isEmpty() && !components.isEmpty()) { + subscriptions.add(new Subscription("local", "pubsub", "topic", "/events")); + } + + for (Component component : components) { + String componentYaml = componentToYaml(component); + withCopyToContainer(Transferable.of(componentYaml), "/dapr-resources/" + component.getName() + ".yaml"); + } + + for (Subscription subscription : subscriptions) { + String subscriptionYaml = subscriptionToYaml(subscription); + withCopyToContainer(Transferable.of(subscriptionYaml), "/dapr-resources/" + subscription.getName() + ".yaml"); + } + } + + public String subscriptionToYaml(Subscription subscription) { + Map subscriptionMap = subscriptionToMap(subscription); + return yaml.dumpAsMap(subscriptionMap); + } + + public String componentToYaml(Component component) { + Map componentMap = componentToMap(component); + return yaml.dumpAsMap(componentMap); + } + + public String getAppName() { + return appName; + } + + public Integer getAppPort() { + return appPort; + } + + public String getAppChannelAddress() { + return appChannelAddress; + } + + public String getPlacementService() { + return placementService; + } + + public static DockerImageName getDefaultImageName() { + return DEFAULT_IMAGE_NAME; + } + + public DaprContainer withPlacementImage(String placementDockerImageName) { + this.placementDockerImageName = placementDockerImageName; + return this; + } + + public DaprContainer withReusablePlacement(boolean reuse) { + this.shouldReusePlacement = reuse; + return this; + } + + public DaprContainer withPlacementContainer(DaprPlacementContainer placementContainer) { + this.placementContainer = placementContainer; + return this; + } + + // Required by spotbugs plugin + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprLogLevel.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprLogLevel.java new file mode 100644 index 0000000000..9cc3dbf534 --- /dev/null +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprLogLevel.java @@ -0,0 +1,21 @@ +/* + * Copyright 2024 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.testcontainers; + +public enum DaprLogLevel { + ERROR, + WARN, + INFO, + DEBUG +} diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprPlacementContainer.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprPlacementContainer.java new file mode 100644 index 0000000000..1144f3c080 --- /dev/null +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprPlacementContainer.java @@ -0,0 +1,75 @@ +/* + * Copyright 2024 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.testcontainers; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +/** + * Test container for Dapr placement service. + */ +public class DaprPlacementContainer extends GenericContainer { + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("daprio/placement"); + private int placementPort = 50005; + + /** + * Creates a new Dapr placement container. + * @param dockerImageName Docker image name. + */ + public DaprPlacementContainer(DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + + withExposedPorts(placementPort); + } + + /** + * Creates a new Dapr placement container. + * @param image Docker image name. + */ + public DaprPlacementContainer(String image) { + this(DockerImageName.parse(image)); + } + + @Override + protected void configure() { + super.configure(); + withCommand("./placement", "-port", Integer.toString(placementPort)); + } + + public static DockerImageName getDefaultImageName() { + return DEFAULT_IMAGE_NAME; + } + + public DaprPlacementContainer withPort(Integer port) { + this.placementPort = port; + return this; + } + + public int getPort() { + return placementPort; + } + + // Required by spotbugs plugin + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprProtocol.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprProtocol.java new file mode 100644 index 0000000000..1d5185dd3d --- /dev/null +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprProtocol.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 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.testcontainers; + +public enum DaprProtocol { + HTTP("http"), + GRPC("grpc"); + + private String name; + + DaprProtocol(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/MetadataEntry.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/MetadataEntry.java new file mode 100644 index 0000000000..e171c681cc --- /dev/null +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/MetadataEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 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.testcontainers; + +public class MetadataEntry { + private String name; + private String value; + + public MetadataEntry(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public void setName(String name) { + this.name = name; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/Subscription.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/Subscription.java new file mode 100644 index 0000000000..faa1b6916d --- /dev/null +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/Subscription.java @@ -0,0 +1,52 @@ +/* + * Copyright 2024 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.testcontainers; + +public class Subscription { + private String name; + private String pubsubName; + private String topic; + private String route; + + /** + * Creates a new subscription. + * + * @param name Subscription name. + * @param pubsubName PubSub name. + * @param topic Topic name. + * @param route Route. + */ + public Subscription(String name, String pubsubName, String topic, String route) { + this.name = name; + this.pubsubName = pubsubName; + this.topic = topic; + this.route = route; + } + + public String getName() { + return name; + } + + public String getPubsubName() { + return pubsubName; + } + + public String getTopic() { + return topic; + } + + public String getRoute() { + return route; + } +} diff --git a/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprComponentTest.java b/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprComponentTest.java new file mode 100644 index 0000000000..eb3067e3f4 --- /dev/null +++ b/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprComponentTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2024 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.testcontainers; + +import org.junit.Assert; +import org.junit.Test; + +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Set; + +import static org.junit.Assert.assertThrows; + +public class DaprComponentTest { + + @Test + public void componentStateStoreSerializationTest() { + DaprContainer dapr = new DaprContainer("daprio/daprd") + .withAppName("dapr-app") + .withAppPort(8081) + .withComponent(new Component( + "statestore", + "state.in-memory", + "v1", + Collections.singletonMap("actorStateStore", "true"))) + .withAppChannelAddress("host.testcontainers.internal"); + + Set components = dapr.getComponents(); + Assert.assertEquals(1, components.size()); + + Component kvstore = components.iterator().next(); + Assert.assertEquals(false, kvstore.getMetadata().isEmpty()); + + String componentYaml = dapr.componentToYaml(kvstore); + String expectedComponentYaml = "metadata:\n" + " name: statestore\n" + + "apiVersion: dapr.io/v1alpha1\n" + + "kind: Component\n" + + "spec:\n" + + " metadata:\n" + + " - name: actorStateStore\n" + + " value: 'true'\n" + + " type: state.in-memory\n" + + " version: v1\n"; + + Assert.assertEquals(expectedComponentYaml, componentYaml); + } + + @Test + public void containerConfigurationTest() { + DaprContainer dapr = new DaprContainer("daprio/daprd") + .withAppName("dapr-app") + .withAppPort(8081) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withAppChannelAddress("host.testcontainers.internal"); + + dapr.configure(); + + assertThrows(IllegalStateException.class, () -> { dapr.getHttpEndpoint(); }); + assertThrows(IllegalStateException.class, () -> { dapr.getGrpcPort(); }); + + + + + } + + @Test + public void subscriptionSerializationTest() { + DaprContainer dapr = new DaprContainer("daprio/daprd") + .withAppName("dapr-app") + .withAppPort(8081) + .withSubscription(new Subscription("my-subscription", "pubsub", "topic", "/events")) + .withAppChannelAddress("host.testcontainers.internal"); + + Set subscriptions = dapr.getSubscriptions(); + Assert.assertEquals(1, subscriptions.size()); + + String subscriptionYaml = dapr.subscriptionToYaml(subscriptions.iterator().next()); + String expectedSubscriptionYaml = "metadata:\n" + " name: my-subscription\n" + + "apiVersion: dapr.io/v1alpha1\n" + + "kind: Subscription\n" + + "spec:\n" + + " route: /events\n" + + " pubsubname: pubsub\n" + + " topic: topic\n"; + Assert.assertEquals(expectedSubscriptionYaml, subscriptionYaml); + } + + @Test + public void withComponentFromPath() { + URL stateStoreYaml = this.getClass().getClassLoader().getResource("dapr-resources/statestore.yaml"); + Path path = Paths.get(stateStoreYaml.getPath()); + + DaprContainer dapr = new DaprContainer("daprio/daprd") + .withAppName("dapr-app") + .withAppPort(8081) + .withComponent(path) + .withAppChannelAddress("host.testcontainers.internal"); + + Set components = dapr.getComponents(); + Assert.assertEquals(1, components.size()); + Component kvstore = components.iterator().next(); + Assert.assertEquals(false, kvstore.getMetadata().isEmpty()); + + String componentYaml = dapr.componentToYaml(kvstore); + String expectedComponentYaml = "metadata:\n" + + " name: statestore\n" + + "apiVersion: dapr.io/v1alpha1\n" + + "kind: Component\n" + + "spec:\n" + + " metadata:\n" + + " - name: name\n" + + " value: keyPrefix\n" + + " - name: value\n" + + " value: name\n" + + " - name: name\n" + + " value: redisHost\n" + + " - name: value\n" + + " value: redis:6379\n" + + " - name: name\n" + + " value: redisPassword\n" + + " - name: value\n" + + " value: ''\n" + + " type: null\n" + + " version: v1\n"; + + Assert.assertEquals(expectedComponentYaml, componentYaml); + } +} diff --git a/testcontainers-dapr/src/test/resources/dapr-resources/statestore.yaml b/testcontainers-dapr/src/test/resources/dapr-resources/statestore.yaml new file mode 100644 index 0000000000..aeeadbe766 --- /dev/null +++ b/testcontainers-dapr/src/test/resources/dapr-resources/statestore.yaml @@ -0,0 +1,14 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.redis + version: v1 + metadata: + - name: keyPrefix + value: name + - name: redisHost + value: redis:6379 + - name: redisPassword + value: ""