From 75eb858cf878b1bbaf1f5c9f524d0469f312ebc7 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Fri, 25 Aug 2023 15:57:25 +0200 Subject: [PATCH] [KOGITO-9594] Adding new relative scheme (#3183) * [KOGITO-9594] Refactoring URIContentLoader * [KOGITO-9594] More refactoring * [KOGITO-9594] Supporting nested schema * [KOGITO-5954] Supporting Java serialization for WorkflowValidator * [KOGITO-9594] Sonar is... * [KOGITO-9594] Handling absolute paths * Helbers comments Co-authored-by: Helber Belmiro --------- Co-authored-by: Helber Belmiro --- .../codegen/process/ProcessCodegen.java | 16 +- .../process/ProcessGenerationUtils.java | 3 +- .../workflow/io/CachedContentLoader.java | 0 .../workflow/io/ClassPathContentLoader.java | 22 ++- .../workflow/io/FileContentLoader.java | 13 +- .../workflow/io/HttpContentLoader.java | 7 +- .../serverless/workflow/io/ResourceCache.java | 0 .../workflow/io/ResourceCacheFactory.java | 0 .../workflow/io/URIContentLoader.java | 0 .../workflow/io/URIContentLoaderFactory.java | 163 ++++++++++++++++++ .../workflow/io/URIContentLoaderType.java | 92 ++++++++++ .../AbstractWorkflowOperationIdFactory.java | 13 +- .../FileNameWorkflowOperationIdFactory.java | 4 +- .../workflow/parser/JsonSchemaReader.java | 111 ++++++++++++ .../parser/ServerlessWorkflowParser.java | 46 +++-- .../JsonSchemaValidatorSupplier.java | 9 +- .../utils/ServerlessWorkflowUtils.java | 78 ++++----- ...AbstractServerlessWorkflowParsingTest.java | 4 +- .../ServerlessWorkflowParsingTest.java | 5 +- .../io/ClassPathContentLoaderTest.java} | 46 +++-- .../workflow/io/ResourceCacheFactoryTest.java | 0 .../workflow/io/URIContentLoaderTest.java | 67 +++++++ .../parser}/JsonSchemaValidatorTest.java | 29 +++- .../resources/exec/expression.schema.sw.json | 2 +- .../src/test/resources/pepe.txt | 0 .../src/test/resources/schema/complex.json | 12 ++ .../src/test/resources/schema/expression.json | 29 ++-- .../OpenAPIWorkflowApplicationTest.java | 13 +- .../SpecWorkflowOperationIdFactory.java | 2 +- .../workflow/utils/OpenAPIFactory.java | 8 +- .../operationid/SpecTitleOperationIdTest.java | 4 +- .../rest/DescriptorRestOperationHandler.java | 3 +- .../exec/callback-state-timeouts.sw.json | 2 +- .../exec/callback-state-timeouts.sw.yml | 2 +- .../resources/exec/callback-state.sw.json | 2 +- .../test/resources/exec/callback-state.sw.yml | 2 +- .../workflow/actions/JsonSchemaValidator.java | 53 +++--- .../workflow/io/FallbackContentLoader.java | 50 ------ .../workflow/io/URIContentLoaderFactory.java | 124 ------------- .../workflow/io/URIContentLoaderTest.java | 51 ------ .../src/test/resources/expression.json | 20 --- .../workflow/WorkflowCodeGenUtils.java | 8 +- .../ServerlessWorkflowAssetsProcessor.java | 5 +- .../openapi/ServerlessWorkflowOASFilter.java | 6 +- .../workflow/io/QuarkusResourceCache.java | 0 .../parser/schema/JsonSchemaImpl.java | 44 ++--- .../schema/OpenApiModelSchemaGenerator.java | 36 ++-- .../workflow/parser/schema/RefSchemas.java | 69 -------- .../src/test/resources/asyncPublisher.sw.json | 2 +- .../resources/openAPIEnumParameter.sw.json | 2 +- .../src/test/resources/rpcgreet.sw.json | 2 +- 51 files changed, 737 insertions(+), 544 deletions(-) rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime => kogito-serverless-workflow-builder}/src/main/java/org/kie/kogito/serverless/workflow/io/CachedContentLoader.java (100%) rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime => kogito-serverless-workflow-builder}/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java (79%) rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime => kogito-serverless-workflow-builder}/src/main/java/org/kie/kogito/serverless/workflow/io/FileContentLoader.java (83%) rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime => kogito-serverless-workflow-builder}/src/main/java/org/kie/kogito/serverless/workflow/io/HttpContentLoader.java (96%) rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime => kogito-serverless-workflow-builder}/src/main/java/org/kie/kogito/serverless/workflow/io/ResourceCache.java (100%) rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime => kogito-serverless-workflow-builder}/src/main/java/org/kie/kogito/serverless/workflow/io/ResourceCacheFactory.java (100%) rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime => kogito-serverless-workflow-builder}/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoader.java (100%) create mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java create mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderType.java create mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/JsonSchemaReader.java rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderType.java => kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoaderTest.java} (51%) rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime => kogito-serverless-workflow-builder}/src/test/java/org/kie/kogito/serverless/workflow/io/ResourceCacheFactoryTest.java (100%) create mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/actions => kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/parser}/JsonSchemaValidatorTest.java (64%) rename kogito-serverless-workflow/{kogito-serverless-workflow-runtime => kogito-serverless-workflow-builder}/src/test/resources/pepe.txt (100%) create mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/schema/complex.json delete mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/FallbackContentLoader.java delete mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java delete mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java delete mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/resources/expression.json rename quarkus/extensions/kogito-quarkus-serverless-workflow-extension/{kogito-quarkus-serverless-workflow => kogito-quarkus-serverless-workflow-deployment}/src/main/java/org/kie/kogito/serverless/workflow/io/QuarkusResourceCache.java (100%) delete mode 100644 quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/RefSchemas.java diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java index 08961779f25..ff21459c4e8 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java @@ -31,6 +31,7 @@ import org.drools.codegen.common.GeneratedFile; import org.drools.codegen.common.GeneratedFileType; +import org.drools.io.InternalResource; import org.jbpm.bpmn2.xml.BPMNDISemanticModule; import org.jbpm.bpmn2.xml.BPMNExtensionsSemanticModule; import org.jbpm.bpmn2.xml.BPMNSemanticModule; @@ -120,7 +121,7 @@ public static ProcessCodegen ofCollectedResources(KogitoBuildContext context, Co } return p.stream().map(KogitoWorkflowProcess.class::cast).map(GeneratedInfo::new).map(info -> addResource(info, resource)); } else if (SupportedExtensions.getSWFExtensions().stream().anyMatch(resource.getSourcePath()::endsWith)) { - GeneratedInfo generatedInfo = parseWorkflowFile(resource, WorkflowFormat.fromFileName(resource.getSourcePath()), context); + GeneratedInfo generatedInfo = parseWorkflowFile(resource, context); notifySourceFileCodegenBindListeners(context, resource, Collections.singletonList(generatedInfo.info())); return Stream.of(addResource(generatedInfo, resource)); } @@ -212,9 +213,16 @@ private static ProcessCodegen ofProcesses(KogitoBuildContext context, List parseWorkflowFile(Resource r, WorkflowFormat format, KogitoBuildContext context) { - try (Reader reader = r.getReader()) { - return ServerlessWorkflowParser.of(reader, format, context).getProcessInfo(); + protected static GeneratedInfo parseWorkflowFile(Resource r, KogitoBuildContext context) { + InternalResource resource = (InternalResource) r; + try (Reader reader = resource.getReader()) { + ServerlessWorkflowParser parser = ServerlessWorkflowParser.of(reader, WorkflowFormat.fromFileName(resource.getSourcePath()), context); + if (resource.hasURL()) { + parser.withBaseURI(resource.getURL()); + } else { + parser.withBaseURI("classpath:" + resource.getSourcePath()); + } + return parser.getProcessInfo(); } catch (Exception e) { throw new ProcessParsingException(e); } diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationUtils.java b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationUtils.java index 666df3d1a9e..2d5b41d9463 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationUtils.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationUtils.java @@ -28,7 +28,6 @@ import org.kie.kogito.codegen.api.context.impl.JavaKogitoBuildContext; import org.kie.kogito.internal.SupportedExtensions; import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess; -import org.kie.kogito.serverless.workflow.utils.WorkflowFormat; /** * Utilities for unit Process generation tests @@ -62,7 +61,7 @@ private static List parseProcesses(Collection processFiles) { if (SupportedExtensions.getBPMNExtensions().stream().anyMatch(processSourceFile.getPath()::endsWith)) { processes.addAll(ProcessCodegen.parseProcessFile(r)); } else if (SupportedExtensions.getSWFExtensions().stream().anyMatch(processSourceFile.getPath()::endsWith)) { - processes.add(ProcessCodegen.parseWorkflowFile(r, WorkflowFormat.fromFileName(processSourceFile.getPath()), JavaKogitoBuildContext.builder().build()).info()); + processes.add(ProcessCodegen.parseWorkflowFile(r, JavaKogitoBuildContext.builder().build()).info()); } if (processes.isEmpty()) { throw new IllegalArgumentException("Unable to process file with unsupported extension: " + processSourceFile); diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/CachedContentLoader.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/CachedContentLoader.java similarity index 100% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/CachedContentLoader.java rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/CachedContentLoader.java diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java similarity index 79% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java index 2e23cdb411f..81f0b4f6c4c 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoader.java @@ -20,7 +20,6 @@ import java.io.UncheckedIOException; import java.net.URI; import java.net.URL; -import java.util.Objects; import java.util.Optional; public class ClassPathContentLoader extends CachedContentLoader { @@ -28,25 +27,32 @@ public class ClassPathContentLoader extends CachedContentLoader { private final Optional resource; private final String path; - public ClassPathContentLoader(URI uri, Optional cl) { + ClassPathContentLoader(URI uri, Optional cl) { super(uri); this.path = getPath(uri); this.resource = Optional.ofNullable(cl.orElse(Thread.currentThread().getContextClassLoader()).getResource(path)); } - private static String getPath(URI uri) { - String path = uri.getPath(); - Objects.requireNonNull(path, "classpath cannot be null"); - while (path.startsWith("/")) { - path = path.substring(1); + static String getPath(URI uri) { + final String classPathPrefix = "classpath:"; + String str = uri.toString(); + if (str.toLowerCase().startsWith(classPathPrefix)) { + str = str.substring(classPathPrefix.length()); + while (str.startsWith("/")) { + str = str.substring(1); + } } - return path; + return str; } public Optional getResource() { return resource; } + public String getPath() { + return path; + } + @Override protected byte[] loadURI(URI uri) { return resource.map(this::loadBytes).orElseThrow(() -> new IllegalArgumentException("cannot find classpath resource " + path)); diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/FileContentLoader.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/FileContentLoader.java similarity index 83% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/FileContentLoader.java rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/FileContentLoader.java index cbcd6613a95..11b7caa6963 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/FileContentLoader.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/FileContentLoader.java @@ -20,15 +20,14 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Optional; -public class FileContentLoader extends FallbackContentLoader { +public class FileContentLoader extends CachedContentLoader { private final Path path; - public FileContentLoader(URI uri, Optional fallbackLoader) { - super(uri, fallbackLoader); - this.path = Path.of(uri); + FileContentLoader(URI uri) { + super(uri); + this.path = Path.of(getPath(uri)); } public Path getPath() { @@ -48,4 +47,8 @@ protected byte[] loadURI(URI uri) { throw new UncheckedIOException(io); } } + + static String getPath(URI uri) { + return uri.getPath(); + } } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/HttpContentLoader.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/HttpContentLoader.java similarity index 96% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/HttpContentLoader.java rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/HttpContentLoader.java index 9a4d413eb5d..efcfc56990a 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/HttpContentLoader.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/HttpContentLoader.java @@ -41,15 +41,15 @@ import io.serverlessworkflow.api.auth.OauthDefinition; import io.serverlessworkflow.api.workflow.Auth; -class HttpContentLoader extends FallbackContentLoader { +class HttpContentLoader extends CachedContentLoader { private static final Logger logger = LoggerFactory.getLogger(HttpContentLoader.class); private Optional workflow; private String authRef; - public HttpContentLoader(URI uri, Optional fallback, Optional workflow, String authRef) { - super(uri, fallback); + HttpContentLoader(URI uri, Optional workflow, String authRef) { + super(uri); this.workflow = workflow; this.authRef = authRef; } @@ -133,6 +133,7 @@ private String eval(String expr) { return BuildEvaluator.eval(ExpressionHandlerUtils.trimExpr(expr)); } + @SuppressWarnings("squid:S2647") private void basicAuth(HttpURLConnection conn, BasicAuthDefinition basicAuth) { conn.setRequestProperty("Authorization", "Basic " + encode(eval(basicAuth.getUsername()) + ":" + eval(basicAuth.getPassword()))); } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/ResourceCache.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ResourceCache.java similarity index 100% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/ResourceCache.java rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ResourceCache.java diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/ResourceCacheFactory.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ResourceCacheFactory.java similarity index 100% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/ResourceCacheFactory.java rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/ResourceCacheFactory.java diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoader.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoader.java similarity index 100% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoader.java rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoader.java diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java new file mode 100644 index 00000000000..02bf30edb09 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java @@ -0,0 +1,163 @@ +/* + * Copyright 2022 Red Hat, Inc. and/or its affiliates. + * + * 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 org.kie.kogito.serverless.workflow.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Optional; + +import org.kie.kogito.serverless.workflow.parser.ParserContext; + +import io.serverlessworkflow.api.Workflow; + +import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.getBaseURI; + +public class URIContentLoaderFactory { + + public static byte[] readAllBytes(URIContentLoader loader) { + try (InputStream is = loader.getInputStream()) { + return is.readAllBytes(); + } catch (IOException io) { + throw new UncheckedIOException(io); + } + } + + public static byte[] readAllBytes(Builder builder) { + return readAllBytes(builder.build()); + } + + public static String readString(URIContentLoader loader) { + return new String(readAllBytes(loader)); + } + + public static String getFileName(URI uri) { + URIContentLoaderType type = URIContentLoaderType.from(uri); + String path = uriToPath(type, uri); + return type.lastPart(path); + } + + private static String uriToPath(URIContentLoaderType type, URI uri) { + switch (type) { + case CLASSPATH: + return ClassPathContentLoader.getPath(uri); + case FILE: + return FileContentLoader.getPath(uri); + case HTTP: + default: + return uri.getPath(); + } + } + + public static String readString(Builder builder) { + return readString(builder.build()); + } + + public static URIContentLoader buildLoader(URI uri, Workflow workflow, Optional context, String authRef) { + Builder builder = new Builder(uri).withWorkflow(workflow).withAuthRef(authRef); + context.map(c -> c.getContext().getClassLoader()).ifPresent(builder::withClassloader); + getBaseURI(workflow).ifPresent(builder::withBaseURI); + return builder.build(); + } + + public static byte[] readBytes(String uriStr, Workflow workflow, ParserContext parserContext) { + return readBytes(uriStr, workflow, Optional.ofNullable(parserContext)); + } + + public static byte[] readBytes(String uriStr, Workflow workflow, Optional parserContext) { + return readAllBytes(buildLoader(URI.create(uriStr), workflow, parserContext, null)); + } + + public static Builder builder(URI uri) { + return new Builder(uri); + } + + public static Builder builder(String uri) { + return new Builder(URI.create(uri)); + } + + public static class Builder { + private URI uri; + private ClassLoader cl; + private Workflow workflow; + private String authRef; + private URI baseURI; + + private Builder(URI uri) { + this.uri = uri; + } + + public Builder withClassloader(ClassLoader cl) { + this.cl = cl; + return this; + } + + public Builder withWorkflow(Workflow workflow) { + this.workflow = workflow; + return this; + } + + public Builder withAuthRef(String authRef) { + this.authRef = authRef; + return this; + } + + public Builder withBaseURI(URI baseURI) { + this.baseURI = baseURI; + return this; + } + + public URIContentLoader build() { + if (baseURI != null) { + uri = compoundURI(baseURI, uri); + } + switch (URIContentLoaderType.from(uri)) { + default: + case FILE: + return new FileContentLoader(uri); + case HTTP: + return new HttpContentLoader(uri, Optional.ofNullable(workflow), authRef); + case CLASSPATH: + return new ClassPathContentLoader(uri, Optional.ofNullable(cl)); + } + } + } + + public static URI compoundURI(URI baseURI, URI uri) { + if (uri.getScheme() != null) { + return uri; + } + URIContentLoaderType type = URIContentLoaderType.from(baseURI); + String basePath = type.trimLast(uriToPath(type, baseURI)); + String additionalPath = uriToPath(type, uri); + String path; + if (type.isAbsolutePath(additionalPath)) { + path = additionalPath; + } else { + path = type.concat(basePath, additionalPath); + } + try { + return new URI(type.toString().toLowerCase(), baseURI.getAuthority(), path, uri.getQuery(), uri.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + private URIContentLoaderFactory() { + } +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderType.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderType.java new file mode 100644 index 00000000000..99b91ca5db1 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderType.java @@ -0,0 +1,92 @@ +/* + * Copyright 2022 Red Hat, Inc. and/or its affiliates. + * + * 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 org.kie.kogito.serverless.workflow.io; + +import java.io.File; +import java.net.URI; + +public enum URIContentLoaderType { + CLASSPATH(), + FILE(File.separatorChar), + HTTP(); + + private final char[] additionalSeparators; + + private URIContentLoaderType(char... additionalSeparators) { + this.additionalSeparators = additionalSeparators; + } + + public static URIContentLoaderType from(URI uri) { + String scheme = uri.getScheme(); + if (scheme == null) { + return FILE; + } + switch (uri.getScheme().toLowerCase()) { + case "file": + return FILE; + case "classpath": + return CLASSPATH; + case "http": + case "https": + return HTTP; + default: + throw new IllegalArgumentException("Unrecognized uri protocol " + uri); + } + } + + public boolean isAbsolutePath(String path) { + if (!path.isBlank()) { + char firstChar = path.trim().charAt(0); + boolean result = firstChar == '/'; + for (int i = 0; !result && i < additionalSeparators.length; i++) { + result = firstChar == additionalSeparators[i]; + } + return result; + } + return false; + } + + public String concat(String basePath, String additionalPath) { + char separator = separator(); + if (!basePath.isBlank() && !isAbsolutePath(basePath)) { + basePath = separator + basePath; + } + return basePath + separator + additionalPath; + } + + public String trimLast(String path) { + int indexOf = lastIndexOf(path); + return indexOf != -1 ? path.substring(0, indexOf) : ""; + } + + public String lastPart(String path) { + int indexOf = lastIndexOf(path); + return indexOf != -1 ? path.substring(indexOf + 1) : path; + } + + private int lastIndexOf(String path) { + int indexOf = path.lastIndexOf('/'); + int i = 0; + while (indexOf == -1 && i < additionalSeparators.length) { + indexOf = path.lastIndexOf(additionalSeparators[i++]); + } + return indexOf; + } + + private char separator() { + return additionalSeparators.length > 0 ? additionalSeparators[0] : '/'; + } +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/operationid/AbstractWorkflowOperationIdFactory.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/operationid/AbstractWorkflowOperationIdFactory.java index 77dedddb952..46b6673130f 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/operationid/AbstractWorkflowOperationIdFactory.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/operationid/AbstractWorkflowOperationIdFactory.java @@ -34,6 +34,7 @@ import io.serverlessworkflow.api.functions.FunctionDefinition; import static java.lang.String.format; +import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.readBytes; import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.onlyChars; import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.removeExt; @@ -74,13 +75,11 @@ private JsonNode getUriDefinitions(Workflow workflow, Optional co JsonNode definitions = uriDefinitions.getDefinitions(); if (definitions == null || definitions.isNull()) { String uri = uriDefinitions.getURI(); - definitions = uri == null ? NullNode.instance : ServerlessWorkflowUtils.loadResourceFile(uri, Optional.of(workflow), context, null).map(bytes -> { - try { - return ObjectMapperFactory.get().readTree(bytes); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }).orElse(NullNode.instance); + try { + definitions = uri == null ? NullNode.instance : ObjectMapperFactory.get().readTree(readBytes(uri, workflow, context)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } uriDefinitions.setDefinitions(definitions); } return definitions; diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/operationid/FileNameWorkflowOperationIdFactory.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/operationid/FileNameWorkflowOperationIdFactory.java index 6008f111977..b73758b31f8 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/operationid/FileNameWorkflowOperationIdFactory.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/operationid/FileNameWorkflowOperationIdFactory.java @@ -16,10 +16,10 @@ package org.kie.kogito.serverless.workflow.operationid; import java.net.URI; -import java.nio.file.Path; import java.util.Optional; import java.util.Set; +import org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory; import org.kie.kogito.serverless.workflow.parser.ParserContext; import io.serverlessworkflow.api.Workflow; @@ -31,7 +31,7 @@ public class FileNameWorkflowOperationIdFactory extends AbstractWorkflowOperatio @Override public String getFileName(Workflow workflow, FunctionDefinition function, Optional context, URI uri, String operation, String service) { - return Path.of(uri.getPath()).getFileName().toString(); + return URIContentLoaderFactory.getFileName(uri); } @Override diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/JsonSchemaReader.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/JsonSchemaReader.java new file mode 100644 index 00000000000..81025966de1 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/JsonSchemaReader.java @@ -0,0 +1,111 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates. + * + * 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 org.kie.kogito.serverless.workflow.parser; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.kie.kogito.jackson.utils.ObjectMapperFactory; +import org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.DEFS_PREFIX; + +class JsonSchemaReader { + + JsonSchemaReader() { + } + + private static class Counter { + private int count; + + public int next() { + return count++; + } + } + + private static class JsonSchema { + private final String id; + private final JsonNode node; + + public JsonSchema(ObjectNode node, Counter counter) { + this.node = node; + this.id = getId(node, counter); + } + + private static String getId(JsonNode schemaContent, Counter counter) { + JsonNode title = schemaContent.get("title"); + return title != null ? title.asText() + "_" + counter.next() : "nested_" + counter.next(); + } + } + + static JsonNode read(URI baseURI, byte[] content) { + try { + ObjectNode node = ObjectMapperFactory.get().readValue(content, ObjectNode.class); + JsonNode id = node.get("$id"); + if (id != null) { + baseURI = URI.create(id.asText()); + } + Objects.requireNonNull(baseURI, "BaseURI must not be null"); + Map schemas = new HashMap<>(); + Counter counter = new Counter(); + replaceRefsWithDefs(node, baseURI, schemas, counter); + if (!schemas.isEmpty()) { + node.set("$defs", fillDefs(schemas)); + } + return node; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static JsonNode fillDefs(Map schemas) { + ObjectNode node = ObjectMapperFactory.get().createObjectNode(); + schemas.values().stream().collect(Collectors.toMap(v -> v.id, v -> v.node)).forEach(node::set); + return node; + } + + private static void replaceRefsWithDefs(JsonNode node, URI baseURI, Map schemas, Counter counter) { + if (node.isArray()) { + node.elements().forEachRemaining(n -> replaceRefsWithDefs(n, baseURI, schemas, counter)); + } else if (node.isObject()) { + ObjectNode objectNode = (ObjectNode) node; + JsonNode refNode = objectNode.get("$ref"); + if (refNode != null) { + JsonSchema schema = schemas.computeIfAbsent(refNode.asText(), schemaRef -> readSchema(schemaRef, baseURI, counter)); + objectNode.put("$ref", DEFS_PREFIX + schema.id); + } else { + objectNode.elements().forEachRemaining(n -> replaceRefsWithDefs(n, baseURI, schemas, counter)); + } + } + } + + private static JsonSchema readSchema(String schemaRef, URI baseURI, Counter counter) { + try { + return new JsonSchema( + ObjectMapperFactory.get().readValue(URIContentLoaderFactory.readAllBytes(URIContentLoaderFactory.builder(schemaRef).withBaseURI(baseURI)), ObjectNode.class), counter); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/ServerlessWorkflowParser.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/ServerlessWorkflowParser.java index b782fe2e75b..0883c48caed 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/ServerlessWorkflowParser.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/ServerlessWorkflowParser.java @@ -17,6 +17,10 @@ import java.io.IOException; import java.io.Reader; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.URL; +import java.nio.file.Path; import java.util.Collection; import java.util.Optional; import java.util.stream.Collectors; @@ -48,7 +52,9 @@ import io.serverlessworkflow.api.timeouts.WorkflowExecTimeout; import io.serverlessworkflow.api.workflow.Constants; -import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.processResourceFile; +import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.compoundURI; +import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.readBytes; +import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.getBaseURI; public class ServerlessWorkflowParser { @@ -63,6 +69,7 @@ public class ServerlessWorkflowParser { private NodeIdGenerator idGenerator = DefaultNodeIdGenerator.get(); private Workflow workflow; + private GeneratedInfo processInfo; private KogitoBuildContext context; @@ -87,6 +94,23 @@ public ServerlessWorkflowParser withIdGenerator(NodeIdGenerator idGenerator) { return this; } + public ServerlessWorkflowParser withBaseURI(Path baseURI) { + return withBaseURI(baseURI.toUri()); + } + + public ServerlessWorkflowParser withBaseURI(URI baseURI) { + return withBaseURI(baseURI.toString()); + } + + public ServerlessWorkflowParser withBaseURI(URL baseURI) { + return withBaseURI(baseURI.toString()); + } + + public ServerlessWorkflowParser withBaseURI(String baseURI) { + ServerlessWorkflowUtils.withBaseURI(workflow, baseURI); + return this; + } + private ServerlessWorkflowParser(Workflow workflow, KogitoBuildContext context) { this.workflow = workflow; this.context = context; @@ -141,11 +165,9 @@ private GeneratedInfo parseProcess() { } private Optional modelValidator(ParserContext parserContext, Optional schema) { - return schema.map(s -> { - // TODO when all uris included auth ref, include authref - processResourceFile(workflow, parserContext, s.getSchema()); - return new JsonSchemaValidatorSupplier(s.getSchema(), s.isFailOnValidationErrors()); - }); + return schema.map(s -> new JsonSchemaValidatorSupplier(JsonSchemaReader.read( + getBaseURI(workflow).map(u -> compoundURI(u, URI.create(s.getSchema()))).orElseGet(() -> URI.create(s.getSchema())), + readBytes(s.getSchema(), workflow, parserContext)), s.isFailOnValidationErrors())); } public GeneratedInfo getProcessInfo() { @@ -159,13 +181,11 @@ private void loadConstants(RuleFlowProcessFactory factory, ParserContext parserC Constants constants = workflow.getConstants(); if (constants != null) { if (constants.getRefValue() != null) { - processResourceFile(workflow, parserContext, constants.getRefValue()).ifPresent(bytes -> { - try { - constants.setConstantsDef(ObjectMapperFactory.get().readValue(bytes, JsonNode.class)); - } catch (IOException e) { - throw new IllegalArgumentException("Invalid file " + constants.getRefValue(), e); - } - }); + try { + constants.setConstantsDef(ObjectMapperFactory.get().readValue(readBytes(constants.getRefValue(), workflow, parserContext), JsonNode.class)); + } catch (IOException e) { + throw new UncheckedIOException("Invalid file " + constants.getRefValue(), e); + } } factory.metaData(Metadata.CONSTANTS, constants.getConstantsDef()); } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/suppliers/JsonSchemaValidatorSupplier.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/suppliers/JsonSchemaValidatorSupplier.java index 40b042398c0..95e60e06169 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/suppliers/JsonSchemaValidatorSupplier.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/suppliers/JsonSchemaValidatorSupplier.java @@ -20,18 +20,23 @@ import org.jbpm.compiler.canonical.descriptors.ExpressionUtils; import org.kie.kogito.serverless.workflow.actions.JsonSchemaValidator; +import com.fasterxml.jackson.databind.JsonNode; import com.github.javaparser.ast.expr.Expression; public class JsonSchemaValidatorSupplier extends JsonSchemaValidator implements Supplier { private static final long serialVersionUID = 1L; - public JsonSchemaValidatorSupplier(String schema, boolean failOnValidationErrors) { + public JsonSchemaValidatorSupplier() { + // parent class implements Externalizable + } + + public JsonSchemaValidatorSupplier(JsonNode schema, boolean failOnValidationErrors) { super(schema, failOnValidationErrors); } @Override public Expression get() { - return ExpressionUtils.getObjectCreationExpr(JsonSchemaValidator.class, schemaRef, failOnValidationErrors); + return ExpressionUtils.getObjectCreationExpr(JsonSchemaValidator.class, jsonNode, failOnValidationErrors); } } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/ServerlessWorkflowUtils.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/ServerlessWorkflowUtils.java index 03ae00308f4..4dc98414a01 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/ServerlessWorkflowUtils.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/utils/ServerlessWorkflowUtils.java @@ -16,17 +16,20 @@ package org.kie.kogito.serverless.workflow.utils; import java.io.IOException; +import java.io.InputStreamReader; import java.io.Reader; -import java.io.UncheckedIOException; import java.io.Writer; import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.function.Predicate; import java.util.function.Supplier; -import org.drools.codegen.common.GeneratedFile; -import org.drools.codegen.common.GeneratedFileType; import org.jbpm.compiler.canonical.ModelMetaData; import org.jbpm.compiler.canonical.VariableDeclarations; import org.kie.api.definition.process.WorkflowProcess; @@ -35,13 +38,8 @@ import org.kie.kogito.serverless.workflow.extensions.FunctionNamespaces; import org.kie.kogito.serverless.workflow.extensions.OutputSchema; import org.kie.kogito.serverless.workflow.extensions.URIDefinitions; -import org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory; -import org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.Builder; import org.kie.kogito.serverless.workflow.models.JsonNodeModel; -import org.kie.kogito.serverless.workflow.parser.ParserContext; import org.kie.kogito.serverless.workflow.suppliers.ConfigWorkItemSupplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.javaparser.ast.expr.Expression; @@ -57,8 +55,6 @@ public class ServerlessWorkflowUtils { - private static final Logger logger = LoggerFactory.getLogger(ServerlessWorkflowUtils.class); - public static final String FAIL_ON_ERROR_PROPERTY = "kogito.codegen.process.failOnError"; public static final String API_KEY_PREFIX = "api_key_prefix"; public static final String API_KEY = "api_key"; @@ -66,6 +62,9 @@ public class ServerlessWorkflowUtils { public static final String USER_PROP = "username"; public static final String PASSWORD_PROP = "password"; public static final String OPERATION_SEPARATOR = "#"; + public static final String DEFS_PREFIX = "#/$defs/"; + private static final String BASE_URI = "baseURI"; + /** * @deprecated Replaced by WorkflowFormat enum */ @@ -104,6 +103,18 @@ public static Workflow getWorkflow(Reader reader, WorkflowFormat workflowFormat) return objectMapper.readValue(reader, Workflow.class); } + public static Workflow getWorkflow(Path path) throws IOException { + try (Reader reader = Files.newBufferedReader(path)) { + return withBaseURI(getWorkflow(reader, WorkflowFormat.fromFileName(path.getFileName())), path.toString()); + } + } + + public static Workflow getWorkflow(URL url) throws IOException { + try (Reader reader = new InputStreamReader(url.openStream())) { + return withBaseURI(getWorkflow(reader, WorkflowFormat.fromFileName(url.getPath())), url.toString()); + } + } + /** * Kept for backward compatibility purposes * @@ -127,6 +138,24 @@ public static void writeWorkflow(Workflow workflow, Writer writer, WorkflowForma objectMapper.writeValue(writer, workflow); } + public static Optional getBaseURI(Workflow workflow) { + return Optional.ofNullable(getMetadata(workflow).get(BASE_URI)).map(URI::create); + } + + public static Workflow withBaseURI(Workflow workflow, String baseURI) { + getMetadata(workflow).put(BASE_URI, baseURI); + return workflow; + } + + public static Map getMetadata(Workflow workflow) { + Map metadata = workflow.getMetadata(); + if (metadata == null) { + metadata = new HashMap<>(); + workflow.setMetadata(metadata); + } + return metadata; + } + private static BaseObjectMapper deserializer(BaseObjectMapper objectMapper) { ExtensionDeserializer deserializer = objectMapper.getWorkflowModule().getExtensionDeserializer(); deserializer.addExtension(URIDefinitions.URI_DEFINITIONS, URIDefinitions.class); @@ -178,35 +207,6 @@ public interface ExpressionBuilder { Supplier create(String key, Class clazz, T defaultValue); } - public static Optional processResourceFile(Workflow workflow, ParserContext parserContext, String uriStr) { - return processResourceFile(workflow, parserContext, uriStr, null); - } - - public static Optional processResourceFile(Workflow workflow, ParserContext parserContext, String uriStr, String authRef) { - final URI uri = URI.create(uriStr); - final Optional bytes = loadResourceFile(workflow, Optional.of(parserContext), uriStr, authRef); - bytes.ifPresent(value -> parserContext.addGeneratedFile(new GeneratedFile(GeneratedFileType.INTERNAL_RESOURCE, uri.getPath(), value))); - return bytes; - } - - public static Optional loadResourceFile(Workflow workflow, Optional parserContext, String uriStr, String authRef) { - return loadResourceFile(uriStr, Optional.of(workflow), parserContext, authRef); - } - - public static Optional loadResourceFile(String uriStr, Optional workflow, Optional parserContext, String authRef) { - final URI uri = URI.create(uriStr); - try { - Builder builder = URIContentLoaderFactory.builder(uri).withAuthRef(authRef); - workflow.ifPresent(builder::withWorkflow); - parserContext.map(p -> p.getContext().getClassLoader()).ifPresent(builder::withClassloader); - return Optional.of(URIContentLoaderFactory.readAllBytes(builder.build())); - } catch (UncheckedIOException io) { - // if file cannot be found in build context, warn it and return the unmodified uri (it might be possible that later the resource is available at runtime) - logger.warn("Resource {} cannot be found at build time, ignoring", uri, io); - } - return Optional.empty(); - } - public static String removeExt(String fileName) { return fileName.replaceFirst(REGEX_NO_EXT, ""); } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/AbstractServerlessWorkflowParsingTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/AbstractServerlessWorkflowParsingTest.java index 866236bcecc..afb8a36137b 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/AbstractServerlessWorkflowParsingTest.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/AbstractServerlessWorkflowParsingTest.java @@ -47,8 +47,8 @@ protected Process getWorkflowParser(String workflowLocation) throws IOException ServerlessWorkflowParser parser = ServerlessWorkflowParser.of( reader, format, - JavaKogitoBuildContext.builder().build()); + JavaKogitoBuildContext.builder().build()).withBaseURI("classpath:" + workflowLocation); return parser.getProcessInfo().info(); } } -} \ No newline at end of file +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/ServerlessWorkflowParsingTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/ServerlessWorkflowParsingTest.java index 24be3026dbe..e613721405e 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/ServerlessWorkflowParsingTest.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/ServerlessWorkflowParsingTest.java @@ -15,7 +15,6 @@ */ package org.kie.kogito.serverless.workflow; -import java.io.IOException; import java.util.List; import org.jbpm.ruleflow.core.Metadata; @@ -612,7 +611,7 @@ public void testSwitchProduceEventsDefaultOnTransitionWorkflow(String workflowLo @ParameterizedTest @ValueSource(strings = { "/examples/applicantworkflow.sw.json", "/exec/error.sw.json", "/exec/callback.sw.json", "/exec/compensation.sw.json", "/exec/compensation.end.sw.json", "/exec/foreach.sw.json" }) - public void testSpecExamplesParsing(String workflowLocation) throws IOException { + public void testSpecExamplesParsing(String workflowLocation) throws Exception { Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); assertThat(workflow).isNotNull(); @@ -627,7 +626,7 @@ public void testSpecExamplesParsing(String workflowLocation) throws IOException @ParameterizedTest @ValueSource(strings = { "/exec/expression.schema.sw.json" }) - public void testSpecWithInputSchema(String workflowLocation) throws IOException { + public void testSpecWithInputSchema(String workflowLocation) throws Exception { Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); assertThat(workflow).isNotNull(); diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderType.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoaderTest.java similarity index 51% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderType.java rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoaderTest.java index 649294ad9c7..0e58c01ed03 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderType.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/ClassPathContentLoaderTest.java @@ -13,26 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.kie.kogito.serverless.workflow.io; import java.net.URI; +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ClassPathContentLoaderTest { + + private static final String PATH = "specs/external1.yaml"; + + @Test + void testNoPrefixPath() { + testPath(""); + } + + @Test + void testPrefixPath() { + testPath("classpath:"); + } -public enum URIContentLoaderType { - CLASSPATH, - FILE, - HTTP; - - public static URIContentLoaderType from(URI uri) { - switch (uri.getScheme().toLowerCase()) { - case "file": - return FILE; - case "classpath": - return CLASSPATH; - case "http": - case "https": - return HTTP; - default: - throw new IllegalArgumentException("Unrecognized uri protocol " + uri); - } + @Test + void testPrefixSlashPath() { + testPath("classpath://"); } + + void testPath(String prefix) { + ClassPathContentLoader contentLoader = new ClassPathContentLoader(URI.create(prefix + PATH), Optional.empty()); + assertThat(contentLoader.getPath()).isEqualTo(PATH); + } + } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/io/ResourceCacheFactoryTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/ResourceCacheFactoryTest.java similarity index 100% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/io/ResourceCacheFactoryTest.java rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/ResourceCacheFactoryTest.java diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java new file mode 100644 index 00000000000..7cda2d978b1 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates. + * + * 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 org.kie.kogito.serverless.workflow.io; + +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.URISyntaxException; + +import org.junit.jupiter.api.Test; +import org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.Builder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.builder; +import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.compoundURI; +import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.readString; + +class URIContentLoaderTest { + + @Test + void testExistingFile() throws URISyntaxException { + assertThat(readString(builder(Thread.currentThread().getContextClassLoader().getResource("pepe.txt").toURI()))).isEqualTo("my name is javierito"); + } + + @Test + void testExistingClasspath() { + assertThat(readString(builder("classpath:pepe.txt"))).isEqualTo("my name is javierito"); + } + + @Test + void testNotExistingFile() { + Builder builder = builder("file:/noPepe.txt"); + assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(() -> readString(builder)); + } + + @Test + void testNotExistingClasspath() { + Builder builder = builder("classpath:/noPepe.txt"); + assertThatIllegalArgumentException().isThrownBy(() -> readString(builder)); + } + + @Test + void testCompoundURI() { + assertThat(compoundURI(URI.create("classpath:pepe.json"), URI.create("pepa.json"))).isEqualTo(URI.create("classpath:/pepa.json")); + assertThat(compoundURI(URI.create("classpath:pepe.json"), URI.create("file:///pepa.json"))).isEqualTo(URI.create("file:///pepa.json")); + assertThat(compoundURI(URI.create("classpath:schema/pepe.json"), URI.create("/pepa.json"))).isEqualTo(URI.create("classpath:/pepa.json")); + assertThat(compoundURI(URI.create("classpath:schema/pepe.json"), URI.create("pepa.json"))).isEqualTo(URI.create("classpath:/schema/pepa.json")); + assertThat(compoundURI(URI.create("pepe.json"), URI.create("pepa.json"))).isEqualTo(URI.create("file:///pepa.json")); + assertThat(compoundURI(URI.create("schema/pepe.json"), URI.create("pepa.json"))).isEqualTo(URI.create("file:///schema/pepa.json")); + assertThat(compoundURI(URI.create("schema/pepe.json"), URI.create("/pepa.json"))).isEqualTo(URI.create("file:///pepa.json")); + assertThat(compoundURI(URI.create("pepe.json"), URI.create("classpath:pepa.json"))).isEqualTo(URI.create("classpath:pepa.json")); + } +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/actions/JsonSchemaValidatorTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/parser/JsonSchemaValidatorTest.java similarity index 64% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/actions/JsonSchemaValidatorTest.java rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/parser/JsonSchemaValidatorTest.java index 8783de1904d..25c94a1034c 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/actions/JsonSchemaValidatorTest.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/java/org/kie/kogito/serverless/workflow/parser/JsonSchemaValidatorTest.java @@ -13,9 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.kie.kogito.serverless.workflow.actions; +package org.kie.kogito.serverless.workflow.parser; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.Collections; import java.util.Map; @@ -23,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.kie.kogito.jackson.utils.ObjectMapperFactory; import org.kie.kogito.serverless.workflow.SWFConstants; +import org.kie.kogito.serverless.workflow.actions.JsonSchemaValidator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -38,8 +44,24 @@ public class JsonSchemaValidatorTest { private static JsonSchemaValidator validator; @BeforeAll - static void init() { - validator = new JsonSchemaValidator("expression.json", true); + static void init() throws IOException { + try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("schema/expression.json")) { + validator = new JsonSchemaValidator(JsonSchemaReader.read(null, is.readAllBytes()), true); + } + } + + @Test + void testValidatorSerialization() throws IOException, ClassNotFoundException { + + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream objectOut = new ObjectOutputStream(out)) { + objectOut.writeObject(validator); + objectOut.flush(); + try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()))) { + JsonSchemaValidator newValidator = (JsonSchemaValidator) in.readObject(); + final Map model = Collections.singletonMap(SWFConstants.DEFAULT_WORKFLOW_VAR, createNode(new IntNode(4), new IntNode(3))); + assertThatNoException().isThrownBy(() -> newValidator.validate(model)); + } + } } @Test @@ -63,5 +85,4 @@ private ObjectNode createNode(JsonNode x, JsonNode y) { ObjectMapper mapper = ObjectMapperFactory.get(); return mapper.createObjectNode().set("numbers", mapper.createArrayNode().add(mapper.createObjectNode(). set("x", x).set("y", y))); } - } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/exec/expression.schema.sw.json b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/exec/expression.schema.sw.json index 7fce693667d..db4e6460f5a 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/exec/expression.schema.sw.json +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/exec/expression.schema.sw.json @@ -12,7 +12,7 @@ "vasco": "txakurra" } }, - "dataInputSchema" : "schema/expression.json", + "dataInputSchema" : "classpath:schema/expression.json", "description": "An example of how to use a JQ expression assignment", "start": "squareState", "functions": [ diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/resources/pepe.txt b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/pepe.txt similarity index 100% rename from kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/resources/pepe.txt rename to kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/pepe.txt diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/schema/complex.json b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/schema/complex.json new file mode 100644 index 00000000000..7a4e0728f62 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/schema/complex.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Complex", + "description": "Complex number", + "type": "object", + "properties": { + "x" : {"type":"number"}, + "y" : {"type":"number"} + + }, + "required": ["x","y"] +} \ No newline at end of file diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/schema/expression.json b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/schema/expression.json index 2842c62fed3..3633e4e5478 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/schema/expression.json +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/test/resources/schema/expression.json @@ -1,20 +1,15 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Expression", - "description": "Schema for expression test", - "type": "object", - "properties": { - "numbers": { - "description": "The array of numbers to be operated with", - "type": "array", - "items" : { - "type" : "object", - "properties" : { - "x" : {"type":"number"}, - "y" : {"type":"number"} + "$id": "classpath:/schema/expression.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Expression", + "description": "Schema for expression test", + "type": "object", + "properties": { + "numbers": { + "description": "The array of numbers to be operated with", + "type": "array", + "items": {"$ref" : "complex.json"} } - } - } - }, - "required": ["numbers"] + }, + "required": ["numbers"] } \ No newline at end of file diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/OpenAPIWorkflowApplicationTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/OpenAPIWorkflowApplicationTest.java index 30bf1a149b8..d501b0aeb4a 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/OpenAPIWorkflowApplicationTest.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/OpenAPIWorkflowApplicationTest.java @@ -16,8 +16,7 @@ package org.kie.kogito.serverless.workflow.executor; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; +import java.net.URL; import java.util.Collections; import org.junit.jupiter.api.AfterAll; @@ -26,13 +25,10 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.kie.kogito.jackson.utils.ObjectMapperFactory; import org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils; -import org.kie.kogito.serverless.workflow.utils.WorkflowFormat; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; -import io.serverlessworkflow.api.Workflow; - import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; @@ -66,10 +62,9 @@ void openAPIInvocation() throws IOException { final double product = difference * 0.5556; wm.stubFor(post("/multiply").willReturn(aResponse().withStatus(200).withJsonBody(ObjectMapperFactory.get().createObjectNode().put("product", product)))); wm.stubFor(post("/substract").willReturn(aResponse().withStatus(200).withJsonBody(ObjectMapperFactory.get().createObjectNode().put("difference", difference)))); - try (StaticWorkflowApplication application = StaticWorkflowApplication.create(); - Reader reader = new InputStreamReader(Thread.currentThread().getContextClassLoader().getResourceAsStream("fahrenheit-to-celsius.sw.json"))) { - Workflow workflow = ServerlessWorkflowUtils.getWorkflow(reader, WorkflowFormat.JSON); - ObjectNode node = (ObjectNode) application.execute(workflow, Collections.singletonMap("fahrenheit", fahrenheit)).getWorkflowdata(); + URL resource = Thread.currentThread().getContextClassLoader().getResource("fahrenheit-to-celsius.sw.json"); + try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) { + ObjectNode node = (ObjectNode) application.execute(ServerlessWorkflowUtils.getWorkflow(resource), Collections.singletonMap("fahrenheit", fahrenheit)).getWorkflowdata(); assertThat(node.get("product").asDouble()).isEqualByComparingTo(product); } } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/main/java/org/kie/kogito/serverless/workflow/operationid/SpecWorkflowOperationIdFactory.java b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/main/java/org/kie/kogito/serverless/workflow/operationid/SpecWorkflowOperationIdFactory.java index dee5562eac0..a257d908299 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/main/java/org/kie/kogito/serverless/workflow/operationid/SpecWorkflowOperationIdFactory.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/main/java/org/kie/kogito/serverless/workflow/operationid/SpecWorkflowOperationIdFactory.java @@ -30,7 +30,7 @@ public class SpecWorkflowOperationIdFactory extends AbstractWorkflowOperationIdF @Override public String getFileName(Workflow workflow, FunctionDefinition function, Optional context, URI uri, String operation, String service) { - return OpenAPIFactory.getOpenAPI(uri, workflow, function, context.map(c -> c.getContext().getClassLoader()).orElse(Thread.currentThread().getContextClassLoader())).getInfo() + return OpenAPIFactory.getOpenAPI(uri, workflow, function, context).getInfo() .getTitle(); } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/main/java/org/kie/kogito/serverless/workflow/utils/OpenAPIFactory.java b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/main/java/org/kie/kogito/serverless/workflow/utils/OpenAPIFactory.java index b32a388c65f..79618c537da 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/main/java/org/kie/kogito/serverless/workflow/utils/OpenAPIFactory.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/main/java/org/kie/kogito/serverless/workflow/utils/OpenAPIFactory.java @@ -16,7 +16,9 @@ package org.kie.kogito.serverless.workflow.utils; import java.net.URI; +import java.util.Optional; +import org.kie.kogito.serverless.workflow.parser.ParserContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,7 +29,7 @@ import io.swagger.v3.parser.core.models.SwaggerParseResult; import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.buildLoader; -import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.readAllBytes; +import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.readString; public class OpenAPIFactory { @@ -36,9 +38,9 @@ public class OpenAPIFactory { private OpenAPIFactory() { } - public static OpenAPI getOpenAPI(URI uri, Workflow workflow, FunctionDefinition function, ClassLoader cl) { + public static OpenAPI getOpenAPI(URI uri, Workflow workflow, FunctionDefinition function, Optional context) { SwaggerParseResult result = - new OpenAPIParser().readContents(new String(readAllBytes(buildLoader(uri, cl, workflow, function.getAuthRef()))), null, null); + new OpenAPIParser().readContents(readString(buildLoader(uri, workflow, context, function.getAuthRef())), null, null); OpenAPI openAPI = result.getOpenAPI(); if (openAPI == null) { throw new IllegalArgumentException("Problem parsing uri " + uri + " Messages" + result.getMessages()); diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/test/java/org/kie/kogito/serverless/workflow/operationid/SpecTitleOperationIdTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/test/java/org/kie/kogito/serverless/workflow/operationid/SpecTitleOperationIdTest.java index 8004b78d9f1..5bfea1899a4 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/test/java/org/kie/kogito/serverless/workflow/operationid/SpecTitleOperationIdTest.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-common/src/test/java/org/kie/kogito/serverless/workflow/operationid/SpecTitleOperationIdTest.java @@ -43,12 +43,12 @@ void setup() { @Test void testOperationId() { definition.setType(Type.REST); - definition.setOperation("specs/external-service.yaml#sendRequest"); + definition.setOperation("classpath:specs/external-service.yaml#sendRequest"); WorkflowOperationId id = factory.from(workflow, definition, Optional.empty()); assertThat(id.getOperation()).isEqualTo("sendRequest"); assertThat(id.getFileName()).isEqualTo("external-service"); assertThat(id.getPackageName()).isEqualTo("externalservice"); - assertThat(id.getUri()).hasToString("specs/external-service.yaml"); + assertThat(id.getUri()).hasToString("classpath:specs/external-service.yaml"); assertThat(id.getService()).isNull(); } } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/main/java/org/kie/kogito/serverless/workflow/parser/rest/DescriptorRestOperationHandler.java b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/main/java/org/kie/kogito/serverless/workflow/parser/rest/DescriptorRestOperationHandler.java index 0afb77c84c8..562b2cd66c9 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/main/java/org/kie/kogito/serverless/workflow/parser/rest/DescriptorRestOperationHandler.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/main/java/org/kie/kogito/serverless/workflow/parser/rest/DescriptorRestOperationHandler.java @@ -18,6 +18,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.Collection; +import java.util.Optional; import java.util.function.Supplier; import org.jbpm.ruleflow.core.RuleFlowNodeContainerFactory; @@ -85,7 +86,7 @@ public class DescriptorRestOperationHandler extends OpenAPITypeHandler { URI uri = operationId.getUri(); String serviceName = replaceNonAlphanumeric(operationId.getFileName()); // although OpenAPIParser has built in support to load uri, it messes up when using contextclassloader, so using our retrieval apis to get the content - OpenAPI openAPI = OpenAPIFactory.getOpenAPI(uri, workflow, function, parserContext.getContext().getClassLoader()); + OpenAPI openAPI = OpenAPIFactory.getOpenAPI(uri, workflow, function, Optional.of(parserContext)); OpenAPIDescriptor openAPIDescriptor = OpenAPIDescriptorFactory.of(openAPI, operationId.getOperation()); addSecurity(node, openAPIDescriptor, serviceName, parserContext); return node.workParameter(RestWorkItemHandler.URL, diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state-timeouts.sw.json b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state-timeouts.sw.json index 908b7541d2c..7fa2e1cf1d0 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state-timeouts.sw.json +++ b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state-timeouts.sw.json @@ -22,7 +22,7 @@ { "name": "callbackFunction", "type": "rest", - "operation": "specs/external-service.yaml#sendRequest" + "operation": "classpath:specs/external-service.yaml#sendRequest" } ], "states": [ diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state-timeouts.sw.yml b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state-timeouts.sw.yml index 989bf75bc56..44c31469919 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state-timeouts.sw.yml +++ b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state-timeouts.sw.yml @@ -14,7 +14,7 @@ errors: functions: - name: callbackFunction type: rest - operation: specs/external-service.yaml#sendRequest + operation: classpath:specs/external-service.yaml#sendRequest states: - name: CallbackState type: callback diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state.sw.json b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state.sw.json index 218c8fc2e99..7d4cb00a53a 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state.sw.json +++ b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state.sw.json @@ -22,7 +22,7 @@ { "name": "callbackFunction", "type": "rest", - "operation": "specs/external-service.yaml#sendRequest" + "operation": "classpath:specs/external-service.yaml#sendRequest" } ], "states": [ diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state.sw.yml b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state.sw.yml index f6cc0283bb4..8a5f1acfc81 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state.sw.yml +++ b/kogito-serverless-workflow/kogito-serverless-workflow-openapi-parser/src/test/resources/exec/callback-state.sw.yml @@ -14,7 +14,7 @@ errors: functions: - name: callbackFunction type: rest - operation: specs/external-service.yaml#sendRequest + operation: classpath:specs/external-service.yaml#sendRequest states: - name: CallbackState type: callback diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/actions/JsonSchemaValidator.java b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/actions/JsonSchemaValidator.java index 3f15bf13e37..e0c9d9db409 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/actions/JsonSchemaValidator.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/actions/JsonSchemaValidator.java @@ -15,8 +15,10 @@ */ package org.kie.kogito.serverless.workflow.actions; +import java.io.Externalizable; import java.io.IOException; -import java.io.UncheckedIOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -35,21 +37,23 @@ import com.networknt.schema.SpecVersion.VersionFlag; import com.networknt.schema.ValidationMessage; -import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.readAllBytes; -import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.runtimeLoader; - -public class JsonSchemaValidator implements WorkflowModelValidator { +public class JsonSchemaValidator implements WorkflowModelValidator, Externalizable { private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(JsonSchemaValidator.class); - protected final String schemaRef; - protected final boolean failOnValidationErrors; - private final AtomicReference schemaObject = new AtomicReference<>(); + @SuppressWarnings("squid:S1948") // sonar apparently does not realize that if a class implements externalizable, it is not mandatory for all each attributes to be serializable + protected JsonNode jsonNode; + protected boolean failOnValidationErrors; + private final transient AtomicReference schemaObject = new AtomicReference<>(); + + public JsonSchemaValidator() { + // for serialization purposes + } - public JsonSchemaValidator(String schema, boolean failOnValidationErrors) { - this.schemaRef = schema; + public JsonSchemaValidator(JsonNode jsonNode, boolean failOnValidationErrors) { + this.jsonNode = jsonNode; this.failOnValidationErrors = failOnValidationErrors; } @@ -66,24 +70,31 @@ public void validate(Map model) { throw new IllegalArgumentException(validationMessage); } } - } @Override public Optional schema(Class clazz) { - return JsonNode.class.isAssignableFrom(clazz) ? Optional.of((T) getSchema().getSchemaNode()) : Optional.empty(); + return JsonNode.class.isAssignableFrom(clazz) ? Optional.of(clazz.cast(getSchema().getSchemaNode())) : Optional.empty(); } private JsonSchema getSchema() { - try { - JsonSchema result = schemaObject.get(); - if (result == null) { - result = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(ObjectMapperFactory.get().readTree(readAllBytes(runtimeLoader(schemaRef)))); - schemaObject.set(result); - } - return result; - } catch (IOException io) { - throw new UncheckedIOException(io); + JsonSchema result = schemaObject.get(); + if (result == null) { + result = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(jsonNode); + schemaObject.set(result); } + return result; + } + + @Override + public void writeExternal(ObjectOutput out) throws IOException { + out.writeBoolean(failOnValidationErrors); + out.writeUTF(ObjectMapperFactory.get().writeValueAsString(jsonNode)); + } + + @Override + public void readExternal(ObjectInput in) throws IOException { + this.failOnValidationErrors = in.readBoolean(); + this.jsonNode = ObjectMapperFactory.get().readTree(in.readUTF()); } } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/FallbackContentLoader.java b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/FallbackContentLoader.java deleted file mode 100644 index b1319f54df9..00000000000 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/FallbackContentLoader.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2022 Red Hat, Inc. and/or its affiliates. - * - * 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 org.kie.kogito.serverless.workflow.io; - -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.net.URI; -import java.util.Optional; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class FallbackContentLoader extends CachedContentLoader { - - private static final Logger logger = LoggerFactory.getLogger(FallbackContentLoader.class); - - private final Optional fallbackLoader; - - protected FallbackContentLoader(URI uri, Optional fallbackContentLoader) { - super(uri); - this.fallbackLoader = fallbackContentLoader; - } - - @Override - public InputStream getInputStream() { - try { - return super.getInputStream(); - } catch (UncheckedIOException io) { - try { - return fallbackLoader.orElseThrow(() -> io).getInputStream(); - } catch (UncheckedIOException | IllegalArgumentException io2) { - logger.error("Fallback loader failed with message \"{}\", throwing original exception", io2.getMessage()); - throw io; - } - } - } -} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java deleted file mode 100644 index 1ea401d4874..00000000000 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderFactory.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2022 Red Hat, Inc. and/or its affiliates. - * - * 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 org.kie.kogito.serverless.workflow.io; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.net.URI; -import java.util.Optional; - -import io.serverlessworkflow.api.Workflow; - -public class URIContentLoaderFactory { - - public static byte[] readAllBytes(URIContentLoader loader) { - try (InputStream is = loader.getInputStream()) { - return is.readAllBytes(); - } catch (IOException io) { - throw new UncheckedIOException(io); - } - } - - public static URIContentLoader runtimeLoader(String uriStr) { - URI uri = URI.create(uriStr); - Builder builder = new Builder(uri); - builder.withFallback(new ClassPathContentLoader(uri, Optional.empty())); - return builder.build(); - - } - - public static URIContentLoader buildLoader(URI uri, ClassLoader cl, Workflow workflow, String authRef) { - return new Builder(uri).withClassloader(cl).withWorkflow(workflow).withAuthRef(authRef).build(); - } - - /** - * @deprecated Use builder - */ - @Deprecated - public static URIContentLoader loader(URI uri, Optional cl, Optional fallback, Optional workflow, String authRef) { - Builder builder = new Builder(uri); - cl.ifPresent(builder::withClassloader); - fallback.ifPresent(builder::withFallback); - workflow.ifPresent(builder::withWorkflow); - builder.withAuthRef(authRef); - return builder.build(); - } - - public static Builder builder(URI uri) { - return new Builder(uri); - } - - public static class Builder { - private URI uri; - private ClassLoader cl; - private URIContentLoader fallback; - private Workflow workflow; - private String authRef; - private String baseURI; - - private Builder(URI uri) { - this.uri = uri; - } - - public Builder withClassloader(ClassLoader cl) { - this.cl = cl; - return this; - } - - public Builder withFallback(URIContentLoader fallback) { - this.fallback = fallback; - return this; - } - - public Builder withWorkflow(Workflow workflow) { - this.workflow = workflow; - return this; - } - - public Builder withAuthRef(String authRef) { - this.authRef = authRef; - return this; - } - - public Builder withBaseURI(String baseURI) { - this.baseURI = baseURI; - return this; - } - - public URIContentLoader build() { - if (uri.getScheme() == null) { - if (baseURI != null) { - uri = URI.create(baseURI + "/" + uri.toString()); - } else { - return new ClassPathContentLoader(uri, Optional.ofNullable(cl)); - } - } - switch (URIContentLoaderType.from(uri)) { - case FILE: - return new FileContentLoader(uri, Optional.ofNullable(fallback)); - case HTTP: - return new HttpContentLoader(uri, Optional.ofNullable(fallback), Optional.ofNullable(workflow), authRef); - default: - case CLASSPATH: - return new ClassPathContentLoader(uri, Optional.ofNullable(cl)); - } - } - } - - private URIContentLoaderFactory() { - } -} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java deleted file mode 100644 index 7d794bf4144..00000000000 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/io/URIContentLoaderTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2021 Red Hat, Inc. and/or its affiliates. - * - * 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 org.kie.kogito.serverless.workflow.io; - -import java.io.IOException; -import java.io.UncheckedIOException; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.readAllBytes; -import static org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory.runtimeLoader; - -class URIContentLoaderTest { - - @Test - void testExistingFile() throws IOException { - assertThat(new String(readAllBytes(runtimeLoader("file:/pepe.txt")))).isEqualTo("my name is javierito"); - } - - @Test - void testExistingClasspath() throws IOException { - assertThat(new String(readAllBytes(runtimeLoader("classpath:/pepe.txt")))).isEqualTo("my name is javierito"); - } - - @Test - void testNotExistingFile() { - assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(() -> readAllBytes(runtimeLoader("file:/noPepe.txt"))); - } - - @Test - void testNotExistingClasspath() { - assertThatIllegalArgumentException().isThrownBy(() -> readAllBytes(runtimeLoader("classpath:/noPepe.txt"))); - } - -} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/resources/expression.json b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/resources/expression.json deleted file mode 100644 index b6822d8fa4e..00000000000 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/resources/expression.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Expression", - "description": "Schema for expression test", - "type": "object", - "properties": { - "numbers": { - "description": "The array of numbers to be operated with", - "type": "array", - "items" : { - "type" : "object", - "properties" : { - "x" : {"type":"number"}, - "y" : {"type":"number"} - } - } - } - }, - "required": ["numbers"] -} \ No newline at end of file diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/WorkflowCodeGenUtils.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/WorkflowCodeGenUtils.java index c3b9c11926a..fec7e80ba95 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/WorkflowCodeGenUtils.java +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/WorkflowCodeGenUtils.java @@ -16,7 +16,6 @@ package org.kie.kogito.quarkus.serverless.workflow; import java.io.IOException; -import java.io.Reader; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; @@ -34,7 +33,6 @@ import org.kie.kogito.serverless.workflow.operationid.WorkflowOperationIdFactory; import org.kie.kogito.serverless.workflow.operationid.WorkflowOperationIdFactoryProvider; import org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils; -import org.kie.kogito.serverless.workflow.utils.WorkflowFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,14 +86,14 @@ private static Stream processFunction(Workflow workfl private static WorkflowOperationResource getResource(Workflow workflow, FunctionDefinition function, WorkflowOperationIdFactory factory) { WorkflowOperationId operationId = factory.from(workflow, function, Optional.empty()); return new WorkflowOperationResource(operationId, - URIContentLoaderFactory.buildLoader(operationId.getUri(), Thread.currentThread().getContextClassLoader(), workflow, function.getAuthRef())); + URIContentLoaderFactory.buildLoader(operationId.getUri(), workflow, Optional.empty(), function.getAuthRef())); } private static Optional getWorkflow(Path path) { if (SupportedExtensions.getSWFExtensions().stream().anyMatch(ext -> path.toString().endsWith(ext))) { return workflowCache.computeIfAbsent(path, p -> { - try (Reader r = Files.newBufferedReader(p)) { - return Optional.of(ServerlessWorkflowUtils.getWorkflow(r, WorkflowFormat.fromFileName(p.getFileName()))); + try { + return Optional.of(ServerlessWorkflowUtils.getWorkflow(p)); } catch (IOException ex) { logger.info("Error reading workflow file {}. Ignoring exception {}", p, ex); return Optional. empty(); diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/deployment/ServerlessWorkflowAssetsProcessor.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/deployment/ServerlessWorkflowAssetsProcessor.java index a83fab59244..6d834bb478d 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/deployment/ServerlessWorkflowAssetsProcessor.java +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/deployment/ServerlessWorkflowAssetsProcessor.java @@ -103,11 +103,12 @@ void addWorkItemHandlers(KogitoBuildContextBuildItem contextBI, LiveReloadExecut @BuildStep void addOpenAPIModelSchema(List processBuildItem, BuildProducer openAPIProducer) { Map schemasInfo = new HashMap<>(); + Map defsSchemas = new HashMap<>(); processBuildItem.stream().flatMap(it -> it.getProcessContainerGenerators().stream()) .map(ProcessContainerGenerator::getProcesses).flatMap(Collection::stream).map(ProcessGenerator::getProcess) - .forEach(process -> OpenApiModelSchemaGenerator.addOpenAPIModelSchema(process, schemasInfo)); + .forEach(process -> OpenApiModelSchemaGenerator.addOpenAPIModelSchema(process, schemasInfo, defsSchemas)); if (!schemasInfo.isEmpty()) { - openAPIProducer.produce(new AddToOpenAPIDefinitionBuildItem(new ServerlessWorkflowOASFilter(schemasInfo))); + openAPIProducer.produce(new AddToOpenAPIDefinitionBuildItem(new ServerlessWorkflowOASFilter(schemasInfo, defsSchemas))); } } diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/openapi/ServerlessWorkflowOASFilter.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/openapi/ServerlessWorkflowOASFilter.java index ec38ebc9e0b..5033a2efc02 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/openapi/ServerlessWorkflowOASFilter.java +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/openapi/ServerlessWorkflowOASFilter.java @@ -25,13 +25,15 @@ public final class ServerlessWorkflowOASFilter implements OASFilter { private final Map schemasInfo; + private final Map defsSchemas; - public ServerlessWorkflowOASFilter(Map schemasInfo) { + public ServerlessWorkflowOASFilter(Map schemasInfo, Map defsSchemas) { this.schemasInfo = schemasInfo; + this.defsSchemas = defsSchemas; } @Override public void filterOpenAPI(OpenAPI openAPI) { - OpenApiModelSchemaGenerator.mergeSchemas(openAPI, schemasInfo); + OpenApiModelSchemaGenerator.mergeSchemas(openAPI, schemasInfo, defsSchemas); } } diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow/src/main/java/org/kie/kogito/serverless/workflow/io/QuarkusResourceCache.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/io/QuarkusResourceCache.java similarity index 100% rename from quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow/src/main/java/org/kie/kogito/serverless/workflow/io/QuarkusResourceCache.java rename to quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/io/QuarkusResourceCache.java diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/JsonSchemaImpl.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/JsonSchemaImpl.java index 60fd6221afc..71403ec6cce 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/JsonSchemaImpl.java +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/JsonSchemaImpl.java @@ -16,18 +16,10 @@ package org.kie.kogito.serverless.workflow.parser.schema; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; import java.util.List; import java.util.Map; import org.eclipse.microprofile.openapi.models.media.Schema; -import org.kie.kogito.jackson.utils.ObjectMapperFactory; -import org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @@ -37,6 +29,8 @@ import io.smallrye.openapi.api.constants.OpenApiConstants; import io.smallrye.openapi.api.models.media.SchemaImpl; +import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.DEFS_PREFIX; + /** * Holder class to map and deserialize a JSON Schema structure to OpenAPI Schema * Exists just to make it easy for JSON Deserializers to convert the given JSON Schema into an OpenAPI Schema. @@ -45,36 +39,22 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class JsonSchemaImpl extends SchemaImpl { - private static final Logger logger = LoggerFactory.getLogger(JsonSchemaImpl.class); + private Map defs; - @JsonSetter("$id") - public void setId(String id) { - RefSchemas.baseURI(id); + @JsonSetter("$defs") + @JsonDeserialize(contentAs = JsonSchemaImpl.class) + public void setDefs(Map defs) { + this.defs = defs; } @JsonSetter("$ref") @Override public void setRef(String ref) { - if (ref != null && !ref.startsWith("#")) { - try (InputStream is = URIContentLoaderFactory.builder(new URI(ref)).withBaseURI(RefSchemas.getBaseURI()).build().getInputStream()) { - JsonSchemaImpl schema = ObjectMapperFactory.get().readValue(is.readAllBytes(), JsonSchemaImpl.class); - String key; - if (schema.getTitle() == null) { - key = RefSchemas.getKey(); - schema.title(key); - } else { - key = schema.getTitle(); - } - if (key != null) { - RefSchemas.get().put(key, schema); - } - ref = OpenApiConstants.REF_PREFIX_SCHEMA + key; - } catch (URISyntaxException | IOException e) { - // if not a valid uri, let super handle it - logger.info("Error loading ref {}", ref, e); - } - } - super.setRef(ref); + super.setRef(ref.replace(DEFS_PREFIX, OpenApiConstants.REF_PREFIX_SCHEMA)); + } + + public Map getDefs() { + return defs; } @JsonDeserialize(as = JsonSchemaImpl.class) diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/OpenApiModelSchemaGenerator.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/OpenApiModelSchemaGenerator.java index eff073d1aa0..46c88c25267 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/OpenApiModelSchemaGenerator.java +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/OpenApiModelSchemaGenerator.java @@ -55,23 +55,26 @@ private OpenApiModelSchemaGenerator() { private static final String INPUT_SUFFIX = "_input"; private static final String OUTPUT_SUFFIX = "_output"; - public static void addOpenAPIModelSchema(KogitoWorkflowProcess workflow, Map schemas) { + public static void addOpenAPIModelSchema(KogitoWorkflowProcess workflow, Map schemas, Map defSchemas) { if (workflow instanceof WorkflowProcess) { WorkflowProcess workflowProcess = (WorkflowProcess) workflow; - RefSchemas.init(workflow.getId()); - try { - getSchema(workflowProcess.getInputValidator()).ifPresent(v -> { - String key = getSchemaName(workflow.getId(), INPUT_SUFFIX); - schemas.put(key, schemaTitle(key, v)); - }); - getSchema(workflowProcess.getOutputValidator()).ifPresent(v -> { - String key = getSchemaName(workflow.getId(), OUTPUT_SUFFIX); - schemas.put(key, createOutputSchema(schemaTitle(key, v))); - }); - schemas.putAll(RefSchemas.get()); - } finally { - RefSchemas.reset(); - } + getSchema(workflowProcess.getInputValidator()).ifPresent(v -> { + String key = getSchemaName(workflow.getId(), INPUT_SUFFIX); + schemas.put(key, schemaTitle(key, v)); + addDefs(defSchemas, v); + }); + getSchema(workflowProcess.getOutputValidator()).ifPresent(v -> { + String key = getSchemaName(workflow.getId(), OUTPUT_SUFFIX); + schemas.put(key, createOutputSchema(schemaTitle(key, v))); + addDefs(defSchemas, v); + }); + } + } + + private static void addDefs(Map schemas, Schema v) { + Map defs = ((JsonSchemaImpl) v).getDefs(); + if (defs != null) { + schemas.putAll(defs); } } @@ -94,7 +97,7 @@ private static Schema createOutputSchema(Schema schema) { return OASFactory.createSchema().addProperty("workflowdata", schema).addProperty("id", ID_SCHEMA).title(schema.getTitle()); } - public static void mergeSchemas(OpenAPI targetSchema, Map schemas) { + public static void mergeSchemas(OpenAPI targetSchema, Map schemas, Map defsSchemas) { Components components = targetSchema.getComponents(); if (components == null) { components = OASFactory.createComponents(); @@ -103,6 +106,7 @@ public static void mergeSchemas(OpenAPI targetSchema, Map schema for (Schema schema : schemas.values()) { components.addSchema(schema.getTitle(), schema); } + defsSchemas.forEach(components::addSchema); if (targetSchema.getPaths() != null && targetSchema.getPaths().getPathItems() != null) { for (PathItem pathItem : targetSchema.getPaths().getPathItems().values()) { processOperation(schemas, pathItem.getPOST()); diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/RefSchemas.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/RefSchemas.java deleted file mode 100644 index f68072a5978..00000000000 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/serverless/workflow/parser/schema/RefSchemas.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2023 Red Hat, Inc. and/or its affiliates. - * - * 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 org.kie.kogito.serverless.workflow.parser.schema; - -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.microprofile.openapi.models.media.Schema; - -class RefSchemas { - - private RefSchemas() { - } - - private static class ThreadInfo { - private final String id; - private final Map map = new HashMap<>(); - private int counter; - private String baseURI; - - private ThreadInfo(String id) { - this.id = id; - } - } - - private static ThreadLocal threadInfo = new ThreadLocal<>(); - - public static void init(String id) { - threadInfo.set(new ThreadInfo(id)); - } - - public static Map get() { - return threadInfo.get().map; - } - - public static void baseURI(String baseURI) { - if (baseURI != null) { - int lastIndexOf = baseURI.lastIndexOf('/'); - threadInfo.get().baseURI = lastIndexOf != -1 ? baseURI.substring(0, lastIndexOf) : baseURI; - } - } - - public static String getBaseURI() { - return threadInfo.get().baseURI; - } - - public static String getKey() { - ThreadInfo t = threadInfo.get(); - return t.id + "_nested_" + ++t.counter; - } - - public static void reset() { - threadInfo.remove(); - } -} diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/asyncPublisher.sw.json b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/asyncPublisher.sw.json index 92643e41450..df9faed48bf 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/asyncPublisher.sw.json +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/asyncPublisher.sw.json @@ -8,7 +8,7 @@ { "name": "publishEvent", "type": "asyncapi", - "operation": "specs/asyncAPI.yaml#sendWait" + "operation": "classpath:specs/asyncAPI.yaml#sendWait" } ], "states": [ diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/openAPIEnumParameter.sw.json b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/openAPIEnumParameter.sw.json index 589cab4e150..321be19f1a2 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/openAPIEnumParameter.sw.json +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/openAPIEnumParameter.sw.json @@ -6,7 +6,7 @@ "functions": [ { "name": "echoFunction", - "operation": "specs/enum-parameter.yaml#echo" + "operation": "classpath:specs/enum-parameter.yaml#echo" } ], "states": [ diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/rpcgreet.sw.json b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/rpcgreet.sw.json index 502ff1b6fa9..6eb109f7d2c 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/rpcgreet.sw.json +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/resources/rpcgreet.sw.json @@ -8,7 +8,7 @@ { "name": "sayHello", "type": "rpc", - "operation": "greeting.proto#Greeter#SayHello" + "operation": "classpath:greeting.proto#Greeter#SayHello" } ], "states": [