From 5acb48ed7a5ac5d4eec0a16e97cfbf38c50925d1 Mon Sep 17 00:00:00 2001 From: svariant Date: Fri, 23 Jun 2023 16:42:13 +0200 Subject: [PATCH 01/12] [fix-improve-memory-usage] Managed the closure of input streams that remained open --- .../client/impl/PdfEngineClientImpl.java | 9 +++--- .../service/GenerateReceiptPdfService.java | 6 ++-- .../pdf/datastore/GenerateReceiptPdfTest.java | 28 +++++++++++++++---- .../client/PdfEngineClientImplTest.java | 27 ++++++++++++------ 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java index 94e3b4d6..1976e236 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java @@ -1,5 +1,6 @@ package it.gov.pagopa.receipt.pdf.datastore.client.impl; +import com.fasterxml.jackson.core.JsonProcessingException; import it.gov.pagopa.receipt.pdf.datastore.client.PdfEngineClient; import it.gov.pagopa.receipt.pdf.datastore.model.PdfEngineErrorResponse; import it.gov.pagopa.receipt.pdf.datastore.model.request.PdfEngineRequest; @@ -105,10 +106,10 @@ private static void handlePdfEngineResponse(PdfEngineResponse pdfEngineResponse, //Handles response if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entityResponse != null) { - InputStream inputStream = entityResponse.getContent(); - - pdfEngineResponse.setStatusCode(HttpStatus.SC_OK); - pdfEngineResponse.setPdf(inputStream.readAllBytes()); + try (InputStream inputStream = entityResponse.getContent()) { + pdfEngineResponse.setStatusCode(HttpStatus.SC_OK); + pdfEngineResponse.setPdf(inputStream.readAllBytes()); + } } else { pdfEngineResponse.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java index 335c0379..e7994c7e 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java @@ -20,6 +20,8 @@ import lombok.NoArgsConstructor; import org.apache.http.HttpStatus; +import java.io.FileInputStream; +import java.io.InputStream; import java.time.LocalDateTime; import java.util.logging.Logger; @@ -90,9 +92,9 @@ private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean co String fileName = completeTemplate ? completeTemplateFileName : partialTemplateFileName; - try { + try(InputStream templateStream = GenerateReceiptPdf.class.getClassLoader().getResourceAsStream(fileName)) { //File to byte[] - byte[] htmlTemplate = GenerateReceiptPdf.class.getClassLoader().getResourceAsStream(fileName).readAllBytes(); + byte[] htmlTemplate = templateStream.readAllBytes(); //Build the request request.setTemplate(htmlTemplate); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java index f9fd2eb0..c79cbcbb 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java @@ -102,7 +102,10 @@ void runOkReceiptStatusInsertedDifferentFiscalCode() throws ReceiptNotFoundExcep PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - byte[] pdf = new FileInputStream("src/test/resources/output.pdf").readAllBytes(); + byte[] pdf; + try (FileInputStream inputStream = new FileInputStream("src/test/resources/output.pdf")) { + pdf = inputStream.readAllBytes(); + } when(pdfEngineResponse.getPdf()).thenReturn(pdf); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -154,7 +157,10 @@ void runOkReceiptStatusInsertedSameFiscalCode() throws ReceiptNotFoundException, PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - byte[] pdf = new FileInputStream("src/test/resources/output.pdf").readAllBytes(); + byte[] pdf; + try (FileInputStream inputStream = new FileInputStream("src/test/resources/output.pdf")) { + pdf = inputStream.readAllBytes(); + } when(pdfEngineResponse.getPdf()).thenReturn(pdf); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -206,7 +212,10 @@ void runOkReceiptStatusRetrySameFiscalCode() throws ReceiptNotFoundException, IO PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - byte[] pdf = new FileInputStream("src/test/resources/output.pdf").readAllBytes(); + byte[] pdf; + try (FileInputStream inputStream = new FileInputStream("src/test/resources/output.pdf")) { + pdf = inputStream.readAllBytes(); + } when(pdfEngineResponse.getPdf()).thenReturn(pdf); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -242,7 +251,7 @@ void runOkReceiptStatusRetrySameFiscalCode() throws ReceiptNotFoundException, IO } @Test - void runKoInvalidBizEventMessage(){ + void runKoInvalidBizEventMessage() { @SuppressWarnings("unchecked") OutputBinding> documentdb = (OutputBinding>) spy(OutputBinding.class); @@ -401,7 +410,10 @@ void runKoBlobStorage() throws ReceiptNotFoundException, IOException { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - byte[] pdf = new FileInputStream("src/test/resources/output.pdf").readAllBytes(); + byte[] pdf; + try (FileInputStream inputStream = new FileInputStream("src/test/resources/output.pdf")) { + pdf = inputStream.readAllBytes(); + } when(pdfEngineResponse.getPdf()).thenReturn(pdf); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -450,7 +462,11 @@ void runKoBlobStorageThrowException() throws Exception { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - byte[] pdf = new FileInputStream("src/test/resources/output.pdf").readAllBytes(); + + byte[] pdf; + try (FileInputStream inputStream = new FileInputStream("src/test/resources/output.pdf")) { + pdf = inputStream.readAllBytes(); + } when(pdfEngineResponse.getPdf()).thenReturn(pdf); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java index 87150e1c..bcf0af95 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java @@ -24,16 +24,19 @@ class PdfEngineClientImplTest { @Test - void testSingleton(){ + void testSingleton() { Assertions.assertDoesNotThrow(PdfEngineClientImpl::getInstance); } @Test void runOk() throws IOException { + byte[] template; + String data; + try (InputStream inputStream = FileInputStream.nullInputStream()) { + template = inputStream.readAllBytes(); - byte[] template = FileInputStream.nullInputStream().readAllBytes(); - - String data = new String(FileInputStream.nullInputStream().readAllBytes()); + data = new String(template); + } PdfEngineRequest pdfEngineRequest = new PdfEngineRequest(); pdfEngineRequest.setTemplate(template); @@ -65,9 +68,13 @@ void runOk() throws IOException { @Test void runKoUnauthorized() throws IOException { - byte[] template = FileInputStream.nullInputStream().readAllBytes(); + byte[] template; + String data; + try (InputStream inputStream = FileInputStream.nullInputStream()) { + template = inputStream.readAllBytes(); - String data = new String(FileInputStream.nullInputStream().readAllBytes()); + data = new String(template); + } PdfEngineRequest pdfEngineRequest = new PdfEngineRequest(); pdfEngineRequest.setTemplate(template); @@ -99,9 +106,13 @@ void runKoUnauthorized() throws IOException { @Test void runKo400() throws IOException { - byte[] template = FileInputStream.nullInputStream().readAllBytes(); + byte[] template; + String data; + try (InputStream inputStream = FileInputStream.nullInputStream()) { + template = inputStream.readAllBytes(); - String data = new String(FileInputStream.nullInputStream().readAllBytes()); + data = new String(template); + } PdfEngineRequest pdfEngineRequest = new PdfEngineRequest(); pdfEngineRequest.setTemplate(template); From 83edbb4e83256281e708f6551eb8b9f475704f5b Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 26 Jun 2023 10:59:55 +0200 Subject: [PATCH 02/12] [fix-improve-memory-usage] Sending zip template as InputStream - removed file byte array read --- .../client/impl/PdfEngineClientImpl.java | 6 ++-- .../model/request/PdfEngineRequest.java | 4 ++- .../service/GenerateReceiptPdfService.java | 6 +--- .../client/PdfEngineClientImplTest.java | 32 ++++++------------- 4 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java index 1976e236..feddebf1 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java @@ -1,6 +1,5 @@ package it.gov.pagopa.receipt.pdf.datastore.client.impl; -import com.fasterxml.jackson.core.JsonProcessingException; import it.gov.pagopa.receipt.pdf.datastore.client.PdfEngineClient; import it.gov.pagopa.receipt.pdf.datastore.model.PdfEngineErrorResponse; import it.gov.pagopa.receipt.pdf.datastore.model.request.PdfEngineRequest; @@ -13,7 +12,6 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.ByteArrayBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -68,13 +66,13 @@ public PdfEngineResponse generatePDF(PdfEngineRequest pdfEngineRequest) { //Generate client try (CloseableHttpClient client = this.httpClientBuilder.build()) { //Encode template and data - ByteArrayBody fileBody = new ByteArrayBody(pdfEngineRequest.getTemplate(), ZIP_FILE_NAME); + StringBody dataBody = new StringBody(pdfEngineRequest.getData(), ContentType.APPLICATION_JSON); //Build the multipart request MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); - builder.addPart(TEMPLATE_KEY, fileBody); + builder.addBinaryBody(TEMPLATE_KEY, pdfEngineRequest.getTemplate(), ContentType.create("application/zip"), ZIP_FILE_NAME); builder.addPart(DATA_KEY, dataBody); HttpEntity entity = builder.build(); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/request/PdfEngineRequest.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/request/PdfEngineRequest.java index c595a08b..8ee0b614 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/request/PdfEngineRequest.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/request/PdfEngineRequest.java @@ -4,6 +4,8 @@ import lombok.NoArgsConstructor; import lombok.Setter; +import java.io.InputStream; + /** * Model class for PDF engine request */ @@ -12,7 +14,7 @@ @NoArgsConstructor public class PdfEngineRequest { - byte[] template; + InputStream template; String data; boolean applySignature; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java index e7994c7e..2021f008 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java @@ -20,7 +20,6 @@ import lombok.NoArgsConstructor; import org.apache.http.HttpStatus; -import java.io.FileInputStream; import java.io.InputStream; import java.time.LocalDateTime; import java.util.logging.Logger; @@ -93,11 +92,8 @@ private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean co String fileName = completeTemplate ? completeTemplateFileName : partialTemplateFileName; try(InputStream templateStream = GenerateReceiptPdf.class.getClassLoader().getResourceAsStream(fileName)) { - //File to byte[] - byte[] htmlTemplate = templateStream.readAllBytes(); - //Build the request - request.setTemplate(htmlTemplate); + request.setTemplate(templateStream); request.setData(ObjectMapperUtils.writeValueAsString(TemplateMapperUtils.convertReceiptToPdfData(bizEvent))); request.setApplySignature(false); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java index bcf0af95..d826c15d 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java @@ -12,10 +12,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.io.ByteArrayInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -31,17 +28,14 @@ void testSingleton() { @Test void runOk() throws IOException { byte[] template; - String data; + PdfEngineRequest pdfEngineRequest = new PdfEngineRequest(); try (InputStream inputStream = FileInputStream.nullInputStream()) { template = inputStream.readAllBytes(); - data = new String(template); + pdfEngineRequest.setTemplate(inputStream); + pdfEngineRequest.setData(new String(template)); } - PdfEngineRequest pdfEngineRequest = new PdfEngineRequest(); - pdfEngineRequest.setTemplate(template); - pdfEngineRequest.setData(data); - HttpClientBuilder mockBuilder = mock(HttpClientBuilder.class); CloseableHttpClient mockClient = mock(CloseableHttpClient.class); @@ -69,17 +63,14 @@ void runOk() throws IOException { void runKoUnauthorized() throws IOException { byte[] template; - String data; + PdfEngineRequest pdfEngineRequest = new PdfEngineRequest(); try (InputStream inputStream = FileInputStream.nullInputStream()) { template = inputStream.readAllBytes(); - data = new String(template); + pdfEngineRequest.setTemplate(inputStream); + pdfEngineRequest.setData(new String(template)); } - PdfEngineRequest pdfEngineRequest = new PdfEngineRequest(); - pdfEngineRequest.setTemplate(template); - pdfEngineRequest.setData(data); - HttpClientBuilder mockBuilder = mock(HttpClientBuilder.class); CloseableHttpClient mockClient = mock(CloseableHttpClient.class); @@ -107,17 +98,14 @@ void runKoUnauthorized() throws IOException { void runKo400() throws IOException { byte[] template; - String data; + PdfEngineRequest pdfEngineRequest = new PdfEngineRequest(); try (InputStream inputStream = FileInputStream.nullInputStream()) { template = inputStream.readAllBytes(); - data = new String(template); + pdfEngineRequest.setTemplate(inputStream); + pdfEngineRequest.setData(new String(template)); } - PdfEngineRequest pdfEngineRequest = new PdfEngineRequest(); - pdfEngineRequest.setTemplate(template); - pdfEngineRequest.setData(data); - HttpClientBuilder mockBuilder = mock(HttpClientBuilder.class); CloseableHttpClient mockClient = mock(CloseableHttpClient.class); From 2133dfdbd193cddfdb6d2a6a887ed44d2b137227 Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 26 Jun 2023 12:22:26 +0200 Subject: [PATCH 03/12] [fix-improve-memory-usage] Pdf-engine response saved to pdf file and re-read before saving it to blob storage --- .../datastore/client/ReceiptBlobClient.java | 4 +- .../client/impl/PdfEngineClientImpl.java | 7 +++- .../client/impl/ReceiptBlobClientImpl.java | 5 ++- .../model/response/PdfEngineResponse.java | 1 - .../service/GenerateReceiptPdfService.java | 20 ++++++---- .../pdf/datastore/GenerateReceiptPdfTest.java | 39 +++++-------------- .../client/PdfEngineClientImplTest.java | 2 - .../client/ReceiptBlobClientImplTest.java | 4 +- 8 files changed, 36 insertions(+), 46 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptBlobClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptBlobClient.java index 65ca0068..5e10c61e 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptBlobClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptBlobClient.java @@ -2,7 +2,9 @@ import it.gov.pagopa.receipt.pdf.datastore.model.response.BlobStorageResponse; +import java.io.InputStream; + public interface ReceiptBlobClient { - BlobStorageResponse savePdfToBlobStorage(byte[] pdf, String fileName); + BlobStorageResponse savePdfToBlobStorage(InputStream pdf, String fileName); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java index feddebf1..0dbec210 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java @@ -5,6 +5,7 @@ import it.gov.pagopa.receipt.pdf.datastore.model.request.PdfEngineRequest; import it.gov.pagopa.receipt.pdf.datastore.model.response.PdfEngineResponse; import it.gov.pagopa.receipt.pdf.datastore.utils.ObjectMapperUtils; +import org.apache.commons.io.FileUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; @@ -17,6 +18,7 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -32,6 +34,7 @@ public class PdfEngineClientImpl implements PdfEngineClient { private final String ocpAimSubKey = System.getenv().getOrDefault("OCP_APIM_SUBSCRIPTION_KEY", ""); private static final String HEADER_AUTH_KEY = "Ocp-Apim-Subscription-Key"; private static final String ZIP_FILE_NAME = "template.zip"; + private static final String TEMP_FILE_PATH = "src/main/resources/tempFile.pdf"; private static final String TEMPLATE_KEY = "template"; private static final String DATA_KEY = "data"; @@ -106,7 +109,9 @@ private static void handlePdfEngineResponse(PdfEngineResponse pdfEngineResponse, if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entityResponse != null) { try (InputStream inputStream = entityResponse.getContent()) { pdfEngineResponse.setStatusCode(HttpStatus.SC_OK); - pdfEngineResponse.setPdf(inputStream.readAllBytes()); + File targetFile = new File(TEMP_FILE_PATH); + + FileUtils.copyInputStreamToFile(inputStream, targetFile); } } else { pdfEngineResponse.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptBlobClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptBlobClientImpl.java index 4dd52fe6..a52b456d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptBlobClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptBlobClientImpl.java @@ -12,6 +12,7 @@ import it.gov.pagopa.receipt.pdf.datastore.model.response.BlobStorageResponse; import java.io.ByteArrayInputStream; +import java.io.InputStream; /** * Client for the Blob Storage @@ -55,7 +56,7 @@ public static ReceiptBlobClientImpl getInstance() { * @param fileName Filename to save the PDF with * @return blob storage response with PDF metadata or error message and status */ - public BlobStorageResponse savePdfToBlobStorage(byte[] pdf, String fileName) { + public BlobStorageResponse savePdfToBlobStorage(InputStream pdf, String fileName) { //Create the container and return a container client object BlobContainerClient blobContainerClient = this.blobServiceClient.getBlobContainerClient(containerName); @@ -67,7 +68,7 @@ public BlobStorageResponse savePdfToBlobStorage(byte[] pdf, String fileName) { //Upload the blob Response blockBlobItemResponse = blobClient.uploadWithResponse( new BlobParallelUploadOptions( - new ByteArrayInputStream(pdf) + pdf ), null, null); BlobStorageResponse blobStorageResponse = new BlobStorageResponse(); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java index 617ebdb4..235a1aab 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java @@ -12,7 +12,6 @@ @NoArgsConstructor public class PdfEngineResponse { - byte[] pdf; int statusCode; String errorMessage; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java index 2021f008..fb147333 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java @@ -20,6 +20,9 @@ import lombok.NoArgsConstructor; import org.apache.http.HttpStatus; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.InputStream; import java.time.LocalDateTime; import java.util.logging.Logger; @@ -28,6 +31,8 @@ public class GenerateReceiptPdfService { private static final int MAX_NUMBER_RETRY = Integer.parseInt(System.getenv().getOrDefault("COSMOS_RECEIPT_QUEUE_MAX_RETRY", "5")); + private static final String TEMP_FILE_PATH = "src/main/resources/tempFile.pdf"; + /** * Handles conditionally the generation of the PDFs based on the generateOnlyDebtor boolean @@ -107,7 +112,7 @@ private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean co //Save the PDF String pdfFileName = bizEvent.getId() + fiscalCode; - handleSaveToBlobStorage(response, pdfEngineResponse, pdfFileName); + handleSaveToBlobStorage(response, pdfFileName); } else { //Handle PDF generation error @@ -127,18 +132,19 @@ private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean co /** * Handles saving PDF to Blob Storage * - * @param response Pdf metadata containing response - * @param pdfEngineResponse Response from the pdf engine - * @param pdfFileName Filename composed of biz-event id and user fiscal code + * @param response Pdf metadata containing response + * @param pdfFileName Filename composed of biz-event id and user fiscal code */ - private void handleSaveToBlobStorage(PdfMetadata response, PdfEngineResponse pdfEngineResponse, String pdfFileName) { + private void handleSaveToBlobStorage(PdfMetadata response, String pdfFileName) { BlobStorageResponse blobStorageResponse; ReceiptBlobClientImpl blobClient = ReceiptBlobClientImpl.getInstance(); //Save to Blob Storage - try { - blobStorageResponse = blobClient.savePdfToBlobStorage(pdfEngineResponse.getPdf(), pdfFileName); + File tempPdf = new File(TEMP_FILE_PATH); + + try(BufferedInputStream pdfStream = new BufferedInputStream(new FileInputStream(tempPdf))) { + blobStorageResponse = blobClient.savePdfToBlobStorage(pdfStream, pdfFileName); if (blobStorageResponse.getStatusCode() == com.microsoft.azure.functions.HttpStatus.CREATED.value()) { //Update PDF metadata diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java index c79cbcbb..80a8c652 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java @@ -102,11 +102,7 @@ void runOkReceiptStatusInsertedDifferentFiscalCode() throws ReceiptNotFoundExcep PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - byte[] pdf; - try (FileInputStream inputStream = new FileInputStream("src/test/resources/output.pdf")) { - pdf = inputStream.readAllBytes(); - } - when(pdfEngineResponse.getPdf()).thenReturn(pdf); + when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); GenerateReceiptPdfTest.setMock(PdfEngineClientImpl.class, pdfEngineClient); @@ -115,7 +111,7 @@ void runOkReceiptStatusInsertedDifferentFiscalCode() throws ReceiptNotFoundExcep when(blobStorageResponse.getStatusCode()).thenReturn(com.microsoft.azure.functions.HttpStatus.CREATED.value()); when(blobStorageResponse.getDocumentUrl()).thenReturn(VALID_BLOB_URL); when(blobStorageResponse.getDocumentName()).thenReturn(VALID_BLOB_NAME); - when(blobClient.savePdfToBlobStorage(eq(pdf), anyString())).thenReturn(blobStorageResponse); + when(blobClient.savePdfToBlobStorage(any(), anyString())).thenReturn(blobStorageResponse); GenerateReceiptPdfTest.setMock(ReceiptBlobClientImpl.class, blobClient); @@ -157,11 +153,7 @@ void runOkReceiptStatusInsertedSameFiscalCode() throws ReceiptNotFoundException, PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - byte[] pdf; - try (FileInputStream inputStream = new FileInputStream("src/test/resources/output.pdf")) { - pdf = inputStream.readAllBytes(); - } - when(pdfEngineResponse.getPdf()).thenReturn(pdf); + when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); GenerateReceiptPdfTest.setMock(PdfEngineClientImpl.class, pdfEngineClient); @@ -170,7 +162,7 @@ void runOkReceiptStatusInsertedSameFiscalCode() throws ReceiptNotFoundException, when(blobStorageResponse.getStatusCode()).thenReturn(com.microsoft.azure.functions.HttpStatus.CREATED.value()); when(blobStorageResponse.getDocumentUrl()).thenReturn(VALID_BLOB_URL); when(blobStorageResponse.getDocumentName()).thenReturn(VALID_BLOB_NAME); - when(blobClient.savePdfToBlobStorage(eq(pdf), anyString())).thenReturn(blobStorageResponse); + when(blobClient.savePdfToBlobStorage(any(), anyString())).thenReturn(blobStorageResponse); GenerateReceiptPdfTest.setMock(ReceiptBlobClientImpl.class, blobClient); @@ -212,11 +204,7 @@ void runOkReceiptStatusRetrySameFiscalCode() throws ReceiptNotFoundException, IO PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - byte[] pdf; - try (FileInputStream inputStream = new FileInputStream("src/test/resources/output.pdf")) { - pdf = inputStream.readAllBytes(); - } - when(pdfEngineResponse.getPdf()).thenReturn(pdf); + when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); GenerateReceiptPdfTest.setMock(PdfEngineClientImpl.class, pdfEngineClient); @@ -225,7 +213,7 @@ void runOkReceiptStatusRetrySameFiscalCode() throws ReceiptNotFoundException, IO when(blobStorageResponse.getStatusCode()).thenReturn(com.microsoft.azure.functions.HttpStatus.CREATED.value()); when(blobStorageResponse.getDocumentUrl()).thenReturn(VALID_BLOB_URL); when(blobStorageResponse.getDocumentName()).thenReturn(VALID_BLOB_NAME); - when(blobClient.savePdfToBlobStorage(eq(pdf), anyString())).thenReturn(blobStorageResponse); + when(blobClient.savePdfToBlobStorage(any(), anyString())).thenReturn(blobStorageResponse); GenerateReceiptPdfTest.setMock(ReceiptBlobClientImpl.class, blobClient); @@ -410,18 +398,14 @@ void runKoBlobStorage() throws ReceiptNotFoundException, IOException { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - byte[] pdf; - try (FileInputStream inputStream = new FileInputStream("src/test/resources/output.pdf")) { - pdf = inputStream.readAllBytes(); - } - when(pdfEngineResponse.getPdf()).thenReturn(pdf); + when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); GenerateReceiptPdfTest.setMock(PdfEngineClientImpl.class, pdfEngineClient); ReceiptBlobClientImpl blobClient = mock(ReceiptBlobClientImpl.class); when(blobStorageResponse.getStatusCode()).thenReturn(com.microsoft.azure.functions.HttpStatus.FORBIDDEN.value()); - when(blobClient.savePdfToBlobStorage(eq(pdf), anyString())).thenReturn(blobStorageResponse); + when(blobClient.savePdfToBlobStorage(any(), anyString())).thenReturn(blobStorageResponse); GenerateReceiptPdfTest.setMock(ReceiptBlobClientImpl.class, blobClient); @@ -463,18 +447,13 @@ void runKoBlobStorageThrowException() throws Exception { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - byte[] pdf; - try (FileInputStream inputStream = new FileInputStream("src/test/resources/output.pdf")) { - pdf = inputStream.readAllBytes(); - } - when(pdfEngineResponse.getPdf()).thenReturn(pdf); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); GenerateReceiptPdfTest.setMock(PdfEngineClientImpl.class, pdfEngineClient); ReceiptBlobClientImpl blobClient = mock(ReceiptBlobClientImpl.class); when(blobStorageResponse.getStatusCode()).thenReturn(com.microsoft.azure.functions.HttpStatus.FORBIDDEN.value()); - when(blobClient.savePdfToBlobStorage(eq(pdf), anyString())).thenReturn(blobStorageResponse); + when(blobClient.savePdfToBlobStorage(any(), anyString())).thenReturn(blobStorageResponse); GenerateReceiptPdfTest.setMock(ReceiptBlobClientImpl.class, blobClient); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java index d826c15d..922ed75e 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java @@ -55,8 +55,6 @@ void runOk() throws IOException { PdfEngineResponse pdfEngineResponse = client.generatePDF(pdfEngineRequest); Assertions.assertEquals(HttpStatus.SC_OK, pdfEngineResponse.getStatusCode()); - Assertions.assertNotNull(pdfEngineResponse.getPdf()); - } @Test diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptBlobClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptBlobClientImplTest.java index fda2f264..1f58d1a0 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptBlobClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptBlobClientImplTest.java @@ -58,7 +58,7 @@ void runOk() throws IOException { ReceiptBlobClientImpl receiptBlobClient = new ReceiptBlobClientImpl(mockServiceClient); - BlobStorageResponse response = receiptBlobClient.savePdfToBlobStorage(InputStream.nullInputStream().readAllBytes(), "filename"); + BlobStorageResponse response = receiptBlobClient.savePdfToBlobStorage(InputStream.nullInputStream(), "filename"); assertEquals(HttpStatus.CREATED.value(), response.getStatusCode()); assertEquals(VALID_BLOB_NAME, response.getDocumentName()); @@ -90,7 +90,7 @@ void runKo() throws IOException { ReceiptBlobClientImpl receiptBlobClient = new ReceiptBlobClientImpl(mockServiceClient); - BlobStorageResponse response = receiptBlobClient.savePdfToBlobStorage(InputStream.nullInputStream().readAllBytes(), "filename"); + BlobStorageResponse response = receiptBlobClient.savePdfToBlobStorage(InputStream.nullInputStream(), "filename"); assertEquals(HttpStatus.NO_CONTENT.value(), response.getStatusCode()); assertNull(response.getDocumentName()); From cb07d4e88387eab2af1d529c523872e9470534fe Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 26 Jun 2023 15:53:20 +0200 Subject: [PATCH 04/12] [fix-improve-memory-usage] Used temporary files with unique names for storing PDFs --- .../datastore/client/impl/PdfEngineClientImpl.java | 7 +++++-- .../datastore/client/impl/ReceiptBlobClientImpl.java | 1 - .../datastore/model/response/PdfEngineResponse.java | 1 + .../datastore/service/GenerateReceiptPdfService.java | 11 +++-------- .../pdf/datastore/GenerateReceiptPdfTest.java | 12 ++++++++++++ 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java index 0dbec210..6d4541ed 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java @@ -34,7 +34,6 @@ public class PdfEngineClientImpl implements PdfEngineClient { private final String ocpAimSubKey = System.getenv().getOrDefault("OCP_APIM_SUBSCRIPTION_KEY", ""); private static final String HEADER_AUTH_KEY = "Ocp-Apim-Subscription-Key"; private static final String ZIP_FILE_NAME = "template.zip"; - private static final String TEMP_FILE_PATH = "src/main/resources/tempFile.pdf"; private static final String TEMPLATE_KEY = "template"; private static final String DATA_KEY = "data"; @@ -109,9 +108,13 @@ private static void handlePdfEngineResponse(PdfEngineResponse pdfEngineResponse, if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entityResponse != null) { try (InputStream inputStream = entityResponse.getContent()) { pdfEngineResponse.setStatusCode(HttpStatus.SC_OK); - File targetFile = new File(TEMP_FILE_PATH); + File targetFile = File.createTempFile("tempFile", ".pdf"); FileUtils.copyInputStreamToFile(inputStream, targetFile); + + pdfEngineResponse.setTempPdfPath(targetFile.getAbsolutePath()); + + targetFile.deleteOnExit(); } } else { pdfEngineResponse.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptBlobClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptBlobClientImpl.java index a52b456d..87e4f138 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptBlobClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptBlobClientImpl.java @@ -11,7 +11,6 @@ import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptBlobClient; import it.gov.pagopa.receipt.pdf.datastore.model.response.BlobStorageResponse; -import java.io.ByteArrayInputStream; import java.io.InputStream; /** diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java index 235a1aab..6b251b88 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java @@ -12,6 +12,7 @@ @NoArgsConstructor public class PdfEngineResponse { + String tempPdfPath; int statusCode; String errorMessage; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java index fb147333..701288d3 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java @@ -21,7 +21,6 @@ import org.apache.http.HttpStatus; import java.io.BufferedInputStream; -import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.time.LocalDateTime; @@ -31,8 +30,6 @@ public class GenerateReceiptPdfService { private static final int MAX_NUMBER_RETRY = Integer.parseInt(System.getenv().getOrDefault("COSMOS_RECEIPT_QUEUE_MAX_RETRY", "5")); - private static final String TEMP_FILE_PATH = "src/main/resources/tempFile.pdf"; - /** * Handles conditionally the generation of the PDFs based on the generateOnlyDebtor boolean @@ -112,7 +109,7 @@ private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean co //Save the PDF String pdfFileName = bizEvent.getId() + fiscalCode; - handleSaveToBlobStorage(response, pdfFileName); + handleSaveToBlobStorage(pdfEngineResponse, response, pdfFileName); } else { //Handle PDF generation error @@ -135,15 +132,13 @@ private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean co * @param response Pdf metadata containing response * @param pdfFileName Filename composed of biz-event id and user fiscal code */ - private void handleSaveToBlobStorage(PdfMetadata response, String pdfFileName) { + private void handleSaveToBlobStorage(PdfEngineResponse pdfEngineResponse,PdfMetadata response, String pdfFileName) { BlobStorageResponse blobStorageResponse; ReceiptBlobClientImpl blobClient = ReceiptBlobClientImpl.getInstance(); //Save to Blob Storage - File tempPdf = new File(TEMP_FILE_PATH); - - try(BufferedInputStream pdfStream = new BufferedInputStream(new FileInputStream(tempPdf))) { + try(BufferedInputStream pdfStream = new BufferedInputStream(new FileInputStream(pdfEngineResponse.getTempPdfPath()))) { blobStorageResponse = blobClient.savePdfToBlobStorage(pdfStream, pdfFileName); if (blobStorageResponse.getStatusCode() == com.microsoft.azure.functions.HttpStatus.CREATED.value()) { diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java index 80a8c652..f3f67110 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java @@ -24,6 +24,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Field; @@ -48,6 +49,7 @@ class GenerateReceiptPdfTest { private final String VALID_BLOB_NAME = "a valid debtor blob name"; private final String PDF_ENGINE_ERROR_MESSAGE = "pdf engine error message"; + private final String OUTPUT_PDF = "src/test/resources/output.pdf"; @Spy private GenerateReceiptPdf function; @@ -102,6 +104,8 @@ void runOkReceiptStatusInsertedDifferentFiscalCode() throws ReceiptNotFoundExcep PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); + File outputTemplate = new File(OUTPUT_PDF); + when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputTemplate.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -153,6 +157,8 @@ void runOkReceiptStatusInsertedSameFiscalCode() throws ReceiptNotFoundException, PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); + File outputTemplate = new File(OUTPUT_PDF); + when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputTemplate.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -204,6 +210,8 @@ void runOkReceiptStatusRetrySameFiscalCode() throws ReceiptNotFoundException, IO PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); + File outputTemplate = new File(OUTPUT_PDF); + when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputTemplate.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -398,6 +406,8 @@ void runKoBlobStorage() throws ReceiptNotFoundException, IOException { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); + File outputTemplate = new File(OUTPUT_PDF); + when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputTemplate.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -446,6 +456,8 @@ void runKoBlobStorageThrowException() throws Exception { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); + File outputTemplate = new File(OUTPUT_PDF); + when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputTemplate.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); From 08a834a074fc43b0e573b63c030f5951c9014edc Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 26 Jun 2023 16:13:11 +0200 Subject: [PATCH 05/12] [fix-improve-memory-usage] Manual delete of temp file --- .../pdf/datastore/client/impl/PdfEngineClientImpl.java | 2 -- .../pdf/datastore/service/GenerateReceiptPdfService.java | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java index 6d4541ed..0cc59a5d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java @@ -113,8 +113,6 @@ private static void handlePdfEngineResponse(PdfEngineResponse pdfEngineResponse, FileUtils.copyInputStreamToFile(inputStream, targetFile); pdfEngineResponse.setTempPdfPath(targetFile.getAbsolutePath()); - - targetFile.deleteOnExit(); } } else { pdfEngineResponse.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java index 701288d3..38543eb1 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java @@ -21,6 +21,7 @@ import org.apache.http.HttpStatus; import java.io.BufferedInputStream; +import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.time.LocalDateTime; @@ -159,6 +160,9 @@ private void handleSaveToBlobStorage(PdfEngineResponse pdfEngineResponse,PdfMeta response.setStatusCode(ReasonErrorCode.ERROR_BLOB_STORAGE.getCode()); response.setErrorMessage("Error saving pdf to blob storage : " + e); } + + File tempFile = new File(pdfEngineResponse.getTempPdfPath()); + tempFile.delete(); } From aff9f2e8cbd7b5a8e9a70a6a30b0c616538ef518 Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 27 Jun 2023 11:01:25 +0200 Subject: [PATCH 06/12] [fix-improve-memory-usage] To be deleted file to commit temp folder --- src/main/resources/temp/temp.tmp | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/temp/temp.tmp diff --git a/src/main/resources/temp/temp.tmp b/src/main/resources/temp/temp.tmp new file mode 100644 index 00000000..e69de29b From df861b3688885bdf8e8e05896fb655d12b9a24a9 Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 27 Jun 2023 11:01:56 +0200 Subject: [PATCH 07/12] [fix-improve-memory-usage] Deleted test pdf file --- src/test/resources/output.pdf | Bin 90487 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/test/resources/output.pdf diff --git a/src/test/resources/output.pdf b/src/test/resources/output.pdf deleted file mode 100644 index a7d60c1a09c936d99b14f9046d2b9e1444b46856..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90487 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 UJEH+?OTJ{tD|P$*&HZin0rV6@qyPW_ From c72a9ba7e95b182a787fdf07a27256a7ffca401f Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 27 Jun 2023 11:03:11 +0200 Subject: [PATCH 08/12] [fix-improve-memory-usage] Improved temp files generation (with protected folder) and deletion --- .../pdf/datastore/GenerateReceiptPdf.java | 2 +- .../client/impl/PdfEngineClientImpl.java | 2 +- .../service/GenerateReceiptPdfService.java | 34 +++++++++++-------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdf.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdf.java index 68600a15..b4ab1db5 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdf.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdf.java @@ -132,7 +132,7 @@ public void processGenerateReceipt( boolean generateOnlyDebtor = payerCF == null || payerCF.equals(debtorCF); //Generate and save PDF - PdfGeneration pdfGeneration = service.handlePdfsGeneration(generateOnlyDebtor, receipt, bizEvent, debtorCF, payerCF); + PdfGeneration pdfGeneration = service.handlePdfsGeneration(generateOnlyDebtor, receipt, bizEvent, debtorCF, payerCF, logger); //Write PDF blob storage metadata on receipt numberOfSavedPdfs = service.addPdfsMetadataToReceipt(receipt, pdfGeneration); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java index 0cc59a5d..84fb35c1 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java @@ -108,7 +108,7 @@ private static void handlePdfEngineResponse(PdfEngineResponse pdfEngineResponse, if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entityResponse != null) { try (InputStream inputStream = entityResponse.getContent()) { pdfEngineResponse.setStatusCode(HttpStatus.SC_OK); - File targetFile = File.createTempFile("tempFile", ".pdf"); + File targetFile = File.createTempFile("tempFile", ".pdf", new File("src/main/resources/temp")); FileUtils.copyInputStreamToFile(inputStream, targetFile); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java index 38543eb1..6ffd95d5 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java @@ -20,10 +20,8 @@ import lombok.NoArgsConstructor; import org.apache.http.HttpStatus; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; +import java.io.*; +import java.nio.file.Files; import java.time.LocalDateTime; import java.util.logging.Logger; @@ -42,7 +40,7 @@ public class GenerateReceiptPdfService { * @param payerCF Payer fiscal code * @return PdfGeneration object with the PDF metadata from the Blob Storage or relatives error messages */ - public PdfGeneration handlePdfsGeneration(boolean generateOnlyDebtor, Receipt receipt, BizEvent bizEvent, String debtorCF, String payerCF) { + public PdfGeneration handlePdfsGeneration(boolean generateOnlyDebtor, Receipt receipt, BizEvent bizEvent, String debtorCF, String payerCF, Logger logger) { PdfGeneration pdfGeneration = new PdfGeneration(); if (generateOnlyDebtor) { @@ -52,7 +50,7 @@ public PdfGeneration handlePdfsGeneration(boolean generateOnlyDebtor, Receipt re receipt.getMdAttach().getUrl() == null || receipt.getMdAttach().getUrl().isEmpty()) ) { - pdfGeneration.setDebtorMetadata(generatePdf(bizEvent, debtorCF, true)); + pdfGeneration.setDebtorMetadata(generatePdf(bizEvent, debtorCF, true, logger)); } } else { //Generate debtor's partial PDF @@ -61,7 +59,7 @@ public PdfGeneration handlePdfsGeneration(boolean generateOnlyDebtor, Receipt re receipt.getMdAttach().getUrl() == null || receipt.getMdAttach().getUrl().isEmpty()) ) { - pdfGeneration.setDebtorMetadata(generatePdf(bizEvent, debtorCF, false)); + pdfGeneration.setDebtorMetadata(generatePdf(bizEvent, debtorCF, false, logger)); } //Generate payer's complete PDF if (payerCF != null && @@ -69,7 +67,7 @@ public PdfGeneration handlePdfsGeneration(boolean generateOnlyDebtor, Receipt re receipt.getMdAttachPayer().getUrl() == null || receipt.getMdAttachPayer().getUrl().isEmpty()) ) { - pdfGeneration.setPayerMetadata(generatePdf(bizEvent, payerCF, true)); + pdfGeneration.setPayerMetadata(generatePdf(bizEvent, payerCF, true, logger)); } } @@ -84,7 +82,7 @@ public PdfGeneration handlePdfsGeneration(boolean generateOnlyDebtor, Receipt re * @param completeTemplate Boolean that indicates what template to use * @return PDF metadata retrieved from Blob Storage or relative error message */ - private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean completeTemplate) { + private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean completeTemplate, Logger logger) { PdfEngineRequest request = new PdfEngineRequest(); PdfMetadata response = new PdfMetadata(); @@ -110,7 +108,7 @@ private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean co //Save the PDF String pdfFileName = bizEvent.getId() + fiscalCode; - handleSaveToBlobStorage(pdfEngineResponse, response, pdfFileName); + handleSaveToBlobStorage(pdfEngineResponse, response, pdfFileName, logger); } else { //Handle PDF generation error @@ -133,13 +131,14 @@ private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean co * @param response Pdf metadata containing response * @param pdfFileName Filename composed of biz-event id and user fiscal code */ - private void handleSaveToBlobStorage(PdfEngineResponse pdfEngineResponse,PdfMetadata response, String pdfFileName) { + private void handleSaveToBlobStorage(PdfEngineResponse pdfEngineResponse,PdfMetadata response, String pdfFileName, Logger logger) { BlobStorageResponse blobStorageResponse; ReceiptBlobClientImpl blobClient = ReceiptBlobClientImpl.getInstance(); + String tempPdfPath = pdfEngineResponse.getTempPdfPath(); //Save to Blob Storage - try(BufferedInputStream pdfStream = new BufferedInputStream(new FileInputStream(pdfEngineResponse.getTempPdfPath()))) { + try(BufferedInputStream pdfStream = new BufferedInputStream(new FileInputStream(tempPdfPath))) { blobStorageResponse = blobClient.savePdfToBlobStorage(pdfStream, pdfFileName); if (blobStorageResponse.getStatusCode() == com.microsoft.azure.functions.HttpStatus.CREATED.value()) { @@ -161,8 +160,15 @@ private void handleSaveToBlobStorage(PdfEngineResponse pdfEngineResponse,PdfMeta response.setErrorMessage("Error saving pdf to blob storage : " + e); } - File tempFile = new File(pdfEngineResponse.getTempPdfPath()); - tempFile.delete(); + File tempFile = new File(tempPdfPath); + if(tempFile.exists()){ + try { + Files.delete(tempFile.toPath()); + } catch (IOException e) { + String logMsg = String.format("Error deleting temporary pdf file from file system: %e", e); + logger.warning(logMsg); + } + } } From fcf8449ebb214b0d209bf0ab331b6cbe5c997937 Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 27 Jun 2023 11:06:23 +0200 Subject: [PATCH 09/12] [fix-improve-memory-usage] Improved test coverage and testing with temp files --- .../pdf/datastore/BizEventToReceiptTest.java | 25 +++++ .../pdf/datastore/GenerateReceiptPdfTest.java | 94 +++++++++++++++---- .../client/PdfEngineClientImplTest.java | 2 + 3 files changed, 102 insertions(+), 19 deletions(-) diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceiptTest.java index bcb30374..52f77bff 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceiptTest.java @@ -83,6 +83,23 @@ void runOk() { assertEquals(DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); } + @Test + void runDiscarded() { + Logger logger = Logger.getLogger("BizEventToReceipt-test-logger"); + when(context.getLogger()).thenReturn(logger); + + List bizEventItems = new ArrayList<>(); + bizEventItems.add(generateNotDoneBizEvent()); + + @SuppressWarnings("unchecked") + OutputBinding> documentdb = (OutputBinding>) spy(OutputBinding.class); + + // test execution + assertDoesNotThrow(() -> function.processBizEventToReceipt(bizEventItems, documentdb, context)); + + verify(documentdb, never()).setValue(any()); + } + @Test void errorAddingMessageToQueue() { Logger logger = Logger.getLogger("BizEventToReceipt-test-logger"); @@ -174,4 +191,12 @@ private BizEvent generateValidBizEvent(){ return item; } + + private BizEvent generateNotDoneBizEvent(){ + BizEvent item = new BizEvent(); + + item.setEventStatus(BizEventStatusType.NA); + + return item; + } } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java index f3f67110..1fb12d55 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java @@ -14,20 +14,18 @@ import it.gov.pagopa.receipt.pdf.datastore.model.response.BlobStorageResponse; import it.gov.pagopa.receipt.pdf.datastore.model.response.PdfEngineResponse; import org.apache.http.HttpStatus; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Field; +import java.nio.file.Files; import java.util.List; import java.util.logging.Logger; @@ -49,7 +47,8 @@ class GenerateReceiptPdfTest { private final String VALID_BLOB_NAME = "a valid debtor blob name"; private final String PDF_ENGINE_ERROR_MESSAGE = "pdf engine error message"; - private final String OUTPUT_PDF = "src/test/resources/output.pdf"; + + private static File outputPdfDebtor; @Spy private GenerateReceiptPdf function; @@ -72,12 +71,22 @@ class GenerateReceiptPdfTest { @Captor private ArgumentCaptor messageCaptor; + @BeforeEach + public void createTemp() throws IOException { + outputPdfDebtor = File.createTempFile("outputDebtor", ".tmp", new File("src/test/resources")); + } + @AfterEach - public void teardown() throws Exception { + public void teardown() throws IOException, NoSuchFieldException, IllegalAccessException { // reset singleton tearDownInstance(ReceiptCosmosClientImpl.class); tearDownInstance(ReceiptBlobClientImpl.class); tearDownInstance(PdfEngineClientImpl.class); + + if(outputPdfDebtor.exists()){ + Files.delete(outputPdfDebtor.toPath()); + } + assertFalse(outputPdfDebtor.exists()); } private void tearDownInstance(Class classInstanced) throws IllegalAccessException, NoSuchFieldException { @@ -104,8 +113,10 @@ void runOkReceiptStatusInsertedDifferentFiscalCode() throws ReceiptNotFoundExcep PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - File outputTemplate = new File(OUTPUT_PDF); - when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputTemplate.getAbsolutePath()); + + File outputPdfPayer = File.createTempFile("outputPayer", ".tmp", new File("src/test/resources")); + when(pdfEngineResponse.getTempPdfPath()) + .thenReturn(outputPdfDebtor.getAbsolutePath(), outputPdfPayer.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -138,10 +149,12 @@ void runOkReceiptStatusInsertedDifferentFiscalCode() throws ReceiptNotFoundExcep assertEquals(VALID_BLOB_NAME, capturedCosmos.getMdAttach().getName()); assertEquals(VALID_BLOB_URL, capturedCosmos.getMdAttachPayer().getUrl()); assertEquals(VALID_BLOB_NAME, capturedCosmos.getMdAttachPayer().getName()); + + assertFalse(outputPdfPayer.exists()); } @Test - void runOkReceiptStatusInsertedSameFiscalCode() throws ReceiptNotFoundException, IOException { + void runOkReceiptStatusInsertedSameFiscalCode() throws ReceiptNotFoundException { Logger logger = Logger.getLogger("BizEventToReceipt-test-logger"); when(context.getLogger()).thenReturn(logger); @@ -157,8 +170,7 @@ void runOkReceiptStatusInsertedSameFiscalCode() throws ReceiptNotFoundException, PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - File outputTemplate = new File(OUTPUT_PDF); - when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputTemplate.getAbsolutePath()); + when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputPdfDebtor.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -194,7 +206,7 @@ void runOkReceiptStatusInsertedSameFiscalCode() throws ReceiptNotFoundException, } @Test - void runOkReceiptStatusRetrySameFiscalCode() throws ReceiptNotFoundException, IOException { + void runOkReceiptStatusRetrySameFiscalCode() throws ReceiptNotFoundException { Logger logger = Logger.getLogger("BizEventToReceipt-test-logger"); when(context.getLogger()).thenReturn(logger); @@ -210,8 +222,7 @@ void runOkReceiptStatusRetrySameFiscalCode() throws ReceiptNotFoundException, IO PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - File outputTemplate = new File(OUTPUT_PDF); - when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputTemplate.getAbsolutePath()); + when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputPdfDebtor.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -246,6 +257,53 @@ void runOkReceiptStatusRetrySameFiscalCode() throws ReceiptNotFoundException, IO ); } + @Test + void runDiscarded() throws ReceiptNotFoundException { + Logger logger = Logger.getLogger("BizEventToReceipt-test-logger"); + when(context.getLogger()).thenReturn(logger); + + ReceiptCosmosClientImpl cosmosClient = mock(ReceiptCosmosClientImpl.class); + receiptMock.setStatus(ReceiptStatusType.NOT_QUEUE_SENT); + EventData eventDataMock = mock(EventData.class); + + receiptMock.setEventData(eventDataMock); + when(cosmosClient.getReceiptDocument(any())).thenReturn(receiptMock); + + GenerateReceiptPdfTest.setMock(ReceiptCosmosClientImpl.class, cosmosClient); + + @SuppressWarnings("unchecked") + OutputBinding> documentdb = (OutputBinding>) spy(OutputBinding.class); + + @SuppressWarnings("unchecked") + OutputBinding requeueMessage = (OutputBinding) spy(OutputBinding.class); + + // test execution + assertDoesNotThrow(() -> function.processGenerateReceipt(BIZ_EVENT_MESSAGE_SAME_CF, documentdb, requeueMessage, context)); + + verify(documentdb, never()).setValue(any()); + } + + @Test + void runReceiptNotFound() throws ReceiptNotFoundException { + Logger logger = Logger.getLogger("BizEventToReceipt-test-logger"); + when(context.getLogger()).thenReturn(logger); + + ReceiptCosmosClientImpl cosmosClient = mock(ReceiptCosmosClientImpl.class); + when(cosmosClient.getReceiptDocument(any())) + .thenThrow(ReceiptNotFoundException.class); + GenerateReceiptPdfTest.setMock(ReceiptCosmosClientImpl.class, cosmosClient); + + @SuppressWarnings("unchecked") + OutputBinding> documentdb = (OutputBinding>) spy(OutputBinding.class); + + @SuppressWarnings("unchecked") + OutputBinding requeueMessage = (OutputBinding) spy(OutputBinding.class); + + // test execution + Assertions.assertThrows(ReceiptNotFoundException.class, + () -> function.processGenerateReceipt(BIZ_EVENT_MESSAGE_SAME_CF, documentdb, requeueMessage, context)); + } + @Test void runKoInvalidBizEventMessage() { @SuppressWarnings("unchecked") @@ -390,7 +448,7 @@ void runKoPdfEngine500() throws ReceiptNotFoundException { } @Test - void runKoBlobStorage() throws ReceiptNotFoundException, IOException { + void runKoBlobStorage() throws ReceiptNotFoundException { Logger logger = Logger.getLogger("BizEventToReceipt-test-logger"); when(context.getLogger()).thenReturn(logger); @@ -406,8 +464,7 @@ void runKoBlobStorage() throws ReceiptNotFoundException, IOException { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - File outputTemplate = new File(OUTPUT_PDF); - when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputTemplate.getAbsolutePath()); + when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputPdfDebtor.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -456,8 +513,7 @@ void runKoBlobStorageThrowException() throws Exception { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - File outputTemplate = new File(OUTPUT_PDF); - when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputTemplate.getAbsolutePath()); + when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputPdfDebtor.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java index 922ed75e..5d1069b6 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/PdfEngineClientImplTest.java @@ -54,6 +54,8 @@ void runOk() throws IOException { PdfEngineClientImpl client = new PdfEngineClientImpl(mockBuilder); PdfEngineResponse pdfEngineResponse = client.generatePDF(pdfEngineRequest); + File tempPdf = new File(pdfEngineResponse.getTempPdfPath()); + Assertions.assertTrue(tempPdf.delete()); Assertions.assertEquals(HttpStatus.SC_OK, pdfEngineResponse.getStatusCode()); } From e67199038ae08939032044c37baeb03bdeccd24e Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 27 Jun 2023 11:07:40 +0200 Subject: [PATCH 10/12] [fix-improve-memory-usage] Deleted file used to commit temp folder --- src/main/resources/temp/temp.tmp | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/resources/temp/temp.tmp diff --git a/src/main/resources/temp/temp.tmp b/src/main/resources/temp/temp.tmp deleted file mode 100644 index e69de29b..00000000 From 338b967cad6f1768e3fa9079484c286b50828312 Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 27 Jun 2023 12:35:07 +0200 Subject: [PATCH 11/12] [fix-improve-memory-usage] Added temp directory creation and deletion - improved exceptions logging --- .../client/impl/PdfEngineClientImpl.java | 41 +++++++++++++------ .../model/response/PdfEngineResponse.java | 1 + .../service/GenerateReceiptPdfService.java | 35 +++++++++++++--- .../pdf/datastore/GenerateReceiptPdfTest.java | 16 +++++++- 4 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java index 84fb35c1..402017d6 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; /** * Client for the PDF Engine @@ -83,9 +84,9 @@ public PdfEngineResponse generatePDF(PdfEngineRequest pdfEngineRequest) { request.setHeader(HEADER_AUTH_KEY, ocpAimSubKey); request.setEntity(entity); - handlePdfEngineResponse(pdfEngineResponse, client, request); + pdfEngineResponse = handlePdfEngineResponse(client, request); } catch (IOException e) { - pdfEngineResponse.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); + handleExceptionErrorMessage(pdfEngineResponse, e); } return pdfEngineResponse; @@ -94,11 +95,12 @@ public PdfEngineResponse generatePDF(PdfEngineRequest pdfEngineRequest) { /** * Calls the PDF Engine and handles its response, updating the PdfEngineResponse accordingly * - * @param pdfEngineResponse Response output - * @param client The previously generated client - * @param request The request to the PDF engine + * @param client The previously generated client + * @param request The request to the PDF engine + * @return pdf engine response */ - private static void handlePdfEngineResponse(PdfEngineResponse pdfEngineResponse, CloseableHttpClient client, HttpPost request) { + private static PdfEngineResponse handlePdfEngineResponse(CloseableHttpClient client, HttpPost request) { + PdfEngineResponse pdfEngineResponse = new PdfEngineResponse(); //Execute call try (CloseableHttpResponse response = client.execute(request)) { //Retrieve response @@ -108,21 +110,29 @@ private static void handlePdfEngineResponse(PdfEngineResponse pdfEngineResponse, if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entityResponse != null) { try (InputStream inputStream = entityResponse.getContent()) { pdfEngineResponse.setStatusCode(HttpStatus.SC_OK); - File targetFile = File.createTempFile("tempFile", ".pdf", new File("src/main/resources/temp")); + File tempDirectory = Files.createTempDirectory("temp").toFile(); + File targetFile = File.createTempFile("tempFile", ".pdf", tempDirectory); FileUtils.copyInputStreamToFile(inputStream, targetFile); pdfEngineResponse.setTempPdfPath(targetFile.getAbsolutePath()); + pdfEngineResponse.setTempDirectoryPath(tempDirectory.getAbsolutePath()); } } else { pdfEngineResponse.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); handleErrorResponse(pdfEngineResponse, response, entityResponse); } - - } catch (IOException e) { - pdfEngineResponse.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); + } catch (Exception e) { + handleExceptionErrorMessage(pdfEngineResponse, e); } + + return pdfEngineResponse; + } + + private static void handleExceptionErrorMessage(PdfEngineResponse pdfEngineResponse, Exception e) { + pdfEngineResponse.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); + pdfEngineResponse.setErrorMessage(String.format("Exception thrown during pdf generation process: %s", e)); } /** @@ -133,9 +143,16 @@ private static void handlePdfEngineResponse(PdfEngineResponse pdfEngineResponse, * @param entityResponse Response content from the PDF Engine * @throws IOException in case of error encoding to string */ - private static void handleErrorResponse(PdfEngineResponse pdfEngineResponse, CloseableHttpResponse response, HttpEntity entityResponse) throws IOException { + private static void handleErrorResponse( + PdfEngineResponse pdfEngineResponse, + CloseableHttpResponse response, + HttpEntity entityResponse + ) throws IOException { //Verify if unauthorized - if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + if (response != null && + response.getStatusLine() != null && + response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED + ) { pdfEngineResponse.setErrorMessage("Unauthorized call to PDF engine function"); } else if (entityResponse != null) { diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java index 6b251b88..357da385 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/response/PdfEngineResponse.java @@ -12,6 +12,7 @@ @NoArgsConstructor public class PdfEngineResponse { + String tempDirectoryPath; String tempPdfPath; int statusCode; String errorMessage; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java index 6ffd95d5..82cd5a65 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/GenerateReceiptPdfService.java @@ -92,7 +92,7 @@ private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean co String fileName = completeTemplate ? completeTemplateFileName : partialTemplateFileName; - try(InputStream templateStream = GenerateReceiptPdf.class.getClassLoader().getResourceAsStream(fileName)) { + try (InputStream templateStream = GenerateReceiptPdf.class.getClassLoader().getResourceAsStream(fileName)) { //Build the request request.setTemplate(templateStream); request.setData(ObjectMapperUtils.writeValueAsString(TemplateMapperUtils.convertReceiptToPdfData(bizEvent))); @@ -131,14 +131,16 @@ private PdfMetadata generatePdf(BizEvent bizEvent, String fiscalCode, boolean co * @param response Pdf metadata containing response * @param pdfFileName Filename composed of biz-event id and user fiscal code */ - private void handleSaveToBlobStorage(PdfEngineResponse pdfEngineResponse,PdfMetadata response, String pdfFileName, Logger logger) { + private void handleSaveToBlobStorage(PdfEngineResponse pdfEngineResponse, PdfMetadata response, String pdfFileName, Logger logger) { BlobStorageResponse blobStorageResponse; ReceiptBlobClientImpl blobClient = ReceiptBlobClientImpl.getInstance(); String tempPdfPath = pdfEngineResponse.getTempPdfPath(); + String tempDirectoryPath = pdfEngineResponse.getTempDirectoryPath(); + //Save to Blob Storage - try(BufferedInputStream pdfStream = new BufferedInputStream(new FileInputStream(tempPdfPath))) { + try (BufferedInputStream pdfStream = new BufferedInputStream(new FileInputStream(tempPdfPath))) { blobStorageResponse = blobClient.savePdfToBlobStorage(pdfStream, pdfFileName); if (blobStorageResponse.getStatusCode() == com.microsoft.azure.functions.HttpStatus.CREATED.value()) { @@ -160,12 +162,35 @@ private void handleSaveToBlobStorage(PdfEngineResponse pdfEngineResponse,PdfMeta response.setErrorMessage("Error saving pdf to blob storage : " + e); } + deleteTempFolderAndFile(tempPdfPath, tempDirectoryPath, logger); + } + + /** + * Delete temporary file and director created for the generated PDF + * + * @param tempPdfPath Path to the temp PDF file + * @param tempDirectoryPath Path to the temp directory containing the PDF file + * @param logger Logger + */ + private static void deleteTempFolderAndFile(String tempPdfPath, String tempDirectoryPath, Logger logger) { + String logMsg; + File tempFile = new File(tempPdfPath); - if(tempFile.exists()){ + if (tempFile.exists()) { try { Files.delete(tempFile.toPath()); } catch (IOException e) { - String logMsg = String.format("Error deleting temporary pdf file from file system: %e", e); + logMsg = String.format("Error deleting temporary pdf file from file system: %s", e); + logger.warning(logMsg); + } + } + + File tempDirectory = new File(tempDirectoryPath); + if (tempDirectory.exists()) { + try { + Files.delete(tempDirectory.toPath()); + } catch (IOException e) { + logMsg = String.format("Error deleting temporary pdf directory from file system: %s", e); logger.warning(logMsg); } } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java index 1fb12d55..9964579b 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/GenerateReceiptPdfTest.java @@ -49,6 +49,7 @@ class GenerateReceiptPdfTest { private final String PDF_ENGINE_ERROR_MESSAGE = "pdf engine error message"; private static File outputPdfDebtor; + private static File tempDirectoryDebtor; @Spy private GenerateReceiptPdf function; @@ -73,6 +74,7 @@ class GenerateReceiptPdfTest { @BeforeEach public void createTemp() throws IOException { + tempDirectoryDebtor = Files.createTempDirectory("temp").toFile(); outputPdfDebtor = File.createTempFile("outputDebtor", ".tmp", new File("src/test/resources")); } @@ -83,9 +85,13 @@ public void teardown() throws IOException, NoSuchFieldException, IllegalAccessEx tearDownInstance(ReceiptBlobClientImpl.class); tearDownInstance(PdfEngineClientImpl.class); + if(tempDirectoryDebtor.exists()){ + Files.delete(tempDirectoryDebtor.toPath()); + } if(outputPdfDebtor.exists()){ Files.delete(outputPdfDebtor.toPath()); } + assertFalse(tempDirectoryDebtor.exists()); assertFalse(outputPdfDebtor.exists()); } @@ -114,7 +120,10 @@ void runOkReceiptStatusInsertedDifferentFiscalCode() throws ReceiptNotFoundExcep PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); - File outputPdfPayer = File.createTempFile("outputPayer", ".tmp", new File("src/test/resources")); + File tempDirectoryPayer = Files.createTempDirectory("temp").toFile(); + File outputPdfPayer = File.createTempFile("outputPayer", ".tmp", tempDirectoryPayer); + when(pdfEngineResponse.getTempDirectoryPath()) + .thenReturn(tempDirectoryDebtor.getAbsolutePath(), tempDirectoryPayer.getAbsolutePath()); when(pdfEngineResponse.getTempPdfPath()) .thenReturn(outputPdfDebtor.getAbsolutePath(), outputPdfPayer.getAbsolutePath()); @@ -150,6 +159,7 @@ void runOkReceiptStatusInsertedDifferentFiscalCode() throws ReceiptNotFoundExcep assertEquals(VALID_BLOB_URL, capturedCosmos.getMdAttachPayer().getUrl()); assertEquals(VALID_BLOB_NAME, capturedCosmos.getMdAttachPayer().getName()); + assertFalse(tempDirectoryPayer.exists()); assertFalse(outputPdfPayer.exists()); } @@ -170,6 +180,7 @@ void runOkReceiptStatusInsertedSameFiscalCode() throws ReceiptNotFoundException PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); + when(pdfEngineResponse.getTempDirectoryPath()).thenReturn(tempDirectoryDebtor.getAbsolutePath()); when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputPdfDebtor.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -222,6 +233,7 @@ void runOkReceiptStatusRetrySameFiscalCode() throws ReceiptNotFoundException { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); + when(pdfEngineResponse.getTempDirectoryPath()).thenReturn(tempDirectoryDebtor.getAbsolutePath()); when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputPdfDebtor.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -464,6 +476,7 @@ void runKoBlobStorage() throws ReceiptNotFoundException { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); + when(pdfEngineResponse.getTempDirectoryPath()).thenReturn(tempDirectoryDebtor.getAbsolutePath()); when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputPdfDebtor.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); @@ -513,6 +526,7 @@ void runKoBlobStorageThrowException() throws Exception { PdfEngineClientImpl pdfEngineClient = mock(PdfEngineClientImpl.class); when(pdfEngineResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK); + when(pdfEngineResponse.getTempDirectoryPath()).thenReturn(tempDirectoryDebtor.getAbsolutePath()); when(pdfEngineResponse.getTempPdfPath()).thenReturn(outputPdfDebtor.getAbsolutePath()); when(pdfEngineClient.generatePDF(any())).thenReturn(pdfEngineResponse); From 3cba82d3333a47a46097c709d0abb663f5efc431 Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 27 Jun 2023 14:31:37 +0200 Subject: [PATCH 12/12] [fix-improve-memory-usage] Extracted save temp file method - condition on temp directory existence --- .../client/impl/PdfEngineClientImpl.java | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java index 402017d6..61a60139 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/PdfEngineClientImpl.java @@ -110,13 +110,8 @@ private static PdfEngineResponse handlePdfEngineResponse(CloseableHttpClient cli if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entityResponse != null) { try (InputStream inputStream = entityResponse.getContent()) { pdfEngineResponse.setStatusCode(HttpStatus.SC_OK); - File tempDirectory = Files.createTempDirectory("temp").toFile(); - File targetFile = File.createTempFile("tempFile", ".pdf", tempDirectory); - FileUtils.copyInputStreamToFile(inputStream, targetFile); - - pdfEngineResponse.setTempPdfPath(targetFile.getAbsolutePath()); - pdfEngineResponse.setTempDirectoryPath(tempDirectory.getAbsolutePath()); + saveTempPdf(pdfEngineResponse, inputStream); } } else { pdfEngineResponse.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); @@ -130,6 +125,33 @@ private static PdfEngineResponse handlePdfEngineResponse(CloseableHttpClient cli return pdfEngineResponse; } + /** + * Saves pdf as temporary file + * + * @param pdfEngineResponse Pdf engine response + * @param inputStream InputStream pdf + * @throws IOException In case of error to save + */ + private static void saveTempPdf(PdfEngineResponse pdfEngineResponse, InputStream inputStream) throws IOException { + File tempDirectory = new File("temp"); + if (!tempDirectory.exists()) { + Files.createDirectory(tempDirectory.toPath()); + } + + File targetFile = File.createTempFile("tempFile", ".pdf", tempDirectory); + + FileUtils.copyInputStreamToFile(inputStream, targetFile); + + pdfEngineResponse.setTempPdfPath(targetFile.getAbsolutePath()); + pdfEngineResponse.setTempDirectoryPath(tempDirectory.getAbsolutePath()); + } + + /** + * Handles error message in case of error thrown + * + * @param pdfEngineResponse Pdf engine respone + * @param e Error thrown + */ private static void handleExceptionErrorMessage(PdfEngineResponse pdfEngineResponse, Exception e) { pdfEngineResponse.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); pdfEngineResponse.setErrorMessage(String.format("Exception thrown during pdf generation process: %s", e));