Compare commits

...

4 Commits

Author SHA1 Message Date
Dapr Bot 3e40d7595b Release 1.16.0-rc-2
Signed-off-by: Dapr Bot <daprweb@microsoft.com>
2025-09-03 16:08:46 +00:00
Javier Aliaga 726c1b062a
Master to 1.16 (#1541)
* process deps too for dapr-spring (#1503)

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* Use camelCase on properties (#1470)

* Use camelCase on properties

Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>

* Add test for cameCalse properties

Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>

* Update daprdocs/content/en/java-sdk-docs/spring-boot/_index.md

Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Signed-off-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com>

---------

Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
Signed-off-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com>
Co-authored-by: artur-ciocanu <artur.ciocanu@gmail.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* fix script (#1506)

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* add support for custom status (#1505)

Signed-off-by: salaboy <Salaboy@gmail.com>
Co-authored-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* try 1.5.7 (#1487)

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Co-authored-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Co-authored-by: artur-ciocanu <artur.ciocanu@gmail.com>
Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* rm protoc cmd from pom.xml (#1498)

* rm protoc cmd bc it uses a local protoc which might be the wrong version and results in a bad error

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* update protoc

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

---------

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Co-authored-by: artur-ciocanu <artur.ciocanu@gmail.com>
Co-authored-by: salaboy <Salaboy@gmail.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* feat: Support registering activities with custom name (#1431)

* feat: Support register of activities with custom name

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: Apply suggestions

Signed-off-by: Javier Aliaga <javier@diagrid.io>

---------

Signed-off-by: Javier Aliaga <javier@diagrid.io>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* Bump codecov/codecov-action from 5.4.3 to 5.5.0 (#1513)

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.3 to 5.5.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.4.3...v5.5.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 5.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* commit: Fix validate job (#1524)

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: New task execution task id test (#1352) (#1526)

* chore: New task execution task id test

test how taskExecutionTaskId can be used for idempotency

* chore: Clean up not used files

* docs: Task execution keys

* test: Modify unit tests

* Remove new lines

---------

Signed-off-by: Javier Aliaga <javier@diagrid.io>
Signed-off-by: artur-ciocanu <artur.ciocanu@gmail.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Co-authored-by: artur-ciocanu <artur.ciocanu@gmail.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* Bump actions/checkout from 4 to 5 (#1504)

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* Improve CI runs (#1527)

* chore: Wait for dapr script

When running mechanical markdown examples wait for dapr to be running

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: Fix build ci

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: Fix build ci

Signed-off-by: Javier Aliaga <javier@diagrid.io>

---------

Signed-off-by: Javier Aliaga <javier@diagrid.io>
Co-authored-by: salaboy <Salaboy@gmail.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* Fix CVEs (#1529)

* chore: Read test certs from resources folder

This will avoid importing bouncy castle library just for tests

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* ci: Split unit test and integration tests

Signed-off-by: Javier Aliaga <javier@diagrid.io>

---------

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* fix: Use correct snapshot url (#1530)

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* Bump actions/setup-java from 4 to 5 (#1512)

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: Bump dapr version (#1539)

* chore: Bump dapr version

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: Fix conversation tests

Only one response expected

Signed-off-by: Javier Aliaga <javier@diagrid.io>

---------

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* Adding logger to WorkflowActivityContext (#1534)

* Adding logger to WorkflowActivityContext

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Fixing a styling issue

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Add unit tests for workflow activity context

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Add more unit tests to make Codecov happy

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Add more unit tests

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Improve code coverage

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Improve test names

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

---------

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: salaboy <Salaboy@gmail.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* Use notimestamp in Javadoc Plugin to disable timestamps in Javadocs (#1538)

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* Feat Cross App CallActivity (#1468)

* cross app ex

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* update protoc cmd

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* feedback

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* builder pattern

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* fix protoc

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* debug log levels for test containers

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* update readme and add debugging info

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* add IT test for cross app call activity

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* cleanup test

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* sysout -> ctx.logger

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* reset pom

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* rm debug lines from readme

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* fix header + rm customports

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* use consts

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* rm waitfor call

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* rm pubsub

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* rm timeout

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* reset empty lines added

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* reset appname for daprcontainer

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* reset empty line diff

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* rm constructor info from readme

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* debug -> info

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* rm super.start

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* reset dapr container diff

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* add test for codecov

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* up timeout time to unblock PR

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* deps: Update durabletask-client to 1.5.10

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* ci: Revert build timeout

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* review: Use ctx.getLogger

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: Fix review comments

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: more review comments fixes

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* test: Use testcontainers in CrossApp IT test

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: Load classpath for IT with all dependencies

Signed-off-by: Javier Aliaga <javier@diagrid.io>

---------

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Signed-off-by: Javier Aliaga <javier@diagrid.io>
Co-authored-by: Javier Aliaga <javier@diagrid.io>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* Release 1.16.0-rc-1

Signed-off-by: Dapr Bot <daprweb@microsoft.com>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* process deps too for dapr-spring (#1502)

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: Alpha components to stable

Signed-off-by: Javier Aliaga <javier@diagrid.io>

---------

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Signed-off-by: Javier Aliaga <javier@diagrid.io>
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
Signed-off-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com>
Signed-off-by: salaboy <Salaboy@gmail.com>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: artur-ciocanu <artur.ciocanu@gmail.com>
Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>
Signed-off-by: Dapr Bot <daprweb@microsoft.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Co-authored-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com>
Co-authored-by: artur-ciocanu <artur.ciocanu@gmail.com>
Co-authored-by: salaboy <Salaboy@gmail.com>
Co-authored-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Dapr Bot <daprweb@microsoft.com>
2025-09-03 09:16:35 +01:00
Cassie Coyle 6e8e37d84d
process deps too for dapr-spring (#1502)
Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
2025-08-15 14:22:44 -05:00
Dapr Bot 8dcc23e61a Release 1.16.0-rc-1
Signed-off-by: Dapr Bot <daprweb@microsoft.com>
2025-08-15 14:47:15 +00:00
82 changed files with 2137 additions and 502 deletions

View File

@ -9,25 +9,12 @@ DAPR_JAVA_SDK_VERSION=$1
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
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-property -Dproperty=dapr.sdk.alpha.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_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
# dapr-spring
mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f dapr-spring/pom.xml
# spring-boot-examples
mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f spring-boot-examples/pom.xml
git clean -f

View File

@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install dependencies
run: pip install PyGithub
- name: Automerge and update

View File

@ -15,6 +15,35 @@ on:
- release-*
jobs:
test:
name: "Unit tests"
runs-on: ubuntu-latest
timeout-minutes: 30
continue-on-error: false
env:
JDK_VER: 17
steps:
- uses: actions/checkout@v5
- name: Set up OpenJDK ${{ env.JDK_VER }}
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}
- name: Run tests
run: ./mvnw clean install -B -q
- name: Codecov
uses: codecov/codecov-action@v5.5.0
- name: Upload test report for sdk
uses: actions/upload-artifact@v4
with:
name: test-dapr-java-sdk-jdk${{ env.JDK_VER }}
path: sdk/target/jacoco-report/
- name: Upload test report for sdk-actors
uses: actions/upload-artifact@v4
with:
name: report-dapr-java-sdk-actors-jdk${{ env.JDK_VER }}
path: sdk-actors/target/jacoco-report/
build:
name: "Build jdk:${{ matrix.java }} sb:${{ matrix.spring-boot-display-version }} exp:${{ matrix.experimental }}"
runs-on: ubuntu-latest
@ -39,7 +68,7 @@ jobs:
GOPROXY: https://proxy.golang.org
JDK_VER: ${{ matrix.java }}
DAPR_CLI_VER: 1.15.0
DAPR_RUNTIME_VER: 1.16.0-rc.3
DAPR_RUNTIME_VER: 1.16.0-rc.5
DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.15.0/install/install.sh
DAPR_CLI_REF:
DAPR_REF:
@ -50,9 +79,9 @@ jobs:
uses: docker/setup-docker-action@v4
- name: Check Docker version
run: docker version
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up OpenJDK ${{ env.JDK_VER }}
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}
@ -64,14 +93,14 @@ jobs:
with:
go-version: ${{ env.GOVER }}
- name: Checkout Dapr CLI repo to override dapr command.
uses: actions/checkout@v4
uses: actions/checkout@v5
if: env.DAPR_CLI_REF != ''
with:
repository: dapr/cli
ref: ${{ env.DAPR_CLI_REF }}
path: cli
- name: Checkout Dapr repo to override daprd.
uses: actions/checkout@v4
uses: actions/checkout@v5
if: env.DAPR_REF != ''
with:
repository: dapr/dapr
@ -112,33 +141,13 @@ jobs:
wget -q ${{ env.TOXIPROXY_URL }} -O /home/runner/.local/bin/toxiproxy-server
chmod +x /home/runner/.local/bin/toxiproxy-server
/home/runner/.local/bin/toxiproxy-server --version
- name: Clean up files
run: ./mvnw clean -B
- name: Build sdk
run: ./mvnw compile -B -q
- name: Unit tests
run: ./mvnw test # making it temporarily verbose.
env:
DOCKER_HOST: ${{steps.setup_docker.outputs.sock}}
- name: Codecov
uses: codecov/codecov-action@v5.4.3
- name: Install jars
run: ./mvnw install -q -B -DskipTests
- name: Clean up and install sdk
run: ./mvnw clean install -B -q -DskipTests
- name: Integration tests using spring boot version ${{ matrix.spring-boot-version }}
id: integration_tests
run: PRODUCT_SPRING_BOOT_VERSION=${{ matrix.spring-boot-version }} ./mvnw -B -Pintegration-tests verify
run: PRODUCT_SPRING_BOOT_VERSION=${{ matrix.spring-boot-version }} ./mvnw -B -Pintegration-tests dependency:copy-dependencies verify
env:
DOCKER_HOST: ${{steps.setup_docker.outputs.sock}}
- name: Upload test report for sdk
uses: actions/upload-artifact@v4
with:
name: report-dapr-java-sdk-jdk${{ matrix.java }}-sb${{ matrix.spring-boot-version }}
path: sdk/target/jacoco-report/
- name: Upload test report for sdk-actors
uses: actions/upload-artifact@v4
with:
name: report-dapr-java-sdk-actors-jdk${{ matrix.java }}-sb${{ matrix.spring-boot-version }}
path: sdk-actors/target/jacoco-report/
- name: Upload failsafe test report for sdk-tests on failure
if: ${{ failure() && steps.integration_tests.conclusion == 'failure' }}
uses: actions/upload-artifact@v4
@ -154,7 +163,7 @@ jobs:
publish:
runs-on: ubuntu-latest
needs: build
needs: [ build, test ]
timeout-minutes: 30
env:
JDK_VER: 17
@ -163,9 +172,9 @@ jobs:
GPG_KEY: ${{ secrets.GPG_KEY }}
GPG_PWD: ${{ secrets.GPG_PWD }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up OpenJDK ${{ env.JDK_VER }}
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}

View File

@ -29,13 +29,13 @@ jobs:
JDK_VER: '17'
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.DAPR_BOT_TOKEN }}
persist-credentials: false
- name: Set up OpenJDK ${{ env.JDK_VER }}
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}

View File

@ -32,7 +32,7 @@ jobs:
FOSSA_API_KEY: b88e1f4287c3108c8751bf106fb46db6 # This is a push-only token that is safe to be exposed.
steps:
- name: "Checkout code"
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: "Run FOSSA Scan"
uses: fossas/fossa-action@v1.7.0 # Use a specific version if locking is preferred

View File

@ -22,9 +22,9 @@ jobs:
env:
JDK_VER: 17
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up OpenJDK ${{ env.JDK_VER }}
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}

View File

@ -38,14 +38,14 @@ jobs:
GOPROXY: https://proxy.golang.org
JDK_VER: ${{ matrix.java }}
DAPR_CLI_VER: 1.15.0
DAPR_RUNTIME_VER: 1.16.0-rc.3
DAPR_RUNTIME_VER: 1.16.0-rc.5
DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.15.0/install/install.sh
DAPR_CLI_REF:
DAPR_REF:
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up OpenJDK ${{ env.JDK_VER }}
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}
@ -62,14 +62,14 @@ jobs:
with:
go-version: ${{ env.GOVER }}
- name: Checkout Dapr CLI repo to override dapr command.
uses: actions/checkout@v4
uses: actions/checkout@v5
if: env.DAPR_CLI_REF != ''
with:
repository: dapr/cli
ref: ${{ env.DAPR_CLI_REF }}
path: cli
- name: Checkout Dapr repo to override daprd.
uses: actions/checkout@v4
uses: actions/checkout@v5
if: env.DAPR_REF != ''
with:
repository: dapr/dapr

View File

@ -6,7 +6,7 @@
<parent>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-parent</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-spring-boot-autoconfigure</artifactId>

View File

@ -25,8 +25,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -111,6 +109,18 @@ class DaprClientAutoConfigurationTest {
verify(builder).withPropertyOverride(Properties.GRPC_PORT, String.valueOf(grpcPort));
}
@Test
@DisplayName("Should override API token if it exists")
void shouldOverrideApiTokenIfExists() {
String apiToken = "token";
when(connectionDetails.getApiToken()).thenReturn(apiToken);
configuration.daprClientBuilder(connectionDetails);
verify(builder).withPropertyOverride(Properties.API_TOKEN, apiToken);
}
@Test
@DisplayName("Should override HTTP endpoint in properties if it exists")
void shouldOverrideHttpEndpointInPropertiesIfExists() {
@ -159,6 +169,18 @@ class DaprClientAutoConfigurationTest {
assertThat(result.getValue(Properties.GRPC_PORT)).isEqualTo(grpcPort);
}
@Test
@DisplayName("Should override API token in properties if it exists")
void shouldOverrideApiTokenPropertiesIfExists() {
String apiToken = "token";
when(connectionDetails.getApiToken()).thenReturn(apiToken);
Properties result = configuration.createPropertiesFromConnectionDetails(connectionDetails);
assertThat(result.getValue(Properties.API_TOKEN)).isEqualTo(apiToken);
}
private static class TestDaprClientAutoConfiguration extends DaprClientAutoConfiguration {
private final DaprClientBuilder daprClientBuilder;

View File

@ -83,7 +83,26 @@ public class DaprClientPropertiesTest {
});
});
}
@Test
@DisplayName("Should map DaprClient properties correctly (camelCase)")
public void shouldMapDaprClientPropertiesCamelCase() {
runner.withSystemProperties(
"dapr.client.httpEndpoint=http://localhost",
"dapr.client.httpPort=3500",
"dapr.client.grpcEndpoint=localhost",
"dapr.client.grpcPort=50001"
).run(context -> {
DaprClientProperties properties = context.getBean(DaprClientProperties.class);
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(properties.getGrpcEndpoint()).isEqualTo("localhost");
softly.assertThat(properties.getHttpEndpoint()).isEqualTo("http://localhost");
softly.assertThat(properties.getHttpPort()).isEqualTo(3500);
softly.assertThat(properties.getGrpcPort()).isEqualTo(50001);
});
});
}
@EnableConfigurationProperties(DaprClientProperties.class)

View File

@ -6,7 +6,7 @@
<parent>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-parent</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-parent</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-parent</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-spring-boot-tests</artifactId>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-parent</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-spring-data</artifactId>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-parent</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-spring-messaging</artifactId>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-parent</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-spring-workflows</artifactId>

View File

@ -7,13 +7,13 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-parent</artifactId>
<packaging>pom</packaging>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<name>dapr-spring-parent</name>
<description>SDK extension for Spring and Spring Boot</description>
@ -33,7 +33,6 @@
<maven.compiler.release>11</maven.compiler.release>
<testcontainers.version>1.19.8</testcontainers.version>
<junit.version>5.11.2</junit.version>
<dapr.spring.version>0.16.0-SNAPSHOT</dapr.spring.version>
</properties>
<dependencyManagement>
@ -57,27 +56,27 @@
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-workflows</artifactId>
<version>${dapr.sdk.alpha.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-data</artifactId>
<version>${dapr.spring.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-messaging</artifactId>
<version>${dapr.spring.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-workflows</artifactId>
<version>${dapr.spring.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-boot-autoconfigure</artifactId>
<version>${dapr.spring.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.dapr</groupId>
@ -119,7 +118,7 @@
<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-boot-tests</artifactId>
<version>${dapr.spring.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<!-- Test dependencies -->
@ -186,6 +185,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<notimestamp>true</notimestamp>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>

View File

@ -6,7 +6,7 @@ weight: 20000
description: How to get up and running with workflows using the Dapr Java SDK
---
Lets create a Dapr workflow and invoke it using the console. With the [provided workflow example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows), you will:
Let's create a Dapr workflow and invoke it using the console. With the [provided workflow example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows), you will:
- Execute the workflow instance using the [Java workflow worker](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java)
- Utilize the Java workflow client and API calls to [start and terminate workflow instances](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java)
@ -85,11 +85,10 @@ You're up and running! Both Dapr and your app logs will appear here.
== APP == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:50001.
```
## Run the `DemoWorkflowClient
## Run the `DemoWorkflowClient`
The `DemoWorkflowClient` starts instances of workflows that have been registered with Dapr.
```java
public class DemoWorkflowClient {
@ -246,4 +245,40 @@ Exiting DemoWorkflowClient.
## Next steps
- [Learn more about Dapr workflow]({{% ref workflow-overview.md %}})
- [Workflow API reference]({{% ref workflow_api.md %}})
- [Workflow API reference]({{% ref workflow_api.md %}})
## Advanced features
### Task Execution Keys
Task execution keys are unique identifiers generated by the durabletask-java library. They are stored in the `WorkflowActivityContext` and can be used to track and manage the execution of workflow activities. They are particularly useful for:
1. **Idempotency**: Ensuring activities are not executed multiple times for the same task
2. **State Management**: Tracking the state of activity execution
3. **Error Handling**: Managing retries and failures in a controlled manner
Here's an example of how to use task execution keys in your workflow activities:
```java
public class TaskExecutionKeyActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext ctx) {
// Get the task execution key for this activity
String taskExecutionKey = ctx.getTaskExecutionKey();
// Use the key to implement idempotency or state management
// For example, check if this task has already been executed
if (isTaskAlreadyExecuted(taskExecutionKey)) {
return getPreviousResult(taskExecutionKey);
}
// Execute the activity logic
Object result = executeActivityLogic();
// Store the result with the task execution key
storeResult(taskExecutionKey, result);
return result;
}
}
```

View File

@ -56,13 +56,13 @@ This will connect to the default Dapr gRPC endpoint `localhost:50001`, requiring
{{% alert title="Note" color="primary" %}}
By default, the following properties are preconfigured for `DaprClient` and `DaprWorkflowClient`:
```properties
dapr.client.http-endpoint=http://localhost
dapr.client.http-port=3500
dapr.client.grpc-endpoint=localhost
dapr.client.grpc-port=50001
dapr.client.api-token=<Your Remote App API Token>
dapr.client.httpEndpoint=http://localhost
dapr.client.httpPort=3500
dapr.client.grpcEndpoint=localhost
dapr.client.grpcPort=50001
dapr.client.apiToken=<your remote api token>
```
These values are used by default, but you can override them in your `application.properties` file to suit your environment.
These values are used by default, but you can override them in your `application.properties` file to suit your environment. Please note that both kebab case and camel case are supported.
{{% /alert %}}
You can use the `DaprClient` to interact with the Dapr APIs anywhere in your application, for example from inside a REST endpoint:
@ -95,7 +95,7 @@ public class DaprTestContainersConfig {
@ServiceConnection
public DaprContainer daprContainer(Network daprNetwork, PostgreSQLContainer<?> postgreSQLContainer){
return new DaprContainer("daprio/daprd:1.16.0-rc.3")
return new DaprContainer("daprio/daprd:1.16.0-rc.5")
.withAppName("producer-app")
.withNetwork(daprNetwork)
.withComponent(new Component("kvstore", "state.postgresql", "v1", STATE_STORE_PROPERTIES))
@ -250,7 +250,7 @@ Finally, because Dapr PubSub requires a bidirectional connection between your ap
@ServiceConnection
public DaprContainer daprContainer(Network daprNetwork, PostgreSQLContainer<?> postgreSQLContainer, RabbitMQContainer rabbitMQContainer){
return new DaprContainer("daprio/daprd:1.16.0-rc.3")
return new DaprContainer("daprio/daprd:1.16.0-rc.5")
.withAppName("producer-app")
.withNetwork(daprNetwork)
.withComponent(new Component("kvstore", "state.postgresql", "v1", STATE_STORE_PROPERTIES))

View File

@ -7,12 +7,12 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-sdk-examples</artifactId>
<packaging>jar</packaging>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<name>dapr-sdk-examples</name>
<properties>
@ -127,7 +127,7 @@
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-workflows</artifactId>
<version>${dapr.sdk.alpha.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.dapr</groupId>

View File

@ -53,6 +53,7 @@ Those examples contain the following workflow patterns:
4. [External Event Pattern](#external-event-pattern)
5. [Child-workflow Pattern](#child-workflow-pattern)
6. [Compensation Pattern](#compensation-pattern)
7. [Cross-App Pattern](#cross-app-pattern)
### Chaining Pattern
In the chaining pattern, a sequence of activities executes in a specific order.
@ -147,7 +148,7 @@ background: true
-->
Execute the following script in order to run DemoChainWorker:
```sh
dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainWorker 50001
dapr run --app-id chainingworker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainWorker 50001
```
Once running, the logs will start displaying the different steps: First, you can see workflow is starting:
@ -169,7 +170,8 @@ timeout_seconds: 20
-->
Then, execute the following script in order to run DemoChainClient:
```sh
sleep 10 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainClient 50001
java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainClient 50001
dapr stop --app-id chainingworker
```
<!-- END_STEP -->
@ -266,7 +268,7 @@ background: true
Execute the following script in order to run DemoFanInOutWorker:
```sh
dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutWorker 50002
dapr run --app-id faninoutworker --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutWorker 50002
```
<!-- END_STEP -->
@ -282,7 +284,8 @@ timeout_seconds: 20
Execute the following script in order to run DemoFanInOutClient:
```sh
sleep 10 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutClient 50002
java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutClient 50002
dapr stop --app-id faninoutworker
```
<!-- END_STEP -->
@ -660,7 +663,8 @@ timeout_seconds: 30
-->
Once running, execute the following script to run the BookTripClient:
```sh
sleep 15 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.compensation.BookTripClient 50003
java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.compensation.BookTripClient 50003
dapr stop --app-id book-trip-worker
```
<!-- END_STEP -->
@ -678,6 +682,158 @@ Key Points:
4. Each activity simulates work with a short delay for demonstration purposes
### Cross-App Pattern
The cross-app pattern allows workflows to call activities that are hosted in different Dapr applications. This is useful for microservices architectures allowing multiple applications to host activities that can be orchestrated by Dapr Workflows.
The `CrossAppWorkflow` class defines the workflow. It demonstrates calling activities in different apps using the `appId` parameter in `WorkflowTaskOptions`. See the code snippet below:
```java
public class CrossAppWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
var logger = ctx.getLogger();
logger.info("=== WORKFLOW STARTING ===");
logger.info("Starting CrossAppWorkflow: {}", ctx.getName());
logger.info("Workflow name: {}", ctx.getName());
logger.info("Workflow instance ID: {}", ctx.getInstanceId());
String input = ctx.getInput(String.class);
logger.info("CrossAppWorkflow received input: {}", input);
logger.info("Workflow input: {}", input);
// Call an activity in another app by passing in an active appID to the WorkflowTaskOptions
logger.info("Calling cross-app activity in 'app2'...");
logger.info("About to call cross-app activity in app2...");
String crossAppResult = ctx.callActivity(
App2TransformActivity.class.getName(),
input,
new WorkflowTaskOptions("app2"),
String.class
).await();
// Call another activity in a different app
logger.info("Calling cross-app activity in 'app3'...");
logger.info("About to call cross-app activity in app3...");
String finalResult = ctx.callActivity(
App3FinalizeActivity.class.getName(),
crossAppResult,
new WorkflowTaskOptions("app3"),
String.class
).await();
logger.info("Final cross-app activity result: {}", finalResult);
logger.info("Final cross-app activity result: {}", finalResult);
logger.info("CrossAppWorkflow finished with: {}", finalResult);
logger.info("=== WORKFLOW COMPLETING WITH: {} ===" , finalResult);
ctx.complete(finalResult);
};
}
}
```
The `App2TransformActivity` class defines an activity in app2 that transforms the input string. See the code snippet below:
```java
public class App2TransformActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext ctx) {
var logger = ctx.getLogger();
logger.info("=== App2: TransformActivity called ===");
String input = ctx.getInput(String.class);
logger.info("Input: {}", input);
// Transform the input
String result = input.toUpperCase() + " [TRANSFORMED BY APP2]";
logger.info("Output: {}", result);
return result;
}
}
```
The `App3FinalizeActivity` class defines an activity in app3 that finalizes the processing. See the code snippet below:
```java
public class App3FinalizeActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext ctx) {
var logger = ctx.getLogger();
logger.info("=== App3: FinalizeActivity called ===");
String input = ctx.getInput(String.class);
logger.info("Input: ", input);
// Finalize the processing
String result = input + " [FINALIZED BY APP3]";
logger.info("Output: {}", result);
return result;
}
}
```
**Key Features:**
- **Cross-app activity calls**: Call activities in different Dapr applications specifying the appID in the WorkflowTaskOptions
- **WorkflowTaskOptions with appId**: Specify which app should handle the activity
- **Combined with retry policies**: Use app ID along with retry policies and handlers
- **Error handling**: Works the same as local activity calls
**Requirements:**
- Multiple Dapr applications running with different app IDs
- Activities registered in the target applications
- Proper Dapr workflow runtime configuration
**Important Limitations:**
- **Cross-app calls are currently supported for activities only**
- **Child workflow cross-app calls (suborchestration) are NOT supported**
- The app ID must match the Dapr application ID of the target service
**Running the Cross-App Example:**
This example requires running multiple Dapr applications simultaneously. You'll need to run the following commands in separate terminals:
1. **Start the main workflow worker (crossapp-worker):**
```sh
dapr run --app-id crossapp-worker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.CrossAppWorker
```
2. **Start app2 worker (handles App2TransformActivity):**
```sh
dapr run --app-id app2 --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.App2Worker
```
3. **Start app3 worker (handles App3FinalizeActivity):**
```sh
dapr run --app-id app3 --resources-path ./components/workflows --dapr-grpc-port 50003 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.App3Worker
```
4. **Run the workflow client:**
```sh
java -Djava.util.logging.ConsoleHandler.level=FINE -Dio.dapr.durabletask.level=FINE -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.CrossAppWorkflowClient "Hello World"
```
**Expected Output:**
The client will show:
```text
=== Starting Cross-App Workflow Client ===
Input: Hello World
Created DaprWorkflowClient successfully
Attempting to start new workflow...
Started a new cross-app workflow with instance ID: 001113f3-b9d9-438c-932a-a9a9b70b9460
Waiting for workflow completion...
Workflow instance with ID: 001113f3-b9d9-438c-932a-a9a9b70b9460 completed with result: HELLO WORLD [TRANSFORMED BY APP2] [FINALIZED BY APP3]
```
The workflow demonstrates:
1. The workflow starts in the main worker (crossapp-worker)
2. Calls an activity in 'app2' using cross-app functionality
3. Calls an activity in 'app3' using cross-app functionality
4. The workflow completes with the final result from all activities
This pattern is particularly useful for:
- Microservices architectures where activities are distributed across multiple services
- Multi-tenant applications where activities are isolated by app ID
### Suspend/Resume Pattern
Workflow instances can be suspended and resumed. This example shows how to use the suspend and resume commands.
@ -702,7 +858,7 @@ timeout_seconds: 30
-->
```sh
dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50004 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeWorker 50004
dapr run --app-id suspendresumeworker --resources-path ./components/workflows --dapr-grpc-port 50004 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeWorker 50004
```
<!-- END_STEP -->
@ -720,7 +876,8 @@ expected_stdout_lines:
timeout_seconds: 30
-->
```sh
sleep 15 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeClient 50004
java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeClient 50004
dapr stop --app-id suspendresumeworker
```
<!-- END_STEP -->

View File

@ -14,9 +14,11 @@ limitations under the License.
package io.dapr.examples.workflows.chain;
import io.dapr.examples.workflows.utils.PropertyUtils;
import io.dapr.examples.workflows.utils.RetryUtils;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
import java.time.Duration;
import java.util.concurrent.TimeoutException;
public class DemoChainClient {
@ -28,15 +30,15 @@ public class DemoChainClient {
*/
public static void main(String[] args) {
try (DaprWorkflowClient client = new DaprWorkflowClient(PropertyUtils.getProperties(args))) {
String instanceId = client.scheduleNewWorkflow(DemoChainWorkflow.class);
String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(DemoChainWorkflow.class),
Duration.ofSeconds(60));
System.out.printf("Started a new chaining model workflow with instance ID: %s%n", instanceId);
WorkflowInstanceStatus workflowInstanceStatus =
client.waitForInstanceCompletion(instanceId, null, true);
String result = workflowInstanceStatus.readOutputAs(String.class);
System.out.printf("workflow instance with ID: %s completed with result: %s%n", instanceId, result);
} catch (TimeoutException | InterruptedException e) {
throw new RuntimeException(e);
}

View File

@ -14,6 +14,7 @@ limitations under the License.
package io.dapr.examples.workflows.compensation;
import io.dapr.examples.workflows.utils.PropertyUtils;
import io.dapr.examples.workflows.utils.RetryUtils;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
@ -23,7 +24,7 @@ import java.util.concurrent.TimeoutException;
public class BookTripClient {
public static void main(String[] args) {
try (DaprWorkflowClient client = new DaprWorkflowClient(PropertyUtils.getProperties(args))) {
String instanceId = client.scheduleNewWorkflow(BookTripWorkflow.class);
String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(BookTripWorkflow.class), Duration.ofSeconds(60));
System.out.printf("Started a new trip booking workflow with instance ID: %s%n", instanceId);
WorkflowInstanceStatus status = client.waitForInstanceCompletion(instanceId, Duration.ofMinutes(30), true);

View File

@ -0,0 +1,34 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
/**
* TransformActivity for App2 - transforms input to uppercase.
* This activity is called cross-app from the main workflow.
*/
public class App2TransformActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext context) {
String input = context.getInput(String.class);
var logger = context.getLogger();
logger.info("=== App2: TransformActivity called ===");
logger.info("Input: {}", input);
String result = input.toUpperCase() + " [TRANSFORMED BY APP2]";
logger.info("Output: {}", result);
return result;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
/**
* App2 Worker - registers only the TransformActivity.
* This app will handle cross-app activity calls from the main workflow.
*/
public class App2Worker {
public static void main(String[] args) throws Exception {
System.out.println("=== Starting App2Worker ===");
// Register the Workflow with the builder
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
.registerActivity(App2TransformActivity.class);
// Build and start the workflow runtime
try (WorkflowRuntime runtime = builder.build()) {
System.out.println("App2 is ready to receive cross-app activity calls...");
runtime.start();
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
/**
* FinalizeActivity for App3 - adds final processing.
* This activity is called cross-app from the main workflow.
*/
public class App3FinalizeActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext context) {
String input = context.getInput(String.class);
var logger = context.getLogger();
logger.info("=== App3: FinalizeActivity called ===");
logger.info("Input: {}", input);
String result = input + " [FINALIZED BY APP3]";
logger.info("Output: {}", result);
return result;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
/**
* App3 Worker - registers only the FinalizeActivity.
* This app will handle cross-app activity calls from the main workflow.
*/
public class App3Worker {
public static void main(String[] args) throws Exception {
System.out.println("=== Starting App3Worker ===");
// Register the Workflow with the builder
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
.registerActivity(App3FinalizeActivity.class);
// Build and start the workflow runtime
try (WorkflowRuntime runtime = builder.build()) {
System.out.println("App3 is ready to receive cross-app activity calls...");
runtime.start();
}
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
public class CrossAppWorker {
public static void main(String[] args) throws Exception {
// Register the Workflow with the builder
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
.registerWorkflow(CrossAppWorkflow.class);
// Build and start the workflow runtime
try (WorkflowRuntime runtime = builder.build()) {
System.out.println("CrossAppWorker started - registered CrossAppWorkflow only");
runtime.start();
}
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import io.dapr.workflows.WorkflowTaskOptions;
/**
* Example workflow that demonstrates cross-app activity calls.
* This workflow calls activities in different apps using the appId parameter.
*/
public class CrossAppWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
var logger = ctx.getLogger();
logger.info("=== WORKFLOW STARTING ===");
logger.info("Starting CrossAppWorkflow: {}", ctx.getName());
logger.info("Workflow name: {}", ctx.getName());
logger.info("Workflow instance ID: {}", ctx.getInstanceId());
String input = ctx.getInput(String.class);
logger.info("CrossAppWorkflow received input: {}", input);
logger.info("Workflow input: {}", input);
// Call an activity in another app by passing in an active appID to the WorkflowTaskOptions
logger.info("Calling cross-app activity in 'app2'...");
logger.info("About to call cross-app activity in app2...");
String crossAppResult = ctx.callActivity(
App2TransformActivity.class.getName(),
input,
new WorkflowTaskOptions("app2"),
String.class
).await();
// Call another activity in a different app
logger.info("Calling cross-app activity in 'app3'...");
logger.info("About to call cross-app activity in app3...");
String finalResult = ctx.callActivity(
App3FinalizeActivity.class.getName(),
crossAppResult,
new WorkflowTaskOptions("app3"),
String.class
).await();
logger.info("Final cross-app activity result: {}", finalResult);
logger.info("CrossAppWorkflow finished with: {}", finalResult);
logger.info("=== WORKFLOW COMPLETING WITH: {} ===", finalResult);
ctx.complete(finalResult);
};
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
import java.util.concurrent.TimeoutException;
/**
* Cross-App Workflow Client - starts and monitors workflows.
*
* 1. Create a workflow client
* 2. Start a new workflow instance
* 3. Wait for completion and get results
*/
public class CrossAppWorkflowClient {
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("Usage: CrossAppWorkflowClientExample <input>");
System.out.println("Example: CrossAppWorkflowClientExample \"Hello World\"");
return;
}
String input = args[0];
System.out.println("=== Starting Cross-App Workflow Client ===");
System.out.println("Input: " + input);
try (DaprWorkflowClient client = new DaprWorkflowClient()) {
System.out.println("Created DaprWorkflowClient successfully");
// Start a new workflow instance
System.out.println("Attempting to start new workflow...");
String instanceId = client.scheduleNewWorkflow(CrossAppWorkflow.class, input);
System.out.printf("Started a new cross-app workflow with instance ID: %s%n", instanceId);
// Wait for the workflow to complete
System.out.println("Waiting for workflow completion...");
WorkflowInstanceStatus workflowInstanceStatus =
client.waitForInstanceCompletion(instanceId, null, true);
// Get the result
String result = workflowInstanceStatus.readOutputAs(String.class);
System.out.printf("Workflow instance with ID: %s completed with result: %s%n", instanceId, result);
} catch (TimeoutException | InterruptedException e) {
System.err.println("Error waiting for workflow completion: " + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
System.err.println("Error creating workflow client or starting workflow: " + e.getMessage());
e.printStackTrace();
}
}
}

View File

@ -14,6 +14,7 @@ limitations under the License.
package io.dapr.examples.workflows.faninout;
import io.dapr.examples.workflows.utils.PropertyUtils;
import io.dapr.examples.workflows.utils.RetryUtils;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
@ -40,9 +41,10 @@ public class DemoFanInOutClient {
"Always remember that you are absolutely unique. Just like everyone else.");
// Schedule an orchestration which will reliably count the number of words in all the given sentences.
String instanceId = client.scheduleNewWorkflow(
String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(
DemoFanInOutWorkflow.class,
listOfStrings);
listOfStrings), Duration.ofSeconds(60));
System.out.printf("Started a new fan out/fan in model workflow with instance ID: %s%n", instanceId);
// Block until the orchestration completes. Then print the final status, which includes the output.

View File

@ -15,9 +15,11 @@ package io.dapr.examples.workflows.suspendresume;
import io.dapr.examples.workflows.externalevent.DemoExternalEventWorkflow;
import io.dapr.examples.workflows.utils.PropertyUtils;
import io.dapr.examples.workflows.utils.RetryUtils;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
import java.time.Duration;
import java.util.concurrent.TimeoutException;
public class DemoSuspendResumeClient {
@ -29,7 +31,7 @@ public class DemoSuspendResumeClient {
*/
public static void main(String[] args) {
try (DaprWorkflowClient client = new DaprWorkflowClient(PropertyUtils.getProperties(args))) {
String instanceId = client.scheduleNewWorkflow(DemoExternalEventWorkflow.class);
String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(DemoExternalEventWorkflow.class), Duration.ofSeconds(60));
System.out.printf("Started a new external-event workflow with instance ID: %s%n", instanceId);

View File

@ -0,0 +1,47 @@
/*
* Copyright 2025 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.workflows.utils;
import java.time.Duration;
import java.util.concurrent.Callable;
public class RetryUtils {
private static final long RETRY_WAIT_MILLISECONDS = 1000;
public static String callWithRetry(Callable<String> function, Duration retryTimeout) throws InterruptedException {
var retryTimeoutMilliseconds = retryTimeout.toMillis();
long started = System.currentTimeMillis();
while (true) {
Throwable exception;
try {
return function.call();
} catch (Exception | AssertionError e) {
exception = e;
}
long elapsed = System.currentTimeMillis() - started;
if (elapsed >= retryTimeoutMilliseconds) {
if (exception instanceof RuntimeException) {
throw (RuntimeException) exception;
}
throw new RuntimeException(exception);
}
long remaining = retryTimeoutMilliseconds - elapsed;
Thread.sleep(Math.min(remaining, RETRY_WAIT_MILLISECONDS));
}
}
}

31
pom.xml
View File

@ -7,7 +7,7 @@
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<packaging>pom</packaging>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<name>dapr-sdk-parent</name>
<description>SDK for Dapr.</description>
<url>https://dapr.io</url>
@ -16,10 +16,10 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<grpc.version>1.69.0</grpc.version>
<protobuf.version>3.25.5</protobuf.version>
<protocCommand>protoc</protocCommand>
<protocCommand>java-sdk-protoc</protocCommand>
<dapr.proto.baseurl>https://raw.githubusercontent.com/dapr/dapr/v1.16.0-rc.3/dapr/proto</dapr.proto.baseurl>
<dapr.sdk.version>1.16.0-SNAPSHOT</dapr.sdk.version>
<dapr.sdk.alpha.version>0.16.0-SNAPSHOT</dapr.sdk.alpha.version>
<dapr.sdk.version>1.16.0-rc-2</dapr.sdk.version>
<dapr.sdk.alpha.version>0.16.0-rc-2</dapr.sdk.alpha.version>
<os-maven-plugin.version>1.7.1</os-maven-plugin.version>
<maven-dependency-plugin.version>3.1.1</maven-dependency-plugin.version>
<maven-antrun-plugin.version>1.8</maven-antrun-plugin.version>
@ -70,7 +70,7 @@
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://ossrh-staging-api.central.sonatype.com/content/repositories/snapshots</url>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
</snapshotRepository>
<site>
<id>localDocsDirectory</id>
@ -207,12 +207,12 @@
<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-boot-starter</artifactId>
<version>${dapr.sdk.alpha.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-boot-starter-test</artifactId>
<version>${dapr.sdk.alpha.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -247,7 +247,7 @@
<dependency>
<groupId>io.dapr</groupId>
<artifactId>testcontainers-dapr</artifactId>
<version>${dapr.sdk.alpha.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
@ -365,7 +365,7 @@
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-workflows</artifactId>
<version>${dapr.sdk.alpha.version}</version>
<version>${dapr.sdk.version}</version>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
@ -610,6 +610,7 @@
<failOnWarnings>false</failOnWarnings>
<failOnError>true</failOnError>
<goal>site</goal>
<notimestamp>true</notimestamp>
<excludePackageNames>io.dapr.examples:io.dapr.springboot:io.dapr.examples.*:io.dapr.springboot.*
</excludePackageNames>
</configuration>
@ -658,6 +659,18 @@
<modules>
<module>sdk-tests</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

View File

@ -7,12 +7,12 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-sdk-actors</artifactId>
<packaging>jar</packaging>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<name>dapr-sdk-actors</name>
<description>SDK for Actors on Dapr</description>
@ -86,6 +86,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<notimestamp>true</notimestamp>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>

View File

@ -7,12 +7,12 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-sdk-autogen</artifactId>
<packaging>jar</packaging>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<name>dapr-sdk-autogen</name>
<description>Auto-generated SDK for Dapr</description>
@ -22,7 +22,7 @@
<protobuf.input.directory>${project.build.directory}/proto</protobuf.input.directory>
<maven.deploy.skip>false</maven.deploy.skip>
<grpc.version>1.69.0</grpc.version>
<protocCommand>protoc</protocCommand>
<protocCommand>java-sdk-protoc</protocCommand>
<protobuf.version>3.25.5</protobuf.version>
</properties>
@ -132,7 +132,7 @@
<goal>run</goal>
</goals>
<configuration>
<protocCommand>protoc</protocCommand>
<protocCommand>java-sdk-protoc</protocCommand>
<protocVersion>${protobuf.version}</protocVersion>
<protocArtifact>com.google.protobuf:protoc:3.25.5</protocArtifact>
<addProtoSources>inputs</addProtoSources>
@ -176,6 +176,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<notimestamp>true</notimestamp>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>

View File

@ -7,12 +7,12 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-sdk-springboot</artifactId>
<packaging>jar</packaging>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<name>dapr-sdk-springboot</name>
<description>SDK extension for Springboot</description>
@ -97,6 +97,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<notimestamp>true</notimestamp>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>

View File

@ -7,11 +7,11 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-sdk-tests</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<name>dapr-sdk-tests</name>
<description>Tests for Dapr's Java SDK - not to be published as a jar.</description>
@ -22,8 +22,6 @@
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.deploy.skip>true</maven.deploy.skip>
<dapr.sdk.version>1.16.0-SNAPSHOT</dapr.sdk.version>
<dapr.sdk.alpha.version>0.16.0-SNAPSHOT</dapr.sdk.alpha.version>
<protobuf.output.directory>${project.build.directory}/generated-sources</protobuf.output.directory>
<protobuf.input.directory>${project.basedir}/proto</protobuf.input.directory>
@ -315,18 +313,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<!--suppress UnresolvedMavenProperty -->
<skip>${skipITs}</skip>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@ -109,11 +109,9 @@ public class DaprConversationIT {
this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)
.setScrubPii(true)).block();
Assertions.assertEquals("", response.getContextId());
Assertions.assertEquals("input this <EMAIL_ADDRESS>",
Assertions.assertEquals("", response.getContextId());
Assertions.assertEquals("input this <EMAIL_ADDRESS>\ninput this <PHONE_NUMBER>",
response.getConversationOutputs().get(0).getResult());
Assertions.assertEquals("input this <PHONE_NUMBER>",
response.getConversationOutputs().get(1).getResult());
}
@Test
@ -126,9 +124,7 @@ public class DaprConversationIT {
this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)).block();
Assertions.assertEquals("", response.getContextId());
Assertions.assertEquals("input this abcd@gmail.com",
Assertions.assertEquals("input this abcd@gmail.com\ninput this <PHONE_NUMBER>",
response.getConversationOutputs().get(0).getResult());
Assertions.assertEquals("input this <PHONE_NUMBER>",
response.getConversationOutputs().get(1).getResult());
}
}

View File

@ -15,6 +15,7 @@ package io.dapr.it.testcontainers.workflows;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dapr.testcontainers.Component;
import io.dapr.testcontainers.DaprContainer;
import io.dapr.testcontainers.DaprLogLevel;
@ -41,6 +42,7 @@ import java.util.Collections;
import java.util.Map;
import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -147,6 +149,51 @@ public class DaprWorkflowsIT {
}
@Test
public void testNamedActivitiesWorkflows() throws Exception {
TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>());
String instanceId = workflowClient.scheduleNewWorkflow(TestNamedActivitiesWorkflow.class, payload);
workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false);
Duration timeout = Duration.ofSeconds(10);
WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, timeout, true);
assertNotNull(workflowStatus);
TestWorkflowPayload workflowOutput = deserialize(workflowStatus.getSerializedOutput());
assertEquals(5, workflowOutput.getPayloads().size());
assertEquals("First Activity", workflowOutput.getPayloads().get(0));
assertEquals("First Activity", workflowOutput.getPayloads().get(1));
assertEquals("Second Activity", workflowOutput.getPayloads().get(2));
assertEquals("Anonymous Activity", workflowOutput.getPayloads().get(3));
assertEquals("Anonymous Activity 2", workflowOutput.getPayloads().get(4));
assertEquals(instanceId, workflowOutput.getWorkflowId());
}
@Test
public void testExecutionKeyWorkflows() throws Exception {
TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>());
String instanceId = workflowClient.scheduleNewWorkflow(TestExecutionKeysWorkflow.class, payload);
workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(100), false);
Duration timeout = Duration.ofSeconds(1000);
WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, timeout, true);
assertNotNull(workflowStatus);
TestWorkflowPayload workflowOutput = deserialize(workflowStatus.getSerializedOutput());
assertEquals(1, workflowOutput.getPayloads().size());
assertEquals("Execution key found", workflowOutput.getPayloads().get(0));
assertTrue(KeyStore.getInstance().size() == 1);
assertEquals(instanceId, workflowOutput.getWorkflowId());
}
private TestWorkflowPayload deserialize(String value) throws JsonProcessingException {
return OBJECT_MAPPER.readValue(value, TestWorkflowPayload.class);

View File

@ -0,0 +1,55 @@
/*
* Copyright 2025 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.workflows;
import java.util.HashMap;
import java.util.Map;
public class KeyStore {
private final Map<String, Boolean> keyStore = new HashMap<>();
private static KeyStore instance;
private KeyStore() {
}
public static KeyStore getInstance() {
if (instance == null) {
synchronized (KeyStore.class) {
if (instance == null) {
instance = new KeyStore();
}
}
}
return instance;
}
public void addKey(String key, Boolean value) {
keyStore.put(key, value);
}
public Boolean getKey(String key) {
return keyStore.get(key);
}
public void removeKey(String key) {
keyStore.remove(key);
}
public int size() {
return keyStore.size();
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2025 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.workflows;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
public class TaskExecutionIdActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext ctx) {
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
KeyStore keyStore = KeyStore.getInstance();
Boolean exists = keyStore.getKey(ctx.getTaskExecutionId());
if (!Boolean.TRUE.equals(exists)) {
keyStore.addKey(ctx.getTaskExecutionId(), true);
workflowPayload.getPayloads().add("Execution key not found");
throw new IllegalStateException("Task execution key not found");
}
workflowPayload.getPayloads().add("Execution key found");
return workflowPayload;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2025 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.workflows;
import io.dapr.durabletask.Task;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import io.dapr.workflows.WorkflowTaskOptions;
import io.dapr.workflows.WorkflowTaskRetryPolicy;
import java.time.Duration;
import org.slf4j.Logger;
public class TestExecutionKeysWorkflow implements 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);
WorkflowTaskOptions options = new WorkflowTaskOptions(WorkflowTaskRetryPolicy.newBuilder()
.setMaxNumberOfAttempts(3)
.setFirstRetryInterval(Duration.ofSeconds(1))
.setMaxRetryInterval(Duration.ofSeconds(10))
.setBackoffCoefficient(2.0)
.setRetryTimeout(Duration.ofSeconds(50))
.build());
Task<TestWorkflowPayload> t = ctx.callActivity(TaskExecutionIdActivity.class.getName(), workflowPayload, options,TestWorkflowPayload.class);
TestWorkflowPayload payloadAfterExecution = t.await();
ctx.complete(payloadAfterExecution);
};
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2025 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.workflows;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.slf4j.Logger;
public class TestNamedActivitiesWorkflow implements 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);
var payloadAfterA = ctx.callActivity("a", workflowPayload, TestWorkflowPayload.class)
.await();
var payloadAfterB = ctx.callActivity("b", payloadAfterA, TestWorkflowPayload.class)
.await();
var payloadAfterC = ctx.callActivity("c", payloadAfterB, TestWorkflowPayload.class)
.await();
var payloadAfterD = ctx.callActivity("d", payloadAfterC, TestWorkflowPayload.class)
.await();
var payloadAfterE = ctx.callActivity("e", payloadAfterD, TestWorkflowPayload.class)
.await();
ctx.complete(payloadAfterE);
};
}
}

View File

@ -15,8 +15,10 @@ package io.dapr.it.testcontainers.workflows;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dapr.config.Properties;
import io.dapr.workflows.WorkflowActivityContext;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
import io.dapr.workflows.WorkflowActivity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -48,17 +50,41 @@ public class TestWorkflowsConfiguration {
@Value("${dapr.http.endpoint}") String daprHttpEndpoint,
@Value("${dapr.grpc.endpoint}") String daprGrpcEndpoint
){
Map<String, String> overrides = Map.of(
"dapr.http.endpoint", daprHttpEndpoint,
"dapr.grpc.endpoint", daprGrpcEndpoint
);
Map<String, String> overrides = Map.of(
"dapr.http.endpoint", daprHttpEndpoint,
"dapr.grpc.endpoint", daprGrpcEndpoint
);
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder(new Properties(overrides));
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder(new Properties(overrides));
builder.registerWorkflow(TestWorkflow.class);
builder.registerActivity(FirstActivity.class);
builder.registerActivity(SecondActivity.class);
builder.registerWorkflow(TestWorkflow.class);
builder.registerWorkflow(TestExecutionKeysWorkflow.class);
builder.registerWorkflow(TestNamedActivitiesWorkflow.class);
return builder;
builder.registerActivity(FirstActivity.class);
builder.registerActivity(SecondActivity.class);
builder.registerActivity(TaskExecutionIdActivity.class);
builder.registerActivity("a", FirstActivity.class);
builder.registerActivity("b", FirstActivity.class);
builder.registerActivity("c", new SecondActivity());
builder.registerActivity("d", new WorkflowActivity() {
@Override
public Object run(WorkflowActivityContext ctx) {
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
workflowPayload.getPayloads().add("Anonymous Activity");
return workflowPayload;
}
});
builder.registerActivity("e", new WorkflowActivity() {
@Override
public Object run(WorkflowActivityContext ctx) {
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
workflowPayload.getPayloads().add("Anonymous Activity 2");
return workflowPayload;
}
});
return builder;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class App2TransformActivity implements WorkflowActivity {
private static final Logger logger = LoggerFactory.getLogger(App2TransformActivity.class);
@Override
public Object run(WorkflowActivityContext ctx) {
String input = ctx.getInput(String.class);
logger.info("=== App2: TransformActivity called ===");
logger.info("Input: {}", input);
String output = input.toUpperCase() + " [TRANSFORMED BY APP2]";
logger.info("Output: {}", output);
return output;
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
/**
* App2Worker - registers the App2TransformActivity.
* This app will handle cross-app activity calls from the main workflow.
*/
public class App2Worker {
public static void main(String[] args) throws Exception {
System.out.println("=== Starting App2Worker (App2TransformActivity) ===");
// Register the Activity with the builder
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
.registerActivity(App2TransformActivity.class);
// Build and start the workflow runtime
try (WorkflowRuntime runtime = builder.build()) {
System.out.println("App2Worker started - registered App2TransformActivity only");
System.out.println("App2 is ready to receive cross-app activity calls...");
System.out.println("Waiting for cross-app activity calls...");
runtime.start();
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class App3FinalizeActivity implements WorkflowActivity {
private static final Logger logger = LoggerFactory.getLogger(App3FinalizeActivity.class);
@Override
public Object run(WorkflowActivityContext ctx) {
String input = ctx.getInput(String.class);
logger.info("=== App3: FinalizeActivity called ===");
logger.info("Input: {}", input);
String output = input + " [FINALIZED BY APP3]";
logger.info("Output: {}", output);
return output;
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
/**
* App3Worker - registers the App3FinalizeActivity.
* This app will handle cross-app activity calls from the main workflow.
*/
public class App3Worker {
public static void main(String[] args) throws Exception {
System.out.println("=== Starting App3Worker (App3FinalizeActivity) ===");
// Register the Activity with the builder
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
.registerActivity(App3FinalizeActivity.class);
// Build and start the workflow runtime
try (WorkflowRuntime runtime = builder.build()) {
System.out.println("App3Worker started - registered App3FinalizeActivity only");
System.out.println("App3 is ready to receive cross-app activity calls...");
System.out.println("Waiting for cross-app activity calls...");
runtime.start();
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
/**
* CrossAppWorker - registers only the CrossAppWorkflow.
* This is the main workflow orchestrator that will call activities in other apps.
*/
public class CrossAppWorker {
public static void main(String[] args) throws Exception {
System.out.println("=== Starting CrossAppWorker (Workflow Orchestrator) ===");
// Register the Workflow with the builder
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
.registerWorkflow(CrossAppWorkflow.class);
// Build and start the workflow runtime
try (WorkflowRuntime runtime = builder.build()) {
System.out.println("CrossAppWorker started - registered CrossAppWorkflow only");
System.out.println("Waiting for workflow orchestration requests...");
runtime.start();
}
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import io.dapr.workflows.WorkflowTaskOptions;
import org.slf4j.Logger;
public class CrossAppWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
Logger logger = ctx.getLogger();
String instanceId = ctx.getInstanceId();
logger.info("Starting CrossAppWorkflow: {}", ctx.getName());
logger.info("Instance ID: {}", instanceId);
String input = ctx.getInput(String.class);
logger.info("Workflow input: {}", input);
// Call App2TransformActivity in app2
logger.info("Calling cross-app activity in 'app2'...");
String transformedByApp2 = ctx.callActivity(
App2TransformActivity.class.getName(),
input,
new WorkflowTaskOptions("app2"),
String.class
).await();
// Call App3FinalizeActivity in app3
logger.info("Calling cross-app activity in 'app3'...");
String finalizedByApp3 = ctx.callActivity(
App3FinalizeActivity.class.getName(),
transformedByApp2,
new WorkflowTaskOptions("app3"),
String.class
).await();
logger.info("Final cross-app activity result: {}", finalizedByApp3);
ctx.complete(finalizedByApp3);
};
}
}

View File

@ -0,0 +1,191 @@
/*
* Copyright 2025 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.workflows.crossapp;
import io.dapr.testcontainers.Component;
import io.dapr.testcontainers.DaprContainer;
import io.dapr.testcontainers.DaprLogLevel;
import io.dapr.testcontainers.DaprPlacementContainer;
import io.dapr.testcontainers.DaprSchedulerContainer;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
import io.dapr.workflows.client.WorkflowRuntimeStatus;
import io.dapr.config.Properties;
import net.bytebuddy.utility.dispatcher.JavaDispatcher;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Network;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import java.time.Duration;
import java.util.Map;
import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
import static io.dapr.testcontainers.DaprContainerConstants.DAPR_PLACEMENT_IMAGE_TAG;
import static io.dapr.testcontainers.DaprContainerConstants.DAPR_SCHEDULER_IMAGE_TAG;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* Cross-App Pattern integration test.
*
* This test demonstrates the cross-app pattern by:
* 1. Starting 3 Dapr containers (crossapp-worker, app2, app3)
* 2. Launching Java processes that register workflows/activities in separate apps
* 3. Executing a cross-app workflow
* 4. Asserting successful completion
*/
@Testcontainers
@Tag("testcontainers")
public class WorkflowsCrossAppCallActivityIT {
private static final Network DAPR_NETWORK = Network.newNetwork();
@Container
private final static DaprPlacementContainer sharedPlacementContainer = new DaprPlacementContainer(DAPR_PLACEMENT_IMAGE_TAG)
.withNetwork(DAPR_NETWORK)
.withNetworkAliases("placement")
.withReuse(false);
@Container
private final static DaprSchedulerContainer sharedSchedulerContainer = new DaprSchedulerContainer(DAPR_SCHEDULER_IMAGE_TAG)
.withNetwork(DAPR_NETWORK)
.withNetworkAliases("scheduler")
.withReuse(false);
// Main workflow orchestrator container
@Container
private final static DaprContainer MAIN_WORKFLOW_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
.withAppName("crossapp-worker")
.withNetwork(DAPR_NETWORK)
.withNetworkAliases("main-workflow-sidecar")
.withPlacementContainer(sharedPlacementContainer)
.withSchedulerContainer(sharedSchedulerContainer)
.withComponent(new Component("kvstore", "state.in-memory", "v1", Map.of("actorStateStore", "true")))
.withDaprLogLevel(DaprLogLevel.DEBUG)
.dependsOn(sharedPlacementContainer, sharedSchedulerContainer)
.withLogConsumer(outputFrame -> System.out.println("MAIN_WORKFLOW: " + outputFrame.getUtf8String()))
.withAppChannelAddress("host.testcontainers.internal");
// App2 container for App2TransformActivity
@Container
private final static DaprContainer APP2_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
.withAppName("app2")
.withNetwork(DAPR_NETWORK)
.withNetworkAliases("app2-sidecar")
.withPlacementContainer(sharedPlacementContainer)
.withSchedulerContainer(sharedSchedulerContainer)
.withAppChannelAddress("main-workflow-sidecar:3500")
.withDaprLogLevel(DaprLogLevel.DEBUG)
.dependsOn(sharedPlacementContainer, sharedSchedulerContainer, MAIN_WORKFLOW_SIDECAR)
.withComponent(new Component("kvstore", "state.in-memory", "v1", Map.of("actorStateStore", "true")))
.withLogConsumer(outputFrame -> System.out.println("APP2: " + outputFrame.getUtf8String()));
// App3 container for App3FinalizeActivity
@Container
private final static DaprContainer APP3_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
.withAppName("app3")
.withNetwork(DAPR_NETWORK)
.withNetworkAliases("app3-sidecar")
.withPlacementContainer(sharedPlacementContainer)
.withSchedulerContainer(sharedSchedulerContainer)
.withAppChannelAddress("main-workflow-sidecar:3500")
.withDaprLogLevel(DaprLogLevel.DEBUG)
.dependsOn(sharedPlacementContainer, sharedSchedulerContainer, MAIN_WORKFLOW_SIDECAR)
.withComponent(new Component("kvstore", "state.in-memory", "v1", Map.of("actorStateStore", "true")))
.withLogConsumer(outputFrame -> System.out.println("APP3: " + outputFrame.getUtf8String()));
// TestContainers for each app
@Container
private static GenericContainer<?> crossappWorker = new GenericContainer<>("openjdk:17-jdk-slim")
.withCopyFileToContainer(MountableFile.forHostPath("target"), "/app")
.withWorkingDirectory("/app")
.withCommand("java", "-cp", "test-classes:classes:dependency/*:*",
"-Ddapr.app.id=crossapp-worker",
"-Ddapr.grpc.endpoint=main-workflow-sidecar:50001",
"-Ddapr.http.endpoint=main-workflow-sidecar:3500",
"io.dapr.it.testcontainers.workflows.crossapp.CrossAppWorker")
.withNetwork(DAPR_NETWORK)
.dependsOn(MAIN_WORKFLOW_SIDECAR)
.waitingFor(Wait.forLogMessage(".*CrossAppWorker started.*", 1))
.withLogConsumer(outputFrame -> System.out.println("CrossAppWorker: " + outputFrame.getUtf8String()));
@Container
private final static GenericContainer<?> app2Worker = new GenericContainer<>("openjdk:17-jdk-slim")
.withCopyFileToContainer(MountableFile.forHostPath("target"), "/app")
.withWorkingDirectory("/app")
.withCommand("java", "-cp", "test-classes:classes:dependency/*:*",
"-Ddapr.app.id=app2",
"-Ddapr.grpc.endpoint=app2-sidecar:50001",
"-Ddapr.http.endpoint=app2-sidecar:3500",
"io.dapr.it.testcontainers.workflows.crossapp.App2Worker")
.withNetwork(DAPR_NETWORK)
.dependsOn(APP2_SIDECAR)
.waitingFor(Wait.forLogMessage(".*App2Worker started.*", 1))
.withLogConsumer(outputFrame -> System.out.println("App2Worker: " + outputFrame.getUtf8String()));
@Container
private final static GenericContainer<?> app3Worker = new GenericContainer<>("openjdk:17-jdk-slim")
.withCopyFileToContainer(MountableFile.forHostPath("target"), "/app")
.withWorkingDirectory("/app")
.withCommand("java", "-cp", "test-classes:classes:dependency/*:*",
"-Ddapr.app.id=app3",
"-Ddapr.grpc.endpoint=app3-sidecar:50001",
"-Ddapr.http.endpoint=app3-sidecar:3500",
"io.dapr.it.testcontainers.workflows.crossapp.App3Worker")
.withNetwork(DAPR_NETWORK)
.dependsOn(APP3_SIDECAR)
.waitingFor(Wait.forLogMessage(".*App3Worker started.*", 1))
.withLogConsumer(outputFrame -> System.out.println("App3Worker: " + outputFrame.getUtf8String()));
@Test
public void testCrossAppWorkflow() throws Exception {
// TestContainers wait strategies ensure all containers are ready before this test runs
String input = "Hello World";
String expectedOutput = "HELLO WORLD [TRANSFORMED BY APP2] [FINALIZED BY APP3]";
// Create workflow client connected to the main workflow orchestrator
// Use the same endpoint configuration that the workers use
// The workers use host.testcontainers.internal:50001
Map<String, String> propertyOverrides = Map.of(
"dapr.grpc.endpoint", MAIN_WORKFLOW_SIDECAR.getGrpcEndpoint(),
"dapr.http.endpoint", MAIN_WORKFLOW_SIDECAR.getHttpEndpoint()
);
Properties clientProperties = new Properties(propertyOverrides);
DaprWorkflowClient workflowClient = new DaprWorkflowClient(clientProperties);
try {
String instanceId = workflowClient.scheduleNewWorkflow(CrossAppWorkflow.class, input);
assertNotNull(instanceId, "Workflow instance ID should not be null");
workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(30), false);
WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, null, true);
assertNotNull(workflowStatus, "Workflow status should not be null");
assertEquals(WorkflowRuntimeStatus.COMPLETED, workflowStatus.getRuntimeStatus(),
"Workflow should complete successfully");
String workflowOutput = workflowStatus.readOutputAs(String.class);
assertEquals(expectedOutput, workflowOutput, "Workflow output should match expected result");
} finally {
workflowClient.close();
}
}
}

View File

@ -7,12 +7,12 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-sdk-workflows</artifactId>
<packaging>jar</packaging>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<name>dapr-sdk-workflows</name>
<description>SDK for Workflows on Dapr</description>
@ -47,7 +47,7 @@
<dependency>
<groupId>io.dapr</groupId>
<artifactId>durabletask-client</artifactId>
<version>1.5.6</version>
<version>1.5.10</version>
</dependency>
<!--
manually declare durabletask-client's jackson dependencies
@ -100,6 +100,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<notimestamp>true</notimestamp>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>

View File

@ -13,10 +13,16 @@ limitations under the License.
package io.dapr.workflows;
import org.slf4j.Logger;
public interface WorkflowActivityContext {
Logger getLogger();
String getName();
String getTaskExecutionId();
<T> T getInput(Class<T> targetType);
}

View File

@ -526,4 +526,12 @@ public interface WorkflowContext {
default UUID newUuid() {
throw new RuntimeException("No implementation found.");
}
/**
* Set a custom status to a workflow execution.
*
* @param status to be set to the current execution
*/
void setCustomStatus(Object status);
}

View File

@ -17,18 +17,48 @@ public class WorkflowTaskOptions {
private final WorkflowTaskRetryPolicy retryPolicy;
private final WorkflowTaskRetryHandler retryHandler;
private final String appId;
public WorkflowTaskOptions(WorkflowTaskRetryPolicy retryPolicy, WorkflowTaskRetryHandler retryHandler) {
this.retryPolicy = retryPolicy;
this.retryHandler = retryHandler;
this(retryPolicy, retryHandler, null);
}
public WorkflowTaskOptions(WorkflowTaskRetryPolicy retryPolicy) {
this(retryPolicy, null);
this(retryPolicy, null, null);
}
public WorkflowTaskOptions(WorkflowTaskRetryHandler retryHandler) {
this(null, retryHandler);
this(null, retryHandler, null);
}
/**
* Constructor for WorkflowTaskOptions with app ID for cross-app calls.
*
* @param appId the ID of the app to call the activity in
*/
public WorkflowTaskOptions(String appId) {
this(null, null, appId);
}
/**
* Constructor for WorkflowTaskOptions with retry policy, retry handler, and app ID.
*
* @param retryPolicy the retry policy
* @param retryHandler the retry handler
* @param appId the app ID for cross-app activity calls
*/
public WorkflowTaskOptions(WorkflowTaskRetryPolicy retryPolicy, WorkflowTaskRetryHandler retryHandler, String appId) {
this.retryPolicy = retryPolicy;
this.retryHandler = retryHandler;
this.appId = appId;
}
public WorkflowTaskOptions(WorkflowTaskRetryPolicy retryPolicy, String appId) {
this(retryPolicy, null, appId);
}
public WorkflowTaskOptions(WorkflowTaskRetryHandler retryHandler, String appId) {
this(null, retryHandler, appId);
}
public WorkflowTaskRetryPolicy getRetryPolicy() {
@ -39,4 +69,8 @@ public class WorkflowTaskOptions {
return retryHandler;
}
public String getAppId() {
return appId;
}
}

View File

@ -15,12 +15,15 @@ package io.dapr.workflows.runtime;
import io.dapr.durabletask.TaskActivityContext;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Wrapper for Durable Task Framework {@link TaskActivityContext}.
*/
class DefaultWorkflowActivityContext implements WorkflowActivityContext {
private final TaskActivityContext innerContext;
private final Logger logger;
/**
* Constructor for WorkflowActivityContext.
@ -29,10 +32,36 @@ class DefaultWorkflowActivityContext implements WorkflowActivityContext {
* @throws IllegalArgumentException if context is null
*/
public DefaultWorkflowActivityContext(TaskActivityContext context) throws IllegalArgumentException {
this(context, LoggerFactory.getLogger(WorkflowActivityContext.class));
}
/**
* Constructor for WorkflowActivityContext.
*
* @param context TaskActivityContext
* @throws IllegalArgumentException if context is null
*/
public DefaultWorkflowActivityContext(TaskActivityContext context, Logger logger) throws IllegalArgumentException {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null");
}
if (logger == null) {
throw new IllegalArgumentException("Logger cannot be null");
}
this.innerContext = context;
this.logger = logger;
}
/**
* Gets the logger for the current activity.
*
* @return the logger for the current activity
*/
@Override
public Logger getLogger() {
return this.logger;
}
/**
@ -56,4 +85,9 @@ class DefaultWorkflowActivityContext implements WorkflowActivityContext {
public <T> T getInput(Class<T> targetType) {
return this.innerContext.getInput(targetType);
}
@Override
public String getTaskExecutionId() {
return this.innerContext.getTaskExecutionId();
}
}

View File

@ -59,10 +59,11 @@ public class DefaultWorkflowContext implements WorkflowContext {
* @throws IllegalArgumentException if context or logger is null
*/
public DefaultWorkflowContext(TaskOrchestrationContext context, Logger logger)
throws IllegalArgumentException {
throws IllegalArgumentException {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null");
}
if (logger == null) {
throw new IllegalArgumentException("Logger cannot be null");
}
@ -114,7 +115,7 @@ public class DefaultWorkflowContext implements WorkflowContext {
*/
@Override
public <V> Task<V> waitForExternalEvent(String name, Duration timeout, Class<V> dataType)
throws TaskCanceledException {
throws TaskCanceledException {
return this.innerContext.waitForExternalEvent(name, timeout, dataType);
}
@ -130,7 +131,7 @@ public class DefaultWorkflowContext implements WorkflowContext {
* @param timeout the amount of time to wait before canceling the returned
* {@code Task}
* @return a new {@link Task} that completes when the external event is received
* or when {@code timeout} expires
* or when {@code timeout} expires
* @throws TaskCanceledException if the specified {@code timeout} value expires
* before the event is received
*/
@ -245,7 +246,11 @@ public class DefaultWorkflowContext implements WorkflowContext {
RetryPolicy retryPolicy = toRetryPolicy(options.getRetryPolicy());
RetryHandler retryHandler = toRetryHandler(options.getRetryHandler());
return new TaskOptions(retryPolicy, retryHandler);
return TaskOptions.builder()
.retryPolicy(retryPolicy)
.retryHandler(retryHandler)
.appID(options.getAppId())
.build();
}
/**
@ -294,4 +299,13 @@ public class DefaultWorkflowContext implements WorkflowContext {
return workflowTaskRetryHandler.handle(workflowRetryContext);
};
}
/**
* Set custom status to a workflow execution.
*
* @param status to set to the execution
*/
public void setCustomStatus(Object status) {
innerContext.setCustomStatus(status);
}
}

View File

@ -30,19 +30,28 @@ public class WorkflowActivityClassWrapper<T extends WorkflowActivity> implements
/**
* Constructor for WorkflowActivityWrapper.
*
* @param name Name of the activity to wrap.
* @param clazz Class of the activity to wrap.
*/
public WorkflowActivityClassWrapper(Class<T> clazz) {
this.name = clazz.getCanonicalName();
public WorkflowActivityClassWrapper(String name, Class<T> clazz) {
this.name = name;
try {
this.activityConstructor = clazz.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new RuntimeException(
String.format("No constructor found for activity class '%s'.", this.name), e
);
String.format("No constructor found for activity class '%s'.", this.name), e);
}
}
/**
* Constructor for WorkflowActivityWrapper.
*
* @param clazz Class of the activity to wrap.
*/
public WorkflowActivityClassWrapper(Class<T> clazz) {
this(clazz.getCanonicalName(), clazz);
}
@Override
public String getName() {
return name;
@ -53,13 +62,12 @@ public class WorkflowActivityClassWrapper<T extends WorkflowActivity> implements
return ctx -> {
Object result;
T activity;
try {
activity = this.activityConstructor.newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(
String.format("Unable to instantiate instance of activity class '%s'", this.name), e
);
String.format("Unable to instantiate instance of activity class '%s'", this.name), e);
}
result = activity.run(new DefaultWorkflowActivityContext(ctx));

View File

@ -24,14 +24,24 @@ public class WorkflowActivityInstanceWrapper<T extends WorkflowActivity> impleme
private final T activity;
private final String name;
/**
* Constructor for WorkflowActivityWrapper.
*
* @param name Name of the activity to wrap.
* @param instance Instance of the activity to wrap.
*/
public WorkflowActivityInstanceWrapper(String name, T instance) {
this.name = name;
this.activity = instance;
}
/**
* Constructor for WorkflowActivityWrapper.
*
* @param instance Instance of the activity to wrap.
*/
public WorkflowActivityInstanceWrapper(T instance) {
this.name = instance.getClass().getCanonicalName();
this.activity = instance;
this(instance.getClass().getCanonicalName(), instance);
}
@Override

View File

@ -149,9 +149,21 @@ public class WorkflowRuntimeBuilder {
* @return the WorkflowRuntimeBuilder
*/
public <T extends WorkflowActivity> WorkflowRuntimeBuilder registerActivity(Class<T> clazz) {
this.builder.addActivity(new WorkflowActivityClassWrapper<>(clazz));
this.activitySet.add(clazz.getCanonicalName());
this.activities.add(clazz.getSimpleName());
return registerActivity(clazz.getCanonicalName(), clazz);
}
/**
* Registers an Activity object.
*
* @param <T> any WorkflowActivity type
* @param name Name of the activity to register.
* @param clazz Class of the activity to register.
* @return the WorkflowRuntimeBuilder
*/
public <T extends WorkflowActivity> WorkflowRuntimeBuilder registerActivity(String name, Class<T> clazz) {
this.builder.addActivity(new WorkflowActivityClassWrapper<>(name, clazz));
this.activitySet.add(name);
this.activities.add(name);
this.logger.info("Registered Activity: {}", clazz.getSimpleName());
@ -166,13 +178,23 @@ public class WorkflowRuntimeBuilder {
* @return the WorkflowRuntimeBuilder
*/
public <T extends WorkflowActivity> WorkflowRuntimeBuilder registerActivity(T instance) {
Class<T> clazz = (Class<T>) instance.getClass();
return this.registerActivity(instance.getClass().getCanonicalName(), instance);
}
this.builder.addActivity(new WorkflowActivityInstanceWrapper<>(instance));
this.activitySet.add(clazz.getCanonicalName());
this.activities.add(clazz.getSimpleName());
/**
* Registers an Activity object.
*
* @param <T> any WorkflowActivity type
* @param name Name of the activity to register.
* @param instance the class instance being registered
* @return the WorkflowRuntimeBuilder
*/
public <T extends WorkflowActivity> WorkflowRuntimeBuilder registerActivity(String name, T instance) {
this.builder.addActivity(new WorkflowActivityInstanceWrapper<>(name, instance));
this.activitySet.add(name);
this.activities.add(name);
this.logger.info("Registered Activity: {}", clazz.getSimpleName());
this.logger.info("Registered Activity: {}", name);
return this;
}

View File

@ -135,6 +135,11 @@ public class DefaultWorkflowContextTest {
@Override
public void continueAsNew(Object input, boolean preserveUnprocessedEvents) {
}
@Override
public void setCustomStatus(Object status) {
}
};
}
@ -403,6 +408,15 @@ public class DefaultWorkflowContextTest {
verify(mockInnerContext, times(1)).callSubOrchestrator(expectedName, expectedInput, null, null, String.class);
}
@Test
public void setCustomStatusWorkflow() {
String customStatus = "CustomStatus";
context.setCustomStatus(customStatus);
verify(mockInnerContext, times(1)).setCustomStatus(customStatus);
}
@Test
public void newUuidTest() {
context.newUuid();

View File

@ -0,0 +1,102 @@
/*
* Copyright 2025 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.workflows;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class WorkflowTaskOptionsTest {
@Test
void testConstructorWithRetryPolicyAndHandler() {
WorkflowTaskRetryPolicy retryPolicy = WorkflowTaskRetryPolicy.newBuilder().build();
WorkflowTaskRetryHandler retryHandler = (context) -> true;
WorkflowTaskOptions options = new WorkflowTaskOptions(retryPolicy, retryHandler);
assertEquals(retryPolicy, options.getRetryPolicy());
assertEquals(retryHandler, options.getRetryHandler());
assertNull(options.getAppId());
}
@Test
void testConstructorWithRetryPolicyOnly() {
WorkflowTaskRetryPolicy retryPolicy = WorkflowTaskRetryPolicy.newBuilder().build();
WorkflowTaskOptions options = new WorkflowTaskOptions(retryPolicy);
assertEquals(retryPolicy, options.getRetryPolicy());
assertNull(options.getRetryHandler());
assertNull(options.getAppId());
}
@Test
void testConstructorWithRetryHandlerOnly() {
WorkflowTaskRetryHandler retryHandler = (context) -> true;
WorkflowTaskOptions options = new WorkflowTaskOptions(retryHandler);
assertNull(options.getRetryPolicy());
assertEquals(retryHandler, options.getRetryHandler());
assertNull(options.getAppId());
}
@Test
void testConstructorWithAppIdOnly() {
String appId = "test-app";
WorkflowTaskOptions options = new WorkflowTaskOptions(appId);
assertNull(options.getRetryPolicy());
assertNull(options.getRetryHandler());
assertEquals(appId, options.getAppId());
}
@Test
void testConstructorWithAllParameters() {
WorkflowTaskRetryPolicy retryPolicy = WorkflowTaskRetryPolicy.newBuilder().build();
WorkflowTaskRetryHandler retryHandler = (context) -> true;
String appId = "test-app";
WorkflowTaskOptions options = new WorkflowTaskOptions(retryPolicy, retryHandler, appId);
assertEquals(retryPolicy, options.getRetryPolicy());
assertEquals(retryHandler, options.getRetryHandler());
assertEquals(appId, options.getAppId());
}
@Test
void testConstructorWithRetryPolicyAndAppId() {
WorkflowTaskRetryPolicy retryPolicy = WorkflowTaskRetryPolicy.newBuilder().build();
String appId = "test-app";
WorkflowTaskOptions options = new WorkflowTaskOptions(retryPolicy, appId);
assertEquals(retryPolicy, options.getRetryPolicy());
assertNull(options.getRetryHandler());
assertEquals(appId, options.getAppId());
}
@Test
void testConstructorWithRetryHandlerAndAppId() {
WorkflowTaskRetryHandler retryHandler = (context) -> true;
String appId = "test-app";
WorkflowTaskOptions options = new WorkflowTaskOptions(retryHandler, appId);
assertNull(options.getRetryPolicy());
assertEquals(retryHandler, options.getRetryHandler());
assertEquals(appId, options.getAppId());
}
}

View File

@ -0,0 +1,52 @@
package io.dapr.workflows.runtime;
import io.dapr.durabletask.TaskActivityContext;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class DefaultWorkflowActivityContextTest {
@Test
@DisplayName("Should successfully create context and return correct values for all methods")
void shouldSuccessfullyCreateContextAndReturnCorrectValuesForAllMethods() {
TaskActivityContext mockInnerContext = mock(TaskActivityContext.class);
DefaultWorkflowActivityContext context = new DefaultWorkflowActivityContext(mockInnerContext);
when(mockInnerContext.getName()).thenReturn("TestActivity");
when(mockInnerContext.getInput(any())).thenReturn("TestInput");
when(mockInnerContext.getTaskExecutionId()).thenReturn("TestExecutionId");
assertNotNull(context.getLogger());
assertEquals("TestActivity", context.getName());
String input = context.getInput(String.class);
assertEquals("TestInput", input);
assertEquals("TestExecutionId", context.getTaskExecutionId());
}
@Test
@DisplayName("Should throw IllegalArgumentException when context parameter is null")
void shouldThrowIllegalArgumentExceptionWhenContextParameterIsNull() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new DefaultWorkflowActivityContext(null);
});
assertEquals("Context cannot be null", exception.getMessage());
}
@Test
@DisplayName("Should throw IllegalArgumentException when logger parameter is null")
void shouldThrowIllegalArgumentExceptionWhenLoggerParameterIsNull() {
TaskActivityContext mockInnerContext = mock(TaskActivityContext.class);
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new DefaultWorkflowActivityContext(mockInnerContext, null);
});
assertEquals("Logger cannot be null", exception.getMessage());
}
}

View File

@ -6,17 +6,14 @@ import io.dapr.workflows.WorkflowActivityContext;
import org.junit.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
public class WorkflowActivityClassWrapperTest {
public static class TestActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext ctx) {
String activityContextName = ctx.getName();
return ctx.getInput(String.class) + " world! from " + activityContextName;
return ctx.getInput(String.class) + " world! from " + activityContextName + " with task execution key " + ctx.getTaskExecutionId();
}
}
@ -37,10 +34,11 @@ public class WorkflowActivityClassWrapperTest {
when(mockContext.getInput(String.class)).thenReturn("Hello");
when(mockContext.getName()).thenReturn("TestActivityContext");
when(mockContext.getTaskExecutionId()).thenReturn("123");
Object result = wrapper.create().run(mockContext);
verify(mockContext, times(1)).getInput(String.class);
assertEquals("Hello world! from TestActivityContext", result);
assertEquals("Hello world! from TestActivityContext with task execution key 123", result);
}
}

View File

@ -19,15 +19,13 @@ import io.dapr.workflows.WorkflowStub;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
public class WorkflowRuntimeBuilderTest {
public static class TestWorkflow implements Workflow {
@Override

View File

@ -7,12 +7,12 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>dapr-sdk</artifactId>
<packaging>jar</packaging>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<name>dapr-sdk</name>
<description>SDK for Dapr</description>
@ -132,18 +132,6 @@
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
@ -194,6 +182,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<notimestamp>true</notimestamp>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>

View File

@ -27,20 +27,9 @@ import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import java.io.File;
import java.nio.file.Files;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
public class NetworkUtilsTest {
private final int defaultGrpcPort = 50001;
@ -48,47 +37,6 @@ public class NetworkUtilsTest {
private ManagedChannel channel;
private static final List<ManagedChannel> channels = new ArrayList<>();
// Helper method to generate a self-signed certificate for testing
private static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
private static X509Certificate generateCertificate(KeyPair keyPair) throws Exception {
X500Name issuer = new X500Name("CN=Test Certificate");
X500Name subject = new X500Name("CN=Test Certificate");
Date notBefore = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
Date notAfter = new Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000L);
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
issuer,
java.math.BigInteger.valueOf(System.currentTimeMillis()),
notBefore,
notAfter,
subject,
publicKeyInfo
);
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate());
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer));
return cert;
}
private static void writeCertificateToFile(X509Certificate cert, File file) throws Exception {
String certPem = "-----BEGIN CERTIFICATE-----\n" +
java.util.Base64.getEncoder().encodeToString(cert.getEncoded()) +
"\n-----END CERTIFICATE-----";
Files.write(file.toPath(), certPem.getBytes());
}
private static void writePrivateKeyToFile(KeyPair keyPair, File file) throws Exception {
String keyPem = "-----BEGIN PRIVATE KEY-----\n" +
java.util.Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()) +
"\n-----END PRIVATE KEY-----";
Files.write(file.toPath(), keyPem.getBytes());
}
@AfterEach
public void tearDown() {
if (channel != null && !channel.isShutdown()) {
@ -157,56 +105,35 @@ public class NetworkUtilsTest {
@Test
public void testBuildGrpcManagedChannelWithTls() throws Exception {
// Generate test certificate and key
KeyPair keyPair = generateKeyPair();
X509Certificate cert = generateCertificate(keyPair);
File certFile = File.createTempFile("test-cert", ".pem");
File keyFile = File.createTempFile("test-key", ".pem");
try {
writeCertificateToFile(cert, certFile);
writePrivateKeyToFile(keyPair, keyFile);
File certFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile());
File keyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile());
var properties = new Properties(Map.of(
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(),
Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath()
));
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort);
Assertions.assertEquals(expectedAuthority, channel.authority());
} finally {
certFile.delete();
keyFile.delete();
}
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort);
Assertions.assertEquals(expectedAuthority, channel.authority());
}
@Test
public void testBuildGrpcManagedChannelWithTlsAndEndpoint() throws Exception {
// Generate test certificate and key
KeyPair keyPair = generateKeyPair();
X509Certificate cert = generateCertificate(keyPair);
File certFile = File.createTempFile("test-cert", ".pem");
File keyFile = File.createTempFile("test-key", ".pem");
try {
writeCertificateToFile(cert, certFile);
writePrivateKeyToFile(keyPair, keyFile);
File certFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile());
File keyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile());
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(),
Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(),
Properties.GRPC_ENDPOINT.getName(), "https://example.com:443"
));
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(),
Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(),
Properties.GRPC_ENDPOINT.getName(), "https://example.com:443"
));
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
Assertions.assertEquals("example.com:443", channel.authority());
} finally {
certFile.delete();
keyFile.delete();
}
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
Assertions.assertEquals("example.com:443", channel.authority());
}
@Test
@ -229,49 +156,32 @@ public class NetworkUtilsTest {
System.getProperty("os.name").toLowerCase().contains("mac"));
// Generate test certificate and key
KeyPair keyPair = generateKeyPair();
X509Certificate cert = generateCertificate(keyPair);
File certFile = File.createTempFile("test-cert", ".pem");
File keyFile = File.createTempFile("test-key", ".pem");
File certFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile());
File keyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile());
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(),
Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(),
Properties.GRPC_ENDPOINT.getName(), "unix:/tmp/test.sock"
));
// For Unix sockets, we expect an exception if the platform doesn't support it
try {
writeCertificateToFile(cert, certFile);
writePrivateKeyToFile(keyPair, keyFile);
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(),
Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(),
Properties.GRPC_ENDPOINT.getName(), "unix:/tmp/test.sock"
));
// For Unix sockets, we expect an exception if the platform doesn't support it
try {
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
// If we get here, Unix sockets are supported
Assertions.assertNotNull(channel.authority(), "Channel authority should not be null");
} catch (Exception e) {
// If we get here, Unix sockets are not supported
Assertions.assertTrue(e.getMessage().contains("DomainSocketAddress"));
}
} finally {
certFile.delete();
keyFile.delete();
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
// If we get here, Unix sockets are supported
Assertions.assertNotNull(channel.authority(), "Channel authority should not be null");
} catch (Exception e) {
// If we get here, Unix sockets are not supported
Assertions.assertTrue(e.getMessage().contains("DomainSocketAddress"));
}
}
@Test
public void testBuildGrpcManagedChannelWithTlsAndDnsAuthority() throws Exception {
// Generate test certificate and key
KeyPair keyPair = generateKeyPair();
X509Certificate cert = generateCertificate(keyPair);
File certFile = File.createTempFile("test-cert", ".pem");
File keyFile = File.createTempFile("test-key", ".pem");
try {
writeCertificateToFile(cert, certFile);
writePrivateKeyToFile(keyPair, keyFile);
File certFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile());
File keyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile());
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(),
Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(),
@ -281,44 +191,26 @@ public class NetworkUtilsTest {
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
Assertions.assertEquals("example.com:443", channel.authority());
} finally {
certFile.delete();
keyFile.delete();
}
}
@Test
public void testBuildGrpcManagedChannelWithTlsAndCaCert() throws Exception {
// Generate test CA certificate
KeyPair caKeyPair = generateKeyPair();
X509Certificate caCert = generateCertificate(caKeyPair);
File caCertFile = File.createTempFile("test-ca-cert", ".pem");
try {
writeCertificateToFile(caCert, caCertFile);
File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile());
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath()
));
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath()
));
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort);
Assertions.assertEquals(expectedAuthority, channel.authority());
} finally {
caCertFile.delete();
}
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort);
Assertions.assertEquals(expectedAuthority, channel.authority());
}
@Test
public void testBuildGrpcManagedChannelWithTlsAndCaCertAndEndpoint() throws Exception {
// Generate test CA certificate
KeyPair caKeyPair = generateKeyPair();
X509Certificate caCert = generateCertificate(caKeyPair);
File caCertFile = File.createTempFile("test-ca-cert", ".pem");
try {
writeCertificateToFile(caCert, caCertFile);
File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile());
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
@ -328,9 +220,7 @@ public class NetworkUtilsTest {
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
Assertions.assertEquals("example.com:443", channel.authority());
} finally {
caCertFile.delete();
}
}
@Test
@ -346,37 +236,22 @@ public class NetworkUtilsTest {
@Test
public void testBuildGrpcManagedChannelWithMtlsAndCaCert() throws Exception {
// Generate test certificates
KeyPair caKeyPair = generateKeyPair();
X509Certificate caCert = generateCertificate(caKeyPair);
KeyPair clientKeyPair = generateKeyPair();
X509Certificate clientCert = generateCertificate(clientKeyPair);
File caCertFile = File.createTempFile("test-ca-cert", ".pem");
File clientCertFile = File.createTempFile("test-client-cert", ".pem");
File clientKeyFile = File.createTempFile("test-client-key", ".pem");
try {
writeCertificateToFile(caCert, caCertFile);
writeCertificateToFile(clientCert, clientCertFile);
writePrivateKeyToFile(clientKeyPair, clientKeyFile);
File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile());
File clientCertFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile());
File clientKeyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile());
// Test mTLS with both client certs and CA cert
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
Properties.GRPC_TLS_CERT_PATH.getName(), clientCertFile.getAbsolutePath(),
Properties.GRPC_TLS_KEY_PATH.getName(), clientKeyFile.getAbsolutePath()
));
// Test mTLS with both client certs and CA cert
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
Properties.GRPC_TLS_CERT_PATH.getName(), clientCertFile.getAbsolutePath(),
Properties.GRPC_TLS_KEY_PATH.getName(), clientKeyFile.getAbsolutePath()
));
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort);
Assertions.assertEquals(expectedAuthority, channel.authority());
Assertions.assertFalse(channel.isTerminated(), "Channel should be active");
} finally {
caCertFile.delete();
clientCertFile.delete();
clientKeyFile.delete();
}
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort);
Assertions.assertEquals(expectedAuthority, channel.authority());
Assertions.assertFalse(channel.isTerminated(), "Channel should be active");
}
@Test
@ -463,57 +338,40 @@ public class NetworkUtilsTest {
@Test
public void testBuildGrpcManagedChannelWithCaCertAndUnixSocket() throws Exception {
// Skip test if Unix domain sockets are not supported
Assumptions.assumeTrue(System.getProperty("os.name").toLowerCase().contains("linux") ||
System.getProperty("os.name").toLowerCase().contains("mac"));
Assumptions.assumeTrue(System.getProperty("os.name").toLowerCase().contains("linux") ||
System.getProperty("os.name").toLowerCase().contains("mac"));
// Generate test CA certificate
KeyPair caKeyPair = generateKeyPair();
X509Certificate caCert = generateCertificate(caKeyPair);
File caCertFile = File.createTempFile("test-ca-cert", ".pem");
File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile());
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
Properties.GRPC_ENDPOINT.getName(), "unix:/tmp/test.sock"
));
// For Unix sockets, we expect an exception if the platform doesn't support it
try {
writeCertificateToFile(caCert, caCertFile);
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
Properties.GRPC_ENDPOINT.getName(), "unix:/tmp/test.sock"
));
// For Unix sockets, we expect an exception if the platform doesn't support it
try {
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
Assertions.assertNotNull(channel.authority(), "Channel authority should not be null");
} catch (Exception e) {
// If we get here, Unix sockets are not supported
Assertions.assertTrue(e.getMessage().contains("DomainSocketAddress"));
}
} finally {
caCertFile.delete();
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
Assertions.assertNotNull(channel.authority(), "Channel authority should not be null");
} catch (Exception e) {
// If we get here, Unix sockets are not supported
Assertions.assertTrue(e.getMessage().contains("DomainSocketAddress"));
}
}
@Test
public void testBuildGrpcManagedChannelWithCaCertAndDnsAuthority() throws Exception {
// Generate test CA certificate
KeyPair caKeyPair = generateKeyPair();
X509Certificate caCert = generateCertificate(caKeyPair);
File caCertFile = File.createTempFile("test-ca-cert", ".pem");
try {
writeCertificateToFile(caCert, caCertFile);
File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile());
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
Properties.GRPC_ENDPOINT.getName(), "dns://authority:53/example.com:443"
));
var properties = new Properties(Map.of(
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
Properties.GRPC_ENDPOINT.getName(), "dns://authority:53/example.com:443"
));
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
Assertions.assertEquals("example.com:443", channel.authority());
} finally {
caCertFile.delete();
}
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
Assertions.assertEquals("example.com:443", channel.authority());
}
@Test
@ -536,43 +394,28 @@ public class NetworkUtilsTest {
@Test
public void testBuildGrpcManagedChannelWithInsecureTlsAndMtls() throws Exception {
// Generate test certificates
KeyPair caKeyPair = generateKeyPair();
X509Certificate caCert = generateCertificate(caKeyPair);
KeyPair clientKeyPair = generateKeyPair();
X509Certificate clientCert = generateCertificate(clientKeyPair);
File caCertFile = File.createTempFile("test-ca-cert", ".pem");
File clientCertFile = File.createTempFile("test-client-cert", ".pem");
File clientKeyFile = File.createTempFile("test-client-key", ".pem");
try {
writeCertificateToFile(caCert, caCertFile);
writeCertificateToFile(clientCert, clientCertFile);
writePrivateKeyToFile(clientKeyPair, clientKeyFile);
File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile());
File clientCertFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile());
File clientKeyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile());
// Test that insecure TLS still works with mTLS settings
// The client certs should be ignored since we're using InsecureTrustManagerFactory
var properties = new Properties(Map.of(
Properties.GRPC_TLS_INSECURE.getName(), "true",
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
Properties.GRPC_TLS_CERT_PATH.getName(), clientCertFile.getAbsolutePath(),
Properties.GRPC_TLS_KEY_PATH.getName(), clientKeyFile.getAbsolutePath(),
Properties.GRPC_ENDPOINT.getName(), "dns:///example.com:443?tls=true"
));
// Test that insecure TLS still works with mTLS settings
// The client certs should be ignored since we're using InsecureTrustManagerFactory
var properties = new Properties(Map.of(
Properties.GRPC_TLS_INSECURE.getName(), "true",
Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(),
Properties.GRPC_TLS_CERT_PATH.getName(), clientCertFile.getAbsolutePath(),
Properties.GRPC_TLS_KEY_PATH.getName(), clientKeyFile.getAbsolutePath(),
Properties.GRPC_ENDPOINT.getName(), "dns:///example.com:443?tls=true"
));
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
// Verify the channel is created with the correct authority
Assertions.assertEquals("example.com:443", channel.authority());
// Verify the channel is active and using TLS (not plaintext)
Assertions.assertFalse(channel.isTerminated(), "Channel should be active");
} finally {
caCertFile.delete();
clientCertFile.delete();
clientKeyFile.delete();
}
channel = NetworkUtils.buildGrpcManagedChannel(properties);
channels.add(channel);
// Verify the channel is created with the correct authority
Assertions.assertEquals("example.com:443", channel.authority());
// Verify the channel is active and using TLS (not plaintext)
Assertions.assertFalse(channel.isTerminated(), "Channel should be active");
}
@Test

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
fg8QEv831uJb0uItcjDy5Yr0TEYMqg==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFUzCCAzugAwIBAgIUFHmlZNi1305lvonC4ePOulB207UwDQYJKoZIhvcNAQEL
BQAwOTENMAsGA1UECgwEREFQUjENMAsGA1UECwwEREFQUjEZMBcGA1UEAwwQVEVT
VCBDRVJUSUZJQ0FURTAeFw0yNTA4MjgxNTI4MTlaFw0yNzA4MjgxNTI4MTlaMDkx
DTALBgNVBAoMBERBUFIxDTALBgNVBAsMBERBUFIxGTAXBgNVBAMMEFRFU1QgQ0VS
VElGSUNBVEUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDbzg+ow2pe
+tWKSISVIF6kFi9VYReq0sJ8gJ+mQRkBK7Hs8G0WD9KS+Ebg15CTEulNbyp3sqPa
28tD1bq69ST8aMjUxD8NyWyYd7NQSd7pWHuR+NMDsh908W+JDyTxGgonXFaU5s8m
+FAS25nC8dzEgolxrQlVraXld1ZxMVW/OnOdl4dlSsXpGR4Zwv2S4BrppQvGhGTK
aURuOI1mot0IZVnV4acg5iK+2wPvDD2UJp6fMR6PplKrgb8dV2SqEEEVPwsXY0nb
idRb3yJ6VRtibkbu7u987BpEEaNeSmsC0BOIeHj90Jc+ilDQEgVRkMvP9zSPh6yY
4kOuMevof2J86FTRZWo7b9u1BG/2WoLeOPk00kAzN2jmk4hnytknWz1G0dxVZSJr
OcLHv9tWKk4TI3l54DrKw+n3pQA2Qs22P97n6d151mi3Yr45YtypmQhn4f4tsLxT
16mp2Vs/D4vU1M4jldpi9vDin+R4wIgDxXThUUd3c6NqrhyRiLteRaZVVsWeudi/
jZt7H676S5lVdLTsU1jmOxef1mTzJxNAhw7VClq8YXeq8Z65Mj84fARxEh+WgN31
5+JlIE6gPdYz7A+h5pupDVXJdhOViafQMBsAkUWautz4n47F5X+oB53rKEvRjjgV
sSVNB3oLBWCoXPxfpANQjNXky9QRb2Ib1wIDAQABo1MwUTAdBgNVHQ4EFgQUnBy0
t/3jxb0LK0SWaKjOlRlebmowHwYDVR0jBBgwFoAUnBy0t/3jxb0LK0SWaKjOlRle
bmowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAh/5qDtYaiTWN
3cX0H/ucdsT1fzVCiWmniRBrvGgLOJ4VfBpcGAeyt3nTOCGBpLIHi6M47mv6+DA8
GVA+k4FgAbFRWgSR5zDsOOC0/jTTEMpcrz13DgpCeh/Jj0MdNe3nzkC+eAT2bp07
ZYLrO++N+IEpDEuDz3YlqIpgKpEREoGKWXrXcCEdsAUbIjOSWBfLBTuF8x11IVi7
V4y3UUVTg6aA8ILmtWStoBuVroOH/HY/8RuQztUejjtd3PIEzPRgITekpWEDxI1D
Ycc7ZgdxEYmzXTvRAmLkIAoSPR5U61i09SSHmCs9hoWUGQGxSsxBFloTH6IoJFls
VDeWw3Shzu1PSPP5NedGgdRC8GKjOVDAgXzYPSoTYab5mGHTbL3HtRELthCmQSaC
SrNwKjd1MmhJ6yrjxKruE9mpSHUu50IDIP+PyMokWMRf504eGvPJstvD6+I4sUS3
/Owu0eGM3incgG/ulAWpRDCXRq49JpAoq7evbyBQyzDfrCSN0U6sgjBVlJuoEuXD
t1cNmvPxf0730HxQYrq1nYahdt5Mi9+Pv8ublylKuGeYiWqiSeSE6wRRTktL7Xmf
BotM8yvJBhpY00GAPSxXQ2Kl+OielJq9QSCOgtQkMCMzKMsfbO+YA+WvMICe+GNJ
sfrgLw0imYT1npKJbNtg/vRxTZFj9hc=
-----END CERTIFICATE-----

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
mfPgTPwyRZiGKrz43GpgD0Zg/eytPw==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFUzCCAzugAwIBAgIUbPytgC1pfbqeBSbdOok03mPcoU8wDQYJKoZIhvcNAQEL
BQAwOTENMAsGA1UECgwEREFQUjENMAsGA1UECwwEREFQUjEZMBcGA1UEAwwQVGVz
dCBDZXJ0aWZpY2F0ZTAeFw0yNTA4MjgxNTIzMzJaFw0yNzA4MjgxNTIzMzJaMDkx
DTALBgNVBAoMBERBUFIxDTALBgNVBAsMBERBUFIxGTAXBgNVBAMMEFRlc3QgQ2Vy
dGlmaWNhdGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCX9WC+9qfC
u7LhLTkm1XpYnIFowqlIAARHxWv08xbOIm/4bUtruuy0nOEVzzYiu8fdRP2LsBp9
iKuRJzBw6QOeASeepCorOFnU24bG3oAdjPHp7Bj76tM1GW5H8izOlln22My4asaO
P4kxC+LQt7aolF1Ca7A9J3TNEBxSaowUpCCDtwNnBTPoFa28vhFjga32BnNEZCTP
WCrn97WfDKj4trb+lQIt77jSBpPmwBJIqsOaM02uIlnF8MElX2TEUfMFGWAzDBml
lB80cZHybC2Ic2nrYKuuHtpZrtMM6atGstxiHDt002p6yxUUEbcXE1SvB6ZGuBDw
9frpF7J5xnRQ7PAaKl17hXinnOPG/b3xtvOrx2rewWHHUxfCDzzaDcr1+31zfoW8
3wcwfcuuS8E9iO4mIIYKoB+3C0430IMlqv61BVlHB9FajSEXhg8KIRDB8dZZGX4Y
95teRGv4s69Wr0gFFrPG5XJ0m4gS7mpkJeYwTrvtgb2Rm+D7vjxKR4xW8zSRnj4W
644YSpBI9n/fCw3wmWAvs+2wt1m6JcvXyOFJPY96BQWE3eIGV052OCc69rXUhEV/
vFRVNn4UJMCGptpJZiTM+/jvC94YEY2GzMu0jfuBYrnzeeasRnnVh6gfFKXui3xK
GApmMoqfd/2DogdmMXilZQ4SHinS6Bj0yQIDAQABo1MwUTAdBgNVHQ4EFgQUuj4S
u+I/pBSJFGVoCK4y+QvGm/QwHwYDVR0jBBgwFoAUuj4Su+I/pBSJFGVoCK4y+QvG
m/QwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAR18zFnyf22+h
bLT8ab0fD6pLQUW0/yr0PpsNNrsnNbTMr+tgOQDZgi/jpnG8eQmyA5aVjBxgxBNq
BhXFMixVputyafbb+fCIGvwWAMGtKL3Jf2yYfn6IhMftMB2e3xp4hUNBfRC92fVT
McypKFf/bWPxk7ZDeZGwNK0CViB2XNDCPWvR+RxQI4stilpN47/fAQdpnAmcDVi6
wVljILTaPhgpWj0Q3c6ccdgFE8ETRQK46dDW6C2KstIjqOxP4Go5HQw5bGTQVRPw
bX2v7kadLfFDJJDwmCRUNzQJfWM+8qROy8YgexFe5rBUkOOFCz2Wd2I0LjiN8SrP
aY3iEoZQO/bIUPJsi3qtLgb9HDZ6iXeB1SHEXnn/l0b1zpb2kumdhiNif2s1NXsw
LQV7xai3xrdT96fnWElqD39gHunLO2hCE4ra7YJ3yZnXyi21EdErhpCaD1aPo96d
0m/2rbIfafrBZFdcu4hvS56qtnVajOfXaN5bCKyRsByA8Ebv2XlWWWCRDii5ft/W
RYrRDZhC6t9dZNv1ObhDLzx/2FNq82lxhi4VCwlAy6Qdc8kY7uh92IWUUQoLonQB
QI0QuROI9W9Vc/DUJFvts/qCKWtD0XdoTVlZc/1B2WFbiwZ5U04x5inFBtudvyEL
ZEyQNL0MVlOgpxK3igY4xKM8r+m4bi4=
-----END CERTIFICATE-----

View File

@ -5,7 +5,7 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>spring-boot-examples</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>consumer-app</artifactId>

View File

@ -30,7 +30,7 @@ Once you have the cluster up and running you can install Dapr:
helm repo add dapr https://dapr.github.io/helm-charts/
helm repo update
helm upgrade --install dapr dapr/dapr \
--version=1.16.0-rc.3 \
--version=1.16.0-rc.5 \
--namespace dapr-system \
--create-namespace \
--wait

View File

@ -6,11 +6,11 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>spring-boot-examples</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<packaging>pom</packaging>
<properties>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>spring-boot-examples</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>producer-app</artifactId>

View File

@ -65,7 +65,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'TOKYO, LONDON, SEATTLE'
background: true
timeout_seconds: 90
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
@ -73,7 +72,7 @@ timeout_seconds: 90
To start the workflow with the three chained activities you can run:
```sh
sleep 35 && curl -X POST localhost:8080/wfp/chain -H 'Content-Type: application/json'
curl -X POST localhost:8080/wfp/chain -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
<!-- END_STEP -->
@ -142,7 +141,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- '!wolfkroW rpaD olleH'
background: true
timeout_seconds: 90
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
@ -150,7 +148,7 @@ timeout_seconds: 90
To start the workflow with the three chained activities you can run:
```sh
sleep 35 && curl -X POST localhost:8080/wfp/child -H 'Content-Type: application/json'
curl -X POST localhost:8080/wfp/child -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
<!-- END_STEP -->
@ -191,13 +189,12 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"cleanUpTimes":5}'
background: true
timeout_seconds: 90
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
```sh
sleep 30 && curl -X POST localhost:8080/wfp/continueasnew -H 'Content-Type: application/json'
curl -X POST localhost:8080/wfp/continueasnew -H 'Content-Type: application/json' --retry 10 --max-time 60 --retry-all-errors --retry-max-time 90
```
<!-- END_STEP -->
@ -260,13 +257,12 @@ To start the workflow you can run:
name: Start External Event Workflow
match_order: none
output_match_mode: substring
background: true
timeout_seconds: 90
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
```sh
sleep 30 && curl -X POST "localhost:8080/wfp/externalevent?orderId=123" -H 'Content-Type: application/json'
curl -X POST "localhost:8080/wfp/externalevent?orderId=123" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
<!-- END_STEP -->
@ -290,7 +286,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"approved":true}'
background: true
timeout_seconds: 90
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
@ -298,7 +293,7 @@ timeout_seconds: 90
To send the event you can run:
```sh
sleep 42 && curl -X POST "localhost:8080/wfp/externalevent-continue?orderId=123&decision=true" -H 'Content-Type: application/json'
curl -X POST "localhost:8080/wfp/externalevent-continue?orderId=123&decision=true" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
<!-- END_STEP -->
@ -346,13 +341,12 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"wordCount":60}'
background: true
timeout_seconds: 90
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
```sh
sleep 45 && curl -X POST localhost:8080/wfp/fanoutin -H 'Content-Type: application/json' -d @body.json
curl -X POST localhost:8080/wfp/fanoutin -H 'Content-Type: application/json' -d @body.json --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
<!-- END_STEP -->
@ -398,13 +392,12 @@ To start the workflow, you can run:
name: Start Suspend/Resume Workflow
match_order: none
output_match_mode: substring
background: true
timeout_seconds: 90
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
```sh
sleep 50 && curl -X POST "localhost:8080/wfp/suspendresume?orderId=456" -H 'Content-Type: application/json'
curl -X POST "localhost:8080/wfp/suspendresume?orderId=456" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
<!-- END_STEP -->
@ -431,7 +424,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'SUSPENDED'
background: true
timeout_seconds: 90
-->
@ -439,7 +431,7 @@ timeout_seconds: 90
Let's suspend the workflow instance by sending the following request:
```sh
sleep 55 && curl -X POST "localhost:8080/wfp/suspendresume/suspend?orderId=456" -H 'Content-Type: application/json'
curl -X POST "localhost:8080/wfp/suspendresume/suspend?orderId=456" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
<!-- END_STEP -->
@ -459,7 +451,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'RUNNING'
background: true
timeout_seconds: 90
-->
@ -467,7 +458,7 @@ timeout_seconds: 90
To send the event you can run:
```sh
sleep 60 && curl -X POST "localhost:8080/wfp/suspendresume/resume?orderId=456" -H 'Content-Type: application/json'
curl -X POST "localhost:8080/wfp/suspendresume/resume?orderId=456" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
<!-- END_STEP -->
@ -487,7 +478,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"approved":true}'
background: true
timeout_seconds: 90
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
@ -495,7 +485,7 @@ timeout_seconds: 90
To send the event you can run:
```sh
sleep 65 && curl -X POST "localhost:8080/wfp/suspendresume/continue?orderId=456&decision=true" -H 'Content-Type: application/json'
curl -X POST "localhost:8080/wfp/suspendresume/continue?orderId=456&decision=true" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
<!-- END_STEP -->

View File

@ -6,7 +6,7 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>spring-boot-examples</artifactId>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>workflows</artifactId>

View File

@ -15,6 +15,7 @@ package io.dapr.springboot.examples.wfp;
import io.dapr.testcontainers.Component;
import io.dapr.testcontainers.DaprContainer;
import io.dapr.testcontainers.DaprLogLevel;
import io.github.microcks.testcontainers.MicrocksContainersEnsemble;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@ -31,6 +32,16 @@ import java.util.List;
import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
/**
* Test configuration for Dapr containers with debug logging enabled.
*
* This configuration sets up Dapr with DEBUG log level and console output
* for detailed logging during test execution.
*
* ADDITIONAL DEBUGGING: For even more detailed logs, you can also:
* 1. Run `docker ps` to find the Dapr container ID
* 2. Run `docker logs --follow <container-id>` to stream real-time logs
*/
@TestConfiguration(proxyBeanMethods = false)
public class DaprTestContainersConfig {

View File

@ -38,6 +38,23 @@ import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* Integration tests for Dapr Workflow Patterns.
*
* DEBUGGING: For more detailed logs during test execution, you can:
* 1. Run `docker ps` to find the Dapr container ID
* 2. Run `docker logs --follow <container-id>` to stream real-time logs
* 3. The container name will typically be something like "dapr-workflow-patterns-app-<hash>"
*
* Example:
* ```bash
* docker ps | grep dapr
* docker logs --follow <container-id>
* ```
*
* This will show you detailed Dapr runtime logs including workflow execution,
* state transitions, and component interactions.
*/
@SpringBootTest(classes = {TestWorkflowPatternsApplication.class, DaprTestContainersConfig.class,
DaprAutoConfiguration.class, },
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ -137,6 +154,12 @@ class WorkflowPatternsAppTests {
}
/**
* Tests the ContinueAsNew workflow pattern.
*
* The ContinueAsNew pattern should execute cleanup activities 5 times
* with 5-second intervals between each iteration.
*/
@Test
void testContinueAsNew() {
//This call blocks until all the clean up activities are executed

View File

@ -5,13 +5,13 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
</parent>
<artifactId>testcontainers-dapr</artifactId>
<name>testcontainers-dapr</name>
<description>Testcontainers Dapr Module</description>
<version>0.16.0-SNAPSHOT</version>
<version>1.16.0-rc-2</version>
<packaging>jar</packaging>
<dependencies>
@ -56,6 +56,9 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<notimestamp>true</notimestamp>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>

View File

@ -14,7 +14,7 @@ limitations under the License.
package io.dapr.testcontainers;
public interface DaprContainerConstants {
String DAPR_VERSION = "1.16.0-rc.3";
String DAPR_VERSION = "1.16.0-rc.5";
String DAPR_RUNTIME_IMAGE_TAG = "daprio/daprd:" + DAPR_VERSION;
String DAPR_PLACEMENT_IMAGE_TAG = "daprio/placement:" + DAPR_VERSION;
String DAPR_SCHEDULER_IMAGE_TAG = "daprio/scheduler:" + DAPR_VERSION;