blob: 99d5156f98ddcd5eb9795a69f61b7b33f9d2ba44 [file] [log] [blame] [view]
# Kogito Codegen
This repository contains the shared (Maven Plug-In, Quarkus Extension, ...)
code generation logic for Kogito: processes, rules, decisions, etc.
The structure of the module is:
- `kogito-codegen-api`: shared API necessary to implement a generator
- `kogito-codegen-core`: common core classes used to wire the generation process together
- `kogito-codegen-*`: module specific generator for processes, rules, etc
- `kogito-codegen-sample`: reference module with a sample implementation of a generator (with a simple -runtime too)
- `kogito-codegen-integration-tests`: integration test modules where it is possible not only to assert on generated source code but also compile it and execute
| NOTE: the `kogito-codegen-integration-tests` module should be only used to test generate engine classes and it is not intended to be used for full end to end test (use `integration-tests` module instead) |
| ---- |
## Generator API
- Each component (process, rules, etc.) implements the `Generator`
interface
- `Generator`s are plugged into the `ApplicationGenerator` instance (in the `core` module)
- Upon construction, a `Generator` is given the path(s) of the directory/files
that it must process. Scanning of the directory take place contextually
- Each `Generator` may come with its own specific configuration
- Each `Generator` can delegate to a subcomponent, to process a single
component. E.g., the `ProcessCodegen` can
delegate to a `ProcessGenerator` to work on a single process; `RuleCodegen`
can delegate to a `RuleUnitGenerator`, etc.
note: naming convention may vary in the future
- Generators **do not** write files to disk, rather return a `GeneratedFile`
instance, with the relative file path (derived from the original path
and further analysis on the contents of the file) and the byte array
of the contents of the file to be dumped to disk.
- `KogitoBuildContext` contains all shared information about the build: it is
platform specific (Quarkus/Spring/Java) and it is shared by all the Generators
- `GeneratorFactory` is an interface a generator can implement and together with SPI
is used to automatically wire the generator (see `META-INF/services/org.kie.kogito.codegen.api.GeneratorFactory`)
## Core module
- `ApplicationGenerator` is the main entry point. The fluent API allows to
configure its global behavior.
```java
ApplicationGenerator appGen =
new ApplicationGenerator(context);
```
- The `ApplicationGenerator#generate()` method starts the code generation
procedure, delegating to each `Generator` where appropriate.
### Generator wiring
The wiring of the generators can be done manually invoking the `setupGenerator` of
`ApplicationGenerator` class
```java
appGen.setupGenerator(RuleCodegen.ofPath(context, ruleSourceDirectory));
appGen.setupGenerator(ProcessCodegen.ofPath(context, processSourceDirectory));
```
Or it can be done via SPI using `ApplicationGeneratorDiscovery` utility class that
automatically loads the main `ApplicationGenerator` and the generators for the available components.
| NOTE: both Spring and Quarkus integration use SPI and `ApplicationGeneratorDiscovery` for automatic wiring |
| ---- |
## Sample generator
This generator is intended to be a prototype/reference implementation of a simple generator that
consumes `.txt` files and exposes the content as REST endpoint.
The example is formed by a `-runtime` module that contains the "engine" implementation (e.g. DMN runtime) and a
`-generator` module with the generation/wiring logic
## Testing a generator
A generator is a component that produces source code or resources. The only real and final test of a generator
is an integration test where it is possible to mimic the whole flow from file to endpoint.
At the same time if there is an error in the generated code this could be quite hard to debug: it could fail during the compilation
or follow an unexpected codepath.
For this reason it is strongly suggested to also implement unit tests for each of the generator classes.
You can check sample generator for a full example but in general the rules are:
- Avoid/minimize assertions on number of files because hard to debug. If you need a similar assertion try to filter generated
resources by resource type before
- Test the generation with all the build context (Spring/Quarkus/Java): you can use `@ParameterizedTest` to help with this:
add following dependencies
```xml
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-codegen-api</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
```
and use `KogitoContextTestUtils` utility
```java
@ParameterizedTest
@MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#contextBuilders")
void test(KogitoBuildContext.Builder contextBuilder) {
KogitoBuildContext context = contextBuilder.build();
...
}
```
- If your generator use `context.hasClassAvailable` method to introspect the classloader you can use
`org.kie.kogito.codegen.KogitoBuildContextTestUtils.mockClassAvailabilityResolver` method to inject
(in `KogitoBuildContext.Builder`) your expectation
```java
contextBuilder.withClassAvailabilityResolver(
mockClassAvailabilityResolver(
includedClasses, excludedClasses));
```
- Avoid (when possible) assertion on full generated statements and prefer more general assertions like:
the block should contain `this` element, or the method should have an annotation with `this` value
- You should have functional test for each engine in a different specific module outside generators,
so you should not need to _execute_ generated code in this module (just write a proper integration test for that).
If you really want/need to do that it is possible (only for `Application` related classes) to use
`kogito-codegen-integration-tests` module and extend `org.kie.kogito.codegen.AbstractCodegenIT`
# Generated Application file
The result of the processing is the main entry point `org.kie.kogito.app.Application`.
- Components are organized into "sections". The idea, is that for a component C,
it is possible to invoke some method such that an instance of C is returned.
e.g.:
* for process P, one may write `new Application().get(Processes.class).create("P")`
* for rule unit R, one may write `new Application().get(RuleUnits.class).create("R")`
note: specific APIs may vary.
```java
package org.kie.kogito.app;
public class Application extends org.kie.kogito.StaticApplication {
public Application() {
super(new ApplicationConfig());
loadEngines(new Processes(), new RuleUnits());
}
}
```
This Application API is intended to be accessed directly from end users and in both Spring/Quarkus scenarios it is possible
and preferable to directly inject each engine like
```java
package org.my.package;
@ApplicationScoped
public class MyCustomBean {
@Inject
private Processes processes;
@Inject
private DecisionModels decisionModels;
...
}
```
# Additionally-generated files
Implementations may (and usually *do*) generate additional source code.
In particular:
- Rules generate source code for the RuleUnit implementation **and** the
executable model description
- Processes generate source code for their Process implementation using
their specific executable model description
- Most Generators also generate `Resources`, i.e. REST API endpoints.