From b066a991d12f512f3506a8ab007e4bae001d8e84 Mon Sep 17 00:00:00 2001 From: "vluk@2fi-solutions.com.hk" Date: Fri, 12 Dec 2025 15:04:47 +0800 Subject: [PATCH] no message --- .../lioner/pdf/service/PdfService.java | 294 ++++++++---------- 1 file changed, 133 insertions(+), 161 deletions(-) diff --git a/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfService.java b/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfService.java index 079e3bf..659414b 100644 --- a/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfService.java +++ b/src/main/java/com/ffii/lioner/modules/lioner/pdf/service/PdfService.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -18,6 +19,7 @@ import org.apache.commons.lang3.math.NumberUtils; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDButton; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -231,214 +233,184 @@ public class PdfService extends AbstractBaseEntityService template = templateService.loadTemplate(templateId); - //Search if there have an old form having the form code - Map sourcePdf = getLastPdf(clientId, templateId); - if (sourcePdf == null) { sourcePdf = template; } + // Search if there have an old form having the form code + Map sourcePdf = getLastPdf(clientId, templateId); + if (sourcePdf == null) { sourcePdf = template; } byte[] sourcePdfBytes = (byte[])sourcePdf.get("blobValue"); String formCode = (String)template.get("remarks"); Client client = clientService.find(clientId).orElseThrow(); - //byte[] modifiedPdfBytes = pdfBytes; - - PdfAcroForm sourceForm = null; - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(sourcePdfBytes); - ByteArrayOutputStream sourceOutputStream = new ByteArrayOutputStream()) { + // Map to hold ALL extracted field data from the source PDF (historical values) + Map sourceFieldValues = new HashMap<>(); - PdfReader sourceReader = new PdfReader(inputStream); - PdfWriter sourceWriter = new PdfWriter(sourceOutputStream); - PdfDocument sourcePdfDoc = new PdfDocument(sourceReader, sourceWriter); - sourceForm = PdfAcroForm.getAcroForm(sourcePdfDoc, true); - } - - - logger.info("form set start:"); - byte[] modifiedPdfBytes = pdfBytes; + // Load common field data (used by both phases) + Long commonFieldId = commonFieldService.getByClientId(clientId); + CommonField commonField = commonFieldService.find(commonFieldId).orElse(null); + if(commonField == null){ + commonField = new CommonField(); + commonField.setClientId(clientId); + commonFieldService.save(commonField); + } + + // --- 1A. Extract ALL Field Values from Historical Source PDF (iText Read Only) --- + // This uses iText to reliably read field data BEFORE any writing/modification happens. + if (sourcePdfBytes != null) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(sourcePdfBytes)) { + PdfReader sourceReader = new PdfReader(inputStream); + PdfDocument sourcePdfDoc = new PdfDocument(sourceReader); + PdfAcroForm sourceForm = PdfAcroForm.getAcroForm(sourcePdfDoc, true); + + if (sourceForm != null) { + for (Map.Entry entry : sourceForm.getFormFields().entrySet()) { + PdfFormField sourceField = entry.getValue(); + if (sourceField.getFieldName() != null && sourceField.getValue() != null) { + sourceFieldValues.put(sourceField.getFieldName().toUnicodeString(), sourceField.getValueAsString()); + } + } + } + sourcePdfDoc.close(); + } + } + + // --- 2. Phase 1: Text Field Filling (iText Write) --- + logger.info("Phase 1 (iText): Starting text field processing for form code: {}" + formCode); + byte[] modifiedPdfBytes = pdfBytes; - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(pdfBytes); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(pdfBytes); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { PdfReader reader = new PdfReader(inputStream); PdfWriter writer = new PdfWriter(outputStream); PdfDocument pdfDoc = new PdfDocument(reader, writer); PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true); - logger.info("form:" + (form != null)); if (form != null) { - logger.info("sourceForm:" + (sourceForm != null)); - /* START of copying the source fields if exist */ - if(sourceForm != null){ - for (Map.Entry entry : sourceForm.getFormFields().entrySet()) { - PdfFormField sourceField = entry.getValue(); - - if(sourceField.getFieldName() != null){ - String sourceFieldName = sourceField.getFieldName().toUnicodeString(); - if(sourceField.getValue() != null){ - String sourceFieldValue = sourceField.getValueAsString(); - PdfFormField targetField = form.getField(sourceFieldName); - - //logger.info("Field '" + sourceFieldName + "' as '" + sourceFieldValue + "' from source set in target PDF."); - - if (targetField != null && sourceField.getFormType().equals(PdfName.Tx)) { - targetField.setValue(sourceFieldValue); // Set the value in the target field - } else { - //System.out.println("Field '" + sourceFieldName + "' from source not found in target PDF."); - } - } - } - } - } - /* END of copying the source fields */ - - // --- START DEBUGGING CODE TO LIST ALL PDF FORM FIELDS --- - // logger.info("--- Listing all form fields in the PDF (for ID: {}) ---", id); - // if (form.getFormFields().isEmpty()) { - // logger.info("No form fields found in this PDF."); - // } else { - // form.getFormFields().forEach((fieldName, pdfFormField) -> { - // logger.info(" - Field Name: '{}', Type: '{}'", fieldName, pdfFormField.getFormType()); - // }); - // } - // logger.info("--- End of form fields list ---"); - // --- END DEBUGGING CODE --- - - // Load common field data - Long commonFieldId = commonFieldService.getByClientId(clientId); - // logger.info("id? " + id); - CommonField commonField = commonFieldService.find(commonFieldId).orElse(null); - - if(commonField == null){ - //logger.info("No common field data found for clientId: " + clientId); - commonField = new CommonField(); - commonField.setClientId(clientId); - commonFieldService.save(commonField); + // ** START: Historical Text Field Copy ** + logger.info("Copying historical text fields..."); + for (Map.Entry entry : sourceFieldValues.entrySet()) { + String sourceFieldName = entry.getKey(); + String sourceFieldValue = entry.getValue(); + PdfFormField targetField = form.getField(sourceFieldName); + + if (targetField != null && targetField.getFormType().equals(PdfName.Tx)) { + targetField.setValue(sourceFieldValue); + logger.info("Historical Text Field '{}' set." + sourceFieldName); + } } + // ** END: Historical Text Field Copy ** - logger.info(formCode); - // It's highly recommended to check if the field exists before setting its value - // This prevents NullPointerExceptions if a field is missing in a template - //temp using the remarks as form code - if("IDA".equals(formCode)){ // This is IDA + // Apply current business logic to Text Fields + logger.info("Applying form code text fields: {}" + formCode); + if("IDA".equals(formCode)){ IDA_textBox(form, commonField, client); - } else if("FNA".equals(formCode)){ // This is FNA + } else if("FNA".equals(formCode)){ FNA_textBox(form, commonField, client); - } else if("HSBCFIN".equals(formCode)){ // This is HSBCFIN + } else if("HSBCFIN".equals(formCode)){ HSBCFIN_textBox(form, commonField, client); - }else if ("MLB03S".equals(formCode)) { + } else if ("MLB03S".equals(formCode)) { MLB03S_textBox(form, commonField, client); - } else if("MLFNA_EN".equals(formCode)){ // This is ML FNA EN - MLFNA_EN_textBox(form, commonField, client); - } else if("MLFNA_CHI".equals(formCode)){ // This is ML FNA CHI + } else if("MLFNA_EN".equals(formCode) || "MLFNA_CHI".equals(formCode)){ MLFNA_EN_textBox(form, commonField, client); - } else if("SLFNA_EN".equals(formCode)){ // This is SL FNA EN + } else if("SLFNA_EN".equals(formCode) || "SLFNA_CHI".equals(formCode)){ SLFNA_EN_textBox(form, commonField, client); - } else if("SLFNA_CHI".equals(formCode)){ // This is SL FNA CHI - SLFNA_EN_textBox(form, commonField, client); - }else if("SLAPP".equals(formCode)){ + } else if("SLAPP".equals(formCode)){ SLAPP_textBox(form, commonField, client); - }else if("HSBCA31".equals(formCode)){ // This is HSBC A31 + } else if("HSBCA31".equals(formCode)){ HSBCA31_textBox(form, commonField, client); - }else if("SLGII".equals(formCode)){ + } else if("SLGII".equals(formCode)){ SLGII_textBox(form, commonField, client); } - - - //form.flattenFields(); // Flatten fields after setting all values + pdfDoc.close(); + modifiedPdfBytes = outputStream.toByteArray(); } else { - // logger.warn("No AcroForm found in PDF for ID: " + id + ". PDF might not have fillable fields."); + logger.warn("iText found no AcroForm. Proceeding to PDFBox phase with original bytes."); } - pdfDoc.close(); - modifiedPdfBytes = outputStream.toByteArray(); - //logger.info("Modified PDF byte array length: " + modifiedPdfBytes.length); - } catch (IOException e) { - // logger.error("Error processing PDF with iText for ID: " + id, e); + logger.error("Error during iText text field processing.", e); + throw e; } - try (PDDocument sourceDoc = Loader.loadPDF(modifiedPdfBytes); - //PDDocument pdfDoc = Loader.loadPDF(modifiedPdfBytes); + // --- 3. Phase 2: Checkbox Filling (PDFBox Write) --- + logger.info("Phase 2 (PDFBox): Starting checkbox processing."); + + try (PDDocument targetDoc = Loader.loadPDF(modifiedPdfBytes); // Load iText's output ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - - PDAcroForm sourceForm2 = sourceDoc.getDocumentCatalog().getAcroForm(); - PDAcroForm form2 = sourceDoc.getDocumentCatalog().getAcroForm(); + + PDAcroForm form2 = targetDoc.getDocumentCatalog().getAcroForm(); if (form2 != null) { - if (sourceForm2 != null) { - for (PDField sourceField : sourceForm2.getFields()) { - String sourceFieldName = sourceField.getPartialName(); - if (sourceFieldName != null) { - String sourceFieldValue = sourceField.getValueAsString(); - PDField targetField = form2.getField(sourceFieldName); - - //only do for check box - if ("Btn".equals(sourceField.getFieldType()) || "Ch".equals(sourceField.getFieldType()) - && targetField != null && sourceFieldValue != null) { - - try{ - targetField.setValue(sourceFieldValue); - }catch (Exception e){ - logger.info("sourceFieldName error:" + sourceFieldName); - } - - } + + // ** START: Historical Checkbox/Button Copy ** + logger.info("Copying historical button fields from map..."); + for (Map.Entry entry : sourceFieldValues.entrySet()) { + String sourceFieldName = entry.getKey(); + String sourceValue = entry.getValue(); + + PDField targetField = form2.getField(sourceFieldName); + + // CRITICAL: Check the field type before casting! + if (targetField instanceof PDButton) { + PDButton button = (PDButton) targetField; + + Set onValues = button.getOnValues(); + + if (sourceValue != null && onValues != null && onValues.contains(sourceValue)) { + button.setValue(sourceValue); + logger.info("Historical Checkbox '{}' set to CHECKED." + sourceFieldName); + } else { + button.setValue("Off"); + logger.info("Historical Checkbox '{}' set to UNCHECKED." + sourceFieldName); } - } - + } + // Ignore other field types (Text was handled by iText). } + // ** END: Historical Checkbox/Button Copy ** - Long commonFieldId = commonFieldService.getByClientId(clientId); - CommonField commonField = commonFieldService.find(commonFieldId).orElse(null); - - if (commonField != null) { - if ("IDA".equals(formCode)) { - IDA_checkBox(form2, commonField); - } else if ("FNA".equals(formCode)) { - FNA_checkBox(form2, commonField); - } else if ("HSBCFIN".equals(formCode)) { - HSBCFIN_checkBox(form2, commonField); - }else if ("MLB03S".equals(formCode)) { - MLB03S_checkBox(form2, commonField); - }else if("MLFNA_EN".equals(formCode)){ // This is ML FNA EN - MLFNA_EN_checkBox(form2, commonField, client); - } else if("MLFNA_CHI".equals(formCode)){ // This is ML FNA CHI - MLFNA_EN_checkBox(form2, commonField, client); - } else if("SLFNA_EN".equals(formCode)){ // This is SL FNA EN - SLFNA_EN_checkBox(form2, commonField); - } else if("SLFNA_CHI".equals(formCode)){ // This is SL FNA CHI - SLFNA_EN_checkBox(form2, commonField); - }else if("SLAPP".equals(formCode)){ - SLAPP_checkBox(form2, commonField); - } else if("HSBCA31".equals(formCode)){ - HSBCA31_checkBox(form2, commonField); - }else if ("SLGII".equals(formCode)) { - SLGII_checkBox(form2, commonField); - } + // Apply current business logic to Checkboxes/Buttons (overwrites historical) + logger.info("Applying form code checkbox fields: {}" + formCode); + if ("IDA".equals(formCode)) { + IDA_checkBox(form2, commonField); + } else if ("FNA".equals(formCode)) { + FNA_checkBox(form2, commonField); + } else if ("HSBCFIN".equals(formCode)) { + HSBCFIN_checkBox(form2, commonField); + } else if ("MLB03S".equals(formCode)) { + MLB03S_checkBox(form2, commonField); + } else if("MLFNA_EN".equals(formCode) || "MLFNA_CHI".equals(formCode)){ + MLFNA_EN_checkBox(form2, commonField, client); + } else if("SLFNA_EN".equals(formCode) || "SLFNA_CHI".equals(formCode)){ + SLFNA_EN_checkBox(form2, commonField); + } else if("SLAPP".equals(formCode)){ + SLAPP_checkBox(form2, commonField); + } else if("HSBCA31".equals(formCode)){ + HSBCA31_checkBox(form2, commonField); + } else if ("SLGII".equals(formCode)) { + SLGII_checkBox(form2, commonField); } - - // Save the modified document to the output stream - sourceDoc.save(outputStream); - sourceDoc.close(); + + // Save the final modified document + targetDoc.save(outputStream); + targetDoc.close(); return outputStream.toByteArray(); } else { - return pdfBytes; // Return original bytes if no form found + logger.warn("PDFBox found no AcroForm. Returning iText output."); + return modifiedPdfBytes; } - } catch (IOException e) { - // logger.error("Error processing PDF with iText for ID: " + id, e); + + } catch (Exception e) { + logger.error("Error during PDFBox checkbox processing.", e); + throw new IOException("Failed to process checkboxes with PDFBox.", e); } - - // try (InputStream is = pdfFile.getInputStream()) { - // return pdfBytes; - // } catch (Exception e) { - // throw new UnprocessableEntityException("Fail to load PDF: " + e); - // } - return modifiedPdfBytes; - // return fileService.getfileBlob((long) 2); - } + } private void IDA_textBox(PdfAcroForm form, CommonField commonField, Client client){ logger.info("Processing IDA form (ID: 1)");