`;
params.forEach(p => {
- const annotation = (p.annotations || []).find(a => a.endsWith("RequestBody") || a.endsWith("PathVariable") || a.endsWith("RequestParam"));
+ const annotation = (p.annotations || []).find(a => a.endsWith("RequestBody") || a.endsWith("PathVariable") || a.endsWith("RequestParam") || a.endsWith("Payload") || a.endsWith("Header") || a.endsWith("Headers"));
const cleanAnn = annotation ? "@" + annotation.substring(annotation.lastIndexOf('.') + 1) : "";
html += `
${cleanAnn}
diff --git a/state_machines/complex_multi_module_sm/.gitignore b/state_machines/complex_multi_module_sm/.gitignore
new file mode 100644
index 0000000..667aaef
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/state_machines/complex_multi_module_sm/domain/.gitignore b/state_machines/complex_multi_module_sm/domain/.gitignore
new file mode 100644
index 0000000..667aaef
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/domain/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/state_machines/complex_multi_module_sm/domain/pom.xml b/state_machines/complex_multi_module_sm/domain/pom.xml
new file mode 100644
index 0000000..f052b12
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/domain/pom.xml
@@ -0,0 +1,24 @@
+
+
+ 4.0.0
+
+ click.kamil.complex
+ complex-multi-module-sm
+ 1.0.0
+
+
+ domain
+
+
+
+ org.springframework.statemachine
+ spring-statemachine-core
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
diff --git a/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/IEvent.java b/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/IEvent.java
new file mode 100644
index 0000000..0ec9c09
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/IEvent.java
@@ -0,0 +1,4 @@
+package click.kamil.domain;
+
+public interface IEvent {
+}
diff --git a/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/IState.java b/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/IState.java
new file mode 100644
index 0000000..18bbe91
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/IState.java
@@ -0,0 +1,4 @@
+package click.kamil.domain;
+
+public interface IState {
+}
diff --git a/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/OrderEvent.java b/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/OrderEvent.java
new file mode 100644
index 0000000..bc4c890
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/OrderEvent.java
@@ -0,0 +1,5 @@
+package click.kamil.domain;
+
+public enum OrderEvent implements IEvent {
+ PROCESS, COMPLETE, CANCEL
+}
diff --git a/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/OrderState.java b/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/OrderState.java
new file mode 100644
index 0000000..ceec376
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/OrderState.java
@@ -0,0 +1,5 @@
+package click.kamil.domain;
+
+public enum OrderState implements IState {
+ NEW, PROCESSING, COMPLETED, CANCELLED
+}
diff --git a/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/StateMachineConfig.java b/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/StateMachineConfig.java
new file mode 100644
index 0000000..5b79aef
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/domain/src/main/java/click/kamil/domain/StateMachineConfig.java
@@ -0,0 +1,38 @@
+package click.kamil.domain;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.statemachine.config.EnableStateMachine;
+import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
+import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
+
+import java.util.EnumSet;
+
+@Configuration
+@EnableStateMachine
+public class StateMachineConfig extends EnumStateMachineConfigurerAdapter
{
+
+ @Override
+ public void configure(StateMachineStateConfigurer states) throws Exception {
+ states
+ .withStates()
+ .initial(OrderState.NEW)
+ .states(EnumSet.allOf(OrderState.class));
+ }
+
+ @Override
+ public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
+ transitions
+ .withExternal()
+ .source(OrderState.NEW).target(OrderState.PROCESSING).event(OrderEvent.PROCESS)
+ .and()
+ .withExternal()
+ .source(OrderState.PROCESSING).target(OrderState.COMPLETED).event(OrderEvent.COMPLETE)
+ .and()
+ .withExternal()
+ .source(OrderState.NEW).target(OrderState.CANCELLED).event(OrderEvent.CANCEL)
+ .and()
+ .withExternal()
+ .source(OrderState.PROCESSING).target(OrderState.CANCELLED).event(OrderEvent.CANCEL);
+ }
+}
diff --git a/state_machines/complex_multi_module_sm/pom.xml b/state_machines/complex_multi_module_sm/pom.xml
new file mode 100644
index 0000000..3214e2d
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+ click.kamil.complex
+ complex-multi-module-sm
+ 1.0.0
+ pom
+
+
+ domain
+ service
+ web
+
+
+
+ 21
+ 21
+ 3.2.0
+ 3.2.0
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+ org.springframework.statemachine
+ spring-statemachine-core
+ ${spring-statemachine.version}
+
+
+ click.kamil.complex
+ domain
+ ${project.version}
+
+
+ click.kamil.complex
+ service
+ ${project.version}
+
+
+
+
diff --git a/state_machines/complex_multi_module_sm/service/.gitignore b/state_machines/complex_multi_module_sm/service/.gitignore
new file mode 100644
index 0000000..667aaef
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/service/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/state_machines/complex_multi_module_sm/service/pom.xml b/state_machines/complex_multi_module_sm/service/pom.xml
new file mode 100644
index 0000000..e6bfa3b
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/service/pom.xml
@@ -0,0 +1,32 @@
+
+
+ 4.0.0
+
+ click.kamil.complex
+ complex-multi-module-sm
+ 1.0.0
+
+
+ service
+
+
+
+ click.kamil.complex
+ domain
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.statemachine
+ spring-statemachine-core
+
+
+ org.springframework
+ spring-tx
+
+
+
diff --git a/state_machines/complex_multi_module_sm/service/src/main/java/click/kamil/service/OutboxAction.java b/state_machines/complex_multi_module_sm/service/src/main/java/click/kamil/service/OutboxAction.java
new file mode 100644
index 0000000..a82d94d
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/service/src/main/java/click/kamil/service/OutboxAction.java
@@ -0,0 +1,7 @@
+package click.kamil.service;
+
+import click.kamil.domain.IEvent;
+
+public interface OutboxAction {
+ void onCommit(T event);
+}
diff --git a/state_machines/complex_multi_module_sm/service/src/main/java/click/kamil/service/StateMachineService.java b/state_machines/complex_multi_module_sm/service/src/main/java/click/kamil/service/StateMachineService.java
new file mode 100644
index 0000000..e49aa2e
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/service/src/main/java/click/kamil/service/StateMachineService.java
@@ -0,0 +1,19 @@
+package click.kamil.service;
+
+import click.kamil.domain.IEvent;
+import click.kamil.domain.IState;
+
+public interface StateMachineService {
+ void sendMessage(T event);
+
+ > void sendMessageWithOutbox(T event, A action);
+
+ // Quirky pattern: Bounded generic supplier instead of reflection
+ void sendMessageWithProvider(java.util.function.Supplier eventProvider);
+
+ // Quirky pattern 2: Generic Varargs
+ void sendEventsVarargs(T... events);
+
+ // Quirky pattern 3: Multiple generic bounds (Intersection Types)
+ T processQuirkyEvent(T event);
+}
diff --git a/state_machines/complex_multi_module_sm/service/src/main/java/click/kamil/service/StateMachineServiceImpl.java b/state_machines/complex_multi_module_sm/service/src/main/java/click/kamil/service/StateMachineServiceImpl.java
new file mode 100644
index 0000000..a1470e9
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/service/src/main/java/click/kamil/service/StateMachineServiceImpl.java
@@ -0,0 +1,69 @@
+package click.kamil.service;
+
+import click.kamil.domain.IEvent;
+import click.kamil.domain.IState;
+import click.kamil.domain.OrderEvent;
+import click.kamil.domain.OrderState;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import reactor.core.publisher.Mono;
+
+@Service
+public class StateMachineServiceImpl implements StateMachineService {
+
+ @Autowired
+ private StateMachine stateMachine;
+
+ @Override
+ public void sendMessage(T event) {
+ if (event instanceof OrderEvent) {
+ stateMachine.sendEvent(Mono.just(MessageBuilder.withPayload((OrderEvent) event).build())).subscribe();
+ }
+ }
+
+ @Override
+ @Transactional
+ public > void sendMessageWithOutbox(T event, A action) {
+ sendMessage(event);
+
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+ @Override
+ public void afterCommit() {
+ action.onCommit(event);
+ }
+ });
+ } else {
+ action.onCommit(event);
+ }
+ }
+
+ @Override
+ public void sendMessageWithProvider(java.util.function.Supplier eventProvider) {
+ T event = eventProvider.get();
+ if (event != null) {
+ stateMachine.sendEvent(Mono.just(MessageBuilder.withPayload((OrderEvent) event).build())).subscribe();
+ }
+ }
+
+ @Override
+ @SafeVarargs
+ public final void sendEventsVarargs(T... events) {
+ // Quirky pattern 2: Iterating over generic varargs
+ for (T event : events) {
+ sendMessage(event);
+ }
+ }
+
+ @Override
+ public T processQuirkyEvent(T event) {
+ // Quirky pattern 3: Multiple bounds, returning the same generic type
+ sendMessage(event);
+ return event; // returns something that is guaranteed to be IEvent, Serializable, AND Cloneable
+ }
+}
diff --git a/state_machines/complex_multi_module_sm/web/.gitignore b/state_machines/complex_multi_module_sm/web/.gitignore
new file mode 100644
index 0000000..667aaef
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/web/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/state_machines/complex_multi_module_sm/web/pom.xml b/state_machines/complex_multi_module_sm/web/pom.xml
new file mode 100644
index 0000000..aba55cb
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/web/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+
+ click.kamil.complex
+ complex-multi-module-sm
+ 1.0.0
+
+
+ web
+
+
+
+ click.kamil.complex
+ service
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-activemq
+
+
+
diff --git a/state_machines/complex_multi_module_sm/web/src/main/java/click/kamil/web/Application.java b/state_machines/complex_multi_module_sm/web/src/main/java/click/kamil/web/Application.java
new file mode 100644
index 0000000..5de38dc
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/web/src/main/java/click/kamil/web/Application.java
@@ -0,0 +1,14 @@
+package click.kamil.web;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.jms.annotation.EnableJms;
+
+@SpringBootApplication(scanBasePackages = "click.kamil")
+@EnableJms
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/state_machines/complex_multi_module_sm/web/src/main/java/click/kamil/web/JmsOrderListener.java b/state_machines/complex_multi_module_sm/web/src/main/java/click/kamil/web/JmsOrderListener.java
new file mode 100644
index 0000000..1977dfd
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/web/src/main/java/click/kamil/web/JmsOrderListener.java
@@ -0,0 +1,31 @@
+package click.kamil.web;
+
+import click.kamil.domain.OrderEvent;
+import click.kamil.service.StateMachineService;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JmsOrderListener {
+
+ private final StateMachineService stateMachineService;
+
+ // Constructor injection
+ public JmsOrderListener(StateMachineService stateMachineService) {
+ this.stateMachineService = stateMachineService;
+ }
+
+ @JmsListener(destination = "order.process.queue")
+ public void receiveProcessCommand(String message) {
+ System.out.println("Received JMS message: " + message);
+ stateMachineService.sendMessageWithOutbox(OrderEvent.PROCESS, event -> {
+ System.out.println("JMS Triggered Faulty Outbox Action executed post-commit for event: " + event);
+ });
+ }
+
+ @JmsListener(destination = "order.cancel.queue")
+ public void receiveCancelCommand(String message) {
+ System.out.println("Received JMS message: " + message);
+ stateMachineService.sendMessageWithProvider(() -> OrderEvent.CANCEL);
+ }
+}
diff --git a/state_machines/complex_multi_module_sm/web/src/main/java/click/kamil/web/OrderController.java b/state_machines/complex_multi_module_sm/web/src/main/java/click/kamil/web/OrderController.java
new file mode 100644
index 0000000..4d0ba24
--- /dev/null
+++ b/state_machines/complex_multi_module_sm/web/src/main/java/click/kamil/web/OrderController.java
@@ -0,0 +1,37 @@
+package click.kamil.web;
+
+import click.kamil.domain.OrderEvent;
+import click.kamil.service.StateMachineService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/orders")
+public class OrderController {
+
+ @Autowired
+ private StateMachineService stateMachineService;
+
+ @PostMapping("/process")
+ public String processOrder() {
+ stateMachineService.sendMessageWithOutbox(OrderEvent.PROCESS, event -> {
+ System.out.println("Faulty Outbox Action executed post-commit for event: " + event);
+ });
+ return "Order process event sent within transaction";
+ }
+
+ @PostMapping("/complete")
+ public String completeOrder() {
+ stateMachineService.sendMessage(OrderEvent.COMPLETE);
+ return "Order complete event sent";
+ }
+
+ @PostMapping("/cancel")
+ public String cancelOrder() {
+ stateMachineService.sendMessage(OrderEvent.CANCEL);
+ return "Order cancel event sent";
+ }
+}