| @@ -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<Pdf, Long, PdfReposito | |||
| //This included the common fields getting | |||
| 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); | |||
| //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"); | |||
| 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<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); | |||
| 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<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); | |||
| } 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<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(); | |||
| } 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)"); | |||