← Level 4 LEVEL 4.4

Universal Multi-Format Export Engine & Headless Streaming Architecture

Exam Progress: 0 / 15
Mastery: 0%
⚡

Warm-up Quick Fire: Retention Check (from 4.3 PDF/A & Cryptography)

Anti-Forgetting Protocol

สถานการณ์จำลอง (Snowball Scenario): หากระบบของคุณต้องส่งออกเอกสารใบเสร็จตามมาตรฐาน ISO 19005-1 (PDF/A-1a) เพื่อส่งเข้าคลังเอกสารระยะยาว แต่ทีม Security ร้องขอให้ใส่ Password Encryption (User/Owner Password) เพื่อป้องกันไม่ให้บุคคลภายนอกเปิดดู คุณในฐานะ Enterprise Architect ควรตัดสินใจและแก้ปัญหาอย่างไรจึงจะถูกต้องตามสเปกสากล?

4.4 Contextual Anchoring: The Multi-Format Matrix & Headless Streaming Bottleneck

Why Single-Template Multi-Format Dispatching Fails in High-Throughput Microservice Architectures

💥 The Memory-Buffering Death Spiral

ในสถาปัตยกรรม Web API ทั่วไป นักพัฒนามักส่งออกรายงานผ่าน ByteArrayOutputStream ก่อนจะแปลงเป็น byte[] ส่งกลับผ่าน Spring Web ResponseEntity<byte[]> หากมีคำขอส่งออกรายงานขนาด 200MB พร้อมกัน 20 เธรด JVM Heap จะถูกจองทันที 8GB ส่งผลให้เกิด Stop-The-World GC และ Crash ทั้ง Pod

🎭 Visual Clutter in Pure Data Feeds (CSV/JSON)

เมื่อรายงานธุรกิจต้องรองรับทั้งการพิมพ์หน้ากระดาษ (PDF), การคำนวณสูตร (XLSX), และการสกัดข้อมูลดิบเข้า Data Lake (CSV/JSON) หากไม่มีระบบ ExporterFilter ตัว Exporter จะนำโลโก้รูปภาพ, ข้อความ Disclaimer, หมายเลขหน้ากระดาษ, และเส้นตาราง มาปะปนในไฟล์ CSV จนกลายเป็นขยะข้อมูลที่ระบบ ETL ไม่สามารถนำไปประมวลผลต่อได้

🛡️ The Unified 7.0.x Streaming Exporter Factory

JasperReports 7.0.x นำเสนอสถาปัตยกรรม JRAbstractExporter Pipeline ร่วมกับ JROriginExporterFilter และ SimpleOutputStreamExporterOutput ที่ผูกเข้ากับ HttpServletResponse.getOutputStream() ได้โดยตรง ทำให้สตรีมไบต์เอกสารแบบ Real-time Chunked Transfer Encoding สู่ Client โดยมี Heap Overhead ใกล้ศูนย์

🏛️ Dynamic Core Pillars (4 เสาหลักระบบส่งออกสากลแบบ Headless)

Pillar 1

Universal Exporter Factory

การออกแบบ Factory Pattern ควบคุมวงจรชีวิตของ Exporter แต่ละตระกูล (JRPdfExporter, JRXlsxExporter, JRCsvExporter, HtmlExporter) จากอินพุต JasperPrint ชุดเดียวกัน พร้อมตั้งค่า Context Classloader และ Metadata สอดคล้องกับ MIME Types

Analogy: Polyglot Serialization Engine
Pillar 2

Granular Origin Filtering

การคัดกรอง Band และ Element ด้วย net.sf.jasperreports.export.{format}.exclude.origin.* เพื่อลบ Header/Footer, ภาพตกแต่ง และแท็กกราฟิกออกจากการส่งออก CSV/XLSX โดยคงความสมบูรณ์ 100% ไว้ใน PDF

Analogy: GraphQL Selective Field Projection
Pillar 3

Direct HTTP Socket Flushing

