Skip to content

Commit 62d1c23

Browse files
committed
initial commit
1 parent f6c9681 commit 62d1c23

37 files changed

Lines changed: 3326 additions & 0 deletions

client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public final class DurableTaskGrpcWorker implements AutoCloseable {
5757

5858
private final TaskHubSidecarServiceBlockingStub sidecarClient;
5959
private final boolean supportsLargePayloads;
60+
private final boolean supportsScheduledTasks;
6061
private final int maxChunkSizeBytes;
6162
private final int largePayloadThresholdBytes;
6263

@@ -93,6 +94,7 @@ public final class DurableTaskGrpcWorker implements AutoCloseable {
9394

9495
this.sidecarClient = TaskHubSidecarServiceGrpc.newBlockingStub(sidecarGrpcChannel);
9596
this.supportsLargePayloads = builder.supportsLargePayloads;
97+
this.supportsScheduledTasks = builder.supportsScheduledTasks;
9698
this.maxChunkSizeBytes = builder.maxChunkSizeBytes;
9799
this.largePayloadThresholdBytes = builder.largePayloadThresholdBytes;
98100
this.dataConverter = builder.dataConverter != null ? builder.dataConverter : new JacksonDataConverter();
@@ -582,6 +584,9 @@ private GetWorkItemsRequest buildGetWorkItemsRequest() {
582584
if (this.supportsLargePayloads) {
583585
builder.addCapabilities(WorkerCapability.WORKER_CAPABILITY_LARGE_PAYLOADS);
584586
}
587+
if (this.supportsScheduledTasks) {
588+
builder.addCapabilities(WorkerCapability.WORKER_CAPABILITY_SCHEDULED_TASKS);
589+
}
585590
if (this.workItemFilter != null) {
586591
builder.setWorkItemFilters(toProtoWorkItemFilters(this.workItemFilter));
587592
}

client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorkerBuilder.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public final class DurableTaskGrpcWorkerBuilder {
3232
private boolean autoGenerateWorkItemFilters;
3333
final List<ClientInterceptor> interceptors = new ArrayList<>();
3434
boolean supportsLargePayloads;
35+
boolean supportsScheduledTasks;
3536

3637
/**
3738
* Default maximum chunk size in bytes for orchestrator responses, matching the .NET
@@ -404,6 +405,21 @@ public DurableTaskGrpcWorkerBuilder setSupportsLargePayloads(boolean enabled) {
404405
return this;
405406
}
406407

408+
/**
409+
* Indicates that this worker supports scheduled tasks.
410+
* <p>
411+
* When enabled, the worker announces the {@code WORKER_CAPABILITY_SCHEDULED_TASKS} capability to the sidecar.
412+
* This is set by feature registration helpers (such as the scheduled-tasks worker extension) after they register
413+
* the built-in schedule entity and operation orchestrator; most applications do not call it directly.
414+
*
415+
* @param enabled whether scheduled-task support is enabled
416+
* @return this builder object
417+
*/
418+
public DurableTaskGrpcWorkerBuilder setSupportsScheduledTasks(boolean enabled) {
419+
this.supportsScheduledTasks = enabled;
420+
return this;
421+
}
422+
407423
/**
408424
* Sets the externalization threshold in bytes used by the worker when estimating
409425
* post-externalization action sizes during pre-send validation.

scheduledtasks/build.gradle

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
plugins {
2+
id 'java-library'
3+
id 'maven-publish'
4+
id 'signing'
5+
id 'com.github.spotbugs' version '6.4.8'
6+
}
7+
8+
group 'com.microsoft'
9+
version = '0.1.0'
10+
archivesBaseName = 'durabletask-scheduledtasks'
11+
12+
def jacksonVersion = '2.18.3'
13+
14+
// Java 11 is used to compile and run all tests. Set the JDK_11 env var to your
15+
// local JDK 11 home directory, e.g. C:/Program Files/Java/openjdk-11.0.12_7/
16+
// If unset, falls back to the current JDK running Gradle.
17+
def rawJdkPath = System.env.JDK_11 ?: System.getProperty("java.home")
18+
def PATH_TO_TEST_JAVA_RUNTIME = rawJdkPath
19+
if (rawJdkPath != null) {
20+
def f = new File(rawJdkPath)
21+
if (f.isFile()) {
22+
PATH_TO_TEST_JAVA_RUNTIME = f.parentFile.parentFile.absolutePath
23+
}
24+
}
25+
def isWindows = System.getProperty("os.name").toLowerCase().contains("win")
26+
def exeSuffix = isWindows ? ".exe" : ""
27+
28+
dependencies {
29+
api project(':client')
30+
31+
// Jackson annotations and databind — required for the .NET-compatible entity-state
32+
// wire shape (explicit PascalCase names, numeric status ordinals, TimeSpan/DateTimeOffset
33+
// converters). The core :client module declares Jackson as 'implementation', so it is not
34+
// exposed transitively; declare it explicitly here.
35+
implementation "com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}"
36+
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
37+
38+
// JSR-305 nullability annotations (javax.annotation.Nullable), matching the core :client module's usage.
39+
implementation "com.google.code.findbugs:jsr305:3.0.2"
40+
41+
testImplementation 'org.mockito:mockito-core:5.21.0'
42+
testImplementation 'org.mockito:mockito-junit-jupiter:5.21.0'
43+
// Integration tests target a Durable Task Scheduler emulator through the azuremanaged worker/client.
44+
testImplementation project(':azuremanaged')
45+
}
46+
47+
compileJava {
48+
sourceCompatibility = JavaVersion.VERSION_1_8
49+
targetCompatibility = JavaVersion.VERSION_1_8
50+
}
51+
compileTestJava {
52+
sourceCompatibility = JavaVersion.VERSION_11
53+
targetCompatibility = JavaVersion.VERSION_11
54+
options.fork = true
55+
options.forkOptions.executable = "${PATH_TO_TEST_JAVA_RUNTIME}/bin/javac${exeSuffix}"
56+
}
57+
58+
tasks.withType(Test) {
59+
executable = new File("${PATH_TO_TEST_JAVA_RUNTIME}", "bin/java${exeSuffix}")
60+
}
61+
62+
test {
63+
useJUnitPlatform {
64+
// Skip tests tagged as "integration" since those require an external DTS emulator.
65+
excludeTags "integration"
66+
}
67+
}
68+
69+
// Integration tests require a DTS emulator (default localhost:4001).
70+
task integrationTest(type: Test) {
71+
useJUnitPlatform {
72+
includeTags 'integration'
73+
}
74+
dependsOn build
75+
shouldRunAfter test
76+
testLogging.showStandardStreams = true
77+
ignoreFailures = false
78+
}
79+
80+
spotbugs {
81+
toolVersion = '4.9.8'
82+
effort = com.github.spotbugs.snom.Effort.valueOf('MAX')
83+
reportLevel = com.github.spotbugs.snom.Confidence.valueOf('HIGH')
84+
ignoreFailures = true
85+
}
86+
87+
spotbugsMain {
88+
reports {
89+
html {
90+
required = true
91+
stylesheet = 'fancy-hist.xsl'
92+
}
93+
xml {
94+
required = true
95+
}
96+
}
97+
}
98+
99+
spotbugsTest {
100+
reports {
101+
html {
102+
required = true
103+
stylesheet = 'fancy-hist.xsl'
104+
}
105+
xml {
106+
required = true
107+
}
108+
}
109+
}
110+
111+
publishing {
112+
repositories {
113+
maven {
114+
url "file://$project.rootDir/repo"
115+
}
116+
}
117+
publications {
118+
mavenJava(MavenPublication) {
119+
from components.java
120+
artifactId = archivesBaseName
121+
pom {
122+
name = 'Durable Task Scheduled Tasks for Java'
123+
description = 'This package provides recurring scheduled tasks for the Durable Task Java SDK.'
124+
url = "https://github.com/microsoft/durabletask-java/tree/main/scheduledtasks"
125+
licenses {
126+
license {
127+
name = "MIT License"
128+
url = "https://opensource.org/licenses/MIT"
129+
distribution = "repo"
130+
}
131+
}
132+
developers {
133+
developer {
134+
id = "Microsoft"
135+
name = "Microsoft Corporation"
136+
}
137+
}
138+
scm {
139+
connection = "scm:git:https://github.com/microsoft/durabletask-java"
140+
developerConnection = "scm:git:git@github.com:microsoft/durabletask-java"
141+
url = "https://github.com/microsoft/durabletask-java/tree/main/scheduledtasks"
142+
}
143+
withXml {
144+
project.configurations.compileOnly.allDependencies.each { dependency ->
145+
asNode().dependencies[0].appendNode("dependency").with {
146+
it.appendNode("groupId", dependency.group)
147+
it.appendNode("artifactId", dependency.name)
148+
it.appendNode("version", dependency.version)
149+
it.appendNode("scope", "provided")
150+
}
151+
}
152+
}
153+
}
154+
}
155+
}
156+
}
157+
158+
signing {
159+
required = !project.hasProperty("skipSigning")
160+
sign publishing.publications.mavenJava
161+
}
162+
163+
java {
164+
withSourcesJar()
165+
withJavadocJar()
166+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package com.microsoft.durabletask.scheduledtasks;
4+
5+
import java.time.OffsetDateTime;
6+
import java.time.format.DateTimeFormatter;
7+
import java.time.format.DateTimeFormatterBuilder;
8+
import java.time.temporal.ChronoField;
9+
import java.util.Locale;
10+
11+
/**
12+
* Converts between {@link OffsetDateTime} and the .NET {@code DateTimeOffset} round-trip ("o") string format
13+
* {@code yyyy-MM-ddTHH:mm:ss.fffffff+00:00} (seven fractional digits, explicit numeric offset).
14+
* <p>
15+
* This exact format is required in two places for .NET parity: the persisted schedule timestamps, and the default
16+
* target-orchestration instance ID {@code {scheduleId}-{scheduledRunTime:o}}. {@code OffsetDateTime#toString()} is
17+
* not equivalent because it uses variable fractional precision and a {@code Z} designator, which would produce a
18+
* different identifier for an equivalent instant.
19+
*/
20+
final class DotNetDateTimeOffset {
21+
22+
// Seven fractional digits (100-ns ticks), always present, with an explicit "+HH:MM" offset.
23+
private static final DateTimeFormatter ROUND_TRIP = new DateTimeFormatterBuilder()
24+
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
25+
.appendFraction(ChronoField.NANO_OF_SECOND, 7, 7, true)
26+
.appendOffset("+HH:MM", "+00:00")
27+
.toFormatter(Locale.ROOT);
28+
29+
private DotNetDateTimeOffset() {
30+
}
31+
32+
/**
33+
* Formats an {@link OffsetDateTime} as a .NET {@code DateTimeOffset} round-trip string.
34+
*
35+
* @param value the value to format
36+
* @return the formatted string
37+
*/
38+
static String format(OffsetDateTime value) {
39+
return value.format(ROUND_TRIP);
40+
}
41+
42+
/**
43+
* Parses a .NET {@code DateTimeOffset} round-trip string into an {@link OffsetDateTime}. Also accepts standard
44+
* ISO-8601 offset date-times (including a trailing {@code Z}).
45+
*
46+
* @param value the string to parse
47+
* @return the parsed value
48+
*/
49+
static OffsetDateTime parse(String value) {
50+
return OffsetDateTime.parse(value.trim());
51+
}
52+
}

0 commit comments

Comments
 (0)