From f0234eb90bd1af4c11e10b73594975910bb23c37 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Thu, 30 Apr 2026 16:00:58 +0200 Subject: [PATCH] [Fix #1351] Generating native file Signed-off-by: fjtirado --- api/pom.xml | 24 ++++- .../graalvm/ReflectNativeFileHandler.java | 92 +++++++++++++++++++ .../generator/jackson/GeneratorUtils.java | 2 +- .../generator/jackson/JacksonMixInPojo.java | 62 +++++++------ .../reflect-config.json | 3 + .../reflect-config.json | 14 +++ 6 files changed, 166 insertions(+), 31 deletions(-) create mode 100644 generators/jackson/src/main/java/io/serverlessworkflow/generator/graalvm/ReflectNativeFileHandler.java create mode 100644 impl/core/src/main/resources/META-INF/native-image/io.serverlessworkflow/serverlessworkflow-impl-core/reflect-config.json create mode 100644 impl/openapi-jackson/src/main/resources/META-INF/native-image/io.serverlessworkflow/serverlessworkflow-impl-openapi/reflect-config.json diff --git a/api/pom.xml b/api/pom.xml index 81d882342..03a95be2e 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -11,6 +11,9 @@ Serverless Workflow :: API jar Java SDK for Serverless Workflow Specification + + 3.6.1 + @@ -75,9 +78,8 @@ test - + - io.serverlessworkflow serverless-workflow-jackson-generator @@ -100,7 +102,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.6.1 + ${version.build-helper-maven-plugin} add-mixin @@ -114,8 +116,22 @@ + + add-resource + generate-sources + + add-resource + + + + + ${project.build.directory}/generated-resources + + + + - + diff --git a/generators/jackson/src/main/java/io/serverlessworkflow/generator/graalvm/ReflectNativeFileHandler.java b/generators/jackson/src/main/java/io/serverlessworkflow/generator/graalvm/ReflectNativeFileHandler.java new file mode 100644 index 000000000..724065c7c --- /dev/null +++ b/generators/jackson/src/main/java/io/serverlessworkflow/generator/graalvm/ReflectNativeFileHandler.java @@ -0,0 +1,92 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.generator.graalvm; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.sun.codemodel.JDefinedClass; +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class ReflectNativeFileHandler { + + private final String groupId; + private final String artifactId; + private final ArrayNode rootObject; + private static final ObjectMapper objectMapper = new ObjectMapper(); + + public ReflectNativeFileHandler(String groupId, String artifactId) { + this.groupId = groupId; + this.artifactId = artifactId; + this.rootObject = objectMapper.createArrayNode(); + } + + public ReflectNativeFileHandler addClasses(Stream classNames) { + classNames.forEach(this::addClass); + return this; + } + + public ReflectNativeFileHandler addClasses(Iterable classNames) { + classNames.forEach(this::addClass); + return this; + } + + public ReflectNativeFileHandler addClass(String refClassName) { + rootObject.add(buildObjectFromClass(refClassName)); + return this; + } + + public ReflectNativeFileHandler addClass(JDefinedClass definedClass) { + addClass(definedClass.fullName()); + return this; + } + + private static JsonNode buildObjectFromClass(String refClassName) { + return objectMapper + .createObjectNode() + .put("name", refClassName) + .put("queryAllPublicConstructors", true) + .put("queryAllDeclaredConstructors", true) + .put("queryAllPublicMethods", true) + .put("queryAllDeclaredMethods", true) + .put("allPublicConstructors", true) + .put("allDeclaredConstructors", true) + .put("allPublicMethods", true) + .put("allDeclaredMethods", true) + .put("allPublicFields", true) + .put("allDeclaredFields", true) + .put("allPublicClasses", true) + .put("allDeclaredClasses", true); + } + + public void generate(Path rootPath) throws IOException { + try (Writer out = + Files.newBufferedWriter( + Files.createDirectories( + rootPath + .resolve("META-INF") + .resolve("native-image") + .resolve(groupId) + .resolve(artifactId)) + .resolve("reflect-config.json"))) { + objectMapper.writeValue(out, rootObject); + } + } +} diff --git a/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/GeneratorUtils.java b/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/GeneratorUtils.java index 5687de8c0..d33be6888 100644 --- a/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/GeneratorUtils.java +++ b/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/GeneratorUtils.java @@ -83,7 +83,7 @@ public static void fillDeserializer( private static JDefinedClass createClass( JPackage jPackage, JClass relatedClass, Class serializerClass, String suffix) throws JClassAlreadyExistsException { - JDefinedClass definedClass = jPackage._class(JMod.NONE, relatedClass.name() + suffix); + JDefinedClass definedClass = jPackage._class(JMod.PUBLIC, relatedClass.name() + suffix); definedClass._extends(definedClass.owner().ref(serializerClass).narrow(relatedClass)); return definedClass; } diff --git a/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/JacksonMixInPojo.java b/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/JacksonMixInPojo.java index 1b633a42c..7cdae4621 100644 --- a/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/JacksonMixInPojo.java +++ b/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/JacksonMixInPojo.java @@ -50,6 +50,7 @@ import io.serverlessworkflow.annotations.Item; import io.serverlessworkflow.annotations.ItemKey; import io.serverlessworkflow.annotations.ItemValue; +import io.serverlessworkflow.generator.graalvm.ReflectNativeFileHandler; import java.io.File; import java.io.IOException; import java.io.PrintStream; @@ -78,6 +79,17 @@ public class JacksonMixInPojo extends AbstractMojo { defaultValue = "${project.build.directory}/generated-sources/jacksonmixinpojo") private File outputDirectory; + @Parameter(defaultValue = "${project.groupId}", readonly = true, required = true) + private String groupId; + + @Parameter(defaultValue = "${project.artifactId}", readonly = true, required = true) + private String artifactId; + + @Parameter( + property = "jacksonmixinpojo.outputResources", + defaultValue = "${project.build.directory}/generated-resources") + private File outputResources; + @Parameter(property = "jacksonmixinpojo.srcPackage") private String srcPackage; @@ -90,6 +102,7 @@ public class JacksonMixInPojo extends AbstractMojo { private JCodeModel codeModel; private JPackage rootPackage; private JMethod setupMethod; + private ReflectNativeFileHandler nativeHandler; @FunctionalInterface interface AnnotationProcessor { @@ -99,7 +112,6 @@ void accept(ClassInfo classInfo, JDefinedClass definedClass) @Override public void execute() throws MojoExecutionException, MojoFailureException { - try (ScanResult result = new ClassGraph() .enableAnnotationInfo() @@ -107,12 +119,14 @@ public void execute() throws MojoExecutionException, MojoFailureException { .acceptPackages(srcPackage) .scan()) { codeModel = new JCodeModel(); + nativeHandler = new ReflectNativeFileHandler(groupId, artifactId); rootPackage = codeModel._package(targetPackage); setupMethod = rootPackage ._class("JacksonMixInModule") ._extends(SimpleModule.class) .method(JMod.PUBLIC, codeModel.VOID, SETUP_METHOD); + nativeHandler.addClasses(result.getAllClasses().stream().map(c -> c.getName())); processAnnotatedClasses(result, ExclusiveUnion.class, this::buildExclusiveUnionMixIn); processAnnotatedClasses(result, InclusiveUnion.class, this::buildInclusiveUnionMixIn); processAnnotatedClasses(result, AdditionalProperties.class, this::buildAdditionalPropsMixIn); @@ -124,6 +138,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { .arg(setupMethod.param(SetupContext.class, "context")); Files.createDirectories(outputDirectory.toPath()); codeModel.build(outputDirectory, (PrintStream) null); + + nativeHandler.generate(outputResources.toPath()); } catch (JClassAlreadyExistsException | IOException e) { getLog().error(e); } @@ -149,7 +165,8 @@ private void processAnnotatedClasses( private JExpression processAnnotatedClass(ClassInfo classInfo, AnnotationProcessor processor) throws JClassAlreadyExistsException { - JDefinedClass result = createMixInClass(classInfo); + JDefinedClass result = rootPackage._class(JMod.ABSTRACT, classInfo.getSimpleName() + "MixIn"); + nativeHandler.addClass(result); processor.accept(classInfo, result); return JExpr.dotclass(result); } @@ -179,31 +196,28 @@ private void buildItemMixIn(ClassInfo classInfo, JDefinedClass mixClass) classInfo.getMethodInfo().filter(m -> m.hasAnnotation(ItemKey.class)).get(0); MethodInfo valueMethod = classInfo.getMethodInfo().filter(m -> m.hasAnnotation(ItemValue.class)).get(0); - mixClass - .annotate(JsonSerialize.class) - .param( - "using", - GeneratorUtils.generateSerializer( - rootPackage, relClass, keyMethod.getName(), valueMethod.getName())); - mixClass - .annotate(JsonDeserialize.class) - .param( - "using", - GeneratorUtils.generateDeserializer(rootPackage, relClass, getReturnType(valueMethod))); + JDefinedClass serializerClass = + GeneratorUtils.generateSerializer( + rootPackage, relClass, keyMethod.getName(), valueMethod.getName()); + nativeHandler.addClass(serializerClass); + mixClass.annotate(JsonSerialize.class).param("using", serializerClass); + JDefinedClass deserializerClass = + GeneratorUtils.generateDeserializer(rootPackage, relClass, getReturnType(valueMethod)); + nativeHandler.addClass(deserializerClass); + mixClass.annotate(JsonDeserialize.class).param("using", deserializerClass); } private void buildExclusiveUnionMixIn(ClassInfo unionClassInfo, JDefinedClass unionMixClass) throws JClassAlreadyExistsException { JClass unionClass = codeModel.ref(unionClassInfo.getName()); - unionMixClass - .annotate(JsonSerialize.class) - .param("using", GeneratorUtils.generateSerializer(rootPackage, unionClass)); - unionMixClass - .annotate(JsonDeserialize.class) - .param( - "using", - GeneratorUtils.generateDeserializer( - rootPackage, unionClass, getUnionClasses(ExclusiveUnion.class, unionClassInfo))); + JDefinedClass serializerClass = GeneratorUtils.generateSerializer(rootPackage, unionClass); + unionMixClass.annotate(JsonSerialize.class).param("using", serializerClass); + nativeHandler.addClass(serializerClass); + JDefinedClass deserializerClass = + GeneratorUtils.generateDeserializer( + rootPackage, unionClass, getUnionClasses(ExclusiveUnion.class, unionClassInfo)); + unionMixClass.annotate(JsonDeserialize.class).param("using", deserializerClass); + nativeHandler.addClass(deserializerClass); } private void buildInclusiveUnionMixIn(ClassInfo unionClassInfo, JDefinedClass unionMixClass) @@ -229,10 +243,6 @@ private void buildEnumMixIn(ClassInfo classInfo, JDefinedClass mixClass) staticMethod.body()._return(JExpr._null()); } - private JDefinedClass createMixInClass(ClassInfo classInfo) throws JClassAlreadyExistsException { - return rootPackage._class(JMod.ABSTRACT, classInfo.getSimpleName() + "MixIn"); - } - private Collection getUnionClasses( Class annotation, ClassInfo unionClassInfo) { AnnotationInfo info = unionClassInfo.getAnnotationInfoRepeatable(annotation).get(0); diff --git a/impl/core/src/main/resources/META-INF/native-image/io.serverlessworkflow/serverlessworkflow-impl-core/reflect-config.json b/impl/core/src/main/resources/META-INF/native-image/io.serverlessworkflow/serverlessworkflow-impl-core/reflect-config.json new file mode 100644 index 000000000..e7d74c96a --- /dev/null +++ b/impl/core/src/main/resources/META-INF/native-image/io.serverlessworkflow/serverlessworkflow-impl-core/reflect-config.json @@ -0,0 +1,3 @@ +[ +{"name":"io.serverlessworkflow.impl.WorkflowError","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true} +] \ No newline at end of file diff --git a/impl/openapi-jackson/src/main/resources/META-INF/native-image/io.serverlessworkflow/serverlessworkflow-impl-openapi/reflect-config.json b/impl/openapi-jackson/src/main/resources/META-INF/native-image/io.serverlessworkflow/serverlessworkflow-impl-openapi/reflect-config.json new file mode 100644 index 000000000..0e6938d70 --- /dev/null +++ b/impl/openapi-jackson/src/main/resources/META-INF/native-image/io.serverlessworkflow/serverlessworkflow-impl-openapi/reflect-config.json @@ -0,0 +1,14 @@ +[ +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$Components","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$Content","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$HttpOperation","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$MediaType","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$Operation","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$Parameter","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$PathItem","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$RequestBody","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$Schema","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$Server","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true}, +{"name":"io.serverlessworkflow.impl.executors.openapi.UnifiedOpenAPI$SwaggerVersion","queryAllPublicConstructors":true,"queryAllDeclaredConstructors":true,"queryAllPublicMethods":true,"queryAllDeclaredMethods":true,"allPublicConstructors":true,"allDeclaredConstructors":true,"allPublicMethods":true,"allDeclaredMethods":true,"allPublicFields":true,"allDeclaredFields":true,"allPublicClasses":true,"allDeclaredClasses":true} +] \ No newline at end of file