การสตรีมผ่าน Socket Stream โดยตรง ขจัด Intermediary In-memory Byte Arrays พร้อมจัดการ HTTP Header: Transfer-Encoding: chunked, Content-Disposition: attachment และการดักจับ Client Abort Exceptions (Broken Pipe)

Analogy: Zero-Copy Linux Sendfile
Pillar 4

RFC 4180 CSV & Delimiter Sanitization

การกำหนดค่า fieldDelimiter, recordDelimiter, และ Character Encoding (UTF-8 with BOM หรือ UTF-8 Plain) ป้องกันอักขระลูกน้ำ แท็บ หรือ Newline ในข้อมูลทำให้โครงสร้างแถวแตก

Analogy: Deterministic JSON Schema Normalizer
STEP 1

Engine Internals: JRAbstractExporter Lifecycle & ExporterFilter Pipelines

เมื่อคำสั่ง exporter.exportReport() ถูกเรียก ตัว Exporter ซึ่งสืบทอดมาจากคลาสแม่ JRAbstractExporter จะดำเนินการตามวงจรชีวิต 5 ขั้นตอน:

  1. Input Resolution & State Validation: ดึงอ็อบเจกต์ JasperPrint ตรวจสอบหน้ากระดาษ (getPages()) และเริ่มต้นสร้าง Exporter Context
  2. ExporterFilter Evaluation: ก่อนที่ Element ใดๆ จะถูกวาดหรือเขียนลง Stream ตัว Engine จะส่ง Element นั้นไปทดสอบกับ ExporterFilter.isToExport(JRPrintElement) หาก Return เป็น false Element นั้นจะถูกข้ามทันทีโดยไม่เสีย CPU Cycle
  3. Grid Mapping vs Absolute Coordinate Layout: หากเป็น Exporter ตระกูล Text/Grid (XLSX, CSV, HTML) Engine จะส่งข้อมูลเข้าสู่ JRAbstractGridEvaluator เพื่อตัด Virtual Grid Lines แต่หากเป็น Page-based (PDF) จะรักษาพิกัดเวกเตอร์ X, Y, Width, Height ไว้ตรงเป๊ะ
  4. Progressive Chunk Flushing: สำหรับ JRCsvExporter ไบต์ข้อความจะถูกสตรีมลง Writer ทันทีทีละ Record ซึ่งช่วยลดหน่วยความจำลงเหลือระดับคงที่ $O(1)$
  5. Stream Finalization & Cleanup: สั่ง Flush บัฟเฟอร์ ปิด Stream และปล่อยตัวจัดการไฟล์ชั่วคราวทิ้ง
