A sophisticated state management system for e-commerce applications, demonstrating advanced design patterns and event-driven architecture. This project showcases how to implement complex state transitions for shopping carts, customers, and orders using domain-driven design principles.
Version: 2.0-SNAPSHOT
- Consolidated Gradle 9 + Java 21 toolchain and CI (GitHub Actions uses Java 21).
- Introduced a composite build under
build-logic/with convention plugins to keepbuild.gradle.ktslean. - New logging conventions via
de.burger.it.build.infrastructure.logging.logging-conventionsand the aggregatede.burger.it.build.application.logging-appfor unified SLF4J + Log4j2 setup. - Clear layering of domain platform conventions, infrastructure conventions (Spring, Lombok, JetBrains annotations, Logging), and application aggregates.
- State Management: Robust state machine implementation for carts, customers, and orders
- Event-Driven Architecture: Event-based system for handling state transitions
- Domain-Driven Design: Clear separation between domain, application, and infrastructure layers
- Immutable Data Structures: Using Java records for clean, immutable domain models
- Dependency Injection: Leveraging Spring Framework for flexible component wiring
- Process Pipeline: Fluent API for building and executing processing pipelines
The project implements a hexagonal architecture (also known as ports and adapters architecture) with clear separation of concerns:
The core of the application, containing business logic, entities, and business rules:
- Models: Simple, immutable domain entities (Cart, Customer, Order) implemented as Java records
- States: State classes implementing the State pattern for each entity
- Events: Event classes representing state change triggers
- Ports: Interfaces (with "Port" suffix) defining the required capabilities from outside the domain
The domain layer is completely independent of external frameworks and infrastructure details, following the dependency inversion principle.
Orchestrates the flow of data and coordinates domain operations:
- Services: Business services implementing use cases
- Event Listeners: Components listening for domain events
- Event Handlers: Components handling specific events based on current state
- Event Dispatcher: Routes events to appropriate handlers based on state
- Process Pipeline: Provides a fluent API for building and executing processing pipelines
Provides implementations for the domain ports:
- Adapters: Implementations of domain ports (with "Adapter" suffix)
- Repositories: In-memory implementations of data storage
All dependencies flow inward toward the domain layer, ensuring that the domain remains isolated from external concerns.
The application demonstrates 9 key workflows:
- Customer Creation: Creating a new customer and associated cart
- Customer Suspension: Suspending an existing customer
- Cart Creation: Creating a new cart for a customer
- Cart Activation: Activating a cart for use
- Cart Closure: Closing a cart when it's no longer needed
- Order Creation: Creating a new order from a cart
- Order Payment: Processing payment for an order
- Order Delivery: Marking an order as delivered
- Order Cancellation: Cancelling an existing order
- Created: Initial state when a cart is created
- Active: State when a cart is being used by a customer
- Ordered: State when a cart has been converted to an order
- Closed: Final state when a cart is no longer needed
- Created: Initial state when a customer is registered
- Activated: State when a customer account is active
- Suspended: State when a customer account is temporarily disabled
- New: Initial state when an order is created
- Paid: State when payment is received
- Delivered: State when order is delivered
- Canceled: State when order is canceled
- Java 21: Strictly Java 21 toolchain; code uses records and pattern matching. Mockito/Byte Buddy stack is validated on Java 21 in this project.
- Spring Framework 6.2.8: Core, Context, Beans (plus Spring Test for testing support).
- Lombok 1.18.38: Reduces boilerplate (compileOnly + annotationProcessor via convention plugin).
- JetBrains Annotations 24.1.0: Static analysis annotations.
- JUnit Jupiter 5.13.4: Primary testing framework on JUnit Platform 1.13.4.
- Spock 2.3 for Groovy 4.0 (Groovy BOM 4.0.22): BDD tests alongside JUnit; HTML reports via spock-reports 2.5.1.
- Mockito 5.18.0 and Hamcrest 3.0: Mocking and fluent assertions.
- SLF4J 2.0.17 + Log4j2 2.25.1 (via convention plugin): Unified logging stack with bridges (JUL/JCL) and optional async logging (LMAX Disruptor 3.4.4). AspectJ 1.9.24 runtime/weaver included for AOP support.
- Gradle 9.x (wrapper): Build automation; composite build with build-logic convention plugins.
- JaCoCo 0.8.12: Coverage reporting and verification (min 86% line coverage enforced).
- PIT Mutation Testing 1.20.1: Mutation testing via Gradle plugin; threshold 80%.
- Qodana 2025.1: Static analysis locally (qodana.yaml) and in CI (GitHub Actions).
This project uses an embedded Gradle convention plugins system (composite build). It keeps build scripts short and reusable.
- Wiring: In
settings.gradle.kts, the build logic is included as a composite build:includeBuild("build-logic")
- Plugin location:
build-logic\src\main\kotlin\de\burger\it\build\...- The package/path defines the plugin ID. Example:
- File:
build-logic/src/main/kotlin/de/burger/it/build/application/spring-app.gradle.kts - Plugin ID:
de.burger.it.build.application.spring-app
- File:
- The package/path defines the plugin ID. Example:
- Applying plugins in this project (see
build.gradle.kts):id("de.burger.it.build.application.spring-app")id("de.burger.it.build.application.lombok-app")id("de.burger.it.build.application.logging-app")← newid("de.burger.it.build.application.jetbrains-annotations-app")id("de.burger.it.build.infrastructure.spring.spring-test-conventions")
- Layering of conventions:
- Domain/platform base:
de.burger.it.build.domain.platform-conventions(toolchain, encoding, release target) - Infrastructure: e.g.,
...spring.spring-core-conventions,...spring.spring-test-conventions,...lombok.lombok-conventions,...jetbrains.jetbrains-annotations-conventions,...logging.logging-conventions← new - App aggregates: e.g.,
de.burger.it.build.application.spring-app,de.burger.it.build.application.logging-app← new, which bundle infrastructure conventions
- Domain/platform base:
- Versions and libraries are managed centrally via the Version Catalog:
gradle\libs.versions.toml- Accessed inside plugins via
VersionCatalogsExtension(e.g.,libs.findBundle("spring"))
- Accessed inside plugins via
- How to add a new convention plugin (quick recipe):
- Create a new file at
build-logic/src/main/kotlin/de/burger/it/build/<scope>/<name>.gradle.kts - Set the
packagedeclaration to match the directory structure - Declare required plugins/dependencies (via the
libscatalog) - Apply it in the project with
id("de.burger.it.build.<scope>.<name>")
- Create a new file at
The logging stack is provided via a reusable convention plugin to ensure a single SLF4J binding and consistent Log4j2 usage across modules:
- Infrastructure plugin:
de.burger.it.build.infrastructure.logging.logging-conventions- Exposes slf4j-api (api), adds log4j-api/core and log4j-slf4j2-impl at runtime
- Bridges for JUL and JCL are included, and conflicting bindings (logback, log4j-to-slf4j) are excluded globally
- Aligns Log4j versions with the Log4j BOM and supports async logging via
disruptor - Adds AspectJ runtime (and weaver at runtime) to support AOP-based logging
- Application aggregate:
de.burger.it.build.application.logging-app- Composes
platform-conventionsandlogging-conventions
- Composes
Usage in root build.gradle.kts (already applied):
plugins {
id("de.burger.it.build.application.logging-app")
}
You can now use SLF4J with Log4j2 backend without manually declaring these dependencies; the convention plugin wires them for you.
- Java Development Kit (JDK) 21 (strictly Java 21, as Mockito's Byte Buddy dependency only supports up to Java 21)
- Gradle 9.0.0
-
Windows (PowerShell/CMD):
.\gradlew.bat build -
macOS/Linux:
./gradlew build
This project does not apply the Gradle 'application' plugin. You can run the demo Main class in your IDE, or from the command line after building:
-
Windows (PowerShell/CMD):
- .\gradlew.bat build
- java -cp build\classes\java\main;build\resources\main de.burger.it.Main
-
macOS/Linux:
- ./gradlew build
- java -cp build/classes/java/main:build/resources/main de.burger.it.Main
-
Windows (PowerShell/CMD):
.\gradlew.bat test -
macOS/Linux:
./gradlew test
This will execute all unit tests in the project. The test results will be available in the build/reports/tests/test directory.
You can also run specific test classes:
- Windows (PowerShell/CMD):
.\gradlew.bat test --tests "de.burger.it.application.cart.service.CartServiceTest"
- macOS/Linux:
./gradlew test --tests "de.burger.it.application.cart.service.CartServiceTest"
Or specific test methods:
- Windows (PowerShell/CMD):
.\gradlew.bat test --tests "de.burger.it.application.order.service.OrderServiceTest.testCreateNewOrder"
- macOS/Linux:
./gradlew test --tests "de.burger.it.application.order.service.OrderServiceTest.testCreateNewOrder"
- Spock specs live under
src/test/groovyand use the*Spec.groovysuffix (e.g.,SanitySpec.groovy). They are executed automatically when you rungradlew test. - Run a specific Spock spec:
- Windows (PowerShell/CMD):
./gradlew.bat test --tests "de.burger.it.SanitySpec" - macOS/Linux:
./gradlew test --tests "de.burger.it.SanitySpec"
- Windows (PowerShell/CMD):
- Run a specific Spock feature method (use the exact feature name in quotes; wildcards are supported):
- Windows (PowerShell/CMD):
./gradlew.bat test --tests "de.burger.it.SanitySpec.*basic sanity check*" - macOS/Linux:
./gradlew test --tests "de.burger.it.SanitySpec.*basic sanity check*"
- Windows (PowerShell/CMD):
-
Windows (PowerShell/CMD):
.\gradlew.bat pitest -
macOS/Linux:
./gradlew pitest
This will execute PIT mutation testing across the configured target classes and tests. The HTML report will be available under build\reports\pitest\<timestamp>\index.html (for example, build\reports\pitest\202508101624\index.html). The build is configured to fail if the mutation score drops below 80%.
The Main.java file demonstrates basic usage of the system:
// Create a customer
var customer = new Customer(UUID.randomUUID(), "John Doe", "john@example.com");
customerService.createNewCustomer(customer);
// Create a cart for the customer
cartService.create(customer);
// Get all carts for the customer
var carts = cartService.findAllCartByCustomer(customer);
// Activate a cart
cartService.activate(carts.getFirst(), customer);
// Suspend a customer
customerService.suspendCustomer(customer);The system uses the Null Object pattern to eliminate null checks. The CartNullObject uses a zero UUID and implements equality so that it also compares equal to NullCartState in tests:
// Example from OrderService
public OrderLike createNewOrder(CartLike cart) {
if (cart.isNull()) {
return NullOrder.getInstance();
}
var order = new Order(UUID.randomUUID());
eventPublisher.publishEvent(new OrderCreateEvent(order));
return order;
}
// Using the returned OrderLike without worrying about nulls
OrderLike order = orderService.createNewOrder(cart);
// No need for null check - isNull() method can be used instead
if (!order.isNull()) {
// Proceed with order operations
}The system uses a fluent Process Pipeline API for handling events:
// Example of building a processing pipeline
ProcessPipeline<CartCreateEvent> pipeline = new ProcessPipeline<CartCreateEvent>()
.append(event -> {
// First step: assign cart status
cartStatusAssignmentPort.assignStatus(event.getCart(), CartStateType.CREATED);
return event;
})
.append(event -> {
// Second step: assign customer to cart
cartCustomerAssignmentPort.assignCustomer(event.getCart(), event.getCustomer());
return event;
})
.appendIf(
event -> !event.getCustomer().isNull(), // Condition
event -> {
// Conditional step: save to repository only if customer is not null
cartRepositoryPort.save(event.getCart());
return event;
}
);
// Execute the pipeline
pipeline.execute(cartCreateEvent);build-logic/
├── src/
│ └── main/
│ └── kotlin/
│ └── de/
│ └── burger/
│ └── it/
│ └── build/
│ ├── domain/
│ │ └── platform-conventions.gradle.kts
│ ├── application/
│ │ ├── spring-app.gradle.kts
│ │ ├── lombok-app.gradle.kts
│ │ ├── logging-app.gradle.kts
│ │ └── jetbrains-annotations-app.gradle.kts
│ └── infrastructure/
│ ├── spring/
│ │ ├── spring-core-conventions.gradle.kts
│ │ └── spring-test-conventions.gradle.kts
│ ├── lombok/
│ │ └── lombok-conventions.gradle.kts
│ ├── logging/
│ │ └── logging-conventions.gradle.kts
│ └── jetbrains/
│ └── jetbrains-annotations-conventions.gradle.kts
gradle/
└── wrapper/
src/
├── main/
│ └── java/
│ └── de/
│ └── burger/
│ └── it/
│ ├── application/
│ │ ├── config/
│ │ ├── process/
│ │ ├── cart/
│ │ │ ├── handler/
│ │ │ ├── listener/
│ │ │ ├── process/
│ │ │ └── service/
│ │ ├── customer/
│ │ │ ├── handler/
│ │ │ ├── listener/
│ │ │ ├── process/
│ │ │ └── service/
│ │ └── order/
│ │ ├── handler/
│ │ ├── listener/
│ │ ├── process/
│ │ └── service/
│ ├── domain/
│ │ ├── cart/
│ │ │ ├── event/
│ │ │ ├── model/
│ │ │ ├── port/
│ │ │ └── state/
│ │ ├── customer/
│ │ │ ├── event/
│ │ │ ├── model/
│ │ │ ├── port/
│ │ │ └── state/
│ │ ├── order/
│ │ │ ├── event/
│ │ │ ├── model/
│ │ │ ├── port/
│ │ │ └── state/
│ │ └── relation/
│ │ ├── model/
│ │ └── port/
│ └── infrastructure/
│ ├── cart/
│ │ ├── adapter/
│ │ └── model/
│ ├── customer/
│ │ ├── adapter/
│ │ └── model/
│ ├── order/
│ │ ├── adapter/
│ │ └── model/
│ └── relation/
│ ├── adapter/
│ └── model/
└── test/
├── generated_tests/
├── java/
│ └── de/
│ └── burger/
│ └── it/
│ ├── application/
│ │ ├── config/
│ │ ├── process/
│ │ ├── cart/
│ │ │ ├── handler/
│ │ │ ├── listener/
│ │ │ ├── process/
│ │ │ └── service/
│ │ ├── customer/
│ │ │ ├── handler/
│ │ │ ├── listener/
│ │ │ ├── process/
│ │ │ └── service/
│ │ └── order/
│ │ ├── handler/
│ │ ├── listener/
│ │ ├── process/
│ │ └── service/
│ ├── domain/
│ │ ├── cart/
│ │ │ ├── event/
│ │ │ ├── model/
│ │ │ ├── port/
│ │ │ └── state/
│ │ ├── customer/
│ │ │ ├── event/
│ │ │ ├── model/
│ │ │ ├── port/
│ │ │ └── state/
│ │ ├── order/
│ │ │ ├── event/
│ │ │ ├── model/
│ │ │ ├── port/
│ │ │ └── state/
│ │ └── relation/
│ │ └── model/
│ └── infrastructure/
│ ├── cart/
│ │ ├── adapter/
│ │ └── model/
│ ├── customer/
│ │ ├── adapter/
│ │ └── model/
│ ├── order/
│ │ ├── adapter/
│ │ └── model/
│ └── relation/
│ ├── adapter/
│ └── model/
└── groovy/
└── de/
└── burger/
└── it/
└── application/
└── customer/
└── service/
- State Pattern: For managing entity states and transitions (e.g., CartState, CustomerState, OrderState)
- Observer Pattern: For event notification and handling through the event-driven architecture
- Dependency Injection: For loose coupling between components using Spring Framework
- Ports and Adapters Pattern: Core of the hexagonal architecture, with ports defined in the domain and implemented by adapters in the infrastructure layer
- Factory Method: For creating state objects and handling state transitions
- Null Object Pattern: For eliminating null checks by providing special "null" implementations (CartNullObject, CustomerNullObject, OrderNullObject) that implement the domain interfaces (Cart, Customer, OrderLike)
- Pipeline Pattern: For building and executing processing pipelines with a fluent API
-
Qodana: Static analysis configured via qodana.yaml with the YAML profile at .qodana/profiles/qodana-config.yaml.
-
Qodana Complexity: Cyclomatic and other complexity metrics are enforced via the YAML profile at .qodana/profiles/qodana-config.yaml.
- Enabled inspections include: JavaCyclomaticComplexity, JavaNPathComplexityInspection, JavaMethodMetrics, JavaClassMetrics, JavaOverlyNestedBlock, and JavaOverlyComplexBooleanExpression.
- Thresholds (e.g., method cyclomatic complexity m_limit=10) can be adjusted in .qodana/profiles/qodana-config.yaml to tune sensitivity.
- Results are available locally in qodana.sarif.json. You can also integrate Qodana in CI if desired.
-
Mutation Testing (PIT): Ensures test suite quality by killing mutations.
- Version: PIT core 1.20.1 (configured via Gradle plugin info.solidsoft.pitest)
- Run: Windows
.\u0067radlew.bat pitest, macOS/Linux./gradlew pitest - Report:
build\reports\pitest\<timestamp>\index.html(e.g.,build\reports\pitest\202508101624\index.html) - Threshold: mutation score threshold set to 80%
- Notes: JDK 21
--add-opensflags anduseClasspathFileare configured to support Mockito/Byte Buddy and long classpaths on Windows.
-
Test Coverage (JaCoCo): Enforces minimum coverage and produces reports.
- Minimum line coverage: 86% (build fails if below)
- Reports: HTML and XML (
build\reports\jacoco\test\jacocoTestReport.xml) - Run: Windows
.\u0067radlew.bat test jacocoTestReport, macOS/Linux./gradlew test jacocoTestReport
-
Spock (BDD tests with Groovy): Behavior-driven tests written in Groovy using Spock 2 on JUnit Platform.
- Versions: Groovy 4.0.22 (BOM), Spock
spock-core:2.3-groovy-4.0(see build.gradle.kts) - Location: Specs live under
src\test\groovy(e.g.,SanitySpec.groovy) - Run: Included in the standard Gradle test task — Windows
.\u0067radlew.bat test, macOS/Linux./gradlew test - Reports: Standard Gradle test report at
build\reports\tests\test\index.html
- Versions: Groovy 4.0.22 (BOM), Spock
-
How to run locally
- Generate reports with Gradle build and tests:
./gradlew build test - Run Qodana locally with Docker (example):
docker run --rm -it -v "${PWD}":/data -v "${PWD}/.qodana":/data/.qodana jetbrains/qodana-jvm:2025.1 --config,qodana.yaml - CI: GitHub Actions workflow .github/workflows/qodana_code_quality.yml runs tests, generates JaCoCo coverage, and executes Qodana using qodana.yaml.
- Generate reports with Gradle build and tests:
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Thanks to all contributors who have helped shape this project
- Inspired by Domain-Driven Design principles and best practices
The project is already configured to apply the plugin with ID de.burger.forensics.btmgen in build.gradle.kts, and to resolve it from your local Maven repository via settings.gradle.kts.
Follow these steps to run it.
- Publish the plugin to your local Maven cache (only needed once per change)
- In the plugin project (the one that declares
id("de.burger.forensics.btmgen")), run:- Windows:
gradlew.bat publishToMavenLocal - macOS/Linux:
./gradlew publishToMavenLocal
- Windows:
- Make sure this project resolves the plugin from mavenLocal
- Already set in
settings.gradle.kts:pluginManagement { repositories { mavenLocal(); gradlePluginPortal(); mavenCentral() } }- And a mapping to the marker module:
useModule("de.burger.forensics.btmgen:de.burger.forensics.btmgen.gradle.plugin:1.0.0")
- Verify the build sees the plugin and list available tasks
- From this project root, run:
gradlew.bat --refresh-dependencies tasks --all
- Then filter for anything the plugin contributes (examples):
gradlew.bat tasks --all | findstr /i btmgradlew.bat tasks --all | findstr /i byteman
- If the plugin adds tasks, you will see them grouped (often under a group like "forensics" or similar).
- Execute the plugin task(s)
- Once you know the task name, run it directly. Examples (the exact name depends on the plugin implementation):
gradlew.bat btmGengradlew.bat generateBytemanRulesgradlew.bat btmGenMain(if the plugin creates per-source-set tasks)
- Add
-ifor info logs or-dfor debug logs to see detailed output:gradlew.bat btmGen -i
- If you don’t see any new tasks
- Some plugins act as convention plugins and perform their work during existing lifecycle tasks.
- Try running a regular build with info logs and look for plugin messages:
gradlew.bat clean build -i
- Also make sure Gradle isn’t reusing a stale configuration-cache entry:
gradlew.bat --no-configuration-cache tasks --all
- If you just published a new plugin version locally, refresh dependencies:
gradlew.bat --refresh-dependencies tasks
- Troubleshooting checklist
- Confirm the plugin is applied in
build.gradle.kts:plugins { id("de.burger.forensics.btmgen") }
- Confirm the IDs and coordinates match the published plugin:
- Plugin ID:
de.burger.forensics.btmgen - Marker module used in settings:
de.burger.forensics.btmgen:de.burger.forensics.btmgen.gradle.plugin:1.0.0
- Plugin ID:
- Ensure Java/Gradle versions match what the plugin was built for (Java 21, Gradle 9.x in this repo).
- Run with
--stacktrace -iif a task fails, and check logs for classes or extensions the plugin registers.
- Where are the outputs?
- If the plugin generates files (e.g., Byteman .btm rule files), they are typically placed under this project’s
build/directory (for example,build/btmgenor a similar folder), unless the plugin exposes configuration to change the output. Inspect the Gradle logs to find the exact output directory.
If you’d like, share the exact task names the plugin registers (if any) and I can wire a convenience aggregate task (e.g., generateTracingRules) in this build that depends on them.
This project applies the Gradle plugin de.burger.forensics.btmgen, which analyzes Kotlin (and optionally simple Java) sources and generates Byteman rules to obtain forensic traces of call-chains, decisions, and selected variable writes.
Features (plugin)
- Generates AT ENTRY / AT EXIT rules for functions and methods to trace call-chains.
- Adds decision tracing for if/when/is expressions (true/false branches).
- Emits AFTER WRITE rules for configured local variables.
- Optional naive Java parser for additional coverage.
- Uses Kotlin PSI via kotlin-compiler-embeddable; no IDE required.
Prerequisites
- Gradle 7.0+ (this repo uses Gradle 9.x) and Kotlin DSL support
- Byteman JAR (e.g., byteman-download-/lib/byteman.jar) for runtime injection
- A helper class implementation such as de.burger.forensics.ForensicsHelper providing methods invoked from rules (enter, exit, iff, writeVar, …)
Configuration in this project
- The plugin is applied in build.gradle.kts: id("de.burger.forensics.btmgen")
- Plugin is resolved from mavenLocal via settings.gradle.kts using the marker module mapping for version 1.0.0
- Defaults are configured via the forensicsBtmGen extension (Java sources, include Java parser, package prefix de.burger.it, helper FQN de.burger.forensics.ForensicsHelper)
- A concrete task generateBtmRules is registered to produce rules under build/forensics
Example configuration (already present in build.gradle.kts)
- Extension defaults: forensicsBtmGen { srcDirs.set(listOf("src/main/java")) pkgPrefix.set("") pkgPrefixes.set(listOf("de.burger.it")) helperFqn.set("de.burger.forensics.ForensicsHelper") entryExit.set(true) trackedVars.set(listOf("approved", "status")) includeJava.set(true) }
- Task to generate rules: tasks.register<de.burger.forensics.plugin.GenerateBtmTask>("generateBtmRules") { ... outputDir.set(layout.buildDirectory.dir("forensics")) }
How to generate Byteman rules
- Publish the plugin to your local Maven cache (only needed when you change the plugin):
- Windows: gradlew.bat publishToMavenLocal
- macOS/Linux: ./gradlew publishToMavenLocal
- From this project root, run:
- Windows: gradlew.bat generateBtmRules
- macOS/Linux: ./gradlew generateBtmRules
- Output: build/forensics/ with shard files such as tracing-0001-00001.btm
Load the generated rules with Byteman
- Start your target JVM with the Byteman agent listening on a port (example: 9091):
- Linux/macOS: java -javaagent:/path/to/byteman.jar=listener:true,port:9091 -jar app.jar
- Windows: java -javaagent:C:\path\to\byteman.jar=listener:true,port:9091 -jar app.jar
- Submit all generated .btm files:
- Linux/macOS: for f in build/forensics/*.btm; do bmsubmit -p 9091 -l "$f" done
- Windows cmd: for %f in (build\forensics*.btm) do bmsubmit.bat -p 9091 -l "%f"
- To unload later:
- bmsubmit -p 9091 -u
Generated rule highlights
- Call chain tracing: paired enter/exit helper calls for selected functions
- Decisions: iff, sw, and kase helper calls with preserved source line numbers
- Variable writes: AFTER WRITE rules for configured trackedVars
Example bootstrap snippet RULE bootstrap@Forensics CLASS * METHOD * HELPER de.burger.forensics.ForensicsHelper AT ENTRY IF true DO startTrace("/var/log/forensics.log"), setQuota(5000), enableSampling(10) ENDRULE
RULE shutdown@Forensics CLASS * METHOD * HELPER de.burger.forensics.ForensicsHelper AT EXIT IF true DO stopTrace() ENDRULE
Kotlin specifics
- Top-level functions appear under synthetic Kt classes
- Nested classes/objects use $ separators in JVM binary names
- Property writes use generated setters; trackedVars targets local variable assignments
- Inline/suspend functions are handled at PSI level; logical structure preserved
CI/CD note
- The plugin repository supports publishing to Plugin Portal and Maven Central via GitHub Actions. Provide credentials to enable automated releases on tags.
Troubleshooting
- Ensure the helper class FQN exists at runtime when loading rules into your JVM
- If tasks aren’t visible, refresh dependencies and disable configuration cache for a run:
- gradlew.bat --refresh-dependencies --no-configuration-cache tasks --all
- Run with --stacktrace -i for more diagnostics