diff --git a/postman/Opal Print.postman_collection.json b/postman/Opal Print.postman_collection.json
new file mode 100644
index 00000000..de0a9a56
--- /dev/null
+++ b/postman/Opal Print.postman_collection.json
@@ -0,0 +1,103 @@
+{
+ "info": {
+ "_postman_id": "47e7c899-66b9-485a-bf81-13002887dbc6",
+ "name": "Opal Print Service",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
+ "_exporter_id": "1068308"
+ },
+ "item": [
+ {
+ "name": "generate-pdf",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"xmlData\": \"John Doe123456789501.55\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n}\n"
+ },
+ "url": {
+ "raw": "http://localhost:4550/api/print/generate-pdf",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "4550",
+ "path": [
+ "api",
+ "print",
+ "generate-pdf"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "enqueue-print-jobs",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "[\n {\n \"xmlData\": \"This one should fail\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Bob Brown112233445250.00\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Alice Green556677889799.99\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Charlie Black2233445561240.50\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Emma White667788990645.25\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Liam Grey334455667320.75\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Olivia Brown445566778510.65\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Noah Blue223344556985.20\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Ava Red778899001450.10\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"William Green112233445702.95\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Sophia Violet667788990250.50\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"James Yellow334455667890.30\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Isabella Orange445566778395.75\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Benjamin Pink223344556980.00\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Mia Purple778899001215.80\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Lucas Grey112233445467.35\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Amelia White667788990730.15\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Henry Black3344556671005.25\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n },\n {\n \"xmlData\": \"Emily Brown445566778589.45\",\n \"docType\": \"TEST_PDF_definition_id\",\n \"docVersion\": \"test_version_1\"\n }\n]\n\n\n"
+ },
+ "url": {
+ "raw": "http://localhost:4550/api/print/enqueue-print-jobs",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "4550",
+ "path": [
+ "api",
+ "print",
+ "enqueue-print-jobs"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "process-pending-jobs",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"info\": {\n \"general\": {\n \"version\": \"00_1\",\n \"docref\": \"AAA\"\n }\n },\n \"data\": {\n \"job\": {\n \"division\": \"MinimalDivision\",\n \"accountnumber\": \"000001\",\n \"casenumber\": \"CASE-00001\",\n \"dob\": \"1990-01-01\",\n \"defendantname\": \"John Doe\",\n \"sex\": \"Male\",\n \"amountoutstanding\": \"£100.00\",\n \"defendantindefault\": \"No\",\n \"dateproduced\": \"2024-04-09\",\n \"dateoforder\": \"2024-03-01\",\n \"defendantaddress\": {\n \"street\": \"123 Minimal St\",\n \"city\": \"Minimal City\",\n \"postalCode\": \"M1234\"\n },\n \"jobcentreaddress\": {\n \"name\": \"Minimal Job Centre\",\n \"address\": {\n \"street\": \"456 Minimal St\",\n \"city\": \"Job Centre City\",\n \"postalCode\": \"JC123\"\n }\n }\n }\n }\n}\n"
+ },
+ "url": {
+ "raw": "http://localhost:4550/api/print/process-pending-jobs",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "4550",
+ "path": [
+ "api",
+ "print",
+ "process-pending-jobs"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/java/uk/gov/hmcts/opal/config/AsyncConfig.java b/src/main/java/uk/gov/hmcts/opal/config/AsyncConfig.java
new file mode 100644
index 00000000..6d539559
--- /dev/null
+++ b/src/main/java/uk/gov/hmcts/opal/config/AsyncConfig.java
@@ -0,0 +1,10 @@
+package uk.gov.hmcts.opal.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@Configuration
+@EnableAsync
+public class AsyncConfig {
+
+}
diff --git a/src/main/java/uk/gov/hmcts/opal/config/DatabaseConfiguration.java b/src/main/java/uk/gov/hmcts/opal/config/DatabaseConfiguration.java
index 938c1de5..4cde6dea 100644
--- a/src/main/java/uk/gov/hmcts/opal/config/DatabaseConfiguration.java
+++ b/src/main/java/uk/gov/hmcts/opal/config/DatabaseConfiguration.java
@@ -35,6 +35,7 @@ public TransactionAwareDataSourceProxy transactionAwareDataSourceProxy(
}
@Bean
+ @Primary
public PlatformTransactionManager transactionManager(
TransactionAwareDataSourceProxy transactionAwareDataSourceProxy
) {
diff --git a/src/main/java/uk/gov/hmcts/opal/config/PrintTransactionManagerConfig.java b/src/main/java/uk/gov/hmcts/opal/config/PrintTransactionManagerConfig.java
new file mode 100644
index 00000000..7e8fab97
--- /dev/null
+++ b/src/main/java/uk/gov/hmcts/opal/config/PrintTransactionManagerConfig.java
@@ -0,0 +1,18 @@
+package uk.gov.hmcts.opal.config;
+
+import jakarta.persistence.EntityManagerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.orm.jpa.JpaTransactionManager;
+
+@Configuration
+public class PrintTransactionManagerConfig {
+
+ @Bean(name = "printTransactionManager")
+ public JpaTransactionManager printTransactionManager(EntityManagerFactory entityManagerFactory) {
+ JpaTransactionManager transactionManager = new JpaTransactionManager();
+ transactionManager.setEntityManagerFactory(entityManagerFactory);
+ return transactionManager;
+ }
+
+}
diff --git a/src/main/java/uk/gov/hmcts/opal/controllers/print/PrintRequestController.java b/src/main/java/uk/gov/hmcts/opal/controllers/print/PrintRequestController.java
index 292ff580..051eaaa9 100644
--- a/src/main/java/uk/gov/hmcts/opal/controllers/print/PrintRequestController.java
+++ b/src/main/java/uk/gov/hmcts/opal/controllers/print/PrintRequestController.java
@@ -12,10 +12,12 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import uk.gov.hmcts.opal.entity.print.PrintJob;
+import uk.gov.hmcts.opal.service.print.AsyncPrintJobProcessor;
import uk.gov.hmcts.opal.service.print.PrintService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ContentDisposition;
+import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@@ -28,6 +30,9 @@ public class PrintRequestController {
private final PrintService printService;
+ private final AsyncPrintJobProcessor asyncPrintJobProcessor;
+
+
@PostMapping(value = "/enqueue-print-jobs", consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Enqueues print jobs for a batch of documents")
public ResponseEntity enqueuePrintJobs(@RequestBody List printJobs) {
@@ -52,5 +57,15 @@ public ResponseEntity generatePdf(@RequestBody PrintJob printJob) {
return ResponseEntity.ok().headers(headers).body(response);
}
+ @PostMapping(value = "/process-pending-jobs")
+ @Operation(summary = "Processes pending print jobs")
+ public ResponseEntity processPendingJobs() {
+ log.info(":POST:processPendingJobs: processing pending print jobs");
+
+ asyncPrintJobProcessor.processPendingJobsAsync(LocalDateTime.now());
+
+ return ResponseEntity.ok().body("OK");
+ }
+
}
diff --git a/src/main/java/uk/gov/hmcts/opal/entity/print/PrintJob.java b/src/main/java/uk/gov/hmcts/opal/entity/print/PrintJob.java
index 9e31123b..06efa0bb 100644
--- a/src/main/java/uk/gov/hmcts/opal/entity/print/PrintJob.java
+++ b/src/main/java/uk/gov/hmcts/opal/entity/print/PrintJob.java
@@ -7,7 +7,6 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
-import jakarta.persistence.Lob;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
@@ -41,7 +40,7 @@ public class PrintJob {
@Column(name = "job_uuid", nullable = false)
private UUID jobId;
- @Lob
+
@Column(name = "xml_data", nullable = false)
private String xmlData;
diff --git a/src/main/java/uk/gov/hmcts/opal/repository/print/PrintDefinitionRepository.java b/src/main/java/uk/gov/hmcts/opal/repository/print/PrintDefinitionRepository.java
index 15188cff..4d40a945 100644
--- a/src/main/java/uk/gov/hmcts/opal/repository/print/PrintDefinitionRepository.java
+++ b/src/main/java/uk/gov/hmcts/opal/repository/print/PrintDefinitionRepository.java
@@ -1,5 +1,6 @@
package uk.gov.hmcts.opal.repository.print;
+import jakarta.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import uk.gov.hmcts.opal.entity.print.PrintDefinition;
@@ -7,6 +8,6 @@
@Repository
public interface PrintDefinitionRepository extends JpaRepository {
-
+ @Transactional
PrintDefinition findByDocTypeAndTemplateId(String docType, String templateId);
}
diff --git a/src/main/java/uk/gov/hmcts/opal/repository/print/PrintJobRepository.java b/src/main/java/uk/gov/hmcts/opal/repository/print/PrintJobRepository.java
index 781e715e..6fbf34b7 100644
--- a/src/main/java/uk/gov/hmcts/opal/repository/print/PrintJobRepository.java
+++ b/src/main/java/uk/gov/hmcts/opal/repository/print/PrintJobRepository.java
@@ -1,8 +1,24 @@
package uk.gov.hmcts.opal.repository.print;
+import jakarta.persistence.LockModeType;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Lock;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
import uk.gov.hmcts.opal.entity.print.PrintJob;
+import uk.gov.hmcts.opal.entity.print.PrintStatus;
+import java.time.LocalDateTime;
+@Repository
public interface PrintJobRepository extends JpaRepository {
+
+ @Lock(LockModeType.PESSIMISTIC_WRITE)
+ @Query("SELECT p FROM PrintJob p WHERE p.status = :status AND p.createdAt <= :cutoffDate")
+ public Page findPendingJobsForUpdate(@Param("status") PrintStatus status,
+ @Param("cutoffDate") LocalDateTime cutoffDate,
+ Pageable pageable);
}
diff --git a/src/main/java/uk/gov/hmcts/opal/service/print/AsyncPrintJobProcessor.java b/src/main/java/uk/gov/hmcts/opal/service/print/AsyncPrintJobProcessor.java
new file mode 100644
index 00000000..43c4d985
--- /dev/null
+++ b/src/main/java/uk/gov/hmcts/opal/service/print/AsyncPrintJobProcessor.java
@@ -0,0 +1,24 @@
+package uk.gov.hmcts.opal.service.print;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+
+@Service
+public class AsyncPrintJobProcessor {
+
+ private final PrintService printService;
+
+ @Autowired
+ public AsyncPrintJobProcessor(PrintService printService) {
+ this.printService = printService;
+ }
+
+ @Async
+ public void processPendingJobsAsync(LocalDateTime cutoffDate) {
+ printService.processPendingJobs(cutoffDate);
+ }
+
+}
diff --git a/src/main/java/uk/gov/hmcts/opal/service/print/PrintService.java b/src/main/java/uk/gov/hmcts/opal/service/print/PrintService.java
index 75222d41..872a13b4 100644
--- a/src/main/java/uk/gov/hmcts/opal/service/print/PrintService.java
+++ b/src/main/java/uk/gov/hmcts/opal/service/print/PrintService.java
@@ -1,18 +1,28 @@
package uk.gov.hmcts.opal.service.print;
-import jakarta.transaction.Transactional;
+
+import lombok.Getter;
import lombok.RequiredArgsConstructor;
+import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
+import net.sf.saxon.TransformerFactoryImpl;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import uk.gov.hmcts.opal.entity.print.PrintDefinition;
import uk.gov.hmcts.opal.entity.print.PrintJob;
import uk.gov.hmcts.opal.entity.print.PrintStatus;
import uk.gov.hmcts.opal.repository.print.PrintDefinitionRepository;
import uk.gov.hmcts.opal.repository.print.PrintJobRepository;
+import uk.gov.hmcts.opal.sftp.SftpLocation;
+import uk.gov.hmcts.opal.sftp.SftpOutboundService;
import javax.xml.XMLConstants;
import javax.xml.transform.Result;
@@ -29,7 +39,9 @@
import java.util.UUID;
@Service
-@Transactional
+@Transactional(transactionManager = "printTransactionManager")
+@Setter
+@Getter
@RequiredArgsConstructor
@Slf4j(topic = "PrintService")
public class PrintService {
@@ -40,6 +52,15 @@ public class PrintService {
private final PrintJobRepository printJobRepository;
+ private final SftpOutboundService sftpOutboundService;
+
+
+ @Value("${printservice.maxRetries:3}")
+ private int maxRetries;
+
+ @Value("${printservice.pageSize:100}")
+ private int pageSize;
+
public byte[] generatePdf(PrintJob printJob) {
// Get print definition from database
@@ -53,7 +74,7 @@ public byte[] generatePdf(PrintJob printJob) {
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, outStream);
- TransformerFactory factory = TransformerFactory.newInstance();
+ TransformerFactory factory = new TransformerFactoryImpl();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
@@ -61,8 +82,6 @@ public byte[] generatePdf(PrintJob printJob) {
// Setup input for XSLT transformation
Source src = new StreamSource(xmlReader);
-
- // Resulting SAX events (the generated FO) must be piped through to FOP
Result res = new SAXResult(fop.getDefaultHandler());
// Start XSLT transformation and FOP processing
@@ -77,16 +96,17 @@ public byte[] generatePdf(PrintJob printJob) {
}
-
-
private PrintDefinition getPrintDefinition(String docType, String templateId) {
return printDefinitionRepository.findByDocTypeAndTemplateId(docType, templateId);
}
+
public UUID savePrintJobs(List printJobs) {
UUID batchId = UUID.randomUUID();
+ log.info("Saving print jobs for batch {}", batchId);
+
for (PrintJob printJob : printJobs) {
printJob.setBatchId(batchId);
printJob.setJobId(UUID.randomUUID());
@@ -100,5 +120,77 @@ public UUID savePrintJobs(List printJobs) {
}
+ public void processPendingJobs(LocalDateTime cutoffDate) {
+ int attempt = 0;
+ boolean success = false;
+
+ while (attempt < maxRetries && !success) {
+ try {
+ attempt++;
+ processJobsWithLock(cutoffDate);
+ success = true;
+ } catch (Exception e) {
+ if (e instanceof jakarta.persistence.PessimisticLockException) {
+ log.error("Could not acquire lock, retrying... ({} / {})", attempt, maxRetries);
+ if (attempt >= maxRetries) {
+ throw e; // Exceeded max retries, rethrow exception
+ }
+ } else {
+ throw e; // Non-locking exception, rethrow
+ }
+ }
+ }
+ log.info("Processed pending jobs");
+ }
+
+
+ protected void processJobsWithLock(LocalDateTime cutoffDate) {
+ Pageable pageable = PageRequest.of(0, pageSize);
+ log.info("Page Size: {}", pageSize);
+ Page page;
+ do {
+ page = this.findPendingJobsForUpdate(PrintStatus.PENDING, cutoffDate, pageable);
+ for (PrintJob job : page.getContent()) {
+ try {
+ processJob(job);
+ } catch (Exception e) {
+ log.error("Error processing job {}", job.getJobId(), e);
+ job.setStatus(PrintStatus.FAILED);
+ printJobRepository.save(job);
+ }
+ }
+ pageable = page.nextPageable();
+ } while (page.hasNext());
+ }
+
+
+ private void processJob(PrintJob job) {
+ job.setStatus(PrintStatus.IN_PROGRESS);
+ printJobRepository.save(job);
+
+ byte[] pdfData = generatePdf(job);
+
+ if (pdfData != null) {
+ savePdfToFile(pdfData, job);
+ job.setStatus(PrintStatus.COMPLETED);
+ } else {
+ job.setStatus(PrintStatus.FAILED);
+ }
+
+ printJobRepository.save(job);
+ }
+
+ private void savePdfToFile(byte[] pdfData, PrintJob job) {
+ String fileName = job.getBatchId() + "_" + job.getJobId() + ".pdf";
+ log.info("Saving PDF to file: {}", fileName);
+
+ sftpOutboundService.uploadFile(pdfData, SftpLocation.PRINT_LOCATION.getPath(), fileName);
+ }
+
+
+ private Page findPendingJobsForUpdate(PrintStatus status, LocalDateTime cutoffDate, Pageable pageable) {
+ log.info("Finding pending jobs for update");
+ return printJobRepository.findPendingJobsForUpdate(status, cutoffDate, pageable);
+ }
}
diff --git a/src/main/java/uk/gov/hmcts/opal/sftp/SftpLocation.java b/src/main/java/uk/gov/hmcts/opal/sftp/SftpLocation.java
index b3397023..cef9ae50 100644
--- a/src/main/java/uk/gov/hmcts/opal/sftp/SftpLocation.java
+++ b/src/main/java/uk/gov/hmcts/opal/sftp/SftpLocation.java
@@ -33,7 +33,9 @@ public enum SftpLocation {
DWP_BAILIFFS_ERROR(INBOUND, "dwp-bailiffs/error", "Error processing for DWP bailiffs"),
ALL_PAY(OUTBOUND, "allpay", "Goes to BAIS (pushed)"),
- ALL_PAY_ARCHIVE(OUTBOUND, "allpay-archive", "Goes to OAGS (pushed)");
+ ALL_PAY_ARCHIVE(OUTBOUND, "allpay-archive", "Goes to OAGS (pushed)"),
+
+ PRINT_LOCATION(OUTBOUND, "print", "Goes to Print Service (pushed)");
private final SftpDirection direction;
private final String path;
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 62d65d31..83013a0c 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -164,3 +164,7 @@ opal:
be-developer-config:
user-role-permissions: ${BE_DEV_ROLE_PERMISSIONS:}
+
+printservice:
+ maxRetries: 3
+ pageSize: 100
diff --git a/src/test/java/uk/gov/hmcts/opal/controllers/print/PrintRequestControllerTest.java b/src/test/java/uk/gov/hmcts/opal/controllers/print/PrintRequestControllerTest.java
index eda61344..78f3732d 100644
--- a/src/test/java/uk/gov/hmcts/opal/controllers/print/PrintRequestControllerTest.java
+++ b/src/test/java/uk/gov/hmcts/opal/controllers/print/PrintRequestControllerTest.java
@@ -8,6 +8,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import uk.gov.hmcts.opal.entity.print.PrintJob;
+import uk.gov.hmcts.opal.service.print.AsyncPrintJobProcessor;
import uk.gov.hmcts.opal.service.print.PrintService;
import java.util.Collections;
@@ -27,6 +28,9 @@ public class PrintRequestControllerTest {
@Mock
private PrintService printService;
+ @Mock
+ private AsyncPrintJobProcessor asyncPrintJobProcessor;
+
@InjectMocks
private PrintRequestController printRequestController;
@@ -65,5 +69,16 @@ void testGeneratePdf() {
assertEquals(pdfData, response.getBody());
verify(printService, times(1)).generatePdf(any(PrintJob.class));
}
+
+ @Test
+ void testProcessPendingJobs() {
+ // Act
+ ResponseEntity response = printRequestController.processPendingJobs();
+
+ // Assert
+ assertEquals(HttpStatus.OK, response.getStatusCode());
+ assertEquals("OK", response.getBody());
+ verify(asyncPrintJobProcessor, times(1)).processPendingJobsAsync(any());
+ }
}
diff --git a/src/test/java/uk/gov/hmcts/opal/service/print/PrintServiceTest.java b/src/test/java/uk/gov/hmcts/opal/service/print/PrintServiceTest.java
index a55024c0..05b1f63b 100644
--- a/src/test/java/uk/gov/hmcts/opal/service/print/PrintServiceTest.java
+++ b/src/test/java/uk/gov/hmcts/opal/service/print/PrintServiceTest.java
@@ -1,5 +1,6 @@
package uk.gov.hmcts.opal.service.print;
+
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.junit.jupiter.api.BeforeEach;
@@ -7,29 +8,41 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
import uk.gov.hmcts.opal.entity.print.PrintDefinition;
import uk.gov.hmcts.opal.entity.print.PrintJob;
import uk.gov.hmcts.opal.entity.print.PrintStatus;
import uk.gov.hmcts.opal.repository.print.PrintDefinitionRepository;
import uk.gov.hmcts.opal.repository.print.PrintJobRepository;
+import uk.gov.hmcts.opal.sftp.SftpOutboundService;
import java.io.ByteArrayInputStream;
+import java.time.LocalDateTime;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
-public class PrintServiceTest {
+class PrintServiceTest {
@Mock
private PrintDefinitionRepository printDefinitionRepository;
@@ -37,16 +50,28 @@ public class PrintServiceTest {
@Mock
private PrintJobRepository printJobRepository;
+ @Mock
+ private SftpOutboundService sftpOutboundService;
+
+
@InjectMocks
private PrintService printService;
+
+
+
+
+
+
private PrintJob printJob1;
private PrintJob printJob2;
private PrintJob printJob;
private PrintDefinition printDefinition;
+
+
@BeforeEach
- public void setUp() {
+ void setUp() {
// Setup for savePrintJobs test
printJob1 = new PrintJob();
printJob1.setXmlData("Data1");
@@ -83,10 +108,13 @@ public void setUp() {
+ ""
+ ""
+ "");
+
+
+
}
@Test
- public void testSavePrintJobs() {
+ void testSavePrintJobs() {
// Arrange
List printJobs = Arrays.asList(printJob1, printJob2);
@@ -96,16 +124,17 @@ public void testSavePrintJobs() {
// Assert
assertEquals(printJob1.getBatchId(), batchId);
assertEquals(printJob2.getBatchId(), batchId);
- assertEquals(printJob1.getStatus(), PrintStatus.PENDING);
- assertEquals(printJob2.getStatus(), PrintStatus.PENDING);
+ assertEquals(PrintStatus.PENDING, printJob1.getStatus());
+ assertEquals(PrintStatus.PENDING, printJob2.getStatus());
verify(printJobRepository, times(2)).save(any(PrintJob.class));
}
@Test
- public void testGeneratePdf() throws Exception {
+ void testGeneratePdf() throws Exception {
+
// Arrange
- when(printDefinitionRepository.findByDocTypeAndTemplateId(eq("docType1"), eq("1.0")))
+ when(printDefinitionRepository.findByDocTypeAndTemplateId("docType1", "1.0"))
.thenReturn(printDefinition);
// Act
@@ -122,4 +151,70 @@ public void testGeneratePdf() throws Exception {
assertTrue(pdfText.contains("Test"));
}
}
+
+ @Test
+ void testProcessJobsWithLock() {
+ // Arrange
+ when(printDefinitionRepository.findByDocTypeAndTemplateId("docType1", "1.0"))
+ .thenReturn(printDefinition);
+ LocalDateTime cutoffDate = LocalDateTime.now();
+ Pageable pageable = PageRequest.of(0, 10);
+ when(printJobRepository.findPendingJobsForUpdate(PrintStatus.PENDING, cutoffDate, pageable))
+ .thenReturn(new PageImpl<>(Collections.singletonList(printJob)))
+ .thenReturn(new PageImpl<>(Collections.emptyList()));
+
+ doNothing().when(sftpOutboundService).uploadFile(any(byte[].class), anyString(), anyString());
+ printService.setPageSize(10);
+ // Act
+ printService.processJobsWithLock(cutoffDate);
+
+ // Assert
+ verify(printJobRepository, atLeastOnce()).findPendingJobsForUpdate(eq(PrintStatus.PENDING), eq(cutoffDate),
+ any(Pageable.class));
+ verify(sftpOutboundService, atLeastOnce()).uploadFile(any(byte[].class), anyString(), anyString());
+ verify(printJobRepository, atLeastOnce()).save(any(PrintJob.class));
+ }
+
+ @Test
+ void testProcessPendingJobsSuccess() {
+ // Arrange
+ PrintService printServiceSpy = Mockito.spy(printService);
+ printServiceSpy.setMaxRetries(3);
+ LocalDateTime cutoffDate = LocalDateTime.now();
+ doNothing().when(printServiceSpy).processJobsWithLock(cutoffDate);
+
+ // Act
+ printServiceSpy.processPendingJobs(cutoffDate);
+
+ // Assert
+ verify(printServiceSpy, times(1)).processJobsWithLock(cutoffDate);
+
+ // Consider verifying the state of printServiceSpy or its dependencies here
+ // to ensure that processPendingJobs has the expected effects.
+ }
+
+ @Test
+ void testProcessPendingJobsMaxRetriesExceeded() {
+ // Arrange
+ PrintService printServiceSpy = Mockito.spy(printService);
+ printServiceSpy.setMaxRetries(3); // Assuming maxRetries is set to 3
+ LocalDateTime cutoffDate = LocalDateTime.now();
+
+ // Simulate failure in processing jobs, causing retries
+ doThrow(new jakarta.persistence.PessimisticLockException("could not acquire lock"))
+ .when(printServiceSpy).processJobsWithLock(cutoffDate);
+
+ // Act & Assert
+ assertThrows(RuntimeException.class, () -> {
+ printServiceSpy.processPendingJobs(cutoffDate);
+ }, "Expected processPendingJobs to throw RuntimeException after max retries exceeded");
+
+ // Verify that processJobsWithLock was attempted maxRetries times
+ verify(printServiceSpy, times(3)).processJobsWithLock(cutoffDate);
+ }
+
}
+
+
+
+
diff --git a/src/test/java/uk/gov/hmcts/opal/sftp/SftpLocationTest.java b/src/test/java/uk/gov/hmcts/opal/sftp/SftpLocationTest.java
index a72b6d8a..d73302c8 100644
--- a/src/test/java/uk/gov/hmcts/opal/sftp/SftpLocationTest.java
+++ b/src/test/java/uk/gov/hmcts/opal/sftp/SftpLocationTest.java
@@ -17,11 +17,12 @@ class SftpLocationTest {
void testGetOutboundLocations() {
List outboundLocations = Arrays.asList(
SftpLocation.ALL_PAY,
- SftpLocation.ALL_PAY_ARCHIVE
+ SftpLocation.ALL_PAY_ARCHIVE,
+ SftpLocation.PRINT_LOCATION
);
List result = SftpLocation.getOutboundLocations();
- assertEquals(2, outboundLocations.size());
+ assertEquals(3, outboundLocations.size());
assertEquals(outboundLocations, result);
}
@@ -44,7 +45,9 @@ void testGetInboundLocations() {
SftpLocation.DWP_BAILIFFS_SUCCESS,
SftpLocation.DWP_BAILIFFS_ERROR
);
- List outboundLocations = Arrays.asList(SftpLocation.ALL_PAY, SftpLocation.ALL_PAY_ARCHIVE);
+ List outboundLocations = Arrays.asList(SftpLocation.ALL_PAY,
+ SftpLocation.ALL_PAY_ARCHIVE,
+ SftpLocation.PRINT_LOCATION);
assertEquals(15, SftpLocation.getInboundLocations().size());
assertEquals(inboundLocations, SftpLocation.getInboundLocations());