Production Streaming Hazard Underlying Root Cause Runtime Symptom / Exception Architectural Defense
Client Disconnect Broken Pipe ผู้ใช้งานกด Cancel Download หรือปิดหน้าต่างเบราว์เซอร์ระหว่างสตรีมไฟล์ขนาดใหญ่ ทำให้ TCP Socket ปลายทางถูกตัด ClientAbortException: java.io.IOException: Broken pipe ดักจับ ClientAbortException ใน Controller Layer และปิด Stream เงียบๆ โดยไม่บันทึก Error Log ระดับ FATAL
CSV Delimiter Escaping Crash ข้อมูลในฟิลด์มีเครื่องหมายจุลภาค (,) หรือเครื่องหมายคำพูด (") ซ้อนอยู่ แต่ Exporter ไม่ได้ตั้ง Quote Qualifier Data Pipeline Corruption: แถวข้อมูลเลื่อนคอลัมน์ (Column Shift) ในระบบ ETL ตั้งค่า net.sf.jasperreports.export.csv.field.delimiter และเปิด force.field.enclosure=true
Heap Exhaustion via byte[] Response ใช้ ByteArrayOutputStream เก็บเอกสารทั้งหมดในหน่วยความจำ RAM ก่อนส่งกลับ OutOfMemoryError: Java heap space ในเธรด Tomcat/Undertow ส่งผ่าน response.getOutputStream() เข้าสู่ Exporter Output โดยตรง พร้อมตั้ง Header chunked
Thai CSV Mojibake in Microsoft Excel ส่งออก CSV เป็น UTF-8 แต่ไม่ได้ใส่ UTF-8 Byte Order Mark (BOM: 0xEF, 0xBB, 0xBF) ที่หัวไฟล์ ภาษาไทยกลายเป็นอักษรต่างดาว (เธเธฒเธฉเธฒเนเธ—เธข) เมื่อดับเบิลคลิกเปิดใน Excel เขียนไบต์ BOM 3 ไบต์แรกนำหน้าลงใน OutputStream หรือตั้งค่า characterEncoding="UTF-8" พร้อม BOM flag
STEP 2

Strict 7.0.x Architecture Implementation

100% Unified Element Model + Multi-Format Filters

เทมเพลต JRXML ด้านล่างแสดงการกำหนดคุณสมบัติการคัดกรองข้ามฟอร์แมต (Origin Filtering) เพื่อให้ Band ตกแต่งถูกกรองทิ้งอัตโนมัติใน CSV/XLSX ขณะที่ยังคงแสดงผลสมบูรณ์ใน PDF โดยใช้แท็กสมัยใหม่ <element kind="..."> 100%:

universal_multiformat_template.jrxml (JasperReports 7.0.x Schema)
<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
              name="UniversalMultiFormatPipeline"
              pageWidth="595" pageHeight="842"
              columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20"
              uuid="a81b2c3d-4e5f-6789-0123-abcdef456789">

    <!-- ================================================================= -->
    <!-- Architectural Origin Filters: Exclude Page Decorators in CSV/XLSX -->
    <!-- ================================================================= -->
    <property name="net.sf.jasperreports.export.csv.exclude.origin.band.1" value="pageHeader"/>
    <property name="net.sf.jasperreports.export.csv.exclude.origin.band.2" value="pageFooter"/>
    <property name="net.sf.jasperreports.export.csv.exclude.origin.band.3" value="lastPageFooter"/>
    <property name="net.sf.jasperreports.export.xls.exclude.origin.band.1" value="pageHeader"/>
    <property name="net.sf.jasperreports.export.xls.exclude.origin.band.2" value="pageFooter"/>

    <!-- CSV Formatting Standards (RFC 4180) -->
    <property name="net.sf.jasperreports.export.csv.field.delimiter" value=","/>
    <property name="net.sf.jasperreports.export.csv.record.delimiter" value="
"/>

    <parameter name="REPORT_TITLE" class="java.lang.String"/>
    <parameter name="EXPORTED_BY" class="java.lang.String"/>

    <field name="transactionId" class="java.lang.String"/>
    <field name="customerCode" class="java.lang.String"/>
    <field name="settlementAmount" class="java.math.BigDecimal"/>
    <field name="transactionDate" class="java.time.LocalDate"/>

    <title height="50">
        <element kind="staticText" x="0" y="0" width="555" height="25">
            <textElement textAlignment="Center" verticalAlignment="Middle">
                <font fontName="Sarabun" size="16" isBold="true"/>
            </textElement>
            <text><![CDATA[รายงานสรุปธุรกรรมการเงินสากล (Universal Clearing Ledger)]]></text>
        </element>
        <element kind="textField" x="0" y="25" width="555" height="20">
            <textElement textAlignment="Center">
                <font fontName="Sarabun" size="10"/>
            </textElement>
            <expression><![CDATA["จัดทำโดย: " + $P{EXPORTED_BY}]]></expression>
        </element>
    </title>

    <columnHeader height="20">
        <element kind="staticText" x="0" y="0" width="120" height="20">
            <textElement><font fontName="Sarabun" isBold="true"/></textElement>
            <text><![CDATA[TXN_ID]]></text>
        </element>
        <element kind="staticText" x="120" y="0" width="180" height="20">
            <textElement><font fontName="Sarabun" isBold="true"/></textElement>
            <text><![CDATA[CUSTOMER_CODE]]></text>
        </element>
        <element kind="staticText" x="300" y="0" width="135" height="20">
            <textElement textAlignment="Right"><font fontName="Sarabun" isBold="true"/></textElement>
            <text><![CDATA[AMOUNT]]></text>
        </element>
        <element kind="staticText" x="435" y="0" width="120" height="20">
            <textElement textAlignment="Right"><font fontName="Sarabun" isBold="true"/></textElement>
            <text><![CDATA[TXN_DATE]]></text>
        </element>
    </columnHeader>

    <detail>
        <band height="20">
            <element kind="textField" x="0" y="0" width="120" height="20">
                <expression><![CDATA[$F{transactionId}]]></expression>
            </element>
            <element kind="textField" x="120" y="0" width="180" height="20">
                <expression><![CDATA[$F{customerCode}]]></expression>
            </element>
            <element kind="textField" x="300" y="0" width="135" height="20" pattern="#,##0.00">
                <textElement textAlignment="Right"/>
                <expression><![CDATA[$F{settlementAmount}]]></expression>
            </element>
            <element kind="textField" x="435" y="0" width="120" height="20">
                <textElement textAlignment="Right"/>
                <expression><![CDATA[$F{transactionDate}.toString()]]></expression>
            </element>
        </band>
    </detail>

    <pageFooter height="20">
        <!-- Page Footer will be automatically suppressed in CSV/XLSX via Exclude Properties -->
        <element kind="textField" x="0" y="0" width="555" height="20">
            <textElement textAlignment="Center"><font size="8"/></textElement>
            <expression><![CDATA["หน้า " + $V{PAGE_NUMBER}]]></expression>
        </element>
    </pageFooter>
</jasperReport>

Enterprise Headless Streaming Controller (Java 21 / Spring Boot 3.x)

HeadlessReportStreamController.java
package com.enterprise.report.controller;

import jakarta.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput;
import net.sf.jasperreports.export.SimpleWriterExporterOutput;
import net.sf.jasperreports.pdf.JRPdfExporter;
import net.sf.jasperreports.csv.JRCsvExporter;
import net.sf.jasperreports.csv.SimpleCsvExporterConfiguration;
import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter;
import org.springframework.web.bind.annotation.*;

import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

@RestController
@RequestMapping("/api/v1/reports")
public class HeadlessReportStreamController {

    private final ReportExecutionService executionService;

    public HeadlessReportStreamController(ReportExecutionService executionService) {
        this.executionService = executionService;
    }

    @GetMapping("/stream")
    public void streamReport(
            @RequestParam String format,
            HttpServletResponse response) throws Exception {

        JasperPrint jasperPrint = executionService.generatePrintObject();
        OutputStream out = response.getOutputStream();

        switch (format.toLowerCase()) {
            case "pdf" -> {
                response.setContentType("application/pdf");
                response.setHeader("Content-Disposition", "attachment; filename=\"clearing_ledger.pdf\"");
                JRPdfExporter exporter = new JRPdfExporter();
                exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
                exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(out));
                exporter.exportReport();
            }
            case "csv" -> {
                response.setContentType("text/csv; charset=UTF-8");
                response.setHeader("Content-Disposition", "attachment; filename=\"clearing_ledger.csv\"");
                // Write UTF-8 BOM so Microsoft Excel renders Thai text properly
                out.write(new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF});
                
                JRCsvExporter exporter = new JRCsvExporter();
                exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
                exporter.setExporterOutput(new SimpleWriterExporterOutput(out, StandardCharsets.UTF_8.name()));
                
                SimpleCsvExporterConfiguration config = new SimpleCsvExporterConfiguration();
                config.setFieldDelimiter(",");
                config.setRecordDelimiter("\n");
                exporter.setConfiguration(config);
                exporter.exportReport();
            }
            case "xlsx" -> {
                response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                response.setHeader("Content-Disposition", "attachment; filename=\"clearing_ledger.xlsx\"");
                JRXlsxExporter exporter = new JRXlsxExporter();
                exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
                exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(out));
                exporter.exportReport();
            }
            default -> throw new IllegalArgumentException("Unsupported format: " + format);
        }
        out.flush();
    }
}
STEP 3

Interactive Architecture Workspace (Split-View Studio)

Lab 4.4.1: Dual-Target Origin Filtering (PDF vs Excel/CSV)

Target: Clean Dual Export

โจทย์สถาปัตยกรรม: ป้องกันไม่ให้องค์ประกอบของหัวกระดาษ (pageHeader) และท้ายกระดาษ (pageFooter) หลุดเข้าไปในไฟล์ CSV และ Excel โดยต้องรักษาโครงสร้างหน้ากระดาษไว้เต็มรูปแบบใน PDF:

  • ต้องประกาศคุณสมบัติ net.sf.jasperreports.export.csv.exclude.origin.band.*
  • ต้องประกาศคุณสมบัติ net.sf.jasperreports.export.xls.exclude.origin.band.*
  • ทุก Visual Element ต้องใช้แท็ก Unified Element Model (<element kind="...">) 100%
  • ต้องระบุ fieldDelimiter ให้ได้มาตรฐาน RFC 4180
Expected Headless Multi-Format Output Standard
TARGET: PDF A4 WITH HEADERS & FOOTERS
INSTITUTIONAL AUDIT LEDGER
Page 1 of 4 (Printed timestamp included)
TXN AMOUNT
TX-901 $450,000.00
Confidential - Legal Archive
TARGET: RAW CSV DATA 100% UNPOLLUTED
TXN_ID,CUSTOMER,AMOUNT,STATUS
TX-901,CITIBANK,450000.00,SETTLED
TX-902,BNP_PARIBAS,1280000.00,SETTLED
// Zero Page Headers, Zero Footers
⚠️ Crash Simulator (Production Streaming Hazards)

ทดสอบจำลองข้อผิดพลาดที่มักเกิดบน Production เมื่อสตรีมรายงานแบบ Headless บนระบบ Microservices:

Universal Export AST & Linter
Awaiting Validation
Click "Verify Universal Pipeline" to scan AST and multi-format rules.
ARTIFACT PROMPTS

Production Image Specification Prompts (Visual Acceptance Standard)

Commercial Multi-Format Acceptance
1. Architectural Flowchart (Vector)

"2D Vector architectural flowchart, modern dark slate background #0b0f19. JasperReports 7.0.3+ Universal Headless Streaming Pipeline: JasperPrint being dispatched through JRAbstractExporter, evaluated by ExporterFilter, splitting into JRPdfExporter, JRXlsxExporter, and JRCsvExporter, streaming directly into HttpServletResponse OutputStream with chunked transfer encoding, neon cyan and emerald data streams, crisp typography, 8k resolution."

2. Lab 4.4.1 Output (Dual Export Comparison)

"Side-by-side realistic enterprise production view: Left side showing a pristine white A4 PDF financial ledger with corporate header, pagination 'Page 1 of 5', subtle blue border. Right side showing unpolluted raw CSV text output with clean comma delimiters and zero page headers, perfect monetary formatting, sharp typography, zero blueprint lines, production-ready."

3. Lab 4.4.2 Output (Enterprise CSV Settlement Feed)

"Macro photography of a realistic financial terminal screen displaying an RFC 4180 compliant CSV stream import. Clear columns for TRANSACTION_REF, COUNTERPARTY, VALUE_USD, VALUE_DATE, properly quoted fields containing commas, razor-sharp monospaced font, emerald-on-black data visualization theme, commercial banking standard."

EXAM ARENA

15 Architectural Mastery Quizzes

เจาะลึก JRAbstractExporter, ExporterFilter, Non-blocking Streaming, RFC 4180 CSV, และ Jakarta EE HTTP Pipeline