Skip to content

Commit

Permalink
KOGITO-9415 Added Live Reload support for CodeGenProviders (#3105)
Browse files Browse the repository at this point in the history
* KOGITO-9415 Added Live Reload support for CodeGenProviders

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

* Update quarkus/extensions/kogito-quarkus-extension-common/kogito-quarkus-common-deployment/src/main/java/org/kie/kogito/quarkus/common/deployment/KogitoQuarkusResourceUtils.java

https://github.com/kiegroup/kogito-runtimes/pull/3105#discussion_r1254162917

Co-authored-by: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com>

* KOGITO-9415 Francisco's review

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

* Update quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/pom.xml

Co-authored-by: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com>

* KOGITO-9415 Rebase

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

* KOGITO-9415 added kogito-serverless-workflow-openapi-generated as dependency to integration test

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

* KOGITO-9415 added kogito-quarkus-workflow-common-deployment as dependency to integration test

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

* KOGITO-9415 added kogito-addons-quarkus-common-deployment as dependency to integration test

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

* KOGITO-9415 added kogito-quarkus-serverless-workflow-deployment as dependency to integration test

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

* KOGITO-9415 Modified package of model in integration test

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

* KOGITO-9415 Fixed sonar warning

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

* Update quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-extension-live-reload-test/src/test/java/org/kie/kogito/quarkus/serverless/workflow/deployment/livereload/LiveReloadProcessorTest.java

Co-authored-by: Tristan Radisson <tristan.radisson@gmail.com>

* KOGITO-9415 Added disabled test for AsyncAPI

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

* KOGITO-9415 Fixed test for OpenAPI

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>

---------

Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>
Co-authored-by: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com>
Co-authored-by: Tristan Radisson <tristan.radisson@gmail.com>
3 people authored Jul 24, 2023
1 parent 5415b30 commit 0e04e3b
Showing 23 changed files with 1,207 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ public void executeWorkItem(KogitoWorkItem workItem, KogitoWorkItemManager manag

protected abstract Object internalExecute(KogitoWorkItem workItem, Map<String, Object> parameters);

protected final <V> V buildBody(Map<String, Object> params, Class<V> clazz) {
protected static <V> V buildBody(Map<String, Object> params, Class<V> clazz) {
for (Object obj : params.values()) {
if (obj != null && clazz.isAssignableFrom(obj.getClass())) {
return clazz.cast(obj);
Original file line number Diff line number Diff line change
@@ -162,13 +162,16 @@ public static void registerResources(Collection<GeneratedFile> generatedFiles,
}

public static IndexView generateAggregatedIndex(IndexView baseIndex, List<KogitoGeneratedClassesBuildItem> generatedKogitoClasses) {
List<IndexView> indexes = new ArrayList<>();
indexes.add(baseIndex);

indexes.addAll(generatedKogitoClasses.stream()
return generateAggregatedIndexNew(baseIndex, generatedKogitoClasses.stream()
.map(KogitoGeneratedClassesBuildItem::getIndexedClasses)
.collect(Collectors.toList()));
return CompositeIndex.create(indexes.toArray(new IndexView[0]));
}

public static IndexView generateAggregatedIndexNew(IndexView baseIndex, List<IndexView> newIndexViews) {
List<IndexView> indexes = new ArrayList<>();
indexes.add(baseIndex);
indexes.addAll(newIndexViews);
return CompositeIndex.create(indexes);
}

public static Path getTargetClassesPath(AppPaths appPaths) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.quarkus.common.deployment;

import org.jboss.jandex.IndexView;

import io.quarkus.builder.item.SimpleBuildItem;

public final class LiveReloadExecutionBuildItem extends SimpleBuildItem {

private final IndexView indexView;

public LiveReloadExecutionBuildItem(IndexView indexView) {
this.indexView = indexView;
}

public IndexView getIndexView() {
return indexView;
}
}
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@
import org.kie.kogito.process.expr.ExpressionHandler;
import org.kie.kogito.quarkus.common.deployment.KogitoAddonsPreGeneratedSourcesBuildItem;
import org.kie.kogito.quarkus.common.deployment.KogitoBuildContextBuildItem;
import org.kie.kogito.quarkus.common.deployment.LiveReloadExecutionBuildItem;
import org.kie.kogito.quarkus.extensions.spi.deployment.KogitoProcessContainerGeneratorBuildItem;
import org.kie.kogito.quarkus.serverless.workflow.WorkflowHandlerGeneratedFile;
import org.kie.kogito.quarkus.serverless.workflow.WorkflowHandlerGenerator;
@@ -86,9 +87,9 @@ NativeImageResourceBuildItem addExpressionHandlers(BuildProducer<ServiceProvider
}

@BuildStep
void addWorkItemHandlers(KogitoBuildContextBuildItem contextBI, CombinedIndexBuildItem indexBuildItem, BuildProducer<KogitoAddonsPreGeneratedSourcesBuildItem> sources) {
void addWorkItemHandlers(KogitoBuildContextBuildItem contextBI, LiveReloadExecutionBuildItem liveReloadExecutionBuildItem, BuildProducer<KogitoAddonsPreGeneratedSourcesBuildItem> sources) {
KogitoBuildContext context = contextBI.getKogitoBuildContext();
IndexView index = indexBuildItem.getIndex();
IndexView index = liveReloadExecutionBuildItem.getIndexView();
Collection<GeneratedFile> generatedFiles = new ArrayList<>();
for (WorkflowHandlerGenerator generator : generators) {
for (WorkflowHandlerGeneratedFile generated : generator.generateHandlerClasses(context, index)) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
* 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.quarkus.serverless.workflow.deployment.livereload;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Stream;

import javax.inject.Inject;

import org.drools.codegen.common.GeneratedFile;
import org.drools.codegen.common.GeneratedFileType;
import org.drools.drl.quarkus.util.deployment.DroolsQuarkusResourceUtils;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Indexer;
import org.kie.kogito.codegen.api.context.KogitoBuildContext;
import org.kie.kogito.quarkus.common.deployment.KogitoAddonsPreGeneratedSourcesBuildItem;
import org.kie.kogito.quarkus.common.deployment.KogitoBuildContextBuildItem;
import org.kie.kogito.quarkus.common.deployment.KogitoQuarkusResourceUtils;
import org.kie.kogito.quarkus.common.deployment.LiveReloadExecutionBuildItem;

import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.bootstrap.model.ApplicationModel;
import io.quarkus.bootstrap.prebuild.CodeGenException;
import io.quarkus.deployment.CodeGenContext;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.LiveReloadBuildItem;
import io.quarkus.deployment.index.IndexingUtil;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;

/**
* This class adds live reload support for {@link io.quarkus.deployment.CodeGenProvider} objects.
*/
public class LiveReloadProcessor {

private final LiveReloadBuildItem liveReloadBuildItem;

private final ApplicationModel applicationModel;

private final Path workDir;

private final IndexView computingIndex;

private final IndexView index;

private final KogitoBuildContext kogitoBuildContext;

@Inject
public LiveReloadProcessor(
CombinedIndexBuildItem combinedIndexBuildItem,
LiveReloadBuildItem liveReloadBuildItem,
CurateOutcomeBuildItem curateOutcomeBuildItem,
OutputTargetBuildItem outputTargetBuildItem,
KogitoBuildContextBuildItem contextBuildItem) {
this.liveReloadBuildItem = liveReloadBuildItem;
applicationModel = curateOutcomeBuildItem.getApplicationModel();
workDir = outputTargetBuildItem.getOutputDirectory();
computingIndex = combinedIndexBuildItem.getComputingIndex();
index = combinedIndexBuildItem.getIndex();
kogitoBuildContext = contextBuildItem.getKogitoBuildContext();
}

@BuildStep(onlyIf = IsDevelopment.class)
public LiveReloadExecutionBuildItem liveReload(BuildProducer<KogitoAddonsPreGeneratedSourcesBuildItem> sourcesProducer) {
Collection<GeneratedFile> generatedFiles = new ArrayList<>();
List<IndexView> indexViews = new ArrayList<>();
if (liveReloadBuildItem.isLiveReload()) {
if (shouldSkipLiveReload()) {
dontSkipNextLiveReload();
} else {
ServiceLoader.load(LiveReloadableCodeGenProvider.class).stream()
.map(ServiceLoader.Provider::get)
.map(this::generateCode)
.forEach(codeGenerationResult -> {
generatedFiles.addAll(codeGenerationResult.getGeneratedFiles());
indexViews.add(codeGenerationResult.getIndexView());
});
}
}
if (!generatedFiles.isEmpty()) {
sourcesProducer.produce(new KogitoAddonsPreGeneratedSourcesBuildItem(generatedFiles));
skipNextLiveReload();
return new LiveReloadExecutionBuildItem(KogitoQuarkusResourceUtils.generateAggregatedIndexNew(computingIndex, indexViews));
} else {
dontSkipNextLiveReload();
return new LiveReloadExecutionBuildItem(computingIndex);
}
}

private CodeGenerationResult generateCode(LiveReloadableCodeGenProvider codeGenProvider) {
try {
Collection<GeneratedFile> generatedFiles = new ArrayList<>(generateSources(codeGenProvider));
return !generatedFiles.isEmpty() ? new CodeGenerationResult(generatedFiles, indexCompiledSources(compileGeneratedSources(generatedFiles)))
: new CodeGenerationResult(List.of(), computingIndex);
} catch (CodeGenException e) {
throw new IllegalStateException(e);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private IndexView indexCompiledSources(Collection<GeneratedBeanBuildItem> generatedBeanBuildItems) {
Indexer kogitoIndexer = new Indexer();

for (GeneratedBeanBuildItem generatedBeanBuildItem : generatedBeanBuildItems) {
IndexingUtil.indexClass(
generatedBeanBuildItem.getName(),
kogitoIndexer,
index,
new HashSet<>(),
kogitoBuildContext.getClassLoader(),
generatedBeanBuildItem.getData());
}

return kogitoIndexer.complete();
}

private Collection<GeneratedBeanBuildItem> compileGeneratedSources(Collection<GeneratedFile> sources) {
return DroolsQuarkusResourceUtils.compileGeneratedSources(
kogitoBuildContext,
applicationModel.getRuntimeDependencies(),
sources,
true);
}

private Collection<GeneratedFile> generateSources(LiveReloadableCodeGenProvider codeGenProvider)
throws CodeGenException, IOException {
Path outDir = workDir.resolve("generated-sources").resolve(codeGenProvider.providerId());
Collection<GeneratedFile> generatedFiles = new ArrayList<>();
Config config = ConfigProvider.getConfig();
for (Path sourcePath : kogitoBuildContext.getAppPaths().getSourcePaths()) {
Path inputDir = sourcePath.resolve("main").resolve(codeGenProvider.inputDirectory());
CodeGenContext codeGenContext = new CodeGenContext(applicationModel, outDir, workDir, inputDir, false, config, false);
if (codeGenProvider.shouldRun(inputDir, config) && codeGenProvider.trigger(codeGenContext)) {
try (Stream<Path> sources = Files.walk(outDir)) {
sources.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".java"))
.map(path -> {
try {
return new GeneratedFile(GeneratedFileType.SOURCE, outDir.relativize(path), Files.readAllBytes(path));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
})
.forEach(generatedFiles::add);
}
}
}

return generatedFiles;
}

private void skipNextLiveReload() {
liveReloadBuildItem.setContextObject(SkipLiveReload.class, SkipLiveReload.TRUE);
}

private void dontSkipNextLiveReload() {
liveReloadBuildItem.setContextObject(SkipLiveReload.class, SkipLiveReload.FALSE);
}

private boolean shouldSkipLiveReload() {
if (liveReloadBuildItem.getContextObject(SkipLiveReload.class) != null) {
return liveReloadBuildItem.getContextObject(SkipLiveReload.class) == SkipLiveReload.TRUE;
}
return false;
}

@BuildStep(onlyIfNot = IsDevelopment.class)
public LiveReloadExecutionBuildItem executeWhenNotDevelopment() {
return new LiveReloadExecutionBuildItem(computingIndex);
}

private static class CodeGenerationResult {

private final Collection<GeneratedFile> generatedFiles;

private final IndexView indexView;

CodeGenerationResult(Collection<GeneratedFile> generatedFiles, IndexView indexView) {
this.generatedFiles = generatedFiles;
this.indexView = indexView;
}

Collection<GeneratedFile> getGeneratedFiles() {
return generatedFiles;
}

IndexView getIndexView() {
return indexView;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.quarkus.serverless.workflow.deployment.livereload;

import io.quarkiverse.asyncapi.generator.input.AsyncApiGeneratorStreamCodeGen;

/**
* Wrapper for {@link AsyncApiGeneratorStreamCodeGen} that implements the {@link LiveReloadableCodeGenProvider} Service Provider Interface.
*/
public class LiveReloadableAsyncApiGeneratorStreamCodeGen extends LiveReloadableCodeGenProviderBase<AsyncApiGeneratorStreamCodeGen> {

public LiveReloadableAsyncApiGeneratorStreamCodeGen() {
super(new AsyncApiGeneratorStreamCodeGen());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.quarkus.serverless.workflow.deployment.livereload;

import java.nio.file.Path;

import org.eclipse.microprofile.config.Config;

import io.quarkus.bootstrap.prebuild.CodeGenException;
import io.quarkus.deployment.CodeGenContext;

/**
* Service Provider Interface for {@link io.quarkus.deployment.CodeGenProvider} objects that need to be invoked on live reloads.
*/
interface LiveReloadableCodeGenProvider {

boolean trigger(CodeGenContext context) throws CodeGenException;

String providerId();

String inputDirectory();

boolean shouldRun(Path sourceDir, Config config);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.quarkus.serverless.workflow.deployment.livereload;

import java.nio.file.Path;

import org.eclipse.microprofile.config.Config;

import io.quarkus.bootstrap.prebuild.CodeGenException;
import io.quarkus.deployment.CodeGenContext;
import io.quarkus.deployment.CodeGenProvider;

abstract class LiveReloadableCodeGenProviderBase<T extends CodeGenProvider> implements LiveReloadableCodeGenProvider {

private final T delegate;

LiveReloadableCodeGenProviderBase(T delegate) {
this.delegate = delegate;
}

@Override
public final boolean trigger(CodeGenContext context) throws CodeGenException {
return delegate.trigger(context);
}

@Override
public String inputDirectory() {
return delegate.inputDirectory();
}

@Override
public String providerId() {
return delegate.providerId();
}

@Override
public boolean shouldRun(Path sourceDir, Config config) {
return delegate.shouldRun(sourceDir, config);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.quarkus.serverless.workflow.deployment.livereload;

import io.quarkiverse.openapi.generator.deployment.codegen.OpenApiGeneratorStreamCodeGen;

/**
* Wrapper for {@link OpenApiGeneratorStreamCodeGen} that implements the {@link LiveReloadableCodeGenProvider} Service Provider Interface.
*/
public class LiveReloadableOpenApiGeneratorStreamCodeGen extends LiveReloadableCodeGenProviderBase<OpenApiGeneratorStreamCodeGen> {

public LiveReloadableOpenApiGeneratorStreamCodeGen() {
super(new OpenApiGeneratorStreamCodeGen());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.quarkus.serverless.workflow.deployment.livereload;

import org.kie.kogito.quarkus.serverless.workflow.rpc.WorkflowRPCCodeGenProvider;

/**
* Wrapper for {@link WorkflowRPCCodeGenProvider} that implements the {@link LiveReloadableCodeGenProvider} Service Provider Interface.
*/
public class LiveReloadableWorkflowRPCCodeGenProvider extends LiveReloadableCodeGenProviderBase<WorkflowRPCCodeGenProvider> {

public LiveReloadableWorkflowRPCCodeGenProvider() {
super(new WorkflowRPCCodeGenProvider());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.quarkus.serverless.workflow.deployment.livereload;

enum SkipLiveReload {

TRUE,
FALSE
}
Original file line number Diff line number Diff line change
@@ -44,7 +44,6 @@
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.NullLiteralExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.expr.SuperExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
@@ -107,7 +106,7 @@ private WorkflowHandlerGeneratedFile generateHandler(KogitoBuildContext context,
if (annotation != null) {
methodCallExpr.addArgument(new CastExpr(fromClass(param), new MethodCallExpr(parameters, "remove").addArgument(new StringLiteralExpr(annotation.value().asString()))));
} else {
methodCallExpr.addArgument(new MethodCallExpr(new SuperExpr(), "buildBody").addArgument(parameters).addArgument(new ClassExpr(fromClass(param))));
methodCallExpr.addArgument(new MethodCallExpr("buildBody").addArgument(parameters).addArgument(new ClassExpr(fromClass(param))));
}
}
clazz.addMethod("getRestClass", Keyword.PROTECTED).setType(parseClassOrInterfaceType(Class.class.getCanonicalName()).setTypeArguments(classNameType))
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
org.kie.kogito.quarkus.serverless.workflow.deployment.livereload.LiveReloadableAsyncApiGeneratorStreamCodeGen
org.kie.kogito.quarkus.serverless.workflow.deployment.livereload.LiveReloadableOpenApiGeneratorStreamCodeGen
org.kie.kogito.quarkus.serverless.workflow.deployment.livereload.LiveReloadableWorkflowRPCCodeGenProvider
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-quarkus-serverless-workflow-extension</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>

<artifactId>kogito-quarkus-serverless-workflow-extension-live-reload-test</artifactId>
<name>Kogito :: Quarkus Workflows Extension :: Image Integration Tests</name>

<dependencies>
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-quarkus-serverless-workflow</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>

<dependency>
<groupId>io.quarkiverse.asyncapi</groupId>
<artifactId>quarkus-asyncapi</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-reactive-messaging-kafka</artifactId>
</dependency>

<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${version.io.grpc}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${version.io.grpc}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${version.io.grpc}</version>
</dependency>
<dependency>
<!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-test-utils</artifactId>
<scope>test</scope>
</dependency>

<!-- these are used implicitly by quarkus tests so let's make Maven aware of them -->
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-serverless-workflow-openapi-generated</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-addons-quarkus-common-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-quarkus-workflow-common-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-quarkus-serverless-workflow-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${version.io.quarkus}</version>
<extensions>true</extensions>
<configuration>
<skip>${skipTests}</skip>
</configuration>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<systemPropertyVariables combine.children="append">
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<container.image.kafka>${container.image.kafka}</container.image.kafka>
<kogito.version>${project.version}</kogito.version>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>${project.basedir}/src/main/proto</directory>
<includes>
<include>greeting.proto</include>
</includes>
</resource>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* 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.quarkus.serverless.workflow.deployment.livereload;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.kie.kogito.quarkus.serverless.workflow.deployment.livereload.Greeting.HelloReply;
import org.kie.kogito.quarkus.serverless.workflow.deployment.livereload.Greeting.HelloRequest;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import io.quarkus.grpc.GrpcService;

@GrpcService
public class GreeterService extends GreeterGrpc.GreeterImplBase {

protected static final String[] SUPPORTED_LANGUAGES = { "English", "Spanish" };

public static void main(String[] args) throws IOException, InterruptedException {
Server server = buildServer(Integer.getInteger("grpc.port", 50051));
server.start();
server.awaitTermination();
}

public static Server buildServer(int port) {
return ServerBuilder.forPort(port).addService(new GreeterService()).build();
}

@Override
public void sayHello(Greeting.HelloRequest request,
StreamObserver<HelloReply> responseObserver) {
responseObserver.onNext(HelloReply.newBuilder().setMessage(getMessage(request)).build());
responseObserver.onCompleted();
}

@Override
public void sayHelloAllLanguages(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
for (String language : SUPPORTED_LANGUAGES) {
HelloRequest languageRequest = HelloRequest.newBuilder(request).setLanguage(language).build();
responseObserver.onNext(HelloReply.newBuilder().setMessage(getMessage(languageRequest)).build());
}
responseObserver.onCompleted();
}

@Override
public StreamObserver<HelloRequest> sayHelloMultipleLanguagesAtOnce(StreamObserver<HelloReply> responseObserver) {
return new StreamObserver<>() {

private final List<String> messages = new ArrayList<>();

@Override
public void onNext(HelloRequest helloRequest) {
messages.add(getMessage(helloRequest));
}

@Override
public void onError(Throwable throwable) {
// ignore
}

@Override
public void onCompleted() {
responseObserver.onNext(HelloReply.newBuilder().setMessage(String.join("\n", messages)).build());
responseObserver.onCompleted();
}
};
}

@Override
public StreamObserver<HelloRequest> sayHelloMultipleLanguages(StreamObserver<HelloReply> responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(HelloRequest helloRequest) {
responseObserver.onNext(HelloReply.newBuilder().setMessage(getMessage(helloRequest)).build());
}

@Override
public void onError(Throwable throwable) {
// ignore
}

@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}

@Override
public StreamObserver<HelloRequest> sayHelloMultipleLanguagesError(StreamObserver<HelloReply> responseObserver) {
return new StreamObserver<>() {
int counter;

@Override
public void onNext(HelloRequest helloRequest) {
counter++;
if (counter == 2) {
responseObserver.onNext(HelloReply.newBuilder().setMessage(getMessage(helloRequest)).build());
RuntimeException ex = Status.OUT_OF_RANGE.asRuntimeException();
responseObserver.onError(ex);
} else if (counter < 2) {
responseObserver.onNext(HelloReply.newBuilder().setMessage(getMessage(helloRequest)).build());
}
}

@Override
public void onError(Throwable throwable) {
// ignore
}

@Override
public void onCompleted() {
if (counter < 2) {
responseObserver.onCompleted();
}
}
};
}

private static String getMessage(HelloRequest request) {
String message;
switch (request.getLanguage().toLowerCase()) {
case "spanish":
message = "Saludos desde gRPC service " + request.getName();
break;
case "english":
default:
message = "Hello from gRPC service " + request.getName();
}
return message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
syntax = "proto3";

option java_package="org.kie.kogito.quarkus.serverless.workflow.deployment.livereload";



// The greeter service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloAllLanguages (HelloRequest) returns (stream HelloReply) {}
rpc SayHelloMultipleLanguagesAtOnce (stream HelloRequest) returns (HelloReply) {}
rpc SayHelloMultipleLanguages (stream HelloRequest) returns (stream HelloReply) {}
rpc SayHelloMultipleLanguagesError (stream HelloRequest) returns (stream HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
string language=2;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
asyncapi: '2.0.0'
id: 'oneConsumer'
info:
title: Kafka Application
version: '1.0.0'
description: Kafka Application
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0
servers:
production:
url: localhost:9092
description: Development server
protocol: kafka
protocolVersion: '1.0.0'
channels:
wait:
description: A message channel
subscribe:
operationId: wait
summary: Get messages
message:
$ref: '#/components/messages/message'
publish:
operationId: sendWait
summary: Send messages
message:
$ref: '#/components/messages/message'
components:
messages:
message:
name: message
title: A message
summary: A message
contentType: application/json
payload:
$ref: "#/components/schemas/message"
schemas:
message:
type: object
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
openapi: 3.0.3
info:
title: echo
version: '1.0.0'
description: ""
paths:
/echo:
post:
summary: Echo
operationId: echo
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Echo'
components:
schemas:
Echo:
type: object
properties:
echoedMsgType:
type: string
Message:
type: object
required:
- msgType
properties:
msgType:
type: string
enum:
- 'text'
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* 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.quarkus.serverless.workflow.deployment.livereload;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.kie.kogito.test.utils.SocketUtils;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;

import io.grpc.Server;
import io.quarkus.test.QuarkusDevModeTest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static io.restassured.RestAssured.given;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;

public class LiveReloadProcessorTest {

private static final int PORT = SocketUtils.findAvailablePort();

@RegisterExtension
public final static QuarkusDevModeTest test = createTest();

private static WireMockServer wireMockServer;

private static QuarkusDevModeTest createTest() {
configureWiremockServer();

return new QuarkusDevModeTest()
.withApplicationRoot(jar -> {
try {
jar.addAsResource(new StringAsset(applicationProperties(wireMockServer.baseUrl())), "/application.properties");
jar.add(new StringAsset(new String(Files.readAllBytes(Path.of("src/main/proto/greeting.proto")))), "src/main/proto/greeting.proto");
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}

private static void configureWiremockServer() {
wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().extensions(new ResponseTemplateTransformer(false)).dynamicPort());
wireMockServer.start();

wireMockServer.stubFor(post(urlEqualTo("/echo"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{ \"echoedMsgType\": \"{{jsonPath request.body '$.msgType'}}\"}")
.withTransformers("response-template")));
}

private static String applicationProperties(String wireMockBaseUrl) {
return Stream.of(
"quarkus.rest-client.\"enum_parameter_yaml\".url=" + wireMockBaseUrl,
"quarkus.grpc.clients.Greeter.host=localhost",
"quarkus.grpc.clients.Greeter.port=" + PORT,
"quarkus.grpc.server.port=" + PORT,
"quarkus.grpc.server.test-port=" + PORT)
.collect(Collectors.joining(System.lineSeparator()));
}

@AfterAll
static void tearDown() {
if (wireMockServer != null) {
wireMockServer.stop();
}
}

@Test
void testOpenApi() throws IOException {
given()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.when()
.body(Map.of())
.post("/openapienumparameter")
.then()
.statusCode(404);

try (FileInputStream inputStream = new FileInputStream("src/test/resources/openAPIEnumParameter.sw.json")) {
test.addResourceFile("openAPIEnumParameter.sw.json", new String(Objects.requireNonNull(inputStream).readAllBytes()));
}

given()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.when()
.body(Map.of())
.post("/openapienumparameter")
.then()
.statusCode(201)
.body("workflowdata.echoedMsgType", is("text"));
}

@Test
void testGrpc() throws InterruptedException, IOException {
Server server = GreeterService.buildServer(PORT);
server.start();
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();

try {
given()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.body("{\"name\" : \"John\", \"language\":\"English\"}").when()
.post("/jsongreet")
.then()
.statusCode(404);

try (FileInputStream inputStream = new FileInputStream("src/test/resources/rpcgreet.sw.json")) {
test.addResourceFile("rpcgreet.sw.json", new String(Objects.requireNonNull(inputStream).readAllBytes()));
}

given()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.body("{\"name\" : \"John\", \"language\":\"English\"}").when()
.post("/jsongreet")
.then()
.statusCode(201)
.body("workflowdata.message", containsString("Hello"));
} finally {
server.shutdownNow();
server.awaitTermination();
}
}

@Test
@Disabled("Disabled until https://issues.redhat.com/browse/KOGITO-9614 is resolved")
void testAsyncApi() throws IOException {
given()
.contentType(ContentType.JSON)
.when()
.body(Collections.singletonMap("workflowdata", Collections.emptyMap()))
.post("/asyncEventPublisher")
.then()
.statusCode(404);

try (FileInputStream inputStream = new FileInputStream("src/test/resources/asyncPublisher.sw.json")) {
test.addResourceFile("asyncPublisher.sw.json", new String(Objects.requireNonNull(inputStream).readAllBytes()));
}

String id = given()
.contentType(ContentType.JSON)
.when()
.body(Collections.singletonMap("workflowdata", Collections.emptyMap()))
.post("/asyncEventPublisher")
.then()
.statusCode(201)
.extract().path("id");

assertThat(id).isNotBlank();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"id": "asyncEventPublisher",
"version": "1.0",
"name": "Workflow async consumer test",
"description": "An test that verifies an async api spec file with a publish channel is really publishing",
"start": "publishEvent",
"functions": [
{
"name": "publishEvent",
"type": "asyncapi",
"operation": "specs/asyncAPI.yaml#sendWait"
}
],
"states": [
{
"name": "publishEvent",
"type": "operation",
"actions": [
{
"name": "publishEvent",
"functionRef": "publishEvent"
}
],
"end": true
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"id": "openapienumparameter",
"name": "OpenAPI Enum Parameter",
"description": "Workflow that sends an Enum parameter to an echo service",
"start": "Start",
"functions": [
{
"name": "echoFunction",
"operation": "specs/enum-parameter.yaml#echo"
}
],
"states": [
{
"name": "Start",
"type": "inject",
"data": {
"msgType": "text"
},
"transition": "Echo"
},
{
"name": "Echo",
"type": "operation",
"actions": [
{
"name": "echoAction",
"functionRef": {
"refName": "echoFunction",
"arguments": {
"msgType": "${ .msgType }"
}
}
}
],
"end": true
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"id": "jsongreet",
"version": "1.0",
"name": "gRPC Greeting workflow",
"description": "JSON based greeting workflow using gRPC",
"start": "GreetPerson",
"functions": [
{
"name": "sayHello",
"type": "rpc",
"operation": "greeting.proto#Greeter#SayHello"
}
],
"states": [
{
"name": "GreetPerson",
"type": "operation",
"actions": [
{
"name": "sayHello",
"functionRef" : {
"refName": "sayHello",
"arguments": {
"name": ".name",
"language": ".language"
}
}
}
],
"end": true
}
]
}
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
<module>kogito-quarkus-serverless-workflow-deployment</module>
<module>kogito-quarkus-serverless-workflow-integration-test</module>
<module>kogito-quarkus-serverless-workflow-image-integration-test</module>
<module>kogito-quarkus-serverless-workflow-extension-live-reload-test</module>
</modules>

</project>

0 comments on commit 0e04e3b

Please sign in to comment.