| @@ -10,6 +10,7 @@ import java.util.HashMap; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.Locale; | import java.util.Locale; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| import java.util.Set; | |||||
| import java.util.regex.Matcher; | import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | 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.Loader; | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; | 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.apache.pdfbox.pdmodel.interactive.form.PDField; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
| @@ -231,214 +233,184 @@ public class PdfService extends AbstractBaseEntityService<Pdf, Long, PdfReposito | |||||
| //This included the common fields getting | //This included the common fields getting | ||||
| public byte[] getTemplateForm(byte[] pdfBytes, Long clientId, Long templateId) throws IOException { | public byte[] getTemplateForm(byte[] pdfBytes, Long clientId, Long templateId) throws IOException { | ||||
| // --- 1. Initial Data Loading & Extraction Phase (iText Read) --- | |||||
| Map<String, Object> template = templateService.loadTemplate(templateId); | Map<String, Object> template = templateService.loadTemplate(templateId); | ||||
| //Search if there have an old form having the form code | |||||
| Map<String, Object> sourcePdf = getLastPdf(clientId, templateId); | |||||
| if (sourcePdf == null) { sourcePdf = template; } | |||||
| // Search if there have an old form having the form code | |||||
| Map<String, Object> sourcePdf = getLastPdf(clientId, templateId); | |||||
| if (sourcePdf == null) { sourcePdf = template; } | |||||
| byte[] sourcePdfBytes = (byte[])sourcePdf.get("blobValue"); | byte[] sourcePdfBytes = (byte[])sourcePdf.get("blobValue"); | ||||
| String formCode = (String)template.get("remarks"); | String formCode = (String)template.get("remarks"); | ||||
| Client client = clientService.find(clientId).orElseThrow(); | 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<String, String> 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<String, PdfFormField> 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); | PdfReader reader = new PdfReader(inputStream); | ||||
| PdfWriter writer = new PdfWriter(outputStream); | PdfWriter writer = new PdfWriter(outputStream); | ||||
| PdfDocument pdfDoc = new PdfDocument(reader, writer); | PdfDocument pdfDoc = new PdfDocument(reader, writer); | ||||
| PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true); | PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true); | ||||
| logger.info("form:" + (form != null)); | |||||
| if (form != null) { | if (form != null) { | ||||
| logger.info("sourceForm:" + (sourceForm != null)); | |||||
| /* START of copying the source fields if exist */ | |||||
| if(sourceForm != null){ | |||||
| for (Map.Entry<String, PdfFormField> 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<String, String> 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); | IDA_textBox(form, commonField, client); | ||||
| } else if("FNA".equals(formCode)){ // This is FNA | |||||
| } else if("FNA".equals(formCode)){ | |||||
| FNA_textBox(form, commonField, client); | FNA_textBox(form, commonField, client); | ||||
| } else if("HSBCFIN".equals(formCode)){ // This is HSBCFIN | |||||
| } else if("HSBCFIN".equals(formCode)){ | |||||
| HSBCFIN_textBox(form, commonField, client); | HSBCFIN_textBox(form, commonField, client); | ||||
| }else if ("MLB03S".equals(formCode)) { | |||||
| } else if ("MLB03S".equals(formCode)) { | |||||
| MLB03S_textBox(form, commonField, client); | 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); | 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); | 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); | SLAPP_textBox(form, commonField, client); | ||||
| }else if("HSBCA31".equals(formCode)){ // This is HSBC A31 | |||||
| } else if("HSBCA31".equals(formCode)){ | |||||
| HSBCA31_textBox(form, commonField, client); | HSBCA31_textBox(form, commonField, client); | ||||
| }else if("SLGII".equals(formCode)){ | |||||
| } else if("SLGII".equals(formCode)){ | |||||
| SLGII_textBox(form, commonField, client); | SLGII_textBox(form, commonField, client); | ||||
| } | } | ||||
| //form.flattenFields(); // Flatten fields after setting all values | |||||
| pdfDoc.close(); | |||||
| modifiedPdfBytes = outputStream.toByteArray(); | |||||
| } else { | } 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) { | } 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()) { | ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { | ||||
| PDAcroForm sourceForm2 = sourceDoc.getDocumentCatalog().getAcroForm(); | |||||
| PDAcroForm form2 = sourceDoc.getDocumentCatalog().getAcroForm(); | |||||
| PDAcroForm form2 = targetDoc.getDocumentCatalog().getAcroForm(); | |||||
| if (form2 != null) { | 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<String, String> 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<String> 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(); | return outputStream.toByteArray(); | ||||
| } else { | } 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){ | private void IDA_textBox(PdfAcroForm form, CommonField commonField, Client client){ | ||||
| logger.info("Processing IDA form (ID: 1)"); | logger.info("Processing IDA form (ID: 1)"); | ||||