Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,46 @@ A [Typescript SDK version](https://github.com/steveandroulakis/temporal-money-tr

The sample is configured by default to connect to a [local Temporal Server](https://docs.temporal.io/cli#starting-the-temporal-server) running on localhost:7233.

To instead connect to Temporal Cloud, set the following environment variables, replacing them with your own Temporal Cloud credentials:
To connect to Temporal Cloud, you have two authentication options:

### Option 1: Certificate-based Authentication (mTLS)

Set the following environment variables with your certificate paths:

```bash
TEMPORAL_ADDRESS=testnamespace.sdvdw.tmprl.cloud:7233
TEMPORAL_NAMESPACE=testnamespace.sdvdw
TEMPORAL_CERT_PATH="/path/to/file.pem"
TEMPORAL_KEY_PATH="/path/to/file.key"
````
```

### Option 2: API Key Authentication

Set the following environment variables with your API key:

```bash
TEMPORAL_ADDRESS=us-west-2.aws.api.temporal.io:7233
TEMPORAL_NAMESPACE=testnamespace.sdvdw
TEMPORAL_API_KEY="your-api-key-here"
```

For more information about API keys, see the [Temporal Cloud API Keys documentation](https://docs.temporal.io/cloud/api-keys).

**Note:** The application will prioritize certificate-based authentication if both certificate paths and API key are provided.

(optional) set a task queue name
```bash
export TEMPORAL_MONEYTRANSFER_TASKQUEUE="MoneyTransferJava"
```

## Run Tests

Run all tests:

```bash
./gradlew test
```

## Run a Workflow

Note: Use a Java 18 SDK.
Expand All @@ -34,6 +60,18 @@ Start a worker:
ENCRYPT_PAYLOADS=true ./gradlew -q execute -PmainClass=io.temporal.samples.moneytransfer.AccountTransferWorker --console=plain
```

Start a new workflow:

```bash
./gradlew -q execute -PmainClass=io.temporal.samples.moneytransfer.TransferRequester --console=plain
```

Check the status of an existing workflow:

```bash
./gradlew -q execute -PmainClass=io.temporal.samples.moneytransfer.TransferRequester --args="--status WORKFLOW_ID" --console=plain
```

Run the money transfer web UI:

```bash
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ subprojects {
ext {
otelVersion = '1.26.0'
otelVersionAlpha = "${otelVersion}-alpha"
javaSDKVersion = '1.25.1'
javaSDKVersion = '1.29.0'
jarVersion = '1.0.0'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package io.temporal.samples.moneytransfer;

import static io.temporal.samples.moneytransfer.TemporalClient.getWorkflowServiceStubs;

import io.temporal.api.enums.v1.TaskQueueKind;
import io.temporal.api.enums.v1.TaskQueueType;
import io.temporal.api.taskqueue.v1.TaskQueue;
import io.temporal.api.workflowservice.v1.DescribeTaskQueueRequest;
import io.temporal.api.workflowservice.v1.DescribeTaskQueueResponse;
import io.temporal.client.WorkflowClient;
import io.temporal.samples.moneytransfer.web.ServerInfo;
import io.temporal.serviceclient.WorkflowServiceStubs;
import java.io.FileNotFoundException;
Expand All @@ -15,7 +14,8 @@
public class DescribeTaskQueue {
public static DescribeTaskQueueResponse getTaskQueueInfo()
throws FileNotFoundException, SSLException {
WorkflowServiceStubs service = getWorkflowServiceStubs();
WorkflowClient client = TemporalClient.get();
WorkflowServiceStubs service = client.getWorkflowServiceStubs();

DescribeTaskQueueResponse res =
service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@

package io.temporal.samples.moneytransfer;

import static io.temporal.samples.moneytransfer.TemporalClient.getWorkflowServiceStubs;

import io.temporal.api.workflowservice.v1.*;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.common.WorkflowExecutionHistory;
import io.temporal.common.converter.CodecDataConverter;
Expand All @@ -44,7 +43,8 @@ public class RecentHistoryReplayer {
public static List<WorkflowExecutionHistory> getWorkflowHistories()
throws FileNotFoundException, SSLException {

WorkflowServiceStubs service = getWorkflowServiceStubs();
WorkflowClient client = TemporalClient.get();
WorkflowServiceStubs service = client.getWorkflowServiceStubs();

String query = "WorkflowType = 'moneyTransferWorkflow'";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
/*
* Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved
* Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved
*
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License is
* located at
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.
* or in the "license" file accompanying this file. This file 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.temporal.samples.moneytransfer;
Expand All @@ -37,48 +37,86 @@
import javax.net.ssl.SSLException;

public class TemporalClient {
public static WorkflowServiceStubs getWorkflowServiceStubs()

/**
* Centralized method to create and configure WorkflowServiceStubs. This is the single source of
* truth for connecting to Temporal. Supports three connection methods: 1. Certificate-based
* (mTLS) - requires TEMPORAL_CERT_PATH and TEMPORAL_KEY_PATH 2. API Key-based - requires
* TEMPORAL_API_KEY 3. Local - fallback when neither certificates nor API key are provided
*/
private static WorkflowServiceStubs createWorkflowServiceStubs()
throws FileNotFoundException, SSLException {
WorkflowServiceStubsOptions.Builder workflowServiceStubsOptionsBuilder =
WorkflowServiceStubsOptions.newBuilder();
String endpoint = ServerInfo.getAddress();
String namespace = ServerInfo.getNamespace();
String apiKey = ServerInfo.getApiKey();
String certPath = ServerInfo.getCertPath();
String keyPath = ServerInfo.getKeyPath();

if (!ServerInfo.getCertPath().equals("") && !"".equals(ServerInfo.getKeyPath())) {
InputStream clientCert = new FileInputStream(ServerInfo.getCertPath());
System.out.println("TEMPORAL_ADDRESS: " + endpoint);
System.out.println("TEMPORAL_NAMESPACE: " + namespace);

InputStream clientKey = new FileInputStream(ServerInfo.getKeyPath());
WorkflowServiceStubsOptions.Builder optionsBuilder =
WorkflowServiceStubsOptions.newBuilder().setTarget(endpoint);

workflowServiceStubsOptionsBuilder.setSslContext(
SimpleSslContextBuilder.forPKCS8(clientCert, clientKey).build());
}
// Check if certificates are provided (mTLS connection)
boolean hasCertificates = !certPath.isEmpty() && !keyPath.isEmpty();

// Check if API key is provided
boolean hasApiKey = apiKey != null && !apiKey.isEmpty();

// Check if using local server
boolean isLocal = "localhost:7233".equals(endpoint);

if (hasCertificates) {
System.out.println("--- Connecting with Certificate Authentication ---");
System.out.println("Cert path: " + certPath);
System.out.println("Key path: " + keyPath);
System.out.println("Endpoint: " + endpoint);
System.out.println("---------------------------------");

// For temporal cloud this would likely be ${namespace}.tmprl.cloud:7233
String targetEndpoint = ServerInfo.getAddress();
// Your registered namespace.
InputStream clientCert = new FileInputStream(certPath);
InputStream clientKey = new FileInputStream(keyPath);

workflowServiceStubsOptionsBuilder.setTarget(targetEndpoint);
WorkflowServiceStubs service = null;
optionsBuilder.setSslContext(SimpleSslContextBuilder.forPKCS8(clientCert, clientKey).build());

return WorkflowServiceStubs.newServiceStubs(optionsBuilder.build());

} else if (hasApiKey && !isLocal) {
System.out.println("--- Connecting with API Key Authentication ---");
System.out.println("Endpoint: " + endpoint);
System.out.println("API key length: " + apiKey.length());
System.out.println("---------------------------------");

return WorkflowServiceStubs.newServiceStubs(
optionsBuilder.setEnableHttps(true).addApiKey(() -> apiKey).build());

if (!ServerInfo.getAddress().equals("localhost:7233")) {
// if not local server, then use the workflowServiceStubsOptionsBuilder
service = WorkflowServiceStubs.newServiceStubs(workflowServiceStubsOptionsBuilder.build());
} else {
service = WorkflowServiceStubs.newLocalServiceStubs();
System.out.println("--- Connecting to Local Temporal ---");
return WorkflowServiceStubs.newLocalServiceStubs();
}
}

return service;
/**
* This method is preserved to prevent build failures across the project. It now uses the new,
* correct connection logic.
*/
public static WorkflowServiceStubs getWorkflowServiceStubs()
throws FileNotFoundException, SSLException {
return createWorkflowServiceStubs();
}

/** Gets a fully configured WorkflowClient. */
public static WorkflowClient get() throws FileNotFoundException, SSLException {
// TODO support local server
// Get worker to poll the common task queue.
// gRPC stubs wrapper that talks to the local docker instance of temporal service.
// WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
WorkflowServiceStubs service = createWorkflowServiceStubs();

WorkflowServiceStubs service = getWorkflowServiceStubs();
String namespace = ServerInfo.getNamespace();
if (namespace == null || namespace.isEmpty()) {
namespace = "default";
}

WorkflowClientOptions.Builder builder = WorkflowClientOptions.newBuilder();

// if environment variable ENCRYPT_PAYLOADS is set to true, then use CryptCodec
// If environment variable ENCRYPT_PAYLOADS is set to true, then use CryptCodec
if (System.getenv("ENCRYPT_PAYLOADS") != null
&& System.getenv("ENCRYPT_PAYLOADS").equals("true")) {
builder.setDataConverter(
Expand All @@ -89,38 +127,26 @@ public static WorkflowClient get() throws FileNotFoundException, SSLException {
}

System.out.println("<<<<SERVER INFO>>>>:\n " + ServerInfo.getServerInfo());
WorkflowClientOptions clientOptions = builder.setNamespace(ServerInfo.getNamespace()).build();
WorkflowClientOptions clientOptions = builder.setNamespace(namespace).build();

// client that can be used to start and signal workflows
WorkflowClient client = WorkflowClient.newInstance(service, clientOptions);
return client;
return WorkflowClient.newInstance(service, clientOptions);
}

/** Gets a fully configured ScheduleClient. */
public static ScheduleClient getScheduleClient() throws FileNotFoundException, SSLException {
// TODO support local server
// Get worker to poll the common task queue.
// gRPC stubs wrapper that talks to the local docker instance of temporal service.
// WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();

WorkflowServiceStubs service = getWorkflowServiceStubs();

ScheduleClientOptions.Builder builder = ScheduleClientOptions.newBuilder();
WorkflowServiceStubs service = createWorkflowServiceStubs();

// if environment variable ENCRYPT_PAYLOADS is set to true, then use CryptCodec
if (System.getenv("ENCRYPT_PAYLOADS") != null
&& System.getenv("ENCRYPT_PAYLOADS").equals("true")) {
builder.setDataConverter(
new CodecDataConverter(
DefaultDataConverter.newDefaultInstance(),
Collections.singletonList(new CryptCodec()),
true /* encode failure attributes */));
String namespace = ServerInfo.getNamespace();
if (namespace == null || namespace.isEmpty()) {
namespace = "default"; // Fallback for local development
}

System.out.println("<<<<SERVER INFO>>>>:\n " + ServerInfo.getServerInfo());
ScheduleClientOptions clientOptions = builder.setNamespace(ServerInfo.getNamespace()).build();
ScheduleClientOptions clientOptions =
ScheduleClientOptions.newBuilder()
.setNamespace(namespace)
// .setDataConverter(...) // Your custom data converter can be added here
.build();

// client that can be used to start and signal workflows
ScheduleClient client = ScheduleClient.newInstance(service, clientOptions);
return client;
return ScheduleClient.newInstance(service, clientOptions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@

package io.temporal.samples.moneytransfer;

import static io.temporal.samples.moneytransfer.TemporalClient.getWorkflowServiceStubs;

import com.google.common.base.Splitter;
import com.google.protobuf.Timestamp;
import io.temporal.api.filter.v1.StartTimeFilter;
import io.temporal.api.filter.v1.WorkflowTypeFilter;
import io.temporal.api.workflow.v1.WorkflowExecutionInfo;
import io.temporal.api.workflowservice.v1.*;
import io.temporal.client.WorkflowClient;
import io.temporal.samples.moneytransfer.dataclasses.WorkflowStatusObj;
import io.temporal.samples.moneytransfer.web.ServerInfo;
import io.temporal.serviceclient.WorkflowServiceStubs;
Expand All @@ -42,18 +41,23 @@ public class TransferLister {

public static List<WorkflowStatusObj> listWorkflows() throws FileNotFoundException, SSLException {

WorkflowServiceStubs service = getWorkflowServiceStubs();
ListOpenWorkflowExecutionsResponse responseOpen =
service
.blockingStub()
.listOpenWorkflowExecutions(
ListOpenWorkflowExecutionsRequest.newBuilder()
.setStartTimeFilter(
StartTimeFilter.newBuilder().setEarliestTime(getOneHourAgo()).build())
.setTypeFilter(
WorkflowTypeFilter.newBuilder().setName("moneyTransferWorkflow").build())
.setNamespace(ServerInfo.getNamespace())
.build());
WorkflowClient client = TemporalClient.get();
WorkflowServiceStubs service = client.getWorkflowServiceStubs();

// Try with minimal request first
ListOpenWorkflowExecutionsResponse responseOpen;
try {
responseOpen =
service
.blockingStub()
.listOpenWorkflowExecutions(
ListOpenWorkflowExecutionsRequest.newBuilder()
.setNamespace(ServerInfo.getNamespace())
.build());
} catch (Exception e) {
System.err.println("Failed to list open workflows: " + e.getMessage());
throw e;
}

ListClosedWorkflowExecutionsResponse responseClosed =
service
Expand All @@ -62,9 +66,9 @@ public static List<WorkflowStatusObj> listWorkflows() throws FileNotFoundExcepti
ListClosedWorkflowExecutionsRequest.newBuilder()
.setStartTimeFilter(
StartTimeFilter.newBuilder().setEarliestTime(getOneHourAgo()).build())
.setNamespace(ServerInfo.getNamespace())
.setTypeFilter(
WorkflowTypeFilter.newBuilder().setName("moneyTransferWorkflow").build())
.setNamespace(ServerInfo.getNamespace())
.build());

// array of WorkflowStatusObj
Expand Down
Loading