From 516c814cb9f5662ee5f43eb74a9ac4605a8cf98f Mon Sep 17 00:00:00 2001 From: Marius Volkhart Date: Thu, 28 May 2026 11:55:01 -0400 Subject: [PATCH 1/2] Add patch 0005: Wire protobuf support WirePayloadConverter for binary encoding of Wire Message types. WireJsonAdapterFactory registered on MoshiJsonPayloadConverter. Wire version added to overlay/gradle.properties for Renovate. --- overlay/gradle.properties | 1 + patches/0005-Add-Wire-protobuf-support.patch | 404 +++++++++++++++++++ 2 files changed, 405 insertions(+) create mode 100644 patches/0005-Add-Wire-protobuf-support.patch diff --git a/overlay/gradle.properties b/overlay/gradle.properties index 93f3f00..68be58e 100644 --- a/overlay/gradle.properties +++ b/overlay/gradle.properties @@ -1 +1,2 @@ moshiVersion=1.15.2 +wireVersion=6.4.0 diff --git a/patches/0005-Add-Wire-protobuf-support.patch b/patches/0005-Add-Wire-protobuf-support.patch new file mode 100644 index 0000000..9c91a83 --- /dev/null +++ b/patches/0005-Add-Wire-protobuf-support.patch @@ -0,0 +1,404 @@ +From a303a66472e147f94d43619ae9982d025dc0efec Mon Sep 17 00:00:00 2001 +From: Marius Volkhart +Date: Thu, 28 May 2026 11:52:21 -0400 +Subject: [PATCH] Add Wire protobuf support + +WirePayloadConverter handles com.squareup.wire.Message types using +binary encoding (protobuf/wire). Added to STANDARD_PAYLOAD_CONVERTERS +between ProtobufPayloadConverter and MoshiJsonPayloadConverter. + +Wire's WireJsonAdapterFactory registered on MoshiJsonPayloadConverter +so Wire types also work through JSON path. + +Adds wire-runtime and wire-moshi-adapter as api dependencies. +Wire Gradle plugin compiles test .proto for WirePayloadConverterTest. +--- + .gitignore | 1 + + build.gradle | 1 + + gradle/publishing.gradle | 48 +++------ + temporal-sdk/build.gradle | 15 +++ + .../converter/DefaultDataConverter.java | 1 + + .../converter/MoshiJsonPayloadConverter.java | 3 + + .../converter/WirePayloadConverter.java | 61 +++++++++++ + .../converter/WirePayloadConverterTest.java | 100 ++++++++++++++++++ + temporal-sdk/src/test/proto/test_wire.proto | 15 +++ + 9 files changed, 211 insertions(+), 34 deletions(-) + create mode 100644 temporal-sdk/src/main/java/io/temporal/common/converter/WirePayloadConverter.java + create mode 100644 temporal-sdk/src/test/java/io/temporal/common/converter/WirePayloadConverterTest.java + create mode 100644 temporal-sdk/src/test/proto/test_wire.proto + +diff --git a/.gitignore b/.gitignore +index 402a044..9ac6717 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -21,3 +21,4 @@ src/main/idls/* + /.claude + mise.local.toml + **/.factorypath ++gradle.properties +diff --git a/build.gradle b/build.gradle +index 6dcbcc7..7590842 100644 +--- a/build.gradle ++++ b/build.gradle +@@ -2,6 +2,7 @@ plugins { + id 'net.ltgt.errorprone' version '4.1.0' apply false + id 'io.github.gradle-nexus.publish-plugin' version '1.3.0' + id 'com.diffplug.spotless' version '7.0.2' apply false ++ id 'com.squareup.wire' version '6.4.0' apply false + id 'com.github.nbaztec.coveralls-jacoco' version "1.2.20" apply false + + // id 'org.jetbrains.kotlin.jvm' version '1.4.32' +diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle +index fdde671..fdc497b 100644 +--- a/gradle/publishing.gradle ++++ b/gradle/publishing.gradle +@@ -1,9 +1,8 @@ + nexusPublishing { +- // to release to sonatype use ./gradlew publishToSonatype + repositories { + sonatype { +- username = project.hasProperty('ossrhUsername') ? project.property('ossrhUsername') : '' +- password = project.hasProperty('ossrhPassword') ? project.property('ossrhPassword') : '' ++ username = System.getenv('NEXUS_USERNAME') ?: '' ++ password = System.getenv('NEXUS_PASSWORD') ?: '' + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) + } +@@ -27,32 +26,19 @@ subprojects { + } + } + } +- +- // To test local publishing use ./gradlew publish and comment nexusPublishing +- // +- // repositories { +- // maven { +- // def releasesRepoUrl = "$System.env.HOME/repos/releases" +- // def snapshotsRepoUrl = "$System.env.HOME/repos/snapshots" +- // url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl +- // } +- // } + } + +- // Customize the pom for all publications and sign all, in afterEvaluate so +- // subproject build.gradle files can contribute (e.g. description) and custom +- // publications (e.g. shadow) exist. + afterEvaluate { subproject -> + subproject.publishing.publications.withType(MavenPublication).each { publication -> + publication.pom { + name = subproject.description + description = subproject.description +- url = 'https://github.com/temporalio/sdk-java' ++ url = 'https://github.com/pkware/temporal-sdk-pkware' + + scm { +- connection = 'scm:git@github.com:temporalio/sdk-java.git' +- developerConnection = 'scm:git@github.com:temporalio/sdk-java.git' +- url = 'https://github.com/temporalio/sdk-java.git' ++ connection = 'scm:git@github.com:pkware/temporal-sdk-pkware.git' ++ developerConnection = 'scm:git@github.com:pkware/temporal-sdk-pkware.git' ++ url = 'https://github.com/pkware/temporal-sdk-pkware.git' + } + + licenses { +@@ -61,23 +47,17 @@ subprojects { + url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } + } +- +- developers { +- developer { +- id = 'mfateev' +- name = 'Maxim Fateev' +- email = 'maxim@temporal.io' +- } +- developer { +- id = 'samarabbas' +- name = 'Samar Abbas' +- email = 'samar@temporal.io' +- } +- } + } + } +- if (subproject.hasProperty('signing.keyId')) { ++ ++ def signingKey = System.getenv('SIGNING_KEY') ++ if (signingKey) { + subproject.signing { ++ useInMemoryPgpKeys( ++ System.getenv('SIGNING_KEY_ID'), ++ signingKey, ++ System.getenv('SIGNING_PASSWORD') ?: '' ++ ) + subproject.publishing.publications.withType(MavenPublication).each { publication -> + sign publication + } +diff --git a/temporal-sdk/build.gradle b/temporal-sdk/build.gradle +index ea02cdf..fc9da30 100644 +--- a/temporal-sdk/build.gradle ++++ b/temporal-sdk/build.gradle +@@ -1,5 +1,18 @@ ++apply plugin: 'com.squareup.wire' ++ + description = '''Temporal Workflow Java SDK''' + ++wire { ++ sourcePath { ++ srcDir 'src/test/proto' ++ } ++ java { ++ out = "${buildDir}/generated/source/wire" ++ } ++} ++ ++sourceSets.test.java.srcDir("${buildDir}/generated/source/wire") ++ + dependencies { + api(platform("io.grpc:grpc-bom:$grpcVersion")) + api(platform("io.micrometer:micrometer-bom:$micrometerVersion")) +@@ -10,6 +23,8 @@ dependencies { + + implementation "com.google.guava:guava:$guavaVersion" + api "com.squareup.moshi:moshi:$moshiVersion" ++ api "com.squareup.wire:wire-runtime:$wireVersion" ++ api "com.squareup.wire:wire-moshi-adapter:$wireVersion" + + // compileOnly and testImplementation because this dependency is needed only to work with json format of history + // which shouldn't be needed for any production usage of temporal-sdk. +diff --git a/temporal-sdk/src/main/java/io/temporal/common/converter/DefaultDataConverter.java b/temporal-sdk/src/main/java/io/temporal/common/converter/DefaultDataConverter.java +index d86e948..b8e0b11 100644 +--- a/temporal-sdk/src/main/java/io/temporal/common/converter/DefaultDataConverter.java ++++ b/temporal-sdk/src/main/java/io/temporal/common/converter/DefaultDataConverter.java +@@ -18,6 +18,7 @@ public class DefaultDataConverter extends PayloadAndFailureDataConverter { + new ByteArrayPayloadConverter(), + new ProtobufJsonPayloadConverter(), + new ProtobufPayloadConverter(), ++ new WirePayloadConverter(), + new MoshiJsonPayloadConverter() + }; + +diff --git a/temporal-sdk/src/main/java/io/temporal/common/converter/MoshiJsonPayloadConverter.java b/temporal-sdk/src/main/java/io/temporal/common/converter/MoshiJsonPayloadConverter.java +index f81922d..019e163 100644 +--- a/temporal-sdk/src/main/java/io/temporal/common/converter/MoshiJsonPayloadConverter.java ++++ b/temporal-sdk/src/main/java/io/temporal/common/converter/MoshiJsonPayloadConverter.java +@@ -3,6 +3,7 @@ package io.temporal.common.converter; + import com.google.protobuf.ByteString; + import com.squareup.moshi.JsonAdapter; + import com.squareup.moshi.Moshi; ++import com.squareup.wire.WireJsonAdapterFactory; + import io.temporal.api.common.v1.Payload; + import java.io.IOException; + import java.lang.reflect.Type; +@@ -29,6 +30,7 @@ public class MoshiJsonPayloadConverter implements PayloadConverter { + this.moshi = + userMoshi + .newBuilder() ++ .add(new WireJsonAdapterFactory()) + .add(new DurationMillisAdapter()) + .add(new OperationTokenTypeAdapter()) + .build(); +@@ -41,6 +43,7 @@ public class MoshiJsonPayloadConverter implements PayloadConverter { + */ + public static Moshi newDefaultMoshi() { + return new Moshi.Builder() ++ .add(new WireJsonAdapterFactory()) + .add(new DurationMillisAdapter()) + .add(new OperationTokenTypeAdapter()) + .build(); +diff --git a/temporal-sdk/src/main/java/io/temporal/common/converter/WirePayloadConverter.java b/temporal-sdk/src/main/java/io/temporal/common/converter/WirePayloadConverter.java +new file mode 100644 +index 0000000..82c8a24 +--- /dev/null ++++ b/temporal-sdk/src/main/java/io/temporal/common/converter/WirePayloadConverter.java +@@ -0,0 +1,61 @@ ++package io.temporal.common.converter; ++ ++import com.google.protobuf.ByteString; ++import com.squareup.wire.Message; ++import com.squareup.wire.ProtoAdapter; ++import io.temporal.api.common.v1.Payload; ++import java.io.IOException; ++import java.lang.reflect.Type; ++import java.nio.charset.StandardCharsets; ++import java.util.Optional; ++ ++/** ++ * {@link PayloadConverter} for Wire-generated protobuf types. ++ * ++ *

Uses Wire's native binary encoding via each message's companion {@link ProtoAdapter}. Non-Wire ++ * types are skipped (returns {@link Optional#empty()}) so the converter chain falls through to the ++ * next converter. ++ */ ++public class WirePayloadConverter implements PayloadConverter { ++ private static final String ENCODING_TYPE = "protobuf/wire"; ++ private static final ByteString ENCODING_METADATA = ++ ByteString.copyFrom(ENCODING_TYPE, StandardCharsets.UTF_8); ++ ++ @Override ++ public String getEncodingType() { ++ return ENCODING_TYPE; ++ } ++ ++ @Override ++ @SuppressWarnings({"unchecked", "rawtypes"}) ++ public Optional toData(Object value) throws DataConverterException { ++ if (value == null || !(value instanceof Message)) return Optional.empty(); ++ try { ++ ProtoAdapter adapter = (ProtoAdapter) value.getClass().getField("ADAPTER").get(null); ++ byte[] bytes = adapter.encode(value); ++ return Optional.of( ++ Payload.newBuilder() ++ .putMetadata(EncodingKeys.METADATA_ENCODING_KEY, ENCODING_METADATA) ++ .setData(ByteString.copyFrom(bytes)) ++ .build()); ++ } catch (NoSuchFieldException | IllegalAccessException e) { ++ throw new DataConverterException( ++ "Class " + value.getClass().getName() + " is not a Wire-generated message", e); ++ } ++ } ++ ++ @Override ++ @SuppressWarnings("unchecked") ++ public T fromData(Payload content, Class valueClass, Type valueType) ++ throws DataConverterException { ++ try { ++ ProtoAdapter adapter = (ProtoAdapter) valueClass.getField("ADAPTER").get(null); ++ return adapter.decode(content.getData().toByteArray()); ++ } catch (IOException e) { ++ throw new DataConverterException(e); ++ } catch (NoSuchFieldException | IllegalAccessException e) { ++ throw new DataConverterException( ++ "Class " + valueClass.getName() + " is not a Wire-generated message", e); ++ } ++ } ++} +diff --git a/temporal-sdk/src/test/java/io/temporal/common/converter/WirePayloadConverterTest.java b/temporal-sdk/src/test/java/io/temporal/common/converter/WirePayloadConverterTest.java +new file mode 100644 +index 0000000..3fe57cb +--- /dev/null ++++ b/temporal-sdk/src/test/java/io/temporal/common/converter/WirePayloadConverterTest.java +@@ -0,0 +1,100 @@ ++package io.temporal.common.converter; ++ ++import static org.junit.Assert.*; ++ ++import com.google.protobuf.ByteString; ++import io.temporal.api.common.v1.Payload; ++import io.temporal.common.converter.wiretest.NestedMessage; ++import io.temporal.common.converter.wiretest.SimpleMessage; ++import java.nio.charset.StandardCharsets; ++import java.util.Arrays; ++import java.util.Optional; ++import org.junit.Test; ++ ++public class WirePayloadConverterTest { ++ ++ private final WirePayloadConverter converter = new WirePayloadConverter(); ++ ++ @Test ++ public void encodingTypeIsProtobufWire() { ++ assertEquals("protobuf/wire", converter.getEncodingType()); ++ } ++ ++ @Test ++ public void nullReturnsEmpty() { ++ assertFalse(converter.toData(null).isPresent()); ++ } ++ ++ @Test ++ public void nonWireTypeReturnsEmpty() { ++ assertFalse(converter.toData("just a string").isPresent()); ++ } ++ ++ @Test ++ public void serializesWireMessage() { ++ SimpleMessage msg = new SimpleMessage.Builder().name("Alice").value(30).build(); ++ ++ Optional result = converter.toData(msg); ++ ++ assertTrue(result.isPresent()); ++ String encoding = ++ result ++ .get() ++ .getMetadataMap() ++ .get(EncodingKeys.METADATA_ENCODING_KEY) ++ .toString(StandardCharsets.UTF_8); ++ assertEquals("protobuf/wire", encoding); ++ assertFalse(result.get().getData().isEmpty()); ++ } ++ ++ @Test ++ public void roundTripsSimpleMessage() { ++ SimpleMessage original = new SimpleMessage.Builder().name("Bob").value(42).build(); ++ ++ Payload payload = converter.toData(original).get(); ++ SimpleMessage restored = converter.fromData(payload, SimpleMessage.class, SimpleMessage.class); ++ ++ assertEquals(original, restored); ++ } ++ ++ @Test ++ public void roundTripsNestedMessage() { ++ NestedMessage original = ++ new NestedMessage.Builder() ++ .id("test-123") ++ .items( ++ Arrays.asList( ++ new SimpleMessage.Builder().name("a").value(1).build(), ++ new SimpleMessage.Builder().name("b").value(2).build())) ++ .build(); ++ ++ Payload payload = converter.toData(original).get(); ++ NestedMessage restored = converter.fromData(payload, NestedMessage.class, NestedMessage.class); ++ ++ assertEquals(original, restored); ++ } ++ ++ @Test ++ public void decodesEmptyPayloadAsIdentityMessage() { ++ Payload payload = wirePayload(new byte[0]); ++ ++ SimpleMessage result = converter.fromData(payload, SimpleMessage.class, SimpleMessage.class); ++ ++ assertEquals(new SimpleMessage.Builder().build(), result); ++ } ++ ++ @Test(expected = DataConverterException.class) ++ public void corruptedBinaryThrows() { ++ Payload payload = wirePayload(new byte[] {(byte) 0xFF, (byte) 0xFE, 0x01, 0x02, 0x03}); ++ converter.fromData(payload, SimpleMessage.class, SimpleMessage.class); ++ } ++ ++ private static Payload wirePayload(byte[] bytes) { ++ return Payload.newBuilder() ++ .putMetadata( ++ EncodingKeys.METADATA_ENCODING_KEY, ++ ByteString.copyFrom("protobuf/wire", StandardCharsets.UTF_8)) ++ .setData(ByteString.copyFrom(bytes)) ++ .build(); ++ } ++} +diff --git a/temporal-sdk/src/test/proto/test_wire.proto b/temporal-sdk/src/test/proto/test_wire.proto +new file mode 100644 +index 0000000..d08747e +--- /dev/null ++++ b/temporal-sdk/src/test/proto/test_wire.proto +@@ -0,0 +1,15 @@ ++syntax = "proto3"; ++ ++package io.temporal.common.converter.wiretest; ++ ++option java_package = "io.temporal.common.converter.wiretest"; ++ ++message SimpleMessage { ++ string name = 1; ++ int32 value = 2; ++} ++ ++message NestedMessage { ++ string id = 1; ++ repeated SimpleMessage items = 2; ++} +-- +2.50.1 (Apple Git-155) + From 1d1d2bb93f1cc0a675363df782fffcec8cdf7d22 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 16:15:46 +0000 Subject: [PATCH 2/2] Update actions/setup-java action to v5 --- .github/workflows/manual-build.yml | 2 +- .github/workflows/sync.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml index 538f054..6967bb5 100644 --- a/.github/workflows/manual-build.yml +++ b/.github/workflows/manual-build.yml @@ -33,7 +33,7 @@ jobs: run: ./scripts/apply-patches.sh "$UPSTREAM_TAG" - name: Set up JDK - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'temurin' java-version: '21' diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 55283e4..8a79f1f 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -62,7 +62,7 @@ jobs: - name: Set up JDK if: steps.detect.outputs.should_sync == 'true' - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'temurin' java-version: '21'