From f77dc94d92e76a4b3699f39a7aaffd9ea19bbe84 Mon Sep 17 00:00:00 2001 From: Enrique Date: Mon, 14 Oct 2024 12:39:15 +0200 Subject: [PATCH] [incubator-kie-issues-1517] Add Transactional Rest endpoints to UserTasks (#3701) --- .../codegen/process/ProcessCodegen.java | 9 +- .../process/ProcessResourceGenerator.java | 5 - .../codegen/process/util/CodegenUtil.java | 105 ++++++++++++++++++ .../codegen/usertask/UserTaskCodegen.java | 21 ++-- .../process/ProcessResourceGeneratorTest.java | 81 ++++++++++++++ 5 files changed, 201 insertions(+), 20 deletions(-) create mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.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 86e6bd80531..528a9e081a0 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 @@ -62,6 +62,7 @@ import org.kie.kogito.codegen.process.config.ProcessConfigGenerator; import org.kie.kogito.codegen.process.events.ProcessCloudEventMeta; import org.kie.kogito.codegen.process.events.ProcessCloudEventMetaFactoryGenerator; +import org.kie.kogito.codegen.process.util.CodegenUtil; import org.kie.kogito.internal.SupportedExtensions; import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess; import org.kie.kogito.process.validation.ValidationException; @@ -76,7 +77,6 @@ import static java.lang.String.format; import static java.util.stream.Collectors.toList; -import static org.kie.kogito.codegen.process.ProcessResourceGenerator.TRANSACTION_ENABLED; import static org.kie.kogito.grafana.GrafanaConfigurationWriter.buildDashboardName; import static org.kie.kogito.grafana.GrafanaConfigurationWriter.generateOperationalDashboard; import static org.kie.kogito.internal.utils.ConversionUtils.sanitizeClassName; @@ -369,7 +369,7 @@ protected Collection internalGenerate() { .withWorkItems(processIdToWorkItemModel.get(workFlowProcess.getId())) .withSignals(metaData.getSignals()) .withTriggers(metaData.isStartable(), metaData.isDynamic(), metaData.getTriggers()) - .withTransaction(isTransactionEnabled()); + .withTransaction(CodegenUtil.isTransactionEnabled(this, context())); rgs.add(processResourceGenerator); } @@ -511,11 +511,6 @@ protected Collection internalGenerate() { return generatedFiles; } - protected boolean isTransactionEnabled() { - String processTransactionProperty = String.format("kogito.%s.%s", GENERATOR_NAME, TRANSACTION_ENABLED); - return "true".equalsIgnoreCase(context().getApplicationProperty(processTransactionProperty).orElse("true")); - } - private void storeFile(GeneratedFileType type, String path, String source) { if (generatedFiles.stream().anyMatch(f -> path.equals(f.relativePath()))) { LOGGER.warn("There's already a generated file named {} to be compiled. Ignoring.", path); diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java index 3e2f8765a40..207af9e6369 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java @@ -77,11 +77,6 @@ */ public class ProcessResourceGenerator { - /** - * Flag used to configure transaction enablement. Default to true - */ - public static final String TRANSACTION_ENABLED = "transactionEnabled"; - static final String INVALID_CONTEXT_TEMPLATE = "ProcessResourceGenerator can't be used for context without Rest %s"; private static final Logger LOG = LoggerFactory.getLogger(ProcessResourceGenerator.class); diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java new file mode 100644 index 00000000000..658507ca1bd --- /dev/null +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.codegen.process.util; + +import java.util.function.Function; + +import org.kie.kogito.codegen.api.Generator; +import org.kie.kogito.codegen.api.context.KogitoBuildContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class CodegenUtil { + + private static final Logger LOG = LoggerFactory.getLogger(CodegenUtil.class); + /** + * Flag used to configure transaction enabling. Default to true + */ + public static final String TRANSACTION_ENABLED = "transactionEnabled"; + + private CodegenUtil() { + // do nothing + } + + /** + * Creates the property for a certain generator. + * + * @param generator + * @param propertyName + * @return returns the property for certain generator + */ + public static String generatorProperty(Generator generator, String propertyName) { + return String.format("kogito.%s.%s", generator.name(), propertyName); + } + + /** + * Creates the property for global application + * + * @param propertyName + * @return + */ + public static String globalProperty(String propertyName) { + return String.format("kogito.%s", propertyName); + } + + /** + * This computes the boolean value of the transaction being enabled based on the logic specified + * the property. Default value it is true + * + * @see CodegenUtil#getProperty + */ + public static boolean isTransactionEnabled(Generator generator, KogitoBuildContext context) { + boolean propertyValue = getProperty(generator, context, TRANSACTION_ENABLED, Boolean::parseBoolean, true); + LOG.info("trying to compute property {} for generator {} property with value {}", TRANSACTION_ENABLED, generator.name(), propertyValue); + return propertyValue; + } + + /** + * This method is a generic method to compute certain property of the given type. + * 1. we compute the global property applicable for all the application. + * 2. we compute the property only applicable for certain generator. + * + * @see CodegenUtil#getApplicationProperty + * @see CodegenUtil#globalProperty + * @see CodegenUtil#generatorProperty + */ + public static T getProperty(Generator generator, KogitoBuildContext context, String propertyName, Function converter, T defaultValue) { + + String generatorProperty = generatorProperty(generator, propertyName); + if (isApplicationPropertyDefined(context, generatorProperty)) { + return converter.apply(getApplicationProperty(context, generatorProperty)); + } + + String globalProperty = globalProperty(propertyName); + + if (isApplicationPropertyDefined(context, globalProperty)) { + return converter.apply(getApplicationProperty(context, globalProperty)); + } + + return defaultValue; + } + + private static boolean isApplicationPropertyDefined(KogitoBuildContext context, String property) { + return context.getApplicationProperty(property).isPresent(); + } + + private static String getApplicationProperty(KogitoBuildContext context, String property) { + return context.getApplicationProperty(property).orElseThrow(() -> new IllegalArgumentException("Property " + property + " defined but does not contain proper value")); + } +} diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java index bd10069d470..c145c332ee1 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java @@ -51,6 +51,7 @@ import org.kie.kogito.codegen.core.AbstractGenerator; import org.kie.kogito.codegen.process.ProcessCodegenException; import org.kie.kogito.codegen.process.ProcessParsingException; +import org.kie.kogito.codegen.process.util.CodegenUtil; import org.kie.kogito.internal.SupportedExtensions; import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess; import org.kie.kogito.process.validation.ValidationException; @@ -62,6 +63,7 @@ import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.expr.CastExpr; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.IntegerLiteralExpr; @@ -97,9 +99,9 @@ public class UserTaskCodegen extends AbstractGenerator { BPMN_SEMANTIC_MODULES.addSemanticModule(new BPMNDISemanticModule()); } - public static final String SECTION_CLASS_NAME = "usertask"; + public static final String SECTION_CLASS_NAME = "usertasks"; - TemplatedGenerator templateGenerator; + private TemplatedGenerator templateGenerator; private List descriptors; private TemplatedGenerator producerTemplateGenerator; private TemplatedGenerator restTemplateGenerator; @@ -158,25 +160,28 @@ protected Collection internalGenerate() { return generatedFiles; } - private GeneratedFile generateRestEndpiont() { + public GeneratedFile generateRestEndpiont() { String packageName = context().getPackageName(); - CompilationUnit compilationUnit = producerTemplateGenerator.compilationUnitOrThrow("Not rest endpoints template found for user tasks"); + CompilationUnit compilationUnit = restTemplateGenerator.compilationUnitOrThrow("Not rest endpoints template found for user tasks"); compilationUnit.setPackageDeclaration(packageName); + if (CodegenUtil.isTransactionEnabled(this, context())) { + compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).forEach(context().getDependencyInjectionAnnotator()::withTransactional); + } String className = compilationUnit.findFirst(ClassOrInterfaceDeclaration.class).get().getNameAsString(); String urlBase = packageName.replaceAll("\\.", File.separator); - return new GeneratedFile(GeneratedFileType.SOURCE, Path.of(urlBase).resolve(className + ".java"), compilationUnit.toString()); + return new GeneratedFile(GeneratedFileType.REST, Path.of(urlBase).resolve(className + ".java"), compilationUnit.toString()); } - private GeneratedFile generateProducer() { + public GeneratedFile generateProducer() { String packageName = context().getPackageName(); - CompilationUnit compilationUnit = restTemplateGenerator.compilationUnitOrThrow("No producer template found for user tasks"); + CompilationUnit compilationUnit = producerTemplateGenerator.compilationUnitOrThrow("No producer template found for user tasks"); compilationUnit.setPackageDeclaration(packageName); String className = compilationUnit.findFirst(ClassOrInterfaceDeclaration.class).get().getNameAsString(); String urlBase = packageName.replaceAll("\\.", File.separator); return new GeneratedFile(GeneratedFileType.SOURCE, Path.of(urlBase).resolve(className + ".java"), compilationUnit.toString()); } - private List generateUserTask() { + public List generateUserTask() { List generatedFiles = new ArrayList<>(); for (Work info : descriptors) { CompilationUnit unit = templateGenerator.compilationUnit().get(); diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessResourceGeneratorTest.java b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessResourceGeneratorTest.java index 52d1665166a..551d5b48b5d 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessResourceGeneratorTest.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessResourceGeneratorTest.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -35,6 +36,8 @@ import org.kie.kogito.codegen.api.AddonsConfig; import org.kie.kogito.codegen.api.context.KogitoBuildContext; import org.kie.kogito.codegen.api.context.impl.JavaKogitoBuildContext; +import org.kie.kogito.codegen.process.util.CodegenUtil; +import org.kie.kogito.codegen.usertask.UserTaskCodegen; import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess; import com.github.javaparser.StaticJavaParser; @@ -209,6 +212,84 @@ void testManageTransactionalDisabled(KogitoBuildContext.Builder contextBuilder) testTransaction(restEndpoints, kogitoBuildContext, transactionEnabled); } + @ParameterizedTest + @MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#restContextBuilders") + void testUserTaskManageTransactionalEnabledByDefault(KogitoBuildContext.Builder contextBuilder) { + KogitoBuildContext context = contextBuilder.build(); + UserTaskCodegen userTaskCodegen = new UserTaskCodegen(context, Collections.emptyList()); + CompilationUnit compilationUnit = StaticJavaParser.parse(new String(userTaskCodegen.generateRestEndpiont().contents())); + List restEndpoints = compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).toList(); + assertThat(restEndpoints).isNotEmpty(); + for (MethodDeclaration method : restEndpoints) { + assertThat(findAnnotationExpr(method, context.getDependencyInjectionAnnotator().getTransactionalAnnotation())).isPresent(); + } + } + + @ParameterizedTest + @MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#restContextBuilders") + void testUserTaskManageTransactionalDisabled(KogitoBuildContext.Builder contextBuilder) { + KogitoBuildContext context = contextBuilder.build(); + UserTaskCodegen userTaskCodegen = new UserTaskCodegen(context, Collections.emptyList()); + context.setApplicationProperty(CodegenUtil.generatorProperty(userTaskCodegen, CodegenUtil.TRANSACTION_ENABLED), "false"); + CompilationUnit compilationUnit = StaticJavaParser.parse(new String(userTaskCodegen.generateRestEndpiont().contents())); + List restEndpoints = compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).toList(); + assertThat(restEndpoints).isNotEmpty(); + for (MethodDeclaration method : restEndpoints) { + assertThat(findAnnotationExpr(method, context.getDependencyInjectionAnnotator().getTransactionalAnnotation())).isEmpty(); + } + } + + @ParameterizedTest + @MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#restContextBuilders") + void testUserTaskManageTransactionalGeneratorDisabled(KogitoBuildContext.Builder contextBuilder) { + KogitoBuildContext context = contextBuilder.build(); + UserTaskCodegen userTaskCodegen = new UserTaskCodegen(context, Collections.emptyList()); + context.setApplicationProperty(CodegenUtil.generatorProperty(userTaskCodegen, CodegenUtil.TRANSACTION_ENABLED), "false"); + CompilationUnit compilationUnit = StaticJavaParser.parse(new String(userTaskCodegen.generateRestEndpiont().contents())); + List restEndpoints = compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).toList(); + assertThat(restEndpoints).isNotEmpty(); + for (MethodDeclaration method : restEndpoints) { + assertThat(findAnnotationExpr(method, context.getDependencyInjectionAnnotator().getTransactionalAnnotation())).isEmpty(); + } + } + + @ParameterizedTest + @MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#restContextBuilders") + void testUserTaskManageTransactionalEnabled(KogitoBuildContext.Builder contextBuilder) { + KogitoBuildContext context = contextBuilder.build(); + UserTaskCodegen userTaskCodegen = new UserTaskCodegen(context, Collections.emptyList()); + context.setApplicationProperty(CodegenUtil.generatorProperty(userTaskCodegen, CodegenUtil.TRANSACTION_ENABLED), "true"); + CompilationUnit compilationUnit = StaticJavaParser.parse(new String(userTaskCodegen.generateRestEndpiont().contents())); + List restEndpoints = compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).toList(); + assertThat(restEndpoints).isNotEmpty(); + for (MethodDeclaration method : restEndpoints) { + assertThat(findAnnotationExpr(method, context.getDependencyInjectionAnnotator().getTransactionalAnnotation())).isPresent(); + } + } + + @ParameterizedTest + @MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#restContextBuilders") + void testUserTaskManageTransactionalGeneratorEnabled(KogitoBuildContext.Builder contextBuilder) { + KogitoBuildContext context = contextBuilder.build(); + UserTaskCodegen userTaskCodegen = new UserTaskCodegen(context, Collections.emptyList()); + context.setApplicationProperty(CodegenUtil.generatorProperty(userTaskCodegen, CodegenUtil.TRANSACTION_ENABLED), "true"); + CompilationUnit compilationUnit = StaticJavaParser.parse(new String(userTaskCodegen.generateRestEndpiont().contents())); + List restEndpoints = compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).toList(); + assertThat(restEndpoints).isNotEmpty(); + for (MethodDeclaration method : restEndpoints) { + assertThat(findAnnotationExpr(method, context.getDependencyInjectionAnnotator().getTransactionalAnnotation())).isPresent(); + } + } + + Optional findAnnotationExpr(MethodDeclaration method, String fqn) { + for (AnnotationExpr expr : method.getAnnotations()) { + if (expr.getNameAsString().equals(fqn)) { + return Optional.of(expr); + } + } + return Optional.empty(); + } + void testTransaction(Collection restEndpoints, KogitoBuildContext kogitoBuildContext, boolean enabled) {