001/* 002 * Copyright 2025 devteam@scivics-lab.com 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, 011 * software distributed under the License is distributed on an 012 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the 014 * specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package com.scivicslab.actoriac; 019 020import java.util.List; 021 022import com.github.ricksbrown.cowsay.Cowsay; 023import com.scivicslab.actoriac.log.DistributedLogStore; 024import com.scivicslab.actoriac.log.LogLevel; 025import com.scivicslab.pojoactor.workflow.IIActorSystem; 026import com.scivicslab.pojoactor.workflow.Interpreter; 027import com.scivicslab.pojoactor.workflow.Vertex; 028 029/** 030 * Level 3 wrapper that adds workflow capabilities to a NodeGroup POJO. 031 * 032 * <p>This class extends {@link Interpreter} to provide workflow execution 033 * capabilities while delegating node group operations to a wrapped {@link NodeGroup} instance.</p> 034 * 035 * <p>This follows the same three-level architecture as NodeInterpreter:</p> 036 * <ul> 037 * <li><strong>Level 1 (POJO):</strong> {@link NodeGroup} - pure POJO with inventory management</li> 038 * <li><strong>Level 2 (Actor):</strong> ActorRef<NodeGroup> - actor wrapper for concurrent execution</li> 039 * <li><strong>Level 3 (Workflow):</strong> NodeGroupInterpreter - workflow capabilities + IIActorRef wrapper</li> 040 * </ul> 041 * 042 * <p><strong>Design principle:</strong> NodeGroup remains a pure POJO, independent of ActorSystem. 043 * NodeGroupInterpreter wraps NodeGroup to add workflow capabilities without modifying the NodeGroup class.</p> 044 * 045 * @author devteam@scivics-lab.com 046 */ 047public class NodeGroupInterpreter extends Interpreter { 048 049 /** 050 * The wrapped NodeGroup POJO that handles inventory and node creation. 051 */ 052 private final NodeGroup nodeGroup; 053 054 /** 055 * The overlay directory path for YAML overlay feature. 056 * When set, workflows are loaded with overlay applied. 057 */ 058 private String overlayDir; 059 060 /** 061 * Verbose output flag. 062 * When true, displays full YAML for each vertex instead of truncated version. 063 */ 064 private boolean verbose = false; 065 066 /** 067 * Distributed log store for structured logging. 068 */ 069 private DistributedLogStore logStore; 070 071 /** 072 * Session ID for the current workflow execution. 073 */ 074 private long sessionId = -1; 075 076 /** 077 * Constructs a NodeGroupInterpreter that wraps the specified NodeGroup. 078 * 079 * @param nodeGroup the {@link NodeGroup} instance to wrap 080 * @param system the actor system for workflow execution 081 */ 082 public NodeGroupInterpreter(NodeGroup nodeGroup, IIActorSystem system) { 083 super(); 084 this.nodeGroup = nodeGroup; 085 this.system = system; 086 } 087 088 /** 089 * Creates Node objects for all hosts in the specified group. 090 * 091 * <p>Delegates to the wrapped {@link NodeGroup#createNodesForGroup(String)} method.</p> 092 * 093 * @param groupName the name of the group from the inventory file 094 * @return the list of created Node objects 095 */ 096 public List<Node> createNodesForGroup(String groupName) { 097 return nodeGroup.createNodesForGroup(groupName); 098 } 099 100 /** 101 * Creates a single Node for localhost execution. 102 * 103 * <p>Delegates to the wrapped {@link NodeGroup#createLocalNode()} method.</p> 104 * 105 * @return a list containing a single localhost Node 106 */ 107 public List<Node> createLocalNode() { 108 return nodeGroup.createLocalNode(); 109 } 110 111 /** 112 * Gets the inventory object. 113 * 114 * @return the loaded inventory, or null if not loaded 115 */ 116 public InventoryParser.Inventory getInventory() { 117 return nodeGroup.getInventory(); 118 } 119 120 /** 121 * Gets the wrapped NodeGroup instance. 122 * 123 * <p>This allows direct access to the underlying POJO when needed.</p> 124 * 125 * @return the wrapped NodeGroup 126 */ 127 public NodeGroup getNodeGroup() { 128 return nodeGroup; 129 } 130 131 /** 132 * Sets the overlay directory for YAML overlay feature. 133 * 134 * <p>When an overlay directory is set, workflows will be loaded with 135 * overlay applied, allowing environment-specific configuration.</p> 136 * 137 * @param overlayDir the path to the overlay directory containing overlay-conf.yaml 138 */ 139 public void setOverlayDir(String overlayDir) { 140 this.overlayDir = overlayDir; 141 } 142 143 /** 144 * Gets the overlay directory path. 145 * 146 * @return the overlay directory path, or null if not set 147 */ 148 public String getOverlayDir() { 149 return overlayDir; 150 } 151 152 /** 153 * Sets verbose mode for detailed output. 154 * 155 * <p>When enabled, displays full YAML for each vertex in cowsay output 156 * instead of the truncated version.</p> 157 * 158 * @param verbose true to enable verbose output 159 */ 160 public void setVerbose(boolean verbose) { 161 this.verbose = verbose; 162 } 163 164 /** 165 * Checks if verbose mode is enabled. 166 * 167 * @return true if verbose mode is enabled 168 */ 169 public boolean isVerbose() { 170 return verbose; 171 } 172 173 /** 174 * Sets the distributed log store for structured logging. 175 * 176 * @param logStore the log store to use 177 * @param sessionId the session ID for this execution 178 */ 179 public void setLogStore(DistributedLogStore logStore, long sessionId) { 180 this.logStore = logStore; 181 this.sessionId = sessionId; 182 } 183 184 /** 185 * Gets the log store. 186 * 187 * @return the log store, or null if not set 188 */ 189 public DistributedLogStore getLogStore() { 190 return logStore; 191 } 192 193 /** 194 * Gets the session ID. 195 * 196 * @return the session ID, or -1 if not set 197 */ 198 public long getSessionId() { 199 return sessionId; 200 } 201 202 /** 203 * Hook called when entering a vertex during workflow execution. 204 * 205 * <p>Displays the workflow name and vertex definition using cowsay. 206 * In normal mode, shows first 10 lines. In verbose mode, shows the full YAML 207 * after the cowsay output.</p> 208 * 209 * @param vertex the vertex being entered 210 */ 211 @Override 212 protected void onEnterVertex(Vertex vertex) { 213 // Get workflow name 214 String workflowName = (getCode() != null && getCode().getName() != null) 215 ? getCode().getName() 216 : "unknown-workflow"; 217 218 // Get YAML-formatted output (first 10 lines for cowsay) 219 String yamlText = vertex.toYamlString(10).trim(); 220 221 // Combine workflow name and vertex YAML 222 String displayText = "[" + workflowName + "]\n" + yamlText; 223 String[] cowsayArgs = { displayText }; 224 System.out.println(Cowsay.say(cowsayArgs)); 225 226 // In verbose mode, show the full YAML after cowsay 227 if (verbose) { 228 String fullYaml = vertex.toYamlString(-1); 229 System.out.println("--- Full vertex YAML ---"); 230 System.out.println(fullYaml); 231 System.out.println("------------------------"); 232 } 233 234 // Log to distributed log store 235 if (logStore != null && sessionId >= 0) { 236 String vertexName = vertex.getVertexName(); 237 if (vertexName == null && vertex.getStates() != null && vertex.getStates().size() >= 2) { 238 vertexName = vertex.getStates().get(0) + " -> " + vertex.getStates().get(1); 239 } 240 logStore.log(sessionId, "nodeGroup", vertexName, LogLevel.INFO, 241 "Entering vertex: " + yamlText.split("\n")[0]); 242 } 243 } 244}