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
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ try (OutputStream out = new FileOutputStream("simple.json")) {
```
For additional writing helper methods, check [WorkflowWriter](api/src/main/java/org/openworkflow/sdk/api/WorkflowWriter.java) class.

## Jackson dependency
#### Jackson dependency

The `openworkflow-api` module depends heavily on Jackson for loading file content into an in-memory POJO. For users who use a JSON handling library other than Jackson, the `openworkflow-types` module contains Java POJOs generated from the OpenWorkflow schema without any Jackson annotations. Using this module as a dependency, they can build a reader/writer module equivalent to `openworkflow-api` with their preferred JSON library.

Expand All @@ -148,4 +148,14 @@ The reference implementation provides a ready-to-use runtime that supports the O


## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fopen-workflow-specification%2Fsdk-java.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fopen-workflow-specification%2Fsdk-java?ref=badge_large)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fopen-workflow-specification%2Fsdk-java.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fopen-workflow-specification%2Fsdk-java?ref=badge_large)

## Third-Party Code Attributions

This project contains source code copied and modified from the following third-party project:
Comment thread
fjtirado marked this conversation as resolved.
Resolved

* **Name:** std-uritemplate
* **Source:** [std-uritemplate GitHub Repository](https://github.com/std-uritemplate/std-uritemplate)
* **File Copied:** `java/src/main/java/io/github/stduritemplate/StdUriTemplate.java`
* **License:** Apache License 2.0
* **Copyright:** Copyright 2024-2026 std-uritemplate contributors
12 changes: 0 additions & 12 deletions examples/simpleGet/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,6 @@
<groupId>org.openworkflow.sdk</groupId>
<artifactId>openworkflow-impl-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.openworkflow.sdk</groupId>
<artifactId>openworkflow-impl-http</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson3</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
Expand Down
51 changes: 29 additions & 22 deletions impl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,36 +81,43 @@ This SDK is modular by design—pull in only what you need:
* **openworkflow-impl-core**
Workflow engine & core interfaces. Depends on generated types and CloudEvents SDK.


* **openworkflow-impl-jackson**
👉 **Most users add this one.**

Adds Jackson integration, JQ expressions, JSON Schema validation, and CloudEvents (de)serialization.

Internally, this module is an aggregation of smaller modules to allow part replacement if needed:
* **openworkflow-impl-jq** JQ expression implementation
* **openworkflow-impl-model** Jackson model implementation
* **openworkflow-impl-validation** Schema validation implementation
* **openworkflow-impl-lifecycle-events** Enable publication of lifecycle events as Json cloud events.
* **openworkflow-impl-function** Function support.
* **openworkflow-impl-template-resolver** JaxRS URI template implementation.
* **openworkflow-impl-json** Json common utilities shared by all modules in this list
* **openworkflow-impl-cron** Cron parsing
* **openworkflow-impl-http** HTTP `Call` task handler, based on Jax-RS client
* **openworkflow-impl-jackson-jwt** Optimized entity converter for Jackson model and OAuth2/OIDC helpers for HTTP calls.
* **openworkflow-impl-openapi** OpenAPI support using Jackson
* **openworkflow-impl-openapi-base** Base OpenAPI support intended to be extended for a specific JSON library
* **openworkflow-impl-grpc** gRPC support
* **openworkflow-impl-script-python** Python support
* **openworkflow-impl-script-js** Javascript support
* **openworkflow-impl-container** Docker container support
👉 **Most users add this one.** Contains implementation of core features using Jackson library, like JQ expressions, JSON Schema validation, and CloudEvents (de)serialization. Internally, it is an aggregation of smaller modules:


* **openworkflow-impl-jq** JQ expression implementation
* **openworkflow-impl-model** Jackson model implementation
* **openworkflow-impl-validation** Schema validation implementation
* **openworkflow-impl-lifecycle-events** Enable publication of lifecycle events as Json cloud events.
* **openworkflow-impl-function** Jackson Function support.
* **openworkflow-impl-json** Jackson common utilities shared by all modules in this list
* **openworkflow-impl-cron** Cron parsing for workflow scheduled execution

Additionally, The OpenWokflow specification defines a set of [call task](https://github.com/open-workflow-specification/specification/blob/main/dsl-reference.md#call), which requires additional dependencies, with the exception of HTTP (see section below). You might add those ones needed depending on the feature you are using in your definition:

* **openworkflow-impl-openapi** OpenAPI support using Jackson
* **openworkflow-impl-openapi-base** Base OpenAPI support intended to be extended for a specific JSON library
* **openworkflow-impl-grpc** gRPC support
* **openworkflow-impl-script-python** Python support
* **openworkflow-impl-script-js** Javascript support
* **openworkflow-impl-container** Docker container support

There are also companion modules/docs for:

* **Fluent DSL** (programmatic builder)
* **Mermaid** (diagramming workflows)

Links below.

### Http dependencies

By default, the SDK reference implementation use `java.net.HttpRequest` to support Http calls. Therefore, no dependency needs to be added for using Http Call task or downloading of http resources, like OpenAPI schemas.

However, for further configuration of Http functionality, users might include the Jakarta based modules, which will allow customizing `jakarta.ws.rs.client.Client` in Jakarta EE environments.

* **openworkflow-impl-http** HTTP `Call` task handler, based on JAX-RS client
* **openworkflow-impl-jackson-jwt** Optimized entity converter for Jackson model and OAuth2/OIDC helpers for Jakarta HTTP calls.

---

## Installation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.stream.Collectors;
import org.openworkflow.sdk.api.types.SchemaInline;
import org.openworkflow.sdk.api.types.Workflow;
import org.openworkflow.sdk.impl.additional.NamedSuppliedAdditionalObject;
import org.openworkflow.sdk.impl.additional.NamedWorkflowAdditionalObject;
import org.openworkflow.sdk.impl.additional.WorkflowAdditionalObject;
import org.openworkflow.sdk.impl.auth.AuthProviderFactory;
Expand All @@ -62,6 +63,10 @@
import org.openworkflow.sdk.impl.executors.TaskExecutorFactory;
import org.openworkflow.sdk.impl.expressions.ExpressionFactory;
import org.openworkflow.sdk.impl.expressions.RuntimeDescriptor;
import org.openworkflow.sdk.impl.http.DefaultHttpRequestBuilderFactory;
import org.openworkflow.sdk.impl.http.DefaultURITemplateResolver;
import org.openworkflow.sdk.impl.http.URITemplateResolver;
import org.openworkflow.sdk.impl.http.WorkflowHttpRequestFactory;
import org.openworkflow.sdk.impl.lifecycle.LifeCycleEventType;
import org.openworkflow.sdk.impl.lifecycle.WorkflowExecutionCompletableListener;
import org.openworkflow.sdk.impl.lifecycle.WorkflowExecutionListener;
Expand All @@ -71,7 +76,6 @@
import org.openworkflow.sdk.impl.resources.DefaultResourceLoaderFactory;
import org.openworkflow.sdk.impl.resources.ExternalResourceHandler;
import org.openworkflow.sdk.impl.resources.ResourceLoaderFactory;
import org.openworkflow.sdk.impl.resources.URITemplateResolver;
import org.openworkflow.sdk.impl.scheduler.AllStrategyCorrelationInfoFactory;
import org.openworkflow.sdk.impl.scheduler.CronResolver;
import org.openworkflow.sdk.impl.scheduler.CronResolverFactory;
Expand Down Expand Up @@ -107,7 +111,7 @@ public class WorkflowApplication implements AutoCloseable {
private final ConfigManager configManager;
private final SecretManager secretManager;
private final SchedulerListener schedulerListener;
private final Optional<URITemplateResolver> templateResolver;
private final URITemplateResolver templateResolver;
private final Optional<FunctionReader> functionReader;
private final URI defaultCatalogURI;
private final WorkflowValueResolver<URI> defaultEventSource;
Expand All @@ -117,6 +121,7 @@ public class WorkflowApplication implements AutoCloseable {
private final WorkflowLifeCycleCloudEventFactory lifeCycleCloudEventFactory;
private final ScheduledExecutorService schedulerExecutorService;
private final Set<String> allowedCommands;
private final WorkflowHttpRequestFactory httpRequestBuilderFactory;
private final Map<Class<?>, ServiceLoader<?>> servicesLoaded = new ConcurrentHashMap<>();
private final Set<LifeCycleEventType> lifeCycleEventTypes;

Expand Down Expand Up @@ -156,6 +161,7 @@ private WorkflowApplication(Builder builder) {
this.schedulerExecutorService = builder.schedulerExecutorService;
this.allowedCommands = Collections.unmodifiableSet(builder.allowedCommands);
this.lifeCycleEventTypes = builder.lifeCycleEventTypes;
this.httpRequestBuilderFactory = builder.httpRequestBuilderFactory;
}

public TaskExecutorFactory taskFactory() {
Expand Down Expand Up @@ -274,7 +280,7 @@ public SchemaValidator getValidator(SchemaInline inline) {
private SecretManager secretManager;
private ConfigManager configManager;
private SchedulerListener schedulerListener;
private Optional<URITemplateResolver> templateResolver;
private URITemplateResolver templateResolver;
private Optional<FunctionReader> functionReader;
private URI defaultCatalogURI;
private WorkflowValueResolver<URI> defaultEventSource;
Expand All @@ -285,10 +291,13 @@ public SchemaValidator getValidator(SchemaInline inline) {
private ScheduledExecutorService schedulerExecutorService;
private Set<String> allowedCommands = new HashSet<>();
private Set<LifeCycleEventType> lifeCycleEventTypes;
private WorkflowHttpRequestFactory httpRequestBuilderFactory;

private Builder() {
ServiceLoader.load(NamedWorkflowAdditionalObject.class)
.forEach(a -> additionalObjects.put(a.name(), a));
ServiceLoader.load(NamedSuppliedAdditionalObject.class)
.forEach(a -> this.additionalObjectSuppliers.put(a.name(), a));
}

public Builder withId(String id) {
Expand Down Expand Up @@ -468,6 +477,17 @@ public Builder withLifeCycleEventTypes(Set<LifeCycleEventType> lifeCycleEventTyp
return this;
}

public Builder withHttpRequestBuilderFactory(
WorkflowHttpRequestFactory httpRequestBuilderFactory) {
this.httpRequestBuilderFactory = httpRequestBuilderFactory;
return this;
}

public Builder withURITemplateResolver(URITemplateResolver templateResolver) {
this.templateResolver = templateResolver;
return this;
}

public WorkflowApplication build() {
if (executorFactory == null) {
executorFactory = new DefaultExecutorServiceFactory();
Expand Down Expand Up @@ -546,7 +566,10 @@ public CronResolver parseCron(String cron) {
secretManager =
loadFirst(SecretManager.class).orElseGet(() -> new ConfigSecretManager(configManager));
}
templateResolver = loadFirst(URITemplateResolver.class);
if (templateResolver == null) {
templateResolver =
loadFirst(URITemplateResolver.class).orElseGet(() -> new DefaultURITemplateResolver());
}
functionReader = loadFirst(FunctionReader.class);
if (cloudEventPredicateFactory == null) {
cloudEventPredicateFactory =
Expand Down Expand Up @@ -578,7 +601,14 @@ public CronResolver parseCron(String cron) {
if (lifeCycleEventTypes == null) {
lifeCycleEventTypes = EnumSet.of(LifeCycleEventType.V8);
}
return new WorkflowApplication(this);
if (httpRequestBuilderFactory == null) {
httpRequestBuilderFactory =
loadFirst(WorkflowHttpRequestFactory.class)
.orElseGet(() -> new DefaultHttpRequestBuilderFactory());
}
WorkflowApplication app = new WorkflowApplication(this);
httpRequestBuilderFactory.init(app);
return app;
}

private <T> List<T> loadFromServiceLoader(Class<T> clazz) {
Expand Down Expand Up @@ -617,6 +647,7 @@ public void close() {
}
listenersByPriority.clear();
}
safeClose(httpRequestBuilderFactory);
}

public WorkflowPositionFactory positionFactory() {
Expand Down Expand Up @@ -668,7 +699,7 @@ SchedulerListener schedulerListener() {
return schedulerListener;
}

public Optional<URITemplateResolver> templateResolver() {
public URITemplateResolver templateResolver() {
return templateResolver;
}

Expand Down Expand Up @@ -699,7 +730,8 @@ public <T> Optional<T> additionalObject(String name) {
public <T> Optional<T> additionalObject(
String name, WorkflowContext workflowContext, TaskContext taskContext) {
return Optional.ofNullable(additionalObjects.get(name))
.map(v -> (T) v.apply(workflowContext, taskContext));
.map(v -> (T) v.apply(workflowContext, taskContext))
.or(() -> additionalObject(name));
}

public AuthProviderFactory authProviderFactory() {
Expand All @@ -726,6 +758,10 @@ public Set<LifeCycleEventType> lifeCycleEventTypes() {
return lifeCycleEventTypes;
}

public WorkflowHttpRequestFactory httpRequestFactory() {
return httpRequestBuilderFactory;
}

@SuppressWarnings("unchecked")
public <T extends Comparable<?>> List<T> serviceLoadedClasses(Class<T> clazz) {
ServiceLoader<?> serviceLoader = servicesLoaded.computeIfAbsent(clazz, ServiceLoader::load);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Optional;
import org.openworkflow.sdk.impl.lifecycle.TaskFailedEvent;
import org.openworkflow.sdk.impl.lifecycle.WorkflowFailedEvent;
import org.openworkflow.sdk.types.Errors;
Expand All @@ -39,15 +40,30 @@ public static Builder communication(int status, TaskContext context, Throwable e
return communication(status, context, ex.getMessage());
}

public static Builder communication(int status, Optional<TaskContext> context, Throwable ex) {
return communication(status, context, ex.getMessage());
}

public static Builder communication(int status, TaskContext context, String title) {
return new Builder(Errors.COMMUNICATION.toString(), status)
.instance(context.position().jsonPointer())
.title(title);
return communication(status, context).title(title);
}

public static Builder communication(int status, Optional<TaskContext> context, String title) {
return communication(status, context).title(title);
}

public static Builder communication(int status, TaskContext context) {
return new Builder(Errors.COMMUNICATION.toString(), status)
.instance(context.position().jsonPointer());
Builder builder = new Builder(Errors.COMMUNICATION.toString(), status);
if (context != null) {
builder.instance(context.position().jsonPointer());
}
return builder;
}

public static Builder communication(int status, Optional<TaskContext> context) {
Comment thread
fjtirado marked this conversation as resolved.
Resolved
Builder result = new Builder(Errors.COMMUNICATION.toString(), status);
context.ifPresent(ctx -> result.instance(ctx.position().jsonPointer()));
return result;
}

public static Builder communication(TaskContext context, String title) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public interface WorkflowModel {

Optional<Map<String, Object>> asMap();

default Optional<byte[]> asJson() {
return Optional.empty();
}

Object asJavaObject();

Class<?> objectClass();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.cloudevents.CloudEventData;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.Optional;

public interface WorkflowModelFactory extends ServicePriority {

Expand Down Expand Up @@ -66,4 +67,8 @@ default WorkflowModel fromAny(Object obj) {
default -> fromOther(obj);
};
}

default Optional<WorkflowModel> fromJson(byte[] bytes) {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,14 @@ public static Optional<WorkflowValueResolver<Duration>> getTaskTimeout(
return Optional.of(WorkflowUtils.fromTimeoutAfter(appl, timeoutDef.getAfter()));
}

public static final String secretProp(WorkflowContext context, String secretName, String prop) {
return (String) secret(context, secretName).get(prop);
public static final String secretProp(
WorkflowApplication application, String secretName, String prop) {
return (String) secret(application, secretName).get(prop);
}

public static final Map<String, Object> secret(WorkflowContext context, String secretName) {
return context.definition().application().secretManager().secret(secretName);
public static final Map<String, Object> secret(
WorkflowApplication application, String secretName) {
return application.secretManager().secret(secretName);
}

public static final String checkSecret(
Expand Down Expand Up @@ -317,10 +319,6 @@ public static WorkflowValueResolver<URI> getURISupplier(
return (w, t, n) ->
application
.templateResolver()
.orElseThrow(
() ->
new IllegalStateException(
"Need an uri template resolver to resolve uri template"))
.resolveTemplates(template.getLiteralUriTemplate(), w, t, n);
}
throw new IllegalArgumentException("Invalid uritemplate definition " + template);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@

public interface WorkflowValueResolver<T> {
T apply(WorkflowContext workflow, TaskContext task, WorkflowModel model);

default T apply(WorkflowContext workflow, TaskContext task) {
return apply(workflow, task, task != null ? task.input() : null);
}
}
Loading
Loading