001/* 002 * Copyright 2025 devteam@scivicslab.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.report.sections.basic; 019 020import com.scivicslab.actoriac.report.SectionBuilder; 021 022import java.sql.Connection; 023import java.sql.PreparedStatement; 024import java.sql.ResultSet; 025import java.sql.SQLException; 026import java.util.ArrayList; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.logging.Logger; 030 031/** 032 * POJO section builder that collects and outputs messages with % prefix. 033 * 034 * <p>Pure business logic - no {@code CallableByActionName}. 035 * Use {@link CheckResultsSectionIIAR} to expose as an actor.</p> 036 * 037 * <p>The % prefix is used in workflows to mark lines that should be 038 * collected and displayed in the final report. This is useful for 039 * check/status reporting:</p> 040 * 041 * <h2>Usage in workflows:</h2> 042 * <pre> 043 * - actor: this 044 * method: executeCommand 045 * arguments: 046 * - | 047 * if command -v node > /dev/null; then 048 * echo "%[OK] Node.js: $(node --version)" 049 * else 050 * echo "%[ERROR] Node.js: not found" 051 * fi 052 * </pre> 053 * 054 * <h2>Output example:</h2> 055 * <pre> 056 * [Check Results] 057 * [OK] Node.js: v18.0.0 058 * [OK] yarn: 1.22.19 059 * [ERROR] Maven: not found 060 * </pre> 061 * 062 * @author devteam@scivicslab.com 063 * @since 2.16.0 064 */ 065public class CheckResultsSection implements SectionBuilder { 066 067 private static final Logger logger = Logger.getLogger(CheckResultsSection.class.getName()); 068 069 /** Prefix for messages to be included in the report. */ 070 private static final String REPORT_PREFIX = "%"; 071 072 private Connection connection; 073 private long sessionId = -1; 074 075 /** 076 * Sets the database connection for log queries. 077 * 078 * @param connection the JDBC connection to the H2 log database 079 */ 080 public void setConnection(Connection connection) { 081 this.connection = connection; 082 } 083 084 /** 085 * Sets the session ID to query logs from. 086 * 087 * @param sessionId the session ID 088 */ 089 public void setSessionId(long sessionId) { 090 this.sessionId = sessionId; 091 } 092 093 @Override 094 public String generate() { 095 if (connection == null || sessionId < 0) { 096 logger.warning("CheckResultsSection: connection or sessionId not set"); 097 return ""; 098 } 099 100 try { 101 List<String> messages = getReportMessages(); 102 if (messages.isEmpty()) { 103 return ""; // No check results, skip this section 104 } 105 106 StringBuilder sb = new StringBuilder(); 107 sb.append("[Check Results]\n"); 108 for (String msg : messages) { 109 sb.append(msg).append("\n"); 110 } 111 return sb.toString(); 112 113 } catch (SQLException e) { 114 logger.warning("CheckResultsSection: SQL error: " + e.getMessage()); 115 return ""; 116 } 117 } 118 119 /** 120 * Get messages with % prefix from logs. 121 * 122 * @return list of messages (without the % prefix) 123 * @throws SQLException if database query fails 124 */ 125 private List<String> getReportMessages() throws SQLException { 126 List<String> messages = new ArrayList<>(); 127 128 String sql = "SELECT message FROM logs WHERE session_id = ? ORDER BY timestamp"; 129 try (PreparedStatement ps = connection.prepareStatement(sql)) { 130 ps.setLong(1, sessionId); 131 try (ResultSet rs = ps.executeQuery()) { 132 while (rs.next()) { 133 String message = rs.getString("message"); 134 if (message != null) { 135 // Extract lines starting with % 136 for (String line : message.split("\n")) { 137 String trimmed = line.trim(); 138 // Handle prefixes like [node-xxx] %message 139 String cleaned = trimmed.replaceFirst("^\\[node-[^\\]]+\\]\\s*", ""); 140 if (cleaned.startsWith(REPORT_PREFIX)) { 141 messages.add(cleaned.substring(1).trim()); 142 } 143 } 144 } 145 } 146 } 147 } 148 149 // Remove duplicates while preserving order, then sort 150 List<String> uniqueMessages = new ArrayList<>(new LinkedHashSet<>(messages)); 151 uniqueMessages.sort(null); 152 return uniqueMessages; 153 } 154 155 @Override 156 public String getTitle() { 157 return null; // Title is embedded in content 158 } 159}