From 73c028969dbc63903ce4552962f362a3429ac130 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Fri, 24 Oct 2025 19:08:36 +0200 Subject: [PATCH] [Fix #889] Dynamic resource loading Signed-off-by: fjtirado --- .../impl/WorkflowApplication.java | 4 +- .../impl/WorkflowDefinition.java | 4 +- .../impl/WorkflowUtils.java | 8 +- ...taticResource.java => CachedResource.java} | 8 +- .../impl/resources/ClasspathResource.java | 2 +- .../impl/resources/DefaultResourceLoader.java | 125 ++++++++++++++---- .../DefaultResourceLoaderFactory.java | 5 +- .../resources/ExternalResourceHandler.java} | 19 ++- .../impl/resources/FileResource.java | 2 +- .../impl/resources/HttpResource.java | 2 +- .../impl/resources/LRUCache.java | 34 +++++ .../impl/resources/ResourceLoader.java | 17 ++- .../impl/resources/ResourceLoaderFactory.java | 3 +- .../impl/resources/ResourceLoaderUtils.java | 33 +++++ ...Resource.java => URITemplateResolver.java} | 8 +- .../impl/schema/SchemaValidatorFactory.java | 4 +- impl/http/pom.xml | 12 +- .../impl/executors/http/HttpExecutor.java | 40 +----- impl/jackson/pom.xml | 4 + .../executors/openapi/OpenAPIProcessor.java | 15 +-- .../openapi/OperationDefinitionSupplier.java | 27 ++-- impl/pom.xml | 8 +- impl/template/pom.xml | 24 ++++ .../template/JaxrsURITemplateResolver.java | 34 +++++ ...orkflow.impl.resources.URITemplateResolver | 1 + .../schema/JsonSchemaValidatorFactory.java | 4 +- 26 files changed, 317 insertions(+), 130 deletions(-) rename impl/core/src/main/java/io/serverlessworkflow/impl/resources/{StaticResource.java => CachedResource.java} (87%) rename impl/{http/src/main/java/io/serverlessworkflow/impl/executors/http/TargetSupplier.java => core/src/main/java/io/serverlessworkflow/impl/resources/ExternalResourceHandler.java} (64%) create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/resources/LRUCache.java create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderUtils.java rename impl/core/src/main/java/io/serverlessworkflow/impl/resources/{DynamicResource.java => URITemplateResolver.java} (81%) create mode 100644 impl/template/pom.xml create mode 100644 impl/template/src/main/java/io/serverlessworkflow/impl/template/JaxrsURITemplateResolver.java create mode 100644 impl/template/src/main/resources/META-INF/services/io.serverlessworkflow.impl.resources.URITemplateResolver diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java index 73fb74dca..58276eeb0 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -28,8 +28,8 @@ import io.serverlessworkflow.impl.expressions.RuntimeDescriptor; import io.serverlessworkflow.impl.lifecycle.WorkflowExecutionListener; import io.serverlessworkflow.impl.resources.DefaultResourceLoaderFactory; +import io.serverlessworkflow.impl.resources.ExternalResourceHandler; import io.serverlessworkflow.impl.resources.ResourceLoaderFactory; -import io.serverlessworkflow.impl.resources.StaticResource; import io.serverlessworkflow.impl.scheduler.DefaultWorkflowScheduler; import io.serverlessworkflow.impl.schema.SchemaValidator; import io.serverlessworkflow.impl.schema.SchemaValidatorFactory; @@ -124,7 +124,7 @@ public void validate(WorkflowModel node) {} }; @Override - public SchemaValidator getValidator(StaticResource resource) { + public SchemaValidator getValidator(ExternalResourceHandler resource) { return NoValidation; } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java index 5af2a12b7..0948459f6 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java @@ -79,7 +79,9 @@ static WorkflowDefinition of(WorkflowApplication application, Workflow workflow) static WorkflowDefinition of(WorkflowApplication application, Workflow workflow, Path path) { WorkflowDefinition definition = new WorkflowDefinition( - application, workflow, application.resourceLoaderFactory().getResourceLoader(path)); + application, + workflow, + application.resourceLoaderFactory().getResourceLoader(application, path)); Schedule schedule = workflow.getSchedule(); if (schedule != null) { ListenTo to = schedule.getOn(); diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java index 138256c45..7b6b3403c 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -48,8 +48,12 @@ public static Optional getSchemaValidator( return Optional.of(validatorFactory.getValidator(schema.getSchemaInline())); } else if (schema.getSchemaExternal() != null) { return Optional.of( - validatorFactory.getValidator( - resourceLoader.loadStatic(schema.getSchemaExternal().getResource()))); + resourceLoader.load( + schema.getSchemaExternal().getResource(), + validatorFactory::getValidator, + null, + null, + null)); } } return Optional.empty(); diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/StaticResource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/CachedResource.java similarity index 87% rename from impl/core/src/main/java/io/serverlessworkflow/impl/resources/StaticResource.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/resources/CachedResource.java index 32443dfc7..10e891885 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/StaticResource.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/CachedResource.java @@ -15,10 +15,6 @@ */ package io.serverlessworkflow.impl.resources; -import java.io.InputStream; +import java.time.Instant; -public interface StaticResource { - InputStream open(); - - String name(); -} +public record CachedResource(Instant lastReload, T content) {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ClasspathResource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ClasspathResource.java index dad392371..c3ac293ee 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ClasspathResource.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ClasspathResource.java @@ -17,7 +17,7 @@ import java.io.InputStream; -public class ClasspathResource implements StaticResource { +public class ClasspathResource implements ExternalResourceHandler { private String path; diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java index a23a92244..4dd975a80 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java @@ -19,48 +19,67 @@ import io.serverlessworkflow.api.types.EndpointUri; import io.serverlessworkflow.api.types.ExternalResource; import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.expressions.ExpressionFactory; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowValueResolver; +import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import java.net.MalformedURLException; import java.net.URI; import java.nio.file.Path; +import java.time.Instant; +import java.util.Map; import java.util.Optional; +import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; public class DefaultResourceLoader implements ResourceLoader { private final Optional workflowPath; + private final WorkflowApplication application; - protected DefaultResourceLoader(Path workflowPath) { - this.workflowPath = Optional.ofNullable(workflowPath); - } + private final AtomicReference templateResolver = + new AtomicReference(); - @Override - public StaticResource loadStatic(ExternalResource resource) { - return processEndpoint(resource.getEndpoint()); - } + private Map resourceCache = new LRUCache<>(100); + private Lock cacheLock = new ReentrantLock(); - @Override - public DynamicResource loadDynamic( - WorkflowContext workflow, ExternalResource resource, ExpressionFactory factory) { - throw new UnsupportedOperationException("Dynamic loading of resources is not suppported"); + protected DefaultResourceLoader(WorkflowApplication application, Path workflowPath) { + this.application = application; + this.workflowPath = Optional.ofNullable(workflowPath); } - private StaticResource buildFromString(String uri) { - return fileResource(uri); + private URITemplateResolver templateResolver() { + URITemplateResolver result = templateResolver.get(); + if (result == null) { + result = + ServiceLoader.load(URITemplateResolver.class) + .findFirst() + .orElseThrow( + () -> + new IllegalStateException( + "Need an uri template resolver to resolve uri template")); + templateResolver.set(result); + } + return result; } - private StaticResource fileResource(String pathStr) { + private ExternalResourceHandler fileResource(String pathStr) { Path path = Path.of(pathStr); if (path.isAbsolute()) { return new FileResource(path); } else { return workflowPath - .map(p -> new FileResource(p.resolve(path))) + .map(p -> new FileResource(p.resolve(path))) .orElseGet(() -> new ClasspathResource(pathStr)); } } - private StaticResource buildFromURI(URI uri) { + private ExternalResourceHandler buildFromURI(URI uri) { String scheme = uri.getScheme(); if (scheme == null || scheme.equalsIgnoreCase("file")) { return fileResource(uri.getPath()); @@ -75,31 +94,79 @@ private StaticResource buildFromURI(URI uri) { } } - private StaticResource processEndpoint(Endpoint endpoint) { + @Override + public T load( + ExternalResource resource, + Function function, + WorkflowContext workflowContext, + TaskContext taskContext, + WorkflowModel model) { + ExternalResourceHandler resourceHandler = + buildFromURI( + uriSupplier(resource.getEndpoint()) + .apply( + workflowContext, + taskContext, + model == null ? application.modelFactory().fromNull() : model)); + try { + CachedResource cachedResource; + cacheLock.lock(); + cachedResource = resourceCache.get(resourceHandler); + cacheLock.unlock(); + if (cachedResource == null || resourceHandler.shouldReload(cachedResource.lastReload())) { + cachedResource = new CachedResource(Instant.now(), function.apply(resourceHandler)); + cacheLock.lock(); + resourceCache.put(resourceHandler, cachedResource); + } + return cachedResource.content(); + } finally { + cacheLock.unlock(); + } + } + + @Override + public WorkflowValueResolver uriSupplier(Endpoint endpoint) { if (endpoint.getEndpointConfiguration() != null) { EndpointUri uri = endpoint.getEndpointConfiguration().getUri(); if (uri.getLiteralEndpointURI() != null) { - return getURI(uri.getLiteralEndpointURI()); + return getURISupplier(uri.getLiteralEndpointURI()); } else if (uri.getExpressionEndpointURI() != null) { - throw new UnsupportedOperationException( - "Expression not supported for loading a static resource"); + return new ExpressionURISupplier( + application + .expressionFactory() + .resolveString(ExpressionDescriptor.from(uri.getExpressionEndpointURI()))); } } else if (endpoint.getRuntimeExpression() != null) { - throw new UnsupportedOperationException( - "Expression not supported for loading a static resource"); + return new ExpressionURISupplier( + application + .expressionFactory() + .resolveString(ExpressionDescriptor.from(endpoint.getRuntimeExpression()))); } else if (endpoint.getUriTemplate() != null) { - return getURI(endpoint.getUriTemplate()); + return getURISupplier(endpoint.getUriTemplate()); } throw new IllegalArgumentException("Invalid endpoint definition " + endpoint); } - private StaticResource getURI(UriTemplate template) { + private WorkflowValueResolver getURISupplier(UriTemplate template) { if (template.getLiteralUri() != null) { - return buildFromURI(template.getLiteralUri()); + return (w, t, n) -> template.getLiteralUri(); } else if (template.getLiteralUriTemplate() != null) { - return buildFromString(template.getLiteralUriTemplate()); - } else { - throw new IllegalStateException("Invalid endpoint definition" + template); + return (w, t, n) -> + templateResolver().resolveTemplates(template.getLiteralUriTemplate(), w, t, n); + } + throw new IllegalArgumentException("Invalid uritemplate definition " + template); + } + + private class ExpressionURISupplier implements WorkflowValueResolver { + private WorkflowValueResolver expr; + + public ExpressionURISupplier(WorkflowValueResolver expr) { + this.expr = expr; + } + + @Override + public URI apply(WorkflowContext workflow, TaskContext task, WorkflowModel node) { + return URI.create(expr.apply(workflow, task, node)); } } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoaderFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoaderFactory.java index 60717e1d3..5bed3c40a 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoaderFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoaderFactory.java @@ -15,6 +15,7 @@ */ package io.serverlessworkflow.impl.resources; +import io.serverlessworkflow.impl.WorkflowApplication; import java.nio.file.Path; public class DefaultResourceLoaderFactory implements ResourceLoaderFactory { @@ -28,7 +29,7 @@ public static final ResourceLoaderFactory get() { private DefaultResourceLoaderFactory() {} @Override - public ResourceLoader getResourceLoader(Path path) { - return new DefaultResourceLoader(path); + public ResourceLoader getResourceLoader(WorkflowApplication application, Path path) { + return new DefaultResourceLoader(application, path); } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/TargetSupplier.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ExternalResourceHandler.java similarity index 64% rename from impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/TargetSupplier.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/resources/ExternalResourceHandler.java index ae7d339bc..9c6c565f2 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/TargetSupplier.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ExternalResourceHandler.java @@ -13,13 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.executors.http; +package io.serverlessworkflow.impl.resources; -import io.serverlessworkflow.impl.TaskContext; -import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowModel; -import jakarta.ws.rs.client.WebTarget; +import java.io.InputStream; +import java.time.Instant; -public interface TargetSupplier { - WebTarget apply(WorkflowContext workflow, TaskContext task, WorkflowModel node); +public interface ExternalResourceHandler { + + String name(); + + InputStream open(); + + default boolean shouldReload(Instant lasUpdate) { + return false; + } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/FileResource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/FileResource.java index 6358b6cea..0d3cdb67b 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/FileResource.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/FileResource.java @@ -21,7 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; -class FileResource implements StaticResource { +class FileResource implements ExternalResourceHandler { private Path path; diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/HttpResource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/HttpResource.java index 906e5c838..df3db9b2c 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/HttpResource.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/HttpResource.java @@ -20,7 +20,7 @@ import java.io.UncheckedIOException; import java.net.URL; -public class HttpResource implements StaticResource { +public class HttpResource implements ExternalResourceHandler { private URL url; diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/LRUCache.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/LRUCache.java new file mode 100644 index 000000000..8e1d115fe --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/LRUCache.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.serverlessworkflow.impl.resources; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class LRUCache extends LinkedHashMap { + + private final int capacity; + + public LRUCache(int capacity) { + super(capacity, 0.75f, true); + this.capacity = capacity; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > capacity; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoader.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoader.java index 346856bd8..8dbb1e0bd 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoader.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoader.java @@ -15,14 +15,23 @@ */ package io.serverlessworkflow.impl.resources; +import io.serverlessworkflow.api.types.Endpoint; import io.serverlessworkflow.api.types.ExternalResource; +import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.expressions.ExpressionFactory; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowValueResolver; +import java.net.URI; +import java.util.function.Function; public interface ResourceLoader { - StaticResource loadStatic(ExternalResource resource); + WorkflowValueResolver uriSupplier(Endpoint endpoint); - DynamicResource loadDynamic( - WorkflowContext context, ExternalResource resource, ExpressionFactory factory); + T load( + ExternalResource resource, + Function function, + WorkflowContext workflowContext, + TaskContext taskContext, + WorkflowModel model); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderFactory.java index c064672b0..41b3b2f21 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderFactory.java @@ -15,8 +15,9 @@ */ package io.serverlessworkflow.impl.resources; +import io.serverlessworkflow.impl.WorkflowApplication; import java.nio.file.Path; public interface ResourceLoaderFactory { - ResourceLoader getResourceLoader(Path path); + ResourceLoader getResourceLoader(WorkflowApplication application, Path path); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderUtils.java new file mode 100644 index 000000000..4ec41f204 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderUtils.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.serverlessworkflow.impl.resources; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; + +public class ResourceLoaderUtils { + + private ResourceLoaderUtils() {} + + public static String readString(ExternalResourceHandler handler) { + try (InputStream in = handler.open()) { + return new String(in.readAllBytes()); + } catch (IOException io) { + throw new UncheckedIOException(io); + } + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DynamicResource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/URITemplateResolver.java similarity index 81% rename from impl/core/src/main/java/io/serverlessworkflow/impl/resources/DynamicResource.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/resources/URITemplateResolver.java index cd8b17802..72e4255c8 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DynamicResource.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/URITemplateResolver.java @@ -18,9 +18,9 @@ import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowModel; -import java.io.InputStream; -import java.util.Optional; +import java.net.URI; -public interface DynamicResource { - InputStream open(WorkflowContext workflow, Optional task, WorkflowModel input); +public interface URITemplateResolver { + URI resolveTemplates( + String uri, WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel model); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidatorFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidatorFactory.java index ff57ef5da..6111cd824 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidatorFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidatorFactory.java @@ -17,10 +17,10 @@ import io.serverlessworkflow.api.types.SchemaInline; import io.serverlessworkflow.impl.ServicePriority; -import io.serverlessworkflow.impl.resources.StaticResource; +import io.serverlessworkflow.impl.resources.ExternalResourceHandler; public interface SchemaValidatorFactory extends ServicePriority { SchemaValidator getValidator(SchemaInline inline); - SchemaValidator getValidator(StaticResource resource); + SchemaValidator getValidator(ExternalResourceHandler resource); } diff --git a/impl/http/pom.xml b/impl/http/pom.xml index ee63b7f03..e6c93080e 100644 --- a/impl/http/pom.xml +++ b/impl/http/pom.xml @@ -8,13 +8,9 @@ serverlessworkflow-impl-http Serverless Workflow :: Impl :: HTTP - - jakarta.ws.rs - jakarta.ws.rs-api - - - io.serverlessworkflow - serverlessworkflow-impl-core - + + io.serverlessworkflow + serverlessworkflow-impl-template-resolver + \ No newline at end of file diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java index 3e8f3c63d..63bcab9df 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java @@ -19,10 +19,8 @@ import io.serverlessworkflow.api.types.CallHTTP; import io.serverlessworkflow.api.types.Endpoint; -import io.serverlessworkflow.api.types.EndpointUri; import io.serverlessworkflow.api.types.HTTPArguments; import io.serverlessworkflow.api.types.TaskBase; -import io.serverlessworkflow.api.types.UriTemplate; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; @@ -32,8 +30,6 @@ import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowValueResolver; import io.serverlessworkflow.impl.executors.CallableTask; -import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; -import io.serverlessworkflow.impl.expressions.ExpressionFactory; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.client.Client; @@ -74,8 +70,7 @@ public void init(CallHTTP task, WorkflowDefinition definition) { definition.workflow(), task.getWith().getEndpoint().getEndpointConfiguration()); - this.targetSupplier = - getTargetSupplier(httpArgs.getEndpoint(), application.expressionFactory()); + this.targetSupplier = getTargetSupplier(definition, httpArgs.getEndpoint()); this.headersMap = httpArgs.getHeaders() != null ? Optional.of( @@ -167,35 +162,14 @@ public boolean accept(Class clazz) { return clazz.equals(CallHTTP.class); } - public static TargetSupplier getTargetSupplier( - Endpoint endpoint, ExpressionFactory expressionFactory) { - if (endpoint.getEndpointConfiguration() != null) { - EndpointUri uri = endpoint.getEndpointConfiguration().getUri(); - if (uri.getLiteralEndpointURI() != null) { - return getURISupplier(uri.getLiteralEndpointURI()); - } else if (uri.getExpressionEndpointURI() != null) { - return new ExpressionURISupplier( - expressionFactory.resolveString( - ExpressionDescriptor.from(uri.getExpressionEndpointURI()))); - } - } else if (endpoint.getRuntimeExpression() != null) { - return new ExpressionURISupplier( - expressionFactory.resolveString( - ExpressionDescriptor.from(endpoint.getRuntimeExpression()))); - } else if (endpoint.getUriTemplate() != null) { - return getURISupplier(endpoint.getUriTemplate()); - } - throw new IllegalArgumentException("Invalid endpoint definition " + endpoint); + private static TargetSupplier getTargetSupplier( + WorkflowDefinition definition, Endpoint endpoint) { + return (w, t, n) -> + client.target(definition.resourceLoader().uriSupplier(endpoint).apply(w, t, n)); } - public static TargetSupplier getURISupplier(UriTemplate template) { - if (template.getLiteralUri() != null) { - return (w, t, n) -> client.target(template.getLiteralUri()); - } else if (template.getLiteralUriTemplate() != null) { - return (w, t, n) -> - client.target(template.getLiteralUriTemplate()).resolveTemplates(n.asMap().orElseThrow()); - } - throw new IllegalArgumentException("Invalid uritemplate definition " + template); + private static interface TargetSupplier { + WebTarget apply(WorkflowContext workflow, TaskContext task, WorkflowModel node); } private static class ExpressionURISupplier implements TargetSupplier { diff --git a/impl/jackson/pom.xml b/impl/jackson/pom.xml index 963be61af..be0c73361 100644 --- a/impl/jackson/pom.xml +++ b/impl/jackson/pom.xml @@ -16,6 +16,10 @@ io.serverlessworkflow serverlessworkflow-impl-jq + + io.serverlessworkflow + serverlessworkflow-impl-template-resolver + io.serverlessworkflow serverlessworkflow-impl-model diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java index 2e05e5fe5..e65ebc2fa 100644 --- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java @@ -20,32 +20,25 @@ import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.parser.OpenAPIV3Parser; import io.swagger.v3.parser.core.models.ParseOptions; -import java.net.URI; -import java.util.List; import java.util.Set; class OpenAPIProcessor { private final String operationId; - private final URI openAPIEndpoint; - OpenAPIProcessor(String operationId, URI openAPIEndpoint) { + OpenAPIProcessor(String operationId) { this.operationId = operationId; - this.openAPIEndpoint = openAPIEndpoint; } - OperationDefinition parse() { + public OperationDefinition parse(String content) { OpenAPIV3Parser parser = new OpenAPIV3Parser(); ParseOptions opts = new ParseOptions(); opts.setResolve(true); opts.setResolveFully(false); - - var result = parser.readLocation(openAPIEndpoint.toString(), List.of(), opts); - var openapi = result.getOpenAPI(); - return getOperation(openapi); + return getOperation(parser.readContents(content).getOpenAPI()); } - OperationDefinition getOperation(OpenAPI openAPI) { + private OperationDefinition getOperation(OpenAPI openAPI) { if (openAPI == null || openAPI.getPaths() == null) { throw new IllegalArgumentException("Invalid OpenAPI document"); } diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinitionSupplier.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinitionSupplier.java index 106c6f96b..5254fdd83 100644 --- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinitionSupplier.java +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinitionSupplier.java @@ -15,31 +15,34 @@ */ package io.serverlessworkflow.impl.executors.openapi; -import static io.serverlessworkflow.impl.executors.http.HttpExecutor.getTargetSupplier; - +import io.serverlessworkflow.api.types.ExternalResource; import io.serverlessworkflow.api.types.OpenAPIArguments; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowModel; -import io.serverlessworkflow.impl.executors.http.TargetSupplier; -import jakarta.ws.rs.client.WebTarget; +import io.serverlessworkflow.impl.resources.ResourceLoaderUtils; class OperationDefinitionSupplier { - private final String operationId; - private final TargetSupplier targetSupplier; + private final OpenAPIProcessor processor; + private final ExternalResource resource; OperationDefinitionSupplier(WorkflowApplication application, OpenAPIArguments with) { - this.operationId = with.getOperationId(); - this.targetSupplier = - getTargetSupplier(with.getDocument().getEndpoint(), application.expressionFactory()); + this.processor = new OpenAPIProcessor(with.getOperationId()); + this.resource = with.getDocument(); } OperationDefinition get( WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) { - WebTarget webTarget = targetSupplier.apply(workflowContext, taskContext, input); - OpenAPIProcessor processor = new OpenAPIProcessor(operationId, webTarget.getUri()); - return processor.parse(); + return workflowContext + .definition() + .resourceLoader() + .load( + resource, + r -> processor.parse(ResourceLoaderUtils.readString(r)), + workflowContext, + taskContext, + input); } } diff --git a/impl/pom.xml b/impl/pom.xml index b3c5e11c9..00d900f91 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -27,6 +27,11 @@ serverlessworkflow-impl-http ${project.version} + + io.serverlessworkflow + serverlessworkflow-impl-template-resolver + ${project.version} + io.serverlessworkflow serverlessworkflow-impl-json @@ -122,8 +127,9 @@ - http core + template + http jackson jwt-impl persistence diff --git a/impl/template/pom.xml b/impl/template/pom.xml new file mode 100644 index 000000000..ab804dc31 --- /dev/null +++ b/impl/template/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-impl + 8.0.0-SNAPSHOT + + + serverlessworkflow-impl-template-resolver + Serverless Workflow :: Impl :: Template :: Jersey + + + + jakarta.ws.rs + jakarta.ws.rs-api + + + io.serverlessworkflow + serverlessworkflow-impl-core + + + + \ No newline at end of file diff --git a/impl/template/src/main/java/io/serverlessworkflow/impl/template/JaxrsURITemplateResolver.java b/impl/template/src/main/java/io/serverlessworkflow/impl/template/JaxrsURITemplateResolver.java new file mode 100644 index 000000000..a205211a5 --- /dev/null +++ b/impl/template/src/main/java/io/serverlessworkflow/impl/template/JaxrsURITemplateResolver.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.serverlessworkflow.impl.template; + +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.resources.URITemplateResolver; +import jakarta.ws.rs.core.UriBuilder; +import java.net.URI; +import java.util.Collections; + +public class JaxrsURITemplateResolver implements URITemplateResolver { + @Override + public URI resolveTemplates( + String uri, WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel model) { + return UriBuilder.fromPath(uri) + .resolveTemplates(model.asMap().orElse(Collections.emptyMap())) + .build(); + } +} diff --git a/impl/template/src/main/resources/META-INF/services/io.serverlessworkflow.impl.resources.URITemplateResolver b/impl/template/src/main/resources/META-INF/services/io.serverlessworkflow.impl.resources.URITemplateResolver new file mode 100644 index 000000000..365f2746e --- /dev/null +++ b/impl/template/src/main/resources/META-INF/services/io.serverlessworkflow.impl.resources.URITemplateResolver @@ -0,0 +1 @@ +io.serverlessworkflow.impl.template.JaxrsURITemplateResolver \ No newline at end of file diff --git a/impl/validation/src/main/java/io/serverlessworkflow/impl/jackson/schema/JsonSchemaValidatorFactory.java b/impl/validation/src/main/java/io/serverlessworkflow/impl/jackson/schema/JsonSchemaValidatorFactory.java index 78e7324db..fa504b80f 100644 --- a/impl/validation/src/main/java/io/serverlessworkflow/impl/jackson/schema/JsonSchemaValidatorFactory.java +++ b/impl/validation/src/main/java/io/serverlessworkflow/impl/jackson/schema/JsonSchemaValidatorFactory.java @@ -19,7 +19,7 @@ import io.serverlessworkflow.api.WorkflowFormat; import io.serverlessworkflow.api.types.SchemaInline; import io.serverlessworkflow.impl.jackson.JsonUtils; -import io.serverlessworkflow.impl.resources.StaticResource; +import io.serverlessworkflow.impl.resources.ExternalResourceHandler; import io.serverlessworkflow.impl.schema.SchemaValidator; import io.serverlessworkflow.impl.schema.SchemaValidatorFactory; import java.io.IOException; @@ -34,7 +34,7 @@ public SchemaValidator getValidator(SchemaInline inline) { } @Override - public SchemaValidator getValidator(StaticResource resource) { + public SchemaValidator getValidator(ExternalResourceHandler resource) { ObjectMapper mapper = WorkflowFormat.fromFileName(resource.name()).mapper(); try (InputStream in = resource.open()) { return new JsonSchemaValidator(mapper.readTree(in));