Skip to content
Open
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
12 changes: 12 additions & 0 deletions .github/settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>central</id>
<username>${env.MAVEN_CENTRAL_USERNAME}</username>
<password>${env.MAVEN_CENTRAL_TOKEN}</password>
</server>
</servers>
</settings>
183 changes: 183 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Release pipeline for zenbpm-java-client.
# Triggered via repository_dispatch from zenbpm (pulls fresh api.yaml, builds,
# deploys, commits api.yaml back) or manually via:
# gh workflow run release.yaml --ref <branch> --field release_tag=vX.Y.Z
# Manual mode deploys whatever is checked in — for testing and ad-hoc releases.

name: Release to Maven Central

on:
repository_dispatch:
types: [release_published]
workflow_dispatch:
inputs:
release_tag:
description: 'Version to release (e.g., v1.2.0, v1.2.1-fix1, v1.3.0-SNAPSHOT)'
required: true
type: string

# Only one release per tag in flight at a time — prevents racing on push
# or stepping on the shared GPG keyring.
concurrency:
group: release-${{ github.event.client_payload.release_tag || github.event.inputs.release_tag || github.run_id }}
cancel-in-progress: false

permissions:
contents: write

env:
# Build on JDK 17; pom.xml targets Java 8 bytecode.
JAVA_VERSION: '17'
MAVEN_CLI_OPTS: '-B -ntp'
ZENBPM_REPO: 'pbinitiative/zenbpm'
API_YAML_PATH: 'zenbpm-client-core/src/main/resources/openapi/api.yaml'

jobs:
release:
runs-on: ubuntu-latest

steps:
- name: Determine release version
id: version
run: |
case "${{ github.event_name }}" in
repository_dispatch)
VERSION="${{ github.event.client_payload.release_tag }}"
;;
workflow_dispatch)
VERSION="${{ github.event.inputs.release_tag }}"
;;
esac

if ! echo "${VERSION}" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then
echo "ERROR: version '${VERSION}' does not match vX.Y.Z[-suffix] format"
exit 1
fi

echo "VERSION=${VERSION}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "version_no_v=${VERSION#v}" >> "$GITHUB_OUTPUT"

- name: Checkout zenbpm-java-client
uses: actions/checkout@v4
with:
fetch-depth: 1
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: temurin
cache: maven

# Auto-only: manual runs deploy whatever api.yaml is checked in.
- name: Pull api.yaml from zenbpm ${{ steps.version.outputs.version }}
if: github.event_name == 'repository_dispatch'
run: |
set -euo pipefail
echo "Cloning ${{ env.ZENBPM_REPO }} at tag ${{ steps.version.outputs.version }}..."
git clone --depth 1 --branch "${{ steps.version.outputs.version }}" \
"https://github.com/${{ env.ZENBPM_REPO }}.git" /tmp/zenbpm

echo "Copying api.yaml..."
cp /tmp/zenbpm/openapi/api.yaml "${{ env.API_YAML_PATH }}"

if [ ! -s "${{ env.API_YAML_PATH }}" ]; then
echo "ERROR: copied api.yaml is empty or missing"
exit 1
fi
echo "api.yaml size: $(wc -c < "${{ env.API_YAML_PATH }}") bytes"
echo "--- api.yaml diff (vs. HEAD) ---"
git diff --stat -- "${{ env.API_YAML_PATH }}" || true

- name: Set project version to match tag
run: |
mvn ${MAVEN_CLI_OPTS} \
versions:set \
-DnewVersion="${{ steps.version.outputs.version_no_v }}" \
-DgenerateBackupPoms=false

- name: Build & run tests
run: mvn ${MAVEN_CLI_OPTS} clean verify -Dgpg.skip=true

# Pre-load passphrase into gpg-agent rather than passing on CLI or as
# system property — both leak via /proc or debug logs.
- name: Import GPG key & prime gpg-agent
run: |
set -euo pipefail

printf '%s' "${{ secrets.MAVEN_GPG_PRIVATE_KEY }}" | gpg --batch --import

# allow-loopback-pinentry + allow-preset-passphrase are both needed
# for non-interactive signing in headless CI.
mkdir -p "${HOME}/.gnupg"
printf 'allow-loopback-pinentry\nallow-preset-passphrase\n' \
> "${HOME}/.gnupg/gpg-agent.conf"
gpgconf --kill gpg-agent 2>/dev/null || true
gpgconf --launch gpg-agent

# Pre-cache passphrase via stdin — never on the CLI.
KEYGRIPS=$(gpg --list-secret-keys --with-keygrip --keyid-format LONG \
| grep -oP 'Keygrip = \K[A-F0-9]+')
if [ -z "${KEYGRIPS:-}" ]; then
echo "ERROR: could not find any secret key keygrips after import"
exit 1
fi
if ! PRESET_CMD=$(command -v gpg-preset-passphrase); then
if [ -x /usr/lib/gnupg2/gpg-preset-passphrase ]; then
PRESET_CMD=/usr/lib/gnupg2/gpg-preset-passphrase
else
echo "ERROR: gpg-preset-passphrase not found"
exit 1
fi
fi

for kg in ${KEYGRIPS}; do
printf '%s' "${{ secrets.MAVEN_GPG_PASSPHRASE }}" \
| "${PRESET_CMD}" --preset "${kg}"
done

echo "--- imported keys ---"
gpg --list-secret-keys --keyid-format LONG

# No GPG_PASSPHRASE here — the agent (primed above) supplies it.
- name: Deploy to Maven Central
env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
run: |
set -euo pipefail
mvn ${MAVEN_CLI_OPTS} deploy \
-P release \
-DskipTests \
-Dgpg.skip=false \
--settings .github/settings.xml

# Auto-only: manual runs don't change api.yaml.
- name: Commit updated api.yaml
if: github.event_name == 'repository_dispatch'
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

git add -- "${{ env.API_YAML_PATH }}"
if git diff --cached --quiet -- "${{ env.API_YAML_PATH }}"; then
echo "No changes to commit — api.yaml is already up to date."
exit 0
fi

# Rebase on concurrent changes so two dispatch events for the same
# tag don't race each other on push.
git pull --rebase --autostash origin HEAD
git commit -m "chore: update api.yaml from zenbpm ${{ steps.version.outputs.version }}"
git push origin HEAD

- name: Notify on failure
if: failure()
run: |
{
echo "::error title=Release ${{ steps.version.outputs.version }} failed::See run logs."
echo "Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
} >> "${GITHUB_STEP_SUMMARY}"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ local.properties
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig
.worktree

# Created by git when using merge tools for conflicts
*.BACKUP.*
Expand Down
96 changes: 66 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,47 @@
## Build this project
``mvn clean package``

## Getting started
## Using the library

Add the starter to your application and the core client as needed.
Artifacts are published to **Maven Central** under the `org.pbinitiative.zenbpm` namespace.

### Add the dependencies

Latest release: **1.3.0**

Maven:
```xml
<properties>
<zenbpm.version>1.3.0</zenbpm.version>
</properties>

<!-- Spring Boot starter (includes auto-configuration) -->
<dependency>
<groupId>org.zenbpm</groupId>
<groupId>org.pbinitiative.zenbpm</groupId>
<artifactId>zenbpm-spring-boot-starter</artifactId>
<version>${project.version}</version>
<version>${zenbpm.version}</version>
</dependency>

<!-- Core REST + gRPC client (without Spring auto-configuration) -->
<dependency>
<groupId>org.zenbpm</groupId>
<groupId>org.pbinitiative.zenbpm</groupId>
<artifactId>zenbpm-client-core</artifactId>
<version>${project.version}</version>
<version>${zenbpm.version}</version>
</dependency>

<!-- gRPC transport. gRPC Java splits API stubs from the transport implementation
on purpose, so this library doesn't pull one in transitively. Pick whichever
transport fits your runtime (grpc-netty-shaded is the most common). -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.78.0</version>
<scope>runtime</scope>
</dependency>
```

Configure connection settings in application.yml
## Configuration

Configure connection settings in `application.yml`.

values shown in `zenbpm` section are defaults.

Expand All @@ -49,13 +71,14 @@ zenbpm:
grpcLoggingEnabled: true
jobWorkerEnabled: true

# Disable OpenTelemetry — this is an OpenTelemetry SDK property, not a library property.
otel.sdk.disabled: true

logging:
level:
root: INFO
org.zenbpm.rest: TRACE
org.zenbpm.grpc: DEBUG
org.pbinitiative.zenbpm.rest: TRACE
org.pbinitiative.zenbpm.grpc: DEBUG

```

Expand All @@ -67,13 +90,15 @@ Inject the provided ZenbpmClientService to obtain the ApiClient, then create a t
```java
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.zenbpm.rest.ZenbpmClientService;
import org.zenbpm.client.ApiException;
import org.zenbpm.client.ApiClient;
import org.zenbpm.client.api.ProcessDefinitionApi;
import org.zenbpm.client.api.ProcessInstanceApi;
import org.zenbpm.client.api.dto.CreateProcessInstanceRequest;

import org.pbinitiative.zenbpm.rest.ZenbpmClientService;
import org.pbinitiative.zenbpm.client.ApiException;
import org.pbinitiative.zenbpm.client.ApiClient;
import org.pbinitiative.zenbpm.client.api.ProcessDefinitionApi;
import org.pbinitiative.zenbpm.client.api.ProcessInstanceApi;
import org.pbinitiative.zenbpm.client.api.dto.CreateProcessInstanceRequest;
import org.pbinitiative.zenbpm.client.api.dto.ProcessInstance;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -86,45 +111,43 @@ public class MyService {
ApiClient apiClient = zenbpm.getApiClient();
ProcessDefinitionApi defApi = new ProcessDefinitionApi(apiClient);

// Example: create a process definition from a BPMN string (adjust to your endpoint contract)
String bpmnXml = "<definitions ...>...</definitions>";
Long definitionKey = defApi.createProcessDefinition(bpmnXml).getProcessDefinitionKey();
return definitionKey;
File bpmnFile = new File("path/to/process.bpmn");
return defApi.createProcessDefinition(bpmnFile).getProcessDefinitionKey();
}

public void startMyProcess() throws ApiException {
public ProcessInstance startMyProcess(Long definitionKey) throws ApiException {
ApiClient apiClient = zenbpm.getApiClient();
ProcessInstanceApi piApi = new ProcessInstanceApi(apiClient);

Map<String, Object> vars = new HashMap<>();
vars.put("orderId", 12345L);

CreateProcessInstanceRequest req = new CreateProcessInstanceRequest()
.processDefinitionKey(123456L)
.processDefinitionKey(definitionKey)
.variables(vars);

piApi.createProcessInstance(req);
return piApi.createProcessInstance(req);
}
}
```

Notes:
- Available typed APIs include ProcessDefinitionApi, ProcessInstanceApi, JobApi, MessageApi, etc. Construct them with the provided ApiClient.
- Methods and DTOs come from the generated package `org.zenbpm.client.api` and `org.zenbpm.client.api.dto`.
- Methods and DTOs come from the generated package `org.pbinitiative.zenbpm.client.api` and `org.pbinitiative.zenbpm.client.api.dto`.

### 2) Register a gRPC job worker
Create a Spring bean with a method annotated by `@JobWorker`. Accepted method signatures:
- no parameters
- one parameter of type `org.zenbpm.proto.Zenbpm.WaitingJob`
- one parameter of type `org.zenbpm.grpc.JobContext`
- one parameter of type `org.pbinitiative.zenbpm.proto.Zenbpm.WaitingJob`
- one parameter of type `org.pbinitiative.zenbpm.grpc.JobContext`
- one parameter of type `Map<String, Object>`

Return value can be any object and will be serialized as variables for job completion. Throwing an exception fails the job.

```java
import org.springframework.stereotype.Component;
import org.zenbpm.grpc.JobWorker;
import org.zenbpm.grpc.JobContext;
import org.pbinitiative.zenbpm.grpc.JobWorker;
import org.pbinitiative.zenbpm.grpc.JobContext;
import java.util.Map;
import java.util.HashMap;

Expand All @@ -149,5 +172,18 @@ The gRPC worker manager connects on application start if `zenbpm.jobWorkerEnable

---

## Releasing a new version

Releases are automated via GitHub Actions (`.github/workflows/release.yaml`):

- **Automatic:** Triggered by a `repository_dispatch` event from the [zenbpm](https://github.com/pbinitiative/zenbpm) repo after a new release is published. The workflow pulls the matching `api.yaml`, builds, deploys to Maven Central, and commits the new `api.yaml` back here.
- **Manual:** For ad-hoc releases and end-to-end testing of the deploy pipeline, run from the repo root:
```
gh workflow run release.yaml --ref <branch> --field release_tag=vX.Y.Z
```
This skips the `api.yaml` pull and deploys whatever is currently checked in.

Required secrets: `MAVEN_GPG_PRIVATE_KEY`, `MAVEN_GPG_PASSPHRASE`, `MAVEN_CENTRAL_USERNAME`, `MAVEN_CENTRAL_TOKEN`.

Feel free to open issues or pull requests if you find bugs or want new features.

Loading
Loading