001package com.studentgui.apphelpers; 002 003import java.io.IOException; 004import java.nio.file.Files; 005import java.nio.file.Path; 006import java.time.Instant; 007import java.time.ZoneId; 008import java.time.format.DateTimeFormatter; 009import java.util.HashMap; 010import java.util.Map; 011 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015import com.fasterxml.jackson.databind.ObjectMapper; 016 017/** 018 * Helper to write per-session JSON exports for app pages. 019 */ 020public final class SessionJsonWriter { 021 private static final Logger LOG = LoggerFactory.getLogger(SessionJsonWriter.class); 022 private static final ObjectMapper MAPPER = new ObjectMapper(); 023 024 private SessionJsonWriter() {} 025 026 /** 027 * Write a per-session JSON file into the student's StudentDataFiles folder. 028 * The filename will include a unix timestamp to ensure uniqueness per session. 029 * 030 * @param student display name of the student 031 * @param pageName short page identifier (e.g. "Abacus") 032 * @param payload arbitrary payload object to serialize (Map or POJO) 033 * @return the path to the written file, or null on failure 034 */ 035 public static Path writeSessionJson(final String student, final String pageName, final Object payload) { 036 return writeSessionJson(student, pageName, payload, null); 037 } 038 039 /** 040 * Write a per-session JSON file and optionally include an explicit sessionId. 041 * If the explicit sessionId is null, this method will look for a "sessionId" 042 * entry inside the payload Map and use that if present. The envelope written 043 * to disk will include the sessionId when available. 044 * 045 * Filename format: {@code PageName-<epoch>-<readable>[-session-<sessionId>].json} 046 * 047 * @param student display name of the student 048 * @param pageName short page identifier (e.g. "Abacus") 049 * @param payload arbitrary payload object to serialize (Map or POJO) 050 * @param explicitSessionId optional session id to use in the envelope and filename 051 * @return the path to the written file, or null on failure 052 */ 053 public static Path writeSessionJson(final String student, final String pageName, final Object payload, final String explicitSessionId) { 054 if (student == null || student.trim().isEmpty() || pageName == null) { 055 return null; 056 } 057 try { 058 Path outDir = Helpers.studentCollectedDataDir(student); 059 Files.createDirectories(outDir); 060 long ts = Instant.now().toEpochMilli(); 061 // format for readability too 062 String readable = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmssSSS").withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(ts)); 063 064 // Determine sessionId preference: explicit param first, then payload if it implements SessionPayload 065 String sid = explicitSessionId; 066 if (sid == null && payload instanceof com.studentgui.apphelpers.dto.SessionPayload) { 067 int s = ((com.studentgui.apphelpers.dto.SessionPayload) payload).getSessionId(); 068 if (s != 0) { 069 sid = Integer.toString(s); 070 } 071 } 072 073 String filename = String.format("%s-%d-%s%s.json", pageName, ts, readable, (sid != null ? "-session-" + sid : "")); 074 Path outFile = outDir.resolve(filename); 075 076 Map<String, Object> envelope = new HashMap<>(); 077 envelope.put("student", student); 078 envelope.put("timestamp", ts); 079 envelope.put("timestampIso", readable); 080 envelope.put("page", pageName); 081 if (sid != null) { 082 envelope.put("sessionId", sid); 083 } 084 envelope.put("payload", payload); 085 086 byte[] bytes = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsBytes(envelope); 087 Files.write(outFile, bytes); 088 LOG.info("Wrote session JSON for {} page {} to {}", student, pageName, outFile); 089 return outFile; 090 } catch (IOException ex) { 091 LOG.warn("Unable to write session JSON for {} page {}: {}", student, pageName, ex.toString()); 092 return null; 093 } 094 } 095 096 /** 097 * Convenience overload that accepts an int sessionId to avoid callers 098 * converting to String. Delegates to the string-based overload. 099 * 100 * @param student display name of the student 101 * @param pageName short page identifier 102 * @param payload arbitrary payload object 103 * @param explicitSessionId numeric session id 104 * @return written file path or null 105 */ 106 public static Path writeSessionJson(final String student, final String pageName, final Object payload, final int explicitSessionId) { 107 return writeSessionJson(student, pageName, payload, Integer.toString(explicitSessionId)); 108 } 109 110 /** 111 * Backwards-compatible convenience method for callers that still have 112 * (codes,scores) arrays. It wraps them in a small Map and delegates to 113 * the main payload-based writer. 114 * 115 * @param student the student's display name 116 * @param pageName short page identifier (e.g. "Abacus") 117 * @param codes array of part codes to include in the payload 118 * @param scores array of scores corresponding to the codes 119 * @return path to the written JSON file, or null on failure 120 */ 121 public static Path writeSessionJson(final String student, final String pageName, final String[] codes, final int[] scores) { 122 com.studentgui.apphelpers.dto.AssessmentPayload payload = new com.studentgui.apphelpers.dto.AssessmentPayload(0, codes, scores); 123 return writeSessionJson(student, pageName, payload); 124 } 125}