diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/PropertyEnricher.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/PropertyEnricher.java index bbae292..ccb5b52 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/PropertyEnricher.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/PropertyEnricher.java @@ -15,7 +15,7 @@ public class PropertyEnricher implements AnalysisEnricher { public void enrich(AnalysisResult result, CodebaseContext context, CodebaseIntelligenceProvider intelligence) { log.info("Enriching {} with properties", result.getName()); - Map properties = intelligence.resolveProperties(); + Map> properties = intelligence.resolveProperties(); result.addMetadata(CodebaseMetadata.builder() .properties(properties) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/AnalysisResult.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/AnalysisResult.java index ff8e346..6b53df2 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/AnalysisResult.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/AnalysisResult.java @@ -6,31 +6,96 @@ import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import click.kamil.springstatemachineexporter.model.Transition; import lombok.Builder; import lombok.Getter; +import lombok.Setter; import lombok.extern.jackson.Jacksonized; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; @Getter -@Builder +@Setter +@Builder(toBuilder = true) @Jacksonized @JsonIgnoreProperties(ignoreUnknown = true) public class AnalysisResult { - private final String name; + private String name; @Builder.Default - private final Set states = java.util.Collections.emptySet(); - private final List transitions; - private final Set startStates; - private final Set endStates; - private final boolean renderChoicesAsDiamonds; + private Set states = java.util.Collections.emptySet(); + private List transitions; + private Set startStates; + private Set endStates; + private boolean renderChoicesAsDiamonds; @Builder.Default - private final List flows = java.util.Collections.emptyList(); + private List flows = java.util.Collections.emptyList(); @Builder.Default private CodebaseMetadata metadata = CodebaseMetadata.empty(); + public void applyResolution(Map properties) { + var resolver = new click.kamil.springstatemachineexporter.analysis.resolver.PropertyResolver(); + + // 1. Resolve start/end states strings + if (startStates != null) { + this.startStates = startStates.stream() + .map(s -> resolver.resolveValue(s, properties)) + .collect(java.util.stream.Collectors.toSet()); + } + if (endStates != null) { + this.endStates = endStates.stream() + .map(s -> resolver.resolveValue(s, properties)) + .collect(java.util.stream.Collectors.toSet()); + } + + // 2. Resolve states (State record is immutable, so recreate) + if (states != null) { + this.states = states.stream() + .map(s -> click.kamil.springstatemachineexporter.model.State.of(s.rawName(), resolver.resolveValue(s.fullIdentifier(), properties))) + .collect(java.util.stream.Collectors.toSet()); + } + + // 3. Resolve transitions + if (transitions != null) { + for (var t : transitions) { + if (t.getEvent() != null) { + t.setEvent(resolver.resolveValue(t.getEvent(), properties)); + } + // Resolve states within transitions + t.setSourceStates(t.getSourceStates().stream() + .map(s -> click.kamil.springstatemachineexporter.model.State.of(s.rawName(), resolver.resolveValue(s.fullIdentifier(), properties))) + .collect(java.util.stream.Collectors.toList())); + t.setTargetStates(t.getTargetStates().stream() + .map(s -> click.kamil.springstatemachineexporter.model.State.of(s.rawName(), resolver.resolveValue(s.fullIdentifier(), properties))) + .collect(java.util.stream.Collectors.toList())); + } + } + + // 4. Resolve metadata (triggers, entry points) + if (metadata != null) { + if (metadata.getTriggers() != null) { + for (var trigger : metadata.getTriggers()) { + if (trigger.getEvent() != null) { + trigger.setEvent(resolver.resolveValue(trigger.getEvent(), properties)); + } + } + } + if (metadata.getEntryPoints() != null) { + for (var ep : metadata.getEntryPoints()) { + if (ep.getName() != null) { + ep.setName(resolver.resolveValue(ep.getName(), properties)); + } + if (ep.getMetadata() != null) { + for (var entry : ep.getMetadata().entrySet()) { + entry.setValue(resolver.resolveValue(entry.getValue(), properties)); + } + } + } + } + } + } + public void addMetadata(CodebaseMetadata newMetadata) { if (newMetadata == null) return; @@ -43,7 +108,7 @@ public class AnalysisResult { List combinedCallChains = new java.util.ArrayList<>(this.metadata.getCallChains()); if (newMetadata.getCallChains() != null) combinedCallChains.addAll(newMetadata.getCallChains()); - java.util.Map combinedProperties = new java.util.HashMap<>(this.metadata.getProperties()); + java.util.Map> combinedProperties = new java.util.HashMap<>(this.metadata.getProperties()); if (newMetadata.getProperties() != null) combinedProperties.putAll(newMetadata.getProperties()); this.metadata = CodebaseMetadata.builder() diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/CodebaseMetadata.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/CodebaseMetadata.java index a0726a5..c2340cb 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/CodebaseMetadata.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/CodebaseMetadata.java @@ -22,7 +22,7 @@ public class CodebaseMetadata { @Builder.Default private final List callChains = Collections.emptyList(); @Builder.Default - private final Map properties = Collections.emptyMap(); + private final Map> properties = Collections.emptyMap(); public static CodebaseMetadata empty() { return CodebaseMetadata.builder().build(); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/EntryPoint.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/EntryPoint.java index b7820b7..c6d11ce 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/EntryPoint.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/EntryPoint.java @@ -26,10 +26,10 @@ public class EntryPoint { } private final Type type; - private final String name; // e.g., "POST /orders" or "Kafka Topic: orders" + private String name; // e.g., "POST /orders" or "Kafka Topic: orders" private final String className; private final String methodName; - private final Map metadata; // e.g., path, topic, exchange + private Map metadata; // e.g., path, topic, exchange @Builder.Default private final List parameters = java.util.Collections.emptyList(); } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java index 0e6f726..44d4cab 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java @@ -12,7 +12,7 @@ import java.util.Map; @Jacksonized @JsonIgnoreProperties(ignoreUnknown = true) public class TriggerPoint { - private final String event; + private String event; private final String className; private final String methodName; private final String sourceModule; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/PropertyResolver.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/PropertyResolver.java index 992bcb3..65db461 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/PropertyResolver.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/PropertyResolver.java @@ -42,6 +42,37 @@ public class PropertyResolver { return allProperties; } + public Map> resolveAllProperties(Path rootDir) { + Map> profiles = new HashMap<>(); + + // 1. Load default profile + Map defaultProps = new HashMap<>(); + loadMatching(rootDir, "application.properties", defaultProps); + loadMatching(rootDir, "application.yml", defaultProps); + loadMatching(rootDir, "application.yaml", defaultProps); + profiles.put("default", defaultProps); + + // 2. Discover and load all other profiles + try (Stream paths = Files.walk(rootDir)) { + paths.filter(Files::isRegularFile) + .filter(p -> { + String name = p.getFileName().toString(); + return name.startsWith("application-") && + (name.endsWith(".properties") || name.endsWith(".yml") || name.endsWith(".yaml")); + }) + .forEach(p -> { + String name = p.getFileName().toString(); + String profile = name.substring("application-".length(), name.lastIndexOf('.')); + Map loaded = p.toString().endsWith(".properties") ? loadProperties(p) : loadYaml(p); + profiles.computeIfAbsent(profile, k -> new HashMap<>()).putAll(loaded); + }); + } catch (IOException e) { + log.error("Failed to scan for profiles in {}", rootDir, e); + } + + return profiles; + } + private void loadMatching(Path rootDir, String fileName, Map target) { try (Stream paths = Files.walk(rootDir)) { paths.filter(p -> Files.isRegularFile(p)) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CodebaseIntelligenceProvider.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CodebaseIntelligenceProvider.java index 8055a73..4e62491 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CodebaseIntelligenceProvider.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CodebaseIntelligenceProvider.java @@ -24,7 +24,7 @@ public interface CodebaseIntelligenceProvider { List findCallChains(List entryPoints, List triggers); /** - * Resolves configuration properties. + * Resolves configuration properties per profile. */ - Map resolveProperties(); + Map> resolveProperties(); } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java index 27d34aa..f2cfa09 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java @@ -70,7 +70,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider { } @Override - public Map resolveProperties() { + public Map> resolveProperties() { return context.getProperties(); } } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java index 2b415f2..5d115c1 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java @@ -27,7 +27,7 @@ public class CodebaseContext { private final Set ambiguousSimpleNames = new HashSet<>(); private final ConstantResolver constantResolver = new ConstantResolver(); private final PropertyResolver propertyResolver = new PropertyResolver(); - private Map properties = new HashMap<>(); + private Map> allProperties = new HashMap<>(); private List libraryHints = new ArrayList<>(); private final ObjectMapper objectMapper = new ObjectMapper(); @@ -48,8 +48,8 @@ public class CodebaseContext { private String[] sourcepath = new String[0]; private boolean resolveBindings = false; - public Map getProperties() { - return Collections.unmodifiableMap(properties); + public Map> getProperties() { + return Collections.unmodifiableMap(allProperties); } public void setClasspath(List classpath) { @@ -77,7 +77,7 @@ public class CodebaseContext { private final Map> interfaceToImpls = new HashMap<>(); public void scan(Path rootDir, Set customIgnorePatterns) throws IOException { - this.properties = propertyResolver.resolveProperties(rootDir); + this.allProperties = propertyResolver.resolveAllProperties(rootDir); Set activeIgnore = new HashSet<>(ignorePatterns); activeIgnore.addAll(customIgnorePatterns); @@ -179,8 +179,7 @@ public class CodebaseContext { if (mod instanceof Annotation ann) { String name = ann.getTypeName().getFullyQualifiedName(); if (name.endsWith("Value")) { - String placeholder = extractAnnotationValue(ann); - return propertyResolver.resolveValue(placeholder, properties); + return extractAnnotationValue(ann); } } } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/command/ExporterCommand.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/command/ExporterCommand.java index 54c004a..a29e52e 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/command/ExporterCommand.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/command/ExporterCommand.java @@ -45,6 +45,9 @@ public class ExporterCommand implements Callable { @Option(names = {"--no-diamonds"}, description = "Render choice/junction states as regular states instead of diamonds.", negatable = true, defaultValue = "true") private boolean renderChoicesAsDiamonds; + @Option(names = {"-p", "--profiles"}, description = "Spring profiles to activate for property resolution.", split = ",") + private List activeProfiles; + @Override public Integer call() throws Exception { var out = spec.commandLine().getOut(); @@ -66,10 +69,11 @@ public class ExporterCommand implements Callable { } try { + List profiles = activeProfiles != null ? activeProfiles : java.util.Collections.emptyList(); if (jsonFile != null) { - exportService.runJsonExporter(jsonFile, outputDir, selectedFormats); + exportService.runJsonExporter(jsonFile, outputDir, selectedFormats, profiles); } else { - exportService.runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds); + exportService.runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds, profiles); } out.println(CommandLine.Help.Ansi.AUTO.string("%n@|bold,green SUCCESS:|@ All state machines have been exported to " + outputDir.toAbsolutePath())); return 0; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java index a62d63d..c43b43f 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java @@ -97,12 +97,40 @@ public class ExportService { } public void runJsonExporter(Path jsonFile, Path outputDir, List selectedFormats) throws IOException { + runJsonExporter(jsonFile, outputDir, selectedFormats, Collections.emptyList()); + } + + public void runJsonExporter(Path jsonFile, Path outputDir, List selectedFormats, List activeProfiles) throws IOException { JsonImportService jsonImportService = new JsonImportService(); AnalysisResult result = jsonImportService.importAnalysisResult(jsonFile); + // Apply property resolution pass if placeholders exist + resolveProperties(result, activeProfiles); + generateOutputs(outputDir, result, selectedFormats); } + private void resolveProperties(AnalysisResult result, List activeProfiles) { + Map> allProps = result.getMetadata().getProperties(); + if (allProps == null || allProps.isEmpty()) return; + + // 1. Merge properties based on active profiles + Map merged = new HashMap<>(); + // Start with default + if (allProps.containsKey("default")) { + merged.putAll(allProps.get("default")); + } + // Overlay active profiles + for (String profile : activeProfiles) { + if (allProps.containsKey(profile)) { + merged.putAll(allProps.get(profile)); + } + } + + // 2. Delegate to result for resolution + result.applyResolution(merged); + } + private void exportAll(CodebaseContext context, CodebaseIntelligenceProvider intelligence, Path outputDir, List selectedFormats, boolean renderChoicesAsDiamonds, List flows, String machineFilter) throws IOException { Set processedLocations = new HashSet<>(); diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/service/DeferredProfileResolutionTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/service/DeferredProfileResolutionTest.java new file mode 100644 index 0000000..2f9cab4 --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/service/DeferredProfileResolutionTest.java @@ -0,0 +1,68 @@ +package click.kamil.springstatemachineexporter.service; + +import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult; +import click.kamil.springstatemachineexporter.model.State; +import click.kamil.springstatemachineexporter.model.Transition; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DeferredProfileResolutionTest { + + @Test + void shouldResolvePlaceholdersWithDefaultProfile() { + // Given + AnalysisResult result = createMockResult(); + Map properties = Map.of("app.state.initial", "START_RESOLVED"); + + // When + result.applyResolution(properties); + + // Then + assertThat(result.getStartStates()).containsExactly("START_RESOLVED"); + + Transition t = result.getTransitions().get(0); + assertThat(t.getEvent()).isEqualTo("RESOLVED_EVENT"); + assertThat(t.getSourceStates().get(0).fullIdentifier()).isEqualTo("START_RESOLVED"); + } + + @Test + void shouldResolveWithFallback() { + // Given + AnalysisResult result = AnalysisResult.builder() + .name("TestSM") + .startStates(Set.of("${missing.key:FALLBACK}")) + .transitions(Collections.emptyList()) + .build(); + + // When + result.applyResolution(Collections.emptyMap()); + + // Then + assertThat(result.getStartStates()).containsExactly("FALLBACK"); + } + + private AnalysisResult createMockResult() { + String placeholder = "${app.state.initial}"; + State s1 = State.of("START", placeholder); + State s2 = State.of("END", "END"); + + Transition t1 = new Transition(); + t1.setEvent("${app.event.name:RESOLVED_EVENT}"); + t1.setSourceStates(List.of(s1)); + t1.setTargetStates(List.of(s2)); + + return AnalysisResult.builder() + .name("MockSM") + .startStates(Set.of(placeholder)) + .endStates(Set.of("END")) + .states(Set.of(s1, s2)) + .transitions(List.of(t1)) + .build(); + } +} diff --git a/state_machine_exporter_html/out_enterprise_v7_no_diamonds/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig.html b/state_machine_exporter_html/out_enterprise_v7_no_diamonds/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig.html new file mode 100644 index 0000000..397bb09 --- /dev/null +++ b/state_machine_exporter_html/out_enterprise_v7_no_diamonds/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig.html @@ -0,0 +1,1037 @@ + + + + + + State Machine Explorer - click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig + + + + + + + + + + + + + + + + +
+ +
+ NEWCHECK_AVAILABILITYPENDING_PAYMENTCANCELLEDPAIDSHIPPEDDELIVEREDRETURNEDPLACE_ORDERλ (order=0)(order=1)PAY_ORDERSHIP_ORDERFINALIZECANCEL_ORDERRETURN_ORDER +
+
+ + + + + + diff --git a/state_machine_exporter_html/out_enterprise_v7_no_diamonds/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig.json b/state_machine_exporter_html/out_enterprise_v7_no_diamonds/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig.json new file mode 100644 index 0000000..324caa4 --- /dev/null +++ b/state_machine_exporter_html/out_enterprise_v7_no_diamonds/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig/click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig.json @@ -0,0 +1,387 @@ +{ + "metadata" : { + "triggers" : [ { + "event" : "AUDIT_EVENT", + "className" : "click.kamil.examples.enterprise.api.SecurityInterceptor", + "methodName" : "preHandle", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 17 + }, { + "event" : "PLACE_ORDER", + "className" : "click.kamil.examples.enterprise.service.OrderServiceImpl", + "methodName" : "place", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 16 + }, { + "event" : "CANCEL_ORDER", + "className" : "click.kamil.examples.enterprise.service.OrderServiceImpl", + "methodName" : "cancel", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 21 + }, { + "event" : "PAY_ORDER", + "className" : "click.kamil.examples.enterprise.service.ReactivePaymentService", + "methodName" : "processPayment", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 18 + }, { + "event" : "SHIP_ORDER", + "className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener", + "methodName" : "onShippingReady", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 17 + }, { + "event" : "RETURN_ORDER", + "className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener", + "methodName" : "onReturn", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 17 + } ], + "entryPoints" : [ { + "type" : "REST", + "name" : "POST /api/enterprise/orders/place", + "className" : "click.kamil.examples.enterprise.api.OrderController", + "methodName" : "placeOrder", + "metadata" : { + "path" : "/api/enterprise/orders/place", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/enterprise/orders/{id}/cancel", + "className" : "click.kamil.examples.enterprise.api.OrderController", + "methodName" : "cancelOrder", + "metadata" : { + "path" : "/api/enterprise/orders/{id}/cancel", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] + }, { + "type" : "CUSTOM", + "name" : "INTERCEPTOR: SecurityInterceptor.preHandle", + "className" : "click.kamil.examples.enterprise.api.SecurityInterceptor", + "methodName" : "preHandle", + "metadata" : { + "interceptorType" : "Spring MVC Interceptor" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/enterprise/payments", + "className" : "click.kamil.examples.enterprise.api.ReactivePaymentController", + "methodName" : "pay", + "metadata" : { + "path" : "/api/enterprise/payments", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "orderId", + "type" : "String", + "annotations" : [ "RequestBody" ] + } ] + }, { + "type" : "JMS", + "name" : "JMS: shipping.queue", + "className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener", + "methodName" : "onShippingReady", + "metadata" : { + "protocol" : "JMS", + "destination" : "shipping.queue" + }, + "parameters" : [ { + "name" : "orderId", + "type" : "String", + "annotations" : [ ] + } ] + }, { + "type" : "RABBIT", + "name" : "RABBIT: returns.queue", + "className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener", + "methodName" : "onReturn", + "metadata" : { + "protocol" : "RABBIT", + "destination" : "returns.queue" + }, + "parameters" : [ { + "name" : "orderId", + "type" : "String", + "annotations" : [ ] + } ] + } ], + "callChains" : [ { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/enterprise/orders/place", + "className" : "click.kamil.examples.enterprise.api.OrderController", + "methodName" : "placeOrder", + "metadata" : { + "path" : "/api/enterprise/orders/place", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.placeOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.place" ], + "triggerPoint" : { + "event" : "PLACE_ORDER", + "className" : "click.kamil.examples.enterprise.service.OrderServiceImpl", + "methodName" : "place", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 16 + } + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/enterprise/orders/{id}/cancel", + "className" : "click.kamil.examples.enterprise.api.OrderController", + "methodName" : "cancelOrder", + "metadata" : { + "path" : "/api/enterprise/orders/{id}/cancel", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] + }, + "methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.cancelOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.cancel" ], + "triggerPoint" : { + "event" : "CANCEL_ORDER", + "className" : "click.kamil.examples.enterprise.service.OrderServiceImpl", + "methodName" : "cancel", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 21 + } + }, { + "entryPoint" : { + "type" : "CUSTOM", + "name" : "INTERCEPTOR: SecurityInterceptor.preHandle", + "className" : "click.kamil.examples.enterprise.api.SecurityInterceptor", + "methodName" : "preHandle", + "metadata" : { + "interceptorType" : "Spring MVC Interceptor" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.enterprise.api.SecurityInterceptor.preHandle" ], + "triggerPoint" : { + "event" : "AUDIT_EVENT", + "className" : "click.kamil.examples.enterprise.api.SecurityInterceptor", + "methodName" : "preHandle", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 17 + } + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/enterprise/payments", + "className" : "click.kamil.examples.enterprise.api.ReactivePaymentController", + "methodName" : "pay", + "metadata" : { + "path" : "/api/enterprise/payments", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "orderId", + "type" : "String", + "annotations" : [ "RequestBody" ] + } ] + }, + "methodChain" : [ "click.kamil.examples.enterprise.api.ReactivePaymentController.pay", "click.kamil.examples.enterprise.service.ReactivePaymentService.processPayment" ], + "triggerPoint" : { + "event" : "PAY_ORDER", + "className" : "click.kamil.examples.enterprise.service.ReactivePaymentService", + "methodName" : "processPayment", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 18 + } + }, { + "entryPoint" : { + "type" : "JMS", + "name" : "JMS: shipping.queue", + "className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener", + "methodName" : "onShippingReady", + "metadata" : { + "protocol" : "JMS", + "destination" : "shipping.queue" + }, + "parameters" : [ { + "name" : "orderId", + "type" : "String", + "annotations" : [ ] + } ] + }, + "methodChain" : [ "click.kamil.examples.enterprise.messaging.ShippingJmsListener.onShippingReady" ], + "triggerPoint" : { + "event" : "SHIP_ORDER", + "className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener", + "methodName" : "onShippingReady", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 17 + } + }, { + "entryPoint" : { + "type" : "RABBIT", + "name" : "RABBIT: returns.queue", + "className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener", + "methodName" : "onReturn", + "metadata" : { + "protocol" : "RABBIT", + "destination" : "returns.queue" + }, + "parameters" : [ { + "name" : "orderId", + "type" : "String", + "annotations" : [ ] + } ] + }, + "methodChain" : [ "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener.onReturn" ], + "triggerPoint" : { + "event" : "RETURN_ORDER", + "className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener", + "methodName" : "onReturn", + "sourceModule" : null, + "stateMachineId" : null, + "lineNumber" : 17 + } + } ], + "properties" : { } + }, + "name" : "click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig", + "renderChoicesAsDiamonds" : false, + "startStates" : [ "NEW" ], + "transitions" : [ { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"NEW\"", + "fullIdentifier" : "NEW" + } ], + "targetStates" : [ { + "rawName" : "\"CHECK_AVAILABILITY\"", + "fullIdentifier" : "CHECK_AVAILABILITY" + } ], + "event" : "PLACE_ORDER", + "guard" : null, + "actions" : [ ], + "order" : null + }, { + "type" : "CHOICE", + "sourceStates" : [ { + "rawName" : "\"CHECK_AVAILABILITY\"", + "fullIdentifier" : "CHECK_AVAILABILITY" + } ], + "targetStates" : [ { + "rawName" : "\"PENDING_PAYMENT\"", + "fullIdentifier" : "PENDING_PAYMENT" + } ], + "event" : null, + "guard" : { + "expression" : "(c) -> true", + "isLambda" : true, + "internalLogic" : null + }, + "actions" : [ ], + "order" : 0 + }, { + "type" : "CHOICE", + "sourceStates" : [ { + "rawName" : "\"CHECK_AVAILABILITY\"", + "fullIdentifier" : "CHECK_AVAILABILITY" + } ], + "targetStates" : [ { + "rawName" : "\"CANCELLED\"", + "fullIdentifier" : "CANCELLED" + } ], + "event" : null, + "guard" : null, + "actions" : [ ], + "order" : 1 + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"PENDING_PAYMENT\"", + "fullIdentifier" : "PENDING_PAYMENT" + } ], + "targetStates" : [ { + "rawName" : "\"PAID\"", + "fullIdentifier" : "PAID" + } ], + "event" : "PAY_ORDER", + "guard" : null, + "actions" : [ ], + "order" : null + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"PAID\"", + "fullIdentifier" : "PAID" + } ], + "targetStates" : [ { + "rawName" : "\"SHIPPED\"", + "fullIdentifier" : "SHIPPED" + } ], + "event" : "SHIP_ORDER", + "guard" : null, + "actions" : [ ], + "order" : null + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"SHIPPED\"", + "fullIdentifier" : "SHIPPED" + } ], + "targetStates" : [ { + "rawName" : "\"DELIVERED\"", + "fullIdentifier" : "DELIVERED" + } ], + "event" : "FINALIZE", + "guard" : null, + "actions" : [ ], + "order" : null + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"PAID\"", + "fullIdentifier" : "PAID" + } ], + "targetStates" : [ { + "rawName" : "\"CANCELLED\"", + "fullIdentifier" : "CANCELLED" + } ], + "event" : "CANCEL_ORDER", + "guard" : null, + "actions" : [ ], + "order" : null + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"DELIVERED\"", + "fullIdentifier" : "DELIVERED" + } ], + "targetStates" : [ { + "rawName" : "\"RETURNED\"", + "fullIdentifier" : "RETURNED" + } ], + "event" : "RETURN_ORDER", + "guard" : null, + "actions" : [ ], + "order" : null + } ], + "endStates" : [ "CANCELLED", "DELIVERED", "RETURNED" ] +} diff --git a/state_machine_exporter_html/src/main/java/click/kamil/springstatemachineexporter/html/command/HtmlExporterCommand.java b/state_machine_exporter_html/src/main/java/click/kamil/springstatemachineexporter/html/command/HtmlExporterCommand.java index 024f1c0..52d3cd2 100644 --- a/state_machine_exporter_html/src/main/java/click/kamil/springstatemachineexporter/html/command/HtmlExporterCommand.java +++ b/state_machine_exporter_html/src/main/java/click/kamil/springstatemachineexporter/html/command/HtmlExporterCommand.java @@ -35,6 +35,9 @@ public class HtmlExporterCommand implements Callable { @Option(names = {"-i", "--input"}, description = "Input directory containing the Spring Boot project source code.") private Path inputDir; + @Option(names = {"-j", "--json"}, description = "Input JSON file containing the state machine definition.") + private Path jsonFile; + @Option(names = {"-o", "--output"}, description = "Output directory for the generated portal.", defaultValue = "./out_html") private Path outputDir; @@ -58,29 +61,29 @@ public class HtmlExporterCommand implements Callable { var out = spec.commandLine().getOut(); var err = spec.commandLine().getErr(); - if (inputDir == null) { + if (inputDir == null && jsonFile == null) { inputDir = Path.of("."); } out.println(CommandLine.Help.Ansi.AUTO.string("@|bold,red Initializing Interactive Portal Generation...|@")); - out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Input Directory:|@ " + inputDir.toAbsolutePath())); + if (jsonFile != null) { + out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Input JSON File:|@ " + jsonFile.toAbsolutePath())); + } else { + out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Input Directory:|@ " + inputDir.toAbsolutePath())); + } out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Output Directory:|@ " + outputDir.toAbsolutePath())); - if (flowsFile != null) { - out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Custom Flows:|@ " + flowsFile.toAbsolutePath())); - } - if (machineFilter != null) { - out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Machine Filter:|@ " + machineFilter)); - } - + try { // Force HTML and JSON formats for the interactive portal List formats = List.of("html", "json"); + List profiles = activeProfiles != null ? activeProfiles : java.util.Collections.emptyList(); - boolean renderChoicesAsDiamonds = !noDiamonds; - - exportService.runExporter(inputDir, outputDir, formats, renderChoicesAsDiamonds, - activeProfiles != null ? activeProfiles : java.util.Collections.emptyList(), - flowsFile, machineFilter); + if (jsonFile != null) { + exportService.runJsonExporter(jsonFile, outputDir, formats, profiles); + } else { + boolean renderChoicesAsDiamonds = !noDiamonds; + exportService.runExporter(inputDir, outputDir, formats, renderChoicesAsDiamonds, profiles, flowsFile, machineFilter); + } out.println(CommandLine.Help.Ansi.AUTO.string("%n@|bold,green SUCCESS:|@ Interactive documentation generated successfully."));