From 0721fc18fcf80536c4b6816ca8ec8ab1b9680ccb Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Thu, 18 Apr 2024 15:24:53 +0200 Subject: [PATCH 01/12] [VAS-828] feat: introducing massive generation API --- .../GenerationRequestController.java | 9 ++++++ .../model/NoticeGenerationMassiveRequest.java | 18 +++++++++++ .../model/NoticeGenerationRequestEH.java | 8 +++++ .../model/NoticeGenerationRequestItem.java | 17 ++++++++++ .../service/NoticeGenerationService.java | 2 ++ .../impl/NoticeGenerationServiceImpl.java | 31 +++++++++++++++++++ src/main/resources/application.properties | 25 +++++++++++++++ 7 files changed, 110 insertions(+) create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java index c05c9b4a..688cce02 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java @@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; import it.gov.pagopa.payment.notices.service.model.ProblemJson; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; import it.gov.pagopa.payment.notices.service.util.OpenApiTableMetadata; @@ -70,4 +71,12 @@ public GetGenerationRequestStatusResource getFolderStatus( return noticeGenerationService.getFolderStatus(folderId, userId); } + + @PostMapping("/generate-massive") + public String getFolderStatus( + @RequestBody NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, + @Parameter(description = "userId to use for request status retrieval") @RequestHeader("X-User-Id") String userId) { + return noticeGenerationService.generateMassive(noticeGenerationMassiveRequest, userId); + } + } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java new file mode 100644 index 00000000..d907cdc9 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java @@ -0,0 +1,18 @@ +package it.gov.pagopa.payment.notices.service.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class NoticeGenerationMassiveRequest { + + private List notices; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java new file mode 100644 index 00000000..05cd4898 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.payment.notices.service.model; + +public class NoticeGenerationRequestEH { + + private NoticeGenerationRequestItem noticeData; + private String folderId; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java new file mode 100644 index 00000000..87a11b6d --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java @@ -0,0 +1,17 @@ +package it.gov.pagopa.payment.notices.service.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class NoticeGenerationRequestItem { + + private String templateId; + private String data; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java index b3cacce4..87adc494 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java @@ -1,6 +1,7 @@ package it.gov.pagopa.payment.notices.service.service; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; /** * Service interface for notice generation @@ -17,4 +18,5 @@ public interface NoticeGenerationService { */ GetGenerationRequestStatusResource getFolderStatus(String folderId, String userId); + String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String userId); } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java index 0ac41710..4bc3b1a2 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java @@ -5,11 +5,16 @@ import it.gov.pagopa.payment.notices.service.exception.AppError; import it.gov.pagopa.payment.notices.service.exception.AppException; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; +import it.gov.pagopa.payment.notices.service.model.enums.PaymentGenerationRequestStatus; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestErrorRepository; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestRepository; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; +import jakarta.transaction.Transactional; import org.springframework.stereotype.Service; +import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -44,4 +49,30 @@ public GetGenerationRequestStatusResource getFolderStatus(String folderId, Strin .build(); } + @Override + @Transactional + public String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String userId) { + + try { + String folderId = + paymentGenerationRequestRepository.save( + PaymentNoticeGenerationRequest.builder() + .status(PaymentGenerationRequestStatus.INSERTED) + .createdAt(Instant.now()) + .items(new ArrayList<>()) + .numberOfElementsTotal(noticeGenerationMassiveRequest.getNotices().size()) + .requestDate(Instant.now()) + .build()).getId(); + + + + return folderId; + + } catch (Exception e) { + //TODO: Error + throw new AppException(AppError.UNKNOWN); + } + + } + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2f0016c6..d4fc92da 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -41,3 +41,28 @@ spring.cloud.azure.storage.blob.templates.connection_string=${TEMPLATE_STORAGE_C spring.cloud.azure.storage.blob.templates.containerName=${TEMPLATE_STORAGE_CONTAINER_NAME:noticetemplateblob} spring.cloud.azure.storage.blob.templates.retry=${TEMPLATE_STORAGE_RETRY:3} spring.cloud.azure.storage.blob.templates.timeout=${TEMPLATE_STORAGE_TIMEOUT:10} + +# EH Kafka Configuration +spring.cloud.function.definition=noticeGeneration +spring.cloud.stream.bindings.noticeGeneration-out-0.destination=${KAFKA_NOTICE_GENERATION_TOPIC} +spring.cloud.stream.bindings.noticeGeneration-out-0.content-type=${KAFKA_CONTENT_TYPE:application/json} +spring.cloud.stream.bindings.noticeGeneration-out-0.binder=notice-generation +spring.cloud.stream.binders.notice-generation.type=kafka +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.brokers=${KAFKA_BROKER:} +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.configuration.sasl.jaas.config=${KAFKA_SASL_JAAS_CONFIG:} +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.configuration.key.serializer=org.apache.kafka.common.serialization.StringSerializer +spring.cloud.stream.kafka.binder.auto-create-topics=false +spring.cloud.stream.kafka.binder.configuration.heartbeat.interval.ms=${KAFKA_CONFIG_HEARTBEAT_INTERVAL_MS:3000} +spring.cloud.stream.kafka.binder.configuration.session.timeout.ms=${KAFKA_CONFIG_SESSION_TIMEOUT_MS:60000} +spring.cloud.stream.kafka.binder.configuration.request.timeout.ms=${KAFKA_CONFIG_REQUEST_TIMEOUT_MS:60000} +spring.cloud.stream.kafka.binder.configuration.sasl.mechanism=${KAFKA_CONFIG_SASL_MECHANISM:PLAIN} +spring.cloud.stream.kafka.binder.configuration.security.protocol=${KAFKA_CONFIG_SECURITY_PROTOCOL:SASL_SSL} +spring.cloud.stream.kafka.binder.configuration.connections.max.idle.ms=${KAFKA_CONFIG_CONNECTION_MAX_IDLE_TIME:180000} +spring.cloud.stream.kafka.binder.configuration.metadata.max.idle.ms=${KAFKA_CONFIG_METADATA_MAX_IDLE_MS:180000} +spring.cloud.stream.kafka.binder.configuration.metadata.max.age.ms=${KAFKA_CONFIG_METADATA_MAX_AGE_INTERVAL:179000} +spring.cloud.stream.kafka.binder.configuration.max.request.size=${KAFKA_CONFIG_METADATA_MAX_REQUEST_SIZE:1000000} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.client.id=noticeGenProcessor +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.connections.max.idle.ms=${KAFKA_REWARD_RESPONSE_CONNECTION_MAX_IDLE_TIME:180000} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.retry.backoff.ms=${KAFKA_REWARD_RESPONSE_KAFKA_RETRY_MS:${KAFKA_RETRY_MS:10000}} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.linger.ms=${KAFKA_REWARD_RESPONSE_LINGER_MS:${KAFKA_LINGER_MS:2}} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.batch.size=${KAFKA_REWARD_RESPONSE_BATCH_SIZE:${KAFKA_BATCH_SIZE:16384}} From fd9baa4c02e3c7067ce969e395032627a6bab1f2 Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Thu, 18 Apr 2024 15:24:53 +0200 Subject: [PATCH 02/12] [VAS-828] feat: introducing massive generation API --- .../GenerationRequestController.java | 9 ++++++ .../model/NoticeGenerationMassiveRequest.java | 18 +++++++++++ .../model/NoticeGenerationRequestEH.java | 8 +++++ .../model/NoticeGenerationRequestItem.java | 17 ++++++++++ .../service/NoticeGenerationService.java | 2 ++ .../impl/NoticeGenerationServiceImpl.java | 31 +++++++++++++++++++ src/main/resources/application.properties | 25 +++++++++++++++ 7 files changed, 110 insertions(+) create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java index c05c9b4a..688cce02 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java @@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; import it.gov.pagopa.payment.notices.service.model.ProblemJson; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; import it.gov.pagopa.payment.notices.service.util.OpenApiTableMetadata; @@ -70,4 +71,12 @@ public GetGenerationRequestStatusResource getFolderStatus( return noticeGenerationService.getFolderStatus(folderId, userId); } + + @PostMapping("/generate-massive") + public String getFolderStatus( + @RequestBody NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, + @Parameter(description = "userId to use for request status retrieval") @RequestHeader("X-User-Id") String userId) { + return noticeGenerationService.generateMassive(noticeGenerationMassiveRequest, userId); + } + } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java new file mode 100644 index 00000000..d907cdc9 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java @@ -0,0 +1,18 @@ +package it.gov.pagopa.payment.notices.service.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class NoticeGenerationMassiveRequest { + + private List notices; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java new file mode 100644 index 00000000..05cd4898 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.payment.notices.service.model; + +public class NoticeGenerationRequestEH { + + private NoticeGenerationRequestItem noticeData; + private String folderId; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java new file mode 100644 index 00000000..87a11b6d --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java @@ -0,0 +1,17 @@ +package it.gov.pagopa.payment.notices.service.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class NoticeGenerationRequestItem { + + private String templateId; + private String data; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java index b3cacce4..87adc494 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java @@ -1,6 +1,7 @@ package it.gov.pagopa.payment.notices.service.service; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; /** * Service interface for notice generation @@ -17,4 +18,5 @@ public interface NoticeGenerationService { */ GetGenerationRequestStatusResource getFolderStatus(String folderId, String userId); + String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String userId); } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java index 0ac41710..4bc3b1a2 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java @@ -5,11 +5,16 @@ import it.gov.pagopa.payment.notices.service.exception.AppError; import it.gov.pagopa.payment.notices.service.exception.AppException; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; +import it.gov.pagopa.payment.notices.service.model.enums.PaymentGenerationRequestStatus; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestErrorRepository; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestRepository; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; +import jakarta.transaction.Transactional; import org.springframework.stereotype.Service; +import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -44,4 +49,30 @@ public GetGenerationRequestStatusResource getFolderStatus(String folderId, Strin .build(); } + @Override + @Transactional + public String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String userId) { + + try { + String folderId = + paymentGenerationRequestRepository.save( + PaymentNoticeGenerationRequest.builder() + .status(PaymentGenerationRequestStatus.INSERTED) + .createdAt(Instant.now()) + .items(new ArrayList<>()) + .numberOfElementsTotal(noticeGenerationMassiveRequest.getNotices().size()) + .requestDate(Instant.now()) + .build()).getId(); + + + + return folderId; + + } catch (Exception e) { + //TODO: Error + throw new AppException(AppError.UNKNOWN); + } + + } + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2f0016c6..d4fc92da 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -41,3 +41,28 @@ spring.cloud.azure.storage.blob.templates.connection_string=${TEMPLATE_STORAGE_C spring.cloud.azure.storage.blob.templates.containerName=${TEMPLATE_STORAGE_CONTAINER_NAME:noticetemplateblob} spring.cloud.azure.storage.blob.templates.retry=${TEMPLATE_STORAGE_RETRY:3} spring.cloud.azure.storage.blob.templates.timeout=${TEMPLATE_STORAGE_TIMEOUT:10} + +# EH Kafka Configuration +spring.cloud.function.definition=noticeGeneration +spring.cloud.stream.bindings.noticeGeneration-out-0.destination=${KAFKA_NOTICE_GENERATION_TOPIC} +spring.cloud.stream.bindings.noticeGeneration-out-0.content-type=${KAFKA_CONTENT_TYPE:application/json} +spring.cloud.stream.bindings.noticeGeneration-out-0.binder=notice-generation +spring.cloud.stream.binders.notice-generation.type=kafka +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.brokers=${KAFKA_BROKER:} +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.configuration.sasl.jaas.config=${KAFKA_SASL_JAAS_CONFIG:} +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.configuration.key.serializer=org.apache.kafka.common.serialization.StringSerializer +spring.cloud.stream.kafka.binder.auto-create-topics=false +spring.cloud.stream.kafka.binder.configuration.heartbeat.interval.ms=${KAFKA_CONFIG_HEARTBEAT_INTERVAL_MS:3000} +spring.cloud.stream.kafka.binder.configuration.session.timeout.ms=${KAFKA_CONFIG_SESSION_TIMEOUT_MS:60000} +spring.cloud.stream.kafka.binder.configuration.request.timeout.ms=${KAFKA_CONFIG_REQUEST_TIMEOUT_MS:60000} +spring.cloud.stream.kafka.binder.configuration.sasl.mechanism=${KAFKA_CONFIG_SASL_MECHANISM:PLAIN} +spring.cloud.stream.kafka.binder.configuration.security.protocol=${KAFKA_CONFIG_SECURITY_PROTOCOL:SASL_SSL} +spring.cloud.stream.kafka.binder.configuration.connections.max.idle.ms=${KAFKA_CONFIG_CONNECTION_MAX_IDLE_TIME:180000} +spring.cloud.stream.kafka.binder.configuration.metadata.max.idle.ms=${KAFKA_CONFIG_METADATA_MAX_IDLE_MS:180000} +spring.cloud.stream.kafka.binder.configuration.metadata.max.age.ms=${KAFKA_CONFIG_METADATA_MAX_AGE_INTERVAL:179000} +spring.cloud.stream.kafka.binder.configuration.max.request.size=${KAFKA_CONFIG_METADATA_MAX_REQUEST_SIZE:1000000} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.client.id=noticeGenProcessor +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.connections.max.idle.ms=${KAFKA_REWARD_RESPONSE_CONNECTION_MAX_IDLE_TIME:180000} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.retry.backoff.ms=${KAFKA_REWARD_RESPONSE_KAFKA_RETRY_MS:${KAFKA_RETRY_MS:10000}} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.linger.ms=${KAFKA_REWARD_RESPONSE_LINGER_MS:${KAFKA_LINGER_MS:2}} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.batch.size=${KAFKA_REWARD_RESPONSE_BATCH_SIZE:${KAFKA_BATCH_SIZE:16384}} From 54911e6ee795454bb6a01eaf89cf62ea05f32a50 Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Fri, 19 Apr 2024 18:20:56 +0200 Subject: [PATCH 03/12] [VAS-828] feat: Introducing massive generation logic and unit testing --- openapi/openapi.json | 162 +++++++++++++++++- pom.xml | 24 ++- .../GenerationRequestController.java | 7 +- .../NoticeGenerationRequestProducer.java | 8 + .../NoticeGenerationRequestProducerImpl.java | 43 +++++ .../service/exception/Aes256Exception.java | 35 ++++ .../notices/service/exception/AppError.java | 3 + .../model/NoticeGenerationMassiveRequest.java | 4 + .../model/NoticeGenerationRequestEH.java | 9 + .../model/NoticeGenerationRequestItem.java | 3 +- .../model/notice/CreditorInstitution.java | 26 +++ .../notices/service/model/notice/Debtor.java | 15 ++ .../service/model/notice/InstallmentData.java | 11 ++ .../service/model/notice/Installments.java | 13 ++ .../notices/service/model/notice/Notice.java | 32 ++++ .../model/notice/NoticeRequestData.java | 21 +++ .../PaymentGenerationRequestRepository.java | 5 + .../impl/NoticeGenerationServiceImpl.java | 61 ++++++- .../notices/service/util/Aes256Utils.java | 103 +++++++++++ src/main/resources/application.properties | 4 + .../GenerationRequestControllerTest.java | 72 ++++++++ .../impl/NoticeGenerationServiceImplTest.java | 69 +++++++- src/test/resources/application.properties | 4 + 23 files changed, 721 insertions(+), 13 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java diff --git a/openapi/openapi.json b/openapi/openapi.json index d3aa4822..a3579824 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -2,7 +2,7 @@ "openapi" : "3.0.1", "info" : { "title" : "print-payment-notices-service", - "description" : "@project.description@", + "description" : "PagoPA Print Payment Notices Service", "termsOfService" : "https://www.pagopa.gov.it/", "version" : "0.0.0" }, @@ -16,7 +16,7 @@ "tags" : [ "Notice Generation Request APIs" ], "summary" : "getFolderStatus", "description" : "Return generation request status for a folder of notices", - "operationId" : "getFolderStatus", + "operationId" : "getFolderStatus_1", "parameters" : [ { "name" : "folder_id", "in" : "path", @@ -154,6 +154,59 @@ } } ] }, + "/notices/generate-massive" : { + "post" : { + "tags" : [ "Notice Generation Request APIs" ], + "operationId" : "getFolderStatus", + "parameters" : [ { + "name" : "X-User-Id", + "in" : "header", + "description" : "userId to use for request status retrieval", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/NoticeGenerationMassiveRequest" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "OK", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "type" : "string" + } + } + } + } + } + }, + "parameters" : [ { + "name" : "X-Request-Id", + "in" : "header", + "description" : "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.", + "schema" : { + "type" : "string" + } + } ] + }, "/notices/templates/{template_id}" : { "get" : { "tags" : [ "Notice Templates APIs" ], @@ -171,7 +224,7 @@ } ], "responses" : { "200" : { - "description" : "OK", + "description" : "Return template data", "headers" : { "X-Request-Id" : { "description" : "This header identifies the call", @@ -288,6 +341,109 @@ }, "components" : { "schemas" : { + "CreditorInstitution" : { + "type" : "object" + }, + "Debtor" : { + "type" : "object" + }, + "InstallmentData" : { + "type" : "object", + "properties" : { + "amount" : { + "type" : "integer", + "format" : "int32" + }, + "dueDate" : { + "type" : "string" + } + } + }, + "Installments" : { + "type" : "object", + "properties" : { + "number" : { + "type" : "integer", + "format" : "int32" + }, + "data" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/InstallmentData" + } + } + } + }, + "Notice" : { + "required" : [ "code", "subject" ], + "type" : "object", + "properties" : { + "subject" : { + "type" : "string" + }, + "paymentAmount" : { + "type" : "integer", + "format" : "int64" + }, + "dueDate" : { + "type" : "string" + }, + "code" : { + "type" : "string" + }, + "installments" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Installments" + } + } + } + }, + "NoticeGenerationMassiveRequest" : { + "required" : [ "notices" ], + "type" : "object", + "properties" : { + "notices" : { + "maxItems" : 2147483647, + "minItems" : 1, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/NoticeGenerationRequestItem" + } + } + } + }, + "NoticeGenerationRequestItem" : { + "type" : "object", + "properties" : { + "templateId" : { + "type" : "string" + }, + "data" : { + "$ref" : "#/components/schemas/NoticeRequestData" + } + } + }, + "NoticeRequestData" : { + "type" : "object", + "properties" : { + "notice" : { + "$ref" : "#/components/schemas/Notice" + }, + "creditorInstitution" : { + "$ref" : "#/components/schemas/CreditorInstitution" + }, + "debtor" : { + "$ref" : "#/components/schemas/Debtor" + }, + "extraData" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + } + }, "ProblemJson" : { "type" : "object", "properties" : { diff --git a/pom.xml b/pom.xml index da6bf70e..f37504b9 100644 --- a/pom.xml +++ b/pom.xml @@ -54,8 +54,11 @@ com.azure.spring spring-cloud-azure-starter-storage-blob - 4.2.0 + + org.springframework.cloud + spring-cloud-starter-stream-kafka + org.springframework.boot spring-boot-starter-cache @@ -128,6 +131,25 @@ + + + + org.springframework.cloud + spring-cloud-dependencies + 2023.0.1 + pom + import + + + com.azure.spring + spring-cloud-azure-dependencies + 5.11.0 + pom + import + + + + diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java index 688cce02..b97c05c6 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java @@ -13,6 +13,8 @@ import it.gov.pagopa.payment.notices.service.model.ProblemJson; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; import it.gov.pagopa.payment.notices.service.util.OpenApiTableMetadata; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -74,8 +76,9 @@ public GetGenerationRequestStatusResource getFolderStatus( @PostMapping("/generate-massive") public String getFolderStatus( - @RequestBody NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, - @Parameter(description = "userId to use for request status retrieval") @RequestHeader("X-User-Id") String userId) { + @Valid @NotNull @RequestBody NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, + @Parameter(description = "userId to use for request status retrieval") + @RequestHeader("X-User-Id") String userId) { return noticeGenerationService.generateMassive(noticeGenerationMassiveRequest, userId); } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java new file mode 100644 index 00000000..e878c27b --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.payment.notices.service.events; + +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestEH; + +public interface NoticeGenerationRequestProducer { + boolean noticeGeneration(NoticeGenerationRequestEH noticeGenerationRequestEH); + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java new file mode 100644 index 00000000..b3d3def6 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java @@ -0,0 +1,43 @@ +package it.gov.pagopa.payment.notices.service.events; + +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestEH; +import org.springframework.cloud.stream.function.StreamBridge; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; + +import java.util.function.Supplier; + +@Service +public class NoticeGenerationRequestProducerImpl implements NoticeGenerationRequestProducer { + + private final StreamBridge streamBridge; + + public NoticeGenerationRequestProducerImpl(StreamBridge streamBridge) { + this.streamBridge = streamBridge; + } + + /** Declared just to let know Spring to connect the producer at startup */ + @Configuration + static class NoticeGenerationRequestProducerConfig { + @Bean + public Supplier>> noticeGenerationRequest() { + return Flux::empty; + } + } + + @Override + public boolean noticeGeneration(NoticeGenerationRequestEH noticeGenerationRequestEH) { + return streamBridge.send("noticeGeneration-out-0", + buildMessage(noticeGenerationRequestEH)); + } + + public static Message buildMessage( + NoticeGenerationRequestEH noticeGenerationRequestEH){ + return MessageBuilder.withPayload(noticeGenerationRequestEH).build(); + } + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java b/src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java new file mode 100644 index 00000000..deabfd49 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java @@ -0,0 +1,35 @@ +package it.gov.pagopa.payment.notices.service.exception; + +import lombok.Getter; + +/** + * Thrown in case an error occur when encrypting or decrypting a BizEvent + */ +@Getter +public class Aes256Exception extends Exception{ + private final int statusCode; + + /** + * Constructs new exception with provided message + * + * @param message Detail message + * @param statusCode status code + */ + public Aes256Exception(String message, int statusCode) { + super(message); + this.statusCode = statusCode; + } + + /** + * Constructs new exception with provided message + * + * @param message Detail message + * @param statusCode status code + * @param cause Exception causing the constructed one + */ + public Aes256Exception(String message, int statusCode, Throwable cause) { + super(message, cause); + this.statusCode = statusCode; + } + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/exception/AppError.java b/src/main/java/it/gov/pagopa/payment/notices/service/exception/AppError.java index 9b4660c6..40bc945f 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/exception/AppError.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/exception/AppError.java @@ -22,6 +22,9 @@ public enum AppError { FOLDER_NOT_AVAILABLE(HttpStatus.NOT_FOUND, "Folder Not Available", "Required folder is either missing or not available to the requirer"), + ERROR_ON_MASSIVE_GENERATION_REQUEST(HttpStatus.INTERNAL_SERVER_ERROR, + "Exception on Massive Generation Request", + "Encountered a blocking exception on the massive generation request"), UNKNOWN(null, null, null); diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java index d907cdc9..e9c380a4 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java @@ -1,5 +1,7 @@ package it.gov.pagopa.payment.notices.service.model; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -13,6 +15,8 @@ @Builder public class NoticeGenerationMassiveRequest { + @NotNull + @Size(min = 1) private List notices; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java index 05cd4898..110aa353 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java @@ -1,5 +1,14 @@ package it.gov.pagopa.payment.notices.service.model; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder public class NoticeGenerationRequestEH { private NoticeGenerationRequestItem noticeData; diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java index 87a11b6d..2dd36d17 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payment.notices.service.model; +import it.gov.pagopa.payment.notices.service.model.notice.NoticeRequestData; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -12,6 +13,6 @@ public class NoticeGenerationRequestItem { private String templateId; - private String data; + private NoticeRequestData data; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java new file mode 100644 index 00000000..23bb6fa8 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java @@ -0,0 +1,26 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +public class CreditorInstitution { + + @NotNull + @NotEmpty + private String taxCode; + @NotNull + @NotEmpty + private String fullName; + private String organization; + @NotNull + @NotEmpty + private String info; + @NotNull + private Boolean webChannel; + private String physicalChannel; + @NotNull + @NotEmpty + private String cbill; + + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java new file mode 100644 index 00000000..4219f2ce --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java @@ -0,0 +1,15 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +public class Debtor { + + private String taxCode; + @NotNull + @NotEmpty + private String fullName; + @NotNull + @NotEmpty + private String address; +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java new file mode 100644 index 00000000..a727f98e --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java @@ -0,0 +1,11 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import lombok.Data; + +@Data +public class InstallmentData { + + private Integer amount; + private String dueDate; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java new file mode 100644 index 00000000..01d53c03 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java @@ -0,0 +1,13 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import lombok.Data; + +import java.util.List; + +@Data +public class Installments { + + private Integer number; + private List data; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java new file mode 100644 index 00000000..6047396d --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java @@ -0,0 +1,32 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class Notice { + + @NotNull + @NotEmpty + private String subject; + + private Long paymentAmount; + + private String dueDate; + + @NotNull + @NotEmpty + private String code; + + private List installments; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java new file mode 100644 index 00000000..7226562f --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java @@ -0,0 +1,21 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class NoticeRequestData { + + private Notice notice; + private CreditorInstitution creditorInstitution; + private Debtor debtor; + private Map extraData; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/repository/PaymentGenerationRequestRepository.java b/src/main/java/it/gov/pagopa/payment/notices/service/repository/PaymentGenerationRequestRepository.java index 08e990b4..fe275915 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/repository/PaymentGenerationRequestRepository.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/repository/PaymentGenerationRequestRepository.java @@ -3,6 +3,7 @@ import it.gov.pagopa.payment.notices.service.entity.PaymentNoticeGenerationRequest; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; +import org.springframework.data.mongodb.repository.Update; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -10,4 +11,8 @@ @Repository public interface PaymentGenerationRequestRepository extends MongoRepository { Optional findByIdAndUserId(String folderId, String userId); + + @Update("{ '$inc' : { 'numberOfElementsFailed' : 1 } }") + long findAndIncrementNumberOfElementsFailedById(String folderId); + } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java index 4bc3b1a2..6684539d 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java @@ -1,16 +1,24 @@ package it.gov.pagopa.payment.notices.service.service.impl; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import it.gov.pagopa.payment.notices.service.entity.PaymentNoticeGenerationRequest; import it.gov.pagopa.payment.notices.service.entity.PaymentNoticeGenerationRequestError; +import it.gov.pagopa.payment.notices.service.events.NoticeGenerationRequestProducer; +import it.gov.pagopa.payment.notices.service.exception.Aes256Exception; import it.gov.pagopa.payment.notices.service.exception.AppError; import it.gov.pagopa.payment.notices.service.exception.AppException; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestEH; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestItem; import it.gov.pagopa.payment.notices.service.model.enums.PaymentGenerationRequestStatus; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestErrorRepository; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestRepository; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; +import it.gov.pagopa.payment.notices.service.util.Aes256Utils; import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.time.Instant; @@ -19,16 +27,27 @@ import java.util.Optional; @Service +@Slf4j public class NoticeGenerationServiceImpl implements NoticeGenerationService { private final PaymentGenerationRequestRepository paymentGenerationRequestRepository; private final PaymentGenerationRequestErrorRepository paymentGenerationRequestErrorRepository; + private final NoticeGenerationRequestProducer noticeGenerationRequestProducer; + + private final ObjectMapper objectMapper; + + private final Aes256Utils aes256Utils; + public NoticeGenerationServiceImpl( PaymentGenerationRequestRepository paymentGenerationRequestRepository, - PaymentGenerationRequestErrorRepository paymentGenerationRequestErrorRepository) { + PaymentGenerationRequestErrorRepository paymentGenerationRequestErrorRepository, + NoticeGenerationRequestProducer noticeGenerationRequestProducer, ObjectMapper objectMapper, Aes256Utils aes256Utils) { this.paymentGenerationRequestRepository = paymentGenerationRequestRepository; this.paymentGenerationRequestErrorRepository = paymentGenerationRequestErrorRepository; + this.noticeGenerationRequestProducer = noticeGenerationRequestProducer; + this.objectMapper = objectMapper; + this.aes256Utils = aes256Utils; } @Override @@ -64,15 +83,49 @@ public String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMas .requestDate(Instant.now()) .build()).getId(); - + noticeGenerationMassiveRequest.getNotices().parallelStream().forEach(noticeGenerationRequestItem -> { + try { + if (!noticeGenerationRequestProducer.noticeGeneration( + NoticeGenerationRequestEH.builder() + .noticeData(noticeGenerationRequestItem) + .folderId(folderId) + .build()) + ) { + saveErrorEvent(folderId, noticeGenerationRequestItem); + } + } catch (Exception e) { + saveErrorEvent(folderId, noticeGenerationRequestItem); + } + }); return folderId; } catch (Exception e) { - //TODO: Error - throw new AppException(AppError.UNKNOWN); + log.error(e.getMessage(), e); + throw new AppException(AppError.ERROR_ON_MASSIVE_GENERATION_REQUEST); } } + private void saveErrorEvent(String folderId, NoticeGenerationRequestItem noticeGenerationRequestItem) { + try { + paymentGenerationRequestErrorRepository.save( + PaymentNoticeGenerationRequestError.builder() + .errorDescription("Encountered error sending notice on EH") + .folderId(folderId) + .data(aes256Utils.encrypt(objectMapper + .writeValueAsString(noticeGenerationRequestItem))) + .createdAt(Instant.now()) + .numberOfAttempts(0) + .build() + ); + paymentGenerationRequestRepository.findAndIncrementNumberOfElementsFailedById(folderId); + } catch (JsonProcessingException | Aes256Exception e) { + log.error( + "Unable to save notice data into error repository for notice with folder " + folderId + + " and noticeId " + noticeGenerationRequestItem.getData().getNotice().getCode() + ); + } + } + } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java b/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java new file mode 100644 index 00000000..676b6c2e --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java @@ -0,0 +1,103 @@ +package it.gov.pagopa.payment.notices.service.util; + +import it.gov.pagopa.payment.notices.service.exception.Aes256Exception; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.security.spec.KeySpec; +import java.util.Base64; + +@Component +public class Aes256Utils { + + private String aesSecretKey; + + private String aesSalt; + + private static final int KEY_LENGTH = 256; + private static final int ITERATION_COUNT = 65536; + public static final String PBKDF_2_WITH_HMAC_SHA_256 = "PBKDF2WithHmacSHA256"; + public static final String AES_CBC_PKCS_5_PADDING = "AES/CBC/PKCS5Padding"; + + public static final String ALGORITHM = "AES"; + + private static final int AES_UNEXPECTED_ERROR = 701; + + + /** + * Hide from public usage. + */ + private Aes256Utils() { + } + + public Aes256Utils( + @Value("${aes.secret.key}") String aesSecretKey, + @Value("${aes.salt}") String aesSalt) { + this.aesSecretKey = aesSecretKey; + this.aesSalt = aesSalt; + } + + public String encrypt(String strToEncrypt) throws Aes256Exception { + + try { + + SecureRandom secureRandom = new SecureRandom(); + byte[] iv = new byte[16]; + secureRandom.nextBytes(iv); + IvParameterSpec ivspec = new IvParameterSpec(iv); + + SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA_256); + KeySpec spec = new PBEKeySpec(aesSecretKey.toCharArray(), aesSalt.getBytes(), ITERATION_COUNT, KEY_LENGTH); + SecretKey tmp = factory.generateSecret(spec); + SecretKeySpec secretKeySpec = new SecretKeySpec(tmp.getEncoded(), ALGORITHM); + + //Padding vulnerability rule java:S5542 ignored because encryption is used inside application workflow + Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivspec); + + byte[] cipherText = cipher.doFinal(strToEncrypt.getBytes(StandardCharsets.UTF_8)); + byte[] encryptedData = new byte[iv.length + cipherText.length]; + System.arraycopy(iv, 0, encryptedData, 0, iv.length); + System.arraycopy(cipherText, 0, encryptedData, iv.length, cipherText.length); + + return Base64.getEncoder().encodeToString(encryptedData); + } catch (Exception e) { + throw new Aes256Exception("Unexpected error when encrypting the given string", AES_UNEXPECTED_ERROR, e); + } + } + + public String decrypt(String strToDecrypt) throws Aes256Exception { + try{ + byte[] encryptedData = Base64.getDecoder().decode(strToDecrypt); + byte[] iv = new byte[16]; + System.arraycopy(encryptedData, 0, iv, 0, iv.length); + IvParameterSpec ivspec = new IvParameterSpec(iv); + + SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA_256); + KeySpec spec = new PBEKeySpec(aesSecretKey.toCharArray(), aesSalt.getBytes(), ITERATION_COUNT, KEY_LENGTH); + SecretKey tmp = factory.generateSecret(spec); + SecretKeySpec secretKeySpec = new SecretKeySpec(tmp.getEncoded(), ALGORITHM); + + //Padding vulnerability rule java:S5542 ignored because decryption is used inside application workflow + Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivspec); + + byte[] cipherText = new byte[encryptedData.length - 16]; + System.arraycopy(encryptedData, 16, cipherText, 0, cipherText.length); + + byte[] decryptedText = cipher.doFinal(cipherText); + return new String(decryptedText, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new Aes256Exception("Unexpected error when decrypting the given string", AES_UNEXPECTED_ERROR, e); + } + } +} + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d4fc92da..849e0aba 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -66,3 +66,7 @@ spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration. spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.retry.backoff.ms=${KAFKA_REWARD_RESPONSE_KAFKA_RETRY_MS:${KAFKA_RETRY_MS:10000}} spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.linger.ms=${KAFKA_REWARD_RESPONSE_LINGER_MS:${KAFKA_LINGER_MS:2}} spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.batch.size=${KAFKA_REWARD_RESPONSE_BATCH_SIZE:${KAFKA_BATCH_SIZE:16384}} + +#Other Configs +aes.secret.key=${AES_SECRET_KEY} +aes.salt=${AES_SALT} diff --git a/src/test/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestControllerTest.java b/src/test/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestControllerTest.java index 08848dd5..023a4c76 100644 --- a/src/test/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestControllerTest.java +++ b/src/test/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestControllerTest.java @@ -1,9 +1,13 @@ package it.gov.pagopa.payment.notices.service.controller; +import com.fasterxml.jackson.databind.ObjectMapper; import it.gov.pagopa.payment.notices.service.exception.AppError; import it.gov.pagopa.payment.notices.service.exception.AppException; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestItem; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -14,10 +18,15 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -79,4 +88,67 @@ void getFolderStatusShouldReturnDataOn500() throws Exception { verify(noticeGenerationService).getFolderStatus("folderTest","userTest"); } + @Test + void generateMassiveShouldReturnFolderIdOn200() throws Exception { + when(noticeGenerationService.generateMassive(any(),any())) + .thenReturn("folderTests"); + String url = "/notices/generate-massive"; + String folderId = mvc.perform(post(url) + .content(new ObjectMapper().writeValueAsBytes( + NoticeGenerationMassiveRequest.builder() + .notices(Collections.singletonList( + NoticeGenerationRequestItem.builder().build() + )).build() + )) + .header("X-User-Id", "userTest") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andExpect(content().contentType("application/json")) + .andReturn().getResponse().getContentAsString(); + Assertions.assertNotNull(folderId); + Assertions.assertEquals("folderTests", folderId); + verify(noticeGenerationService).generateMassive(any(),any()); + } + + @Test + void generateMassiveShouldReturn400OnMissingItems() throws Exception { + String url = "/notices/generate-massive"; + mvc.perform(post(url) + .content(new ObjectMapper().writeValueAsBytes( + NoticeGenerationMassiveRequest.builder() + .notices(Collections.emptyList( + + )).build() + )) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()) + .andExpect(content().contentType("application/json")); + } + + @Test + void generateMassiveShouldReturn400OnMissingBody() throws Exception { + String url = "/notices/generate-massive"; + mvc.perform(post(url) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()) + .andExpect(content().contentType("application/json")); + } + + @Test + void generateMassiveShouldReturn400OnMissingUserId() throws Exception { + when(noticeGenerationService.generateMassive(any(),any())) + .thenReturn("folderTests"); + String url = "/notices/generate-massive"; + mvc.perform(post(url) + .content(new ObjectMapper().writeValueAsBytes( + NoticeGenerationMassiveRequest.builder() + .notices(Collections.singletonList( + NoticeGenerationRequestItem.builder().build() + )).build() + )) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + + } + } diff --git a/src/test/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImplTest.java b/src/test/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImplTest.java index 339c750f..a45e3e33 100644 --- a/src/test/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImplTest.java +++ b/src/test/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImplTest.java @@ -1,17 +1,26 @@ package it.gov.pagopa.payment.notices.service.service.impl; +import com.fasterxml.jackson.databind.ObjectMapper; import it.gov.pagopa.payment.notices.service.entity.PaymentNoticeGenerationRequest; import it.gov.pagopa.payment.notices.service.entity.PaymentNoticeGenerationRequestError; +import it.gov.pagopa.payment.notices.service.events.NoticeGenerationRequestProducer; +import it.gov.pagopa.payment.notices.service.exception.Aes256Exception; import it.gov.pagopa.payment.notices.service.exception.AppException; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestItem; import it.gov.pagopa.payment.notices.service.model.enums.PaymentGenerationRequestStatus; +import it.gov.pagopa.payment.notices.service.model.notice.Notice; +import it.gov.pagopa.payment.notices.service.model.notice.NoticeRequestData; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestErrorRepository; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestRepository; +import it.gov.pagopa.payment.notices.service.util.Aes256Utils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Collections; @@ -30,13 +39,23 @@ class NoticeGenerationServiceImplTest { @Mock private PaymentGenerationRequestErrorRepository paymentGenerationRequestErrorRepository; + @Mock + private NoticeGenerationRequestProducer noticeGenerationRequestProducer; + + @Spy + private ObjectMapper objectMapper; + private NoticeGenerationServiceImpl noticeGenerationService; @BeforeEach public void init() { - Mockito.reset(paymentGenerationRequestErrorRepository, paymentGenerationRequestRepository); + Mockito.reset( + paymentGenerationRequestErrorRepository, + paymentGenerationRequestRepository, noticeGenerationRequestProducer); noticeGenerationService = new NoticeGenerationServiceImpl( - paymentGenerationRequestRepository, paymentGenerationRequestErrorRepository); + paymentGenerationRequestRepository, + paymentGenerationRequestErrorRepository, + noticeGenerationRequestProducer, objectMapper, new Aes256Utils("test","test")); } @Test @@ -113,4 +132,50 @@ void getFolderStatusShouldReturnExceptionWhenUnexpectedExceptionOnErrorRepositor verify(paymentGenerationRequestErrorRepository).findErrors(any()); } + @Test + void generateMassiveRequestShouldSendEventOnSuccess() { + NoticeGenerationRequestItem noticeGenerationRequestItem = NoticeGenerationRequestItem.builder() + .templateId("testTemplate") + .data(NoticeRequestData.builder().notice( + Notice.builder().code("testCode") + .build() + ).build() + ).build(); + NoticeGenerationMassiveRequest noticeGenerationMassiveRequest = + NoticeGenerationMassiveRequest.builder().notices( + Collections.singletonList(noticeGenerationRequestItem)).build(); + when(paymentGenerationRequestRepository.save(any())).thenReturn( + PaymentNoticeGenerationRequest.builder().id("testFolderId").build()); + when(noticeGenerationRequestProducer.noticeGeneration(any())).thenReturn(true); + String folderId = noticeGenerationService.generateMassive(noticeGenerationMassiveRequest, "testUserId"); + assertNotNull(folderId); + assertEquals("testFolderId", folderId); + verify(paymentGenerationRequestRepository).save(any()); + verify(noticeGenerationRequestProducer).noticeGeneration(any()); + verifyNoInteractions(paymentGenerationRequestErrorRepository); + } + + @Test + void generateMassiveRequestShouldSaveErrorEventOnSendFailure() throws Aes256Exception { + NoticeGenerationRequestItem noticeGenerationRequestItem = NoticeGenerationRequestItem.builder() + .templateId("testTemplate") + .data(NoticeRequestData.builder().notice( + Notice.builder().code("testCode") + .build() + ).build() + ).build(); + NoticeGenerationMassiveRequest noticeGenerationMassiveRequest = + NoticeGenerationMassiveRequest.builder().notices( + Collections.singletonList(noticeGenerationRequestItem)).build(); + when(paymentGenerationRequestRepository.save(any())).thenReturn( + PaymentNoticeGenerationRequest.builder().id("testFolderId").build()); + when(noticeGenerationRequestProducer.noticeGeneration(any())).thenReturn(false); + String folderId = noticeGenerationService.generateMassive(noticeGenerationMassiveRequest, "testUserId"); + assertNotNull(folderId); + assertEquals("testFolderId", folderId); + verify(paymentGenerationRequestRepository).save(any()); + verify(noticeGenerationRequestProducer).noticeGeneration(any()); + verify(paymentGenerationRequestErrorRepository).save(any()); + } + } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index c3eb102f..a97115d0 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -34,3 +34,7 @@ spring.cloud.azure.storage.blob.templates.connection_string=${TEMPLATE_STORAGE_C spring.cloud.azure.storage.blob.templates.containerName=${TEMPLATE_STORAGE_CONTAINER_NAME:} spring.cloud.azure.storage.blob.templates.retry=${TEMPLATE_STORAGE_RETRY:3} spring.cloud.azure.storage.blob.templates.timeout=${TEMPLATE_STORAGE_TIMEOUT:10} + +#Other Configs +aes.secret.key=${AES_SECRET_KEY:test} +aes.salt=${AES_SALT:test} From 3aa98e16167b21f70a5b8f2c5b6c57c6796af790 Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Mon, 22 Apr 2024 11:29:06 +0200 Subject: [PATCH 04/12] [VAS-828] feat: Updated notice generation massive request and tests --- openapi/openapi.json | 121 +++++++++++++++--- pom.xml | 56 ++++---- .../payment/notices/service/Application.java | 5 +- .../GenerationRequestController.java | 35 ++++- .../NoticeGenerationRequestProducer.java | 10 ++ .../NoticeGenerationRequestProducerImpl.java | 6 +- .../service/model/AppCorsConfiguration.java | 1 + .../enums/PaymentGenerationRequestStatus.java | 3 + .../model/notice/CreditorInstitution.java | 12 ++ .../notices/service/model/notice/Debtor.java | 4 + .../service/model/notice/InstallmentData.java | 3 + .../service/model/notice/Installments.java | 3 + .../notices/service/model/notice/Notice.java | 6 + .../model/notice/NoticeRequestData.java | 5 +- .../service/NoticeGenerationService.java | 10 ++ .../impl/NoticeGenerationServiceImpl.java | 38 ++++-- .../notices/service/util/Aes256Utils.java | 9 +- src/main/resources/application.properties | 22 ++-- 18 files changed, 266 insertions(+), 83 deletions(-) diff --git a/openapi/openapi.json b/openapi/openapi.json index a3579824..ffc12271 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -157,6 +157,8 @@ "/notices/generate-massive" : { "post" : { "tags" : [ "Notice Generation Request APIs" ], + "summary" : "generateMassiveNotice", + "description" : "Insert massive notice generation request and returns folderId for reference an future recovery", "operationId" : "getFolderStatus", "parameters" : [ { "name" : "X-User-Id", @@ -195,8 +197,80 @@ } } } + }, + "400" : { + "description" : "Bad Request", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ProblemJson" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + } + }, + "403" : { + "description" : "Forbidden", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + } + }, + "429" : { + "description" : "Too many requests", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Service unavailable", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ProblemJson" + } + } + } } - } + }, + "security" : [ { + "ApiKey" : [ ] + } ] }, "parameters" : [ { "name" : "X-Request-Id", @@ -342,62 +416,77 @@ "components" : { "schemas" : { "CreditorInstitution" : { - "type" : "object" + "type" : "object", + "description" : "Creditor Institution data" }, "Debtor" : { - "type" : "object" + "type" : "object", + "description" : "Debtor data" }, "InstallmentData" : { + "required" : [ "amount", "dueDate" ], "type" : "object", "properties" : { "amount" : { "type" : "integer", + "description" : "Installment amount", "format" : "int32" }, "dueDate" : { - "type" : "string" + "type" : "string", + "description" : "Installment dueDate" } - } + }, + "description" : "Installments of the notice" }, "Installments" : { "type" : "object", "properties" : { "number" : { "type" : "integer", + "description" : "Number of installments", "format" : "int32" }, "data" : { "type" : "array", + "description" : "Installments of the notice", "items" : { "$ref" : "#/components/schemas/InstallmentData" } } - } + }, + "description" : "Notice installments (if present)" }, "Notice" : { - "required" : [ "code", "subject" ], + "required" : [ "code", "dueDate", "paymentAmount", "subject" ], "type" : "object", "properties" : { "subject" : { - "type" : "string" + "type" : "string", + "description" : "Notice subject" }, "paymentAmount" : { "type" : "integer", + "description" : "Notice total amount to pay", "format" : "int64" }, "dueDate" : { - "type" : "string" + "type" : "string", + "description" : "Notice due date" }, "code" : { - "type" : "string" + "type" : "string", + "description" : "Notice code" }, "installments" : { "type" : "array", + "description" : "Notice installments (if present)", "items" : { "$ref" : "#/components/schemas/Installments" } } - } + }, + "description" : "Notice data" }, "NoticeGenerationMassiveRequest" : { "required" : [ "notices" ], @@ -411,7 +500,8 @@ "$ref" : "#/components/schemas/NoticeGenerationRequestItem" } } - } + }, + "description" : "massive notice generation request data" }, "NoticeGenerationRequestItem" : { "type" : "object", @@ -425,6 +515,7 @@ } }, "NoticeRequestData" : { + "required" : [ "creditorInstitution", "debtor", "notice" ], "type" : "object", "properties" : { "notice" : { @@ -435,12 +526,6 @@ }, "debtor" : { "$ref" : "#/components/schemas/Debtor" - }, - "extraData" : { - "type" : "object", - "additionalProperties" : { - "type" : "object" - } } } }, diff --git a/pom.xml b/pom.xml index f37504b9..215e6b16 100644 --- a/pom.xml +++ b/pom.xml @@ -68,37 +68,37 @@ caffeine - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.3.0 - + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + - - - io.swagger - swagger-annotations - 1.6.12 - + + + io.swagger + swagger-annotations + 1.6.12 + - - - org.hibernate.orm - hibernate-core - 6.4.0.Final - - - org.springframework.boot - spring-boot-starter-data-mongodb - - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - 4.12.2 - test - + + + org.hibernate.orm + hibernate-core + 6.4.0.Final + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + 4.12.2 + test + org.springframework.cloud diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/Application.java b/src/main/java/it/gov/pagopa/payment/notices/service/Application.java index 8df3749d..abf279eb 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/Application.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/Application.java @@ -2,12 +2,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.kafka.test.context.EmbeddedKafka; +import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication +@EnableAsync public class Application { - public static void main(String[] args) { SpringApplication.run(Application.class, args); } - } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java index b97c05c6..94d04020 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java @@ -73,9 +73,42 @@ public GetGenerationRequestStatusResource getFolderStatus( return noticeGenerationService.getFolderStatus(folderId, userId); } - + /** + * Generate the request of massive notice generation using input data, sending data through the EH channel + * to kickstart notice generation in an async manner. Any error will be saved into notice generation request + * error + * @param noticeGenerationMassiveRequest generation request data, containing a list of notice data and templates + * to use + * @param userId userId requiring the generation. the request will refer to this user when recovery of data regarding + * the folder is executed + * @return folderId produced when inserting the request + */ + @Operation(summary = "generateMassiveNotice", + description = "Insert massive notice generation request and returns folderId for reference an" + + " future recovery", + security = {@SecurityRequirement(name = "ApiKey")}) + @OpenApiTableMetadata(readWriteIntense = OpenApiTableMetadata.ReadWrite.WRITE, + external = true, internal = false) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ProblemJson.class))), + @ApiResponse(responseCode = "401", + description = "Unauthorized", content = @Content(schema = @Schema())), + @ApiResponse(responseCode = "403", + description = "Forbidden", content = @Content(schema = @Schema())), + @ApiResponse(responseCode = "429", + description = "Too many requests", content = @Content(schema = @Schema())), + @ApiResponse(responseCode = "500", + description = "Service unavailable", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ProblemJson.class))) + }) @PostMapping("/generate-massive") public String getFolderStatus( + @Parameter(description = "massive notice generation request data") @Valid @NotNull @RequestBody NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, @Parameter(description = "userId to use for request status retrieval") @RequestHeader("X-User-Id") String userId) { diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java index e878c27b..882bdd6b 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java @@ -2,7 +2,17 @@ import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestEH; +/** + * Interface to use when required to execute sending of a notice generation request through + * the eventhub channel + */ public interface NoticeGenerationRequestProducer { + + /** + * Send notige generation request through EH + * @param noticeGenerationRequestEH data to send + * @return boolean referring if the insertion on the sending channel was successfully + */ boolean noticeGeneration(NoticeGenerationRequestEH noticeGenerationRequestEH); } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java index b3d3def6..f8e0142e 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java @@ -1,6 +1,7 @@ package it.gov.pagopa.payment.notices.service.events; import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestEH; +import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -21,12 +22,15 @@ public NoticeGenerationRequestProducerImpl(StreamBridge streamBridge) { } /** Declared just to let know Spring to connect the producer at startup */ + @Slf4j @Configuration static class NoticeGenerationRequestProducerConfig { + @Bean - public Supplier>> noticeGenerationRequest() { + public Supplier>> noticeGeneration() { return Flux::empty; } + } @Override diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/AppCorsConfiguration.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/AppCorsConfiguration.java index a6566ed2..a4f4188d 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/AppCorsConfiguration.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/AppCorsConfiguration.java @@ -20,4 +20,5 @@ public class AppCorsConfiguration { private String[] origins; private String[] methods; + } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/enums/PaymentGenerationRequestStatus.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/enums/PaymentGenerationRequestStatus.java index cd6c62ea..5d35f005 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/enums/PaymentGenerationRequestStatus.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/enums/PaymentGenerationRequestStatus.java @@ -1,5 +1,8 @@ package it.gov.pagopa.payment.notices.service.model.enums; +/** + * Enum containing generation request status + */ public enum PaymentGenerationRequestStatus { INSERTED, diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java index 23bb6fa8..b360e454 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java @@ -1,23 +1,35 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; public class CreditorInstitution { + @Schema(description = "CI tax code", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String taxCode; + + @Schema(description = "CI full name", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String fullName; private String organization; + + @Schema(description = "CI info", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String info; + + @Schema(description = "Boolean to refer if it has a web channel", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull private Boolean webChannel; + + @Schema(description = "CI physical channel data") private String physicalChannel; + + @Schema(description = "CI cbill", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String cbill; diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java index 4219f2ce..3a0152ad 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java @@ -1,14 +1,18 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; public class Debtor { + @Schema(description = "Debtor taxCode") private String taxCode; + @Schema(description = "Debtor full name", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String fullName; + @Schema(description = "Debtor address", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String address; diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java index a727f98e..eea94ede 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java @@ -1,11 +1,14 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data public class InstallmentData { + @Schema(description = "Installment amount", requiredMode = Schema.RequiredMode.REQUIRED) private Integer amount; + @Schema(description = "Installment dueDate", requiredMode = Schema.RequiredMode.REQUIRED) private String dueDate; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java index 01d53c03..9e5e2fda 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @@ -7,7 +8,9 @@ @Data public class Installments { + @Schema(description = "Number of installments") private Integer number; + @Schema(description = "Installments of the notice") private List data; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java index 6047396d..5e888642 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -15,18 +16,23 @@ @Builder public class Notice { + @Schema(description = "Notice subject", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String subject; + @Schema(description = "Notice total amount to pay", requiredMode = Schema.RequiredMode.REQUIRED) private Long paymentAmount; + @Schema(description = "Notice due date", requiredMode = Schema.RequiredMode.REQUIRED) private String dueDate; + @Schema(description = "Notice code", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String code; + @Schema(description = "Notice installments (if present)") private List installments; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java index 7226562f..b14693fa 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -13,9 +14,11 @@ @Builder public class NoticeRequestData { + @Schema(description = "Notice data", requiredMode = Schema.RequiredMode.REQUIRED) private Notice notice; + @Schema(description = "Creditor Institution data", requiredMode = Schema.RequiredMode.REQUIRED) private CreditorInstitution creditorInstitution; + @Schema(description = "Debtor data", requiredMode = Schema.RequiredMode.REQUIRED) private Debtor debtor; - private Map extraData; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java index 87adc494..0d263148 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java @@ -18,5 +18,15 @@ public interface NoticeGenerationService { */ GetGenerationRequestStatusResource getFolderStatus(String folderId, String userId); + /** + * Generate the request of massive notice generation using input data, sending data through the EH channel + * to kickstart notice generation in an async manner. Any error will be saved into notice generation request + * error + * @param noticeGenerationMassiveRequest generation request data, containing a list of notice data and templates + * to use + * @param userId userId requiring the generation. the request will refer to this user when recovery of data regarding + * the folder is executed + * @return folderId produced when inserting the request + */ String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String userId); } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java index 6684539d..137e00e6 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java @@ -19,6 +19,7 @@ import it.gov.pagopa.payment.notices.service.util.Aes256Utils; import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.time.Instant; @@ -26,6 +27,9 @@ import java.util.List; import java.util.Optional; +/** + * @see it.gov.pagopa.payment.notices.service.service.NoticeGenerationService + */ @Service @Slf4j public class NoticeGenerationServiceImpl implements NoticeGenerationService { @@ -79,24 +83,12 @@ public String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMas .status(PaymentGenerationRequestStatus.INSERTED) .createdAt(Instant.now()) .items(new ArrayList<>()) + .userId(userId) .numberOfElementsTotal(noticeGenerationMassiveRequest.getNotices().size()) .requestDate(Instant.now()) .build()).getId(); - noticeGenerationMassiveRequest.getNotices().parallelStream().forEach(noticeGenerationRequestItem -> { - try { - if (!noticeGenerationRequestProducer.noticeGeneration( - NoticeGenerationRequestEH.builder() - .noticeData(noticeGenerationRequestItem) - .folderId(folderId) - .build()) - ) { - saveErrorEvent(folderId, noticeGenerationRequestItem); - } - } catch (Exception e) { - saveErrorEvent(folderId, noticeGenerationRequestItem); - } - }); + sendNotices(noticeGenerationMassiveRequest, folderId); return folderId; @@ -107,6 +99,24 @@ public String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMas } + @Async + private void sendNotices(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String folderId) { + noticeGenerationMassiveRequest.getNotices().parallelStream().forEach(noticeGenerationRequestItem -> { + try { + if (!noticeGenerationRequestProducer.noticeGeneration( + NoticeGenerationRequestEH.builder() + .noticeData(noticeGenerationRequestItem) + .folderId(folderId) + .build()) + ) { + saveErrorEvent(folderId, noticeGenerationRequestItem); + } + } catch (Exception e) { + saveErrorEvent(folderId, noticeGenerationRequestItem); + } + }); + } + private void saveErrorEvent(String folderId, NoticeGenerationRequestItem noticeGenerationRequestItem) { try { paymentGenerationRequestErrorRepository.save( diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java b/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java index 676b6c2e..a043ed72 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java @@ -1,6 +1,7 @@ package it.gov.pagopa.payment.notices.service.util; import it.gov.pagopa.payment.notices.service.exception.Aes256Exception; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -31,13 +32,7 @@ public class Aes256Utils { private static final int AES_UNEXPECTED_ERROR = 701; - - /** - * Hide from public usage. - */ - private Aes256Utils() { - } - + @Autowired public Aes256Utils( @Value("${aes.secret.key}") String aesSecretKey, @Value("${aes.salt}") String aesSalt) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 849e0aba..eb71e492 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -24,7 +24,7 @@ logging.level.it.gov.pagopa=${APP_LOGGING_LEVEL:INFO} # CORS configuration cors.configuration=${CORS_CONFIGURATION:{"origins": ["*"], "methods": ["*"]}} # Mongo Configuration -spring.data.mongodb.uri=${MONGODB_CONNECTION_URI} +spring.data.mongodb.uri=${MONGODB_CONNECTION_URI:} spring.data.mongodb.database=${MONGODB_NAME:noticesMongoDb} # Cache configuration spring.cache.type=caffeine @@ -37,18 +37,18 @@ spring.jackson.serialization.fail-on-empty-beans=false spring.jackson.deserialization.fail-on-unknown-properties=false spring.cloud.azure.storage.blob.templates.enabled=${TEMPLATE_STORAGE_ENABLED:true} -spring.cloud.azure.storage.blob.templates.connection_string=${TEMPLATE_STORAGE_CONN_STRING} +spring.cloud.azure.storage.blob.templates.connection_string=${TEMPLATE_STORAGE_CONN_STRING:} spring.cloud.azure.storage.blob.templates.containerName=${TEMPLATE_STORAGE_CONTAINER_NAME:noticetemplateblob} spring.cloud.azure.storage.blob.templates.retry=${TEMPLATE_STORAGE_RETRY:3} spring.cloud.azure.storage.blob.templates.timeout=${TEMPLATE_STORAGE_TIMEOUT:10} # EH Kafka Configuration spring.cloud.function.definition=noticeGeneration -spring.cloud.stream.bindings.noticeGeneration-out-0.destination=${KAFKA_NOTICE_GENERATION_TOPIC} +spring.cloud.stream.bindings.noticeGeneration-out-0.destination=${KAFKA_NOTICE_GENERATION_TOPIC:pagopa-notice-evt-rx} spring.cloud.stream.bindings.noticeGeneration-out-0.content-type=${KAFKA_CONTENT_TYPE:application/json} spring.cloud.stream.bindings.noticeGeneration-out-0.binder=notice-generation spring.cloud.stream.binders.notice-generation.type=kafka -spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.brokers=${KAFKA_BROKER:} +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.brokers=${KAFKA_BROKER:localhost:9092} spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.configuration.sasl.jaas.config=${KAFKA_SASL_JAAS_CONFIG:} spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.configuration.key.serializer=org.apache.kafka.common.serialization.StringSerializer spring.cloud.stream.kafka.binder.auto-create-topics=false @@ -61,12 +61,12 @@ spring.cloud.stream.kafka.binder.configuration.connections.max.idle.ms=${KAFKA_C spring.cloud.stream.kafka.binder.configuration.metadata.max.idle.ms=${KAFKA_CONFIG_METADATA_MAX_IDLE_MS:180000} spring.cloud.stream.kafka.binder.configuration.metadata.max.age.ms=${KAFKA_CONFIG_METADATA_MAX_AGE_INTERVAL:179000} spring.cloud.stream.kafka.binder.configuration.max.request.size=${KAFKA_CONFIG_METADATA_MAX_REQUEST_SIZE:1000000} -spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.client.id=noticeGenProcessor -spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.connections.max.idle.ms=${KAFKA_REWARD_RESPONSE_CONNECTION_MAX_IDLE_TIME:180000} -spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.retry.backoff.ms=${KAFKA_REWARD_RESPONSE_KAFKA_RETRY_MS:${KAFKA_RETRY_MS:10000}} -spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.linger.ms=${KAFKA_REWARD_RESPONSE_LINGER_MS:${KAFKA_LINGER_MS:2}} -spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.batch.size=${KAFKA_REWARD_RESPONSE_BATCH_SIZE:${KAFKA_BATCH_SIZE:16384}} +spring.cloud.stream.kafka.bindings.noticeGeneration-out-0.producer.configuration.client.id=noticeGenProducer +spring.cloud.stream.kafka.bindings.noticeGeneration-out-0.producer.configuration.connections.max.idle.ms=${KAFKA_REWARD_RESPONSE_CONNECTION_MAX_IDLE_TIME:180000} +spring.cloud.stream.kafka.bindings.noticeGeneration-out-0.producer.configuration.retry.backoff.ms=${KAFKA_REWARD_RESPONSE_KAFKA_RETRY_MS:${KAFKA_RETRY_MS:10000}} +spring.cloud.stream.kafka.bindings.noticeGeneration-out-0.producer.configuration.linger.ms=${KAFKA_REWARD_RESPONSE_LINGER_MS:${KAFKA_LINGER_MS:2}} +spring.cloud.stream.kafka.bindings.noticeGeneration-out-0.producer.configuration.batch.size=${KAFKA_REWARD_RESPONSE_BATCH_SIZE:${KAFKA_BATCH_SIZE:16384}} #Other Configs -aes.secret.key=${AES_SECRET_KEY} -aes.salt=${AES_SALT} +aes.secret.key=${AES_SECRET_KEY:} +aes.salt=${AES_SALT:} From 58695be69fb620a49320df58b318029034a60545 Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Thu, 18 Apr 2024 15:24:53 +0200 Subject: [PATCH 05/12] [VAS-828] feat: introducing massive generation API --- .../GenerationRequestController.java | 9 ++++++ .../model/NoticeGenerationMassiveRequest.java | 18 +++++++++++ .../model/NoticeGenerationRequestEH.java | 8 +++++ .../model/NoticeGenerationRequestItem.java | 17 ++++++++++ .../service/NoticeGenerationService.java | 2 ++ .../impl/NoticeGenerationServiceImpl.java | 31 +++++++++++++++++++ src/main/resources/application.properties | 25 +++++++++++++++ 7 files changed, 110 insertions(+) create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java index c05c9b4a..688cce02 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java @@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; import it.gov.pagopa.payment.notices.service.model.ProblemJson; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; import it.gov.pagopa.payment.notices.service.util.OpenApiTableMetadata; @@ -70,4 +71,12 @@ public GetGenerationRequestStatusResource getFolderStatus( return noticeGenerationService.getFolderStatus(folderId, userId); } + + @PostMapping("/generate-massive") + public String getFolderStatus( + @RequestBody NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, + @Parameter(description = "userId to use for request status retrieval") @RequestHeader("X-User-Id") String userId) { + return noticeGenerationService.generateMassive(noticeGenerationMassiveRequest, userId); + } + } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java new file mode 100644 index 00000000..d907cdc9 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java @@ -0,0 +1,18 @@ +package it.gov.pagopa.payment.notices.service.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class NoticeGenerationMassiveRequest { + + private List notices; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java new file mode 100644 index 00000000..05cd4898 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.payment.notices.service.model; + +public class NoticeGenerationRequestEH { + + private NoticeGenerationRequestItem noticeData; + private String folderId; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java new file mode 100644 index 00000000..87a11b6d --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java @@ -0,0 +1,17 @@ +package it.gov.pagopa.payment.notices.service.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class NoticeGenerationRequestItem { + + private String templateId; + private String data; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java index b3cacce4..87adc494 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java @@ -1,6 +1,7 @@ package it.gov.pagopa.payment.notices.service.service; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; /** * Service interface for notice generation @@ -17,4 +18,5 @@ public interface NoticeGenerationService { */ GetGenerationRequestStatusResource getFolderStatus(String folderId, String userId); + String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String userId); } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java index 0ac41710..4bc3b1a2 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java @@ -5,11 +5,16 @@ import it.gov.pagopa.payment.notices.service.exception.AppError; import it.gov.pagopa.payment.notices.service.exception.AppException; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; +import it.gov.pagopa.payment.notices.service.model.enums.PaymentGenerationRequestStatus; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestErrorRepository; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestRepository; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; +import jakarta.transaction.Transactional; import org.springframework.stereotype.Service; +import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -44,4 +49,30 @@ public GetGenerationRequestStatusResource getFolderStatus(String folderId, Strin .build(); } + @Override + @Transactional + public String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String userId) { + + try { + String folderId = + paymentGenerationRequestRepository.save( + PaymentNoticeGenerationRequest.builder() + .status(PaymentGenerationRequestStatus.INSERTED) + .createdAt(Instant.now()) + .items(new ArrayList<>()) + .numberOfElementsTotal(noticeGenerationMassiveRequest.getNotices().size()) + .requestDate(Instant.now()) + .build()).getId(); + + + + return folderId; + + } catch (Exception e) { + //TODO: Error + throw new AppException(AppError.UNKNOWN); + } + + } + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2f0016c6..d4fc92da 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -41,3 +41,28 @@ spring.cloud.azure.storage.blob.templates.connection_string=${TEMPLATE_STORAGE_C spring.cloud.azure.storage.blob.templates.containerName=${TEMPLATE_STORAGE_CONTAINER_NAME:noticetemplateblob} spring.cloud.azure.storage.blob.templates.retry=${TEMPLATE_STORAGE_RETRY:3} spring.cloud.azure.storage.blob.templates.timeout=${TEMPLATE_STORAGE_TIMEOUT:10} + +# EH Kafka Configuration +spring.cloud.function.definition=noticeGeneration +spring.cloud.stream.bindings.noticeGeneration-out-0.destination=${KAFKA_NOTICE_GENERATION_TOPIC} +spring.cloud.stream.bindings.noticeGeneration-out-0.content-type=${KAFKA_CONTENT_TYPE:application/json} +spring.cloud.stream.bindings.noticeGeneration-out-0.binder=notice-generation +spring.cloud.stream.binders.notice-generation.type=kafka +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.brokers=${KAFKA_BROKER:} +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.configuration.sasl.jaas.config=${KAFKA_SASL_JAAS_CONFIG:} +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.configuration.key.serializer=org.apache.kafka.common.serialization.StringSerializer +spring.cloud.stream.kafka.binder.auto-create-topics=false +spring.cloud.stream.kafka.binder.configuration.heartbeat.interval.ms=${KAFKA_CONFIG_HEARTBEAT_INTERVAL_MS:3000} +spring.cloud.stream.kafka.binder.configuration.session.timeout.ms=${KAFKA_CONFIG_SESSION_TIMEOUT_MS:60000} +spring.cloud.stream.kafka.binder.configuration.request.timeout.ms=${KAFKA_CONFIG_REQUEST_TIMEOUT_MS:60000} +spring.cloud.stream.kafka.binder.configuration.sasl.mechanism=${KAFKA_CONFIG_SASL_MECHANISM:PLAIN} +spring.cloud.stream.kafka.binder.configuration.security.protocol=${KAFKA_CONFIG_SECURITY_PROTOCOL:SASL_SSL} +spring.cloud.stream.kafka.binder.configuration.connections.max.idle.ms=${KAFKA_CONFIG_CONNECTION_MAX_IDLE_TIME:180000} +spring.cloud.stream.kafka.binder.configuration.metadata.max.idle.ms=${KAFKA_CONFIG_METADATA_MAX_IDLE_MS:180000} +spring.cloud.stream.kafka.binder.configuration.metadata.max.age.ms=${KAFKA_CONFIG_METADATA_MAX_AGE_INTERVAL:179000} +spring.cloud.stream.kafka.binder.configuration.max.request.size=${KAFKA_CONFIG_METADATA_MAX_REQUEST_SIZE:1000000} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.client.id=noticeGenProcessor +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.connections.max.idle.ms=${KAFKA_REWARD_RESPONSE_CONNECTION_MAX_IDLE_TIME:180000} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.retry.backoff.ms=${KAFKA_REWARD_RESPONSE_KAFKA_RETRY_MS:${KAFKA_RETRY_MS:10000}} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.linger.ms=${KAFKA_REWARD_RESPONSE_LINGER_MS:${KAFKA_LINGER_MS:2}} +spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.batch.size=${KAFKA_REWARD_RESPONSE_BATCH_SIZE:${KAFKA_BATCH_SIZE:16384}} From 466a82ec213abbf2e4a6535f305c081655a6e9e5 Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Fri, 19 Apr 2024 18:20:56 +0200 Subject: [PATCH 06/12] [VAS-828] feat: Introducing massive generation logic and unit testing --- openapi/openapi.json | 158 +++++++++++++++++- pom.xml | 24 ++- .../GenerationRequestController.java | 7 +- .../NoticeGenerationRequestProducer.java | 8 + .../NoticeGenerationRequestProducerImpl.java | 43 +++++ .../service/exception/Aes256Exception.java | 35 ++++ .../notices/service/exception/AppError.java | 3 + .../model/NoticeGenerationMassiveRequest.java | 4 + .../model/NoticeGenerationRequestEH.java | 9 + .../model/NoticeGenerationRequestItem.java | 3 +- .../model/notice/CreditorInstitution.java | 26 +++ .../notices/service/model/notice/Debtor.java | 15 ++ .../service/model/notice/InstallmentData.java | 11 ++ .../service/model/notice/Installments.java | 13 ++ .../notices/service/model/notice/Notice.java | 32 ++++ .../model/notice/NoticeRequestData.java | 21 +++ .../PaymentGenerationRequestRepository.java | 5 + .../impl/NoticeGenerationServiceImpl.java | 61 ++++++- .../notices/service/util/Aes256Utils.java | 103 ++++++++++++ src/main/resources/application.properties | 4 + .../GenerationRequestControllerTest.java | 72 ++++++++ .../impl/NoticeGenerationServiceImplTest.java | 69 +++++++- src/test/resources/application.properties | 4 + 23 files changed, 719 insertions(+), 11 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java create mode 100644 src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java diff --git a/openapi/openapi.json b/openapi/openapi.json index 14577cdc..e4a07dda 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -16,7 +16,7 @@ "tags" : [ "Notice Generation Request APIs" ], "summary" : "getFolderStatus", "description" : "Return generation request status for a folder of notices", - "operationId" : "getFolderStatus", + "operationId" : "getFolderStatus_1", "parameters" : [ { "name" : "folder_id", "in" : "path", @@ -154,6 +154,59 @@ } } ] }, + "/notices/generate-massive" : { + "post" : { + "tags" : [ "Notice Generation Request APIs" ], + "operationId" : "getFolderStatus", + "parameters" : [ { + "name" : "X-User-Id", + "in" : "header", + "description" : "userId to use for request status retrieval", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/NoticeGenerationMassiveRequest" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "OK", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "type" : "string" + } + } + } + } + } + }, + "parameters" : [ { + "name" : "X-Request-Id", + "in" : "header", + "description" : "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.", + "schema" : { + "type" : "string" + } + } ] + }, "/notices/templates/{template_id}" : { "get" : { "tags" : [ "Notice Templates APIs" ], @@ -306,6 +359,109 @@ }, "components" : { "schemas" : { + "CreditorInstitution" : { + "type" : "object" + }, + "Debtor" : { + "type" : "object" + }, + "InstallmentData" : { + "type" : "object", + "properties" : { + "amount" : { + "type" : "integer", + "format" : "int32" + }, + "dueDate" : { + "type" : "string" + } + } + }, + "Installments" : { + "type" : "object", + "properties" : { + "number" : { + "type" : "integer", + "format" : "int32" + }, + "data" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/InstallmentData" + } + } + } + }, + "Notice" : { + "required" : [ "code", "subject" ], + "type" : "object", + "properties" : { + "subject" : { + "type" : "string" + }, + "paymentAmount" : { + "type" : "integer", + "format" : "int64" + }, + "dueDate" : { + "type" : "string" + }, + "code" : { + "type" : "string" + }, + "installments" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Installments" + } + } + } + }, + "NoticeGenerationMassiveRequest" : { + "required" : [ "notices" ], + "type" : "object", + "properties" : { + "notices" : { + "maxItems" : 2147483647, + "minItems" : 1, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/NoticeGenerationRequestItem" + } + } + } + }, + "NoticeGenerationRequestItem" : { + "type" : "object", + "properties" : { + "templateId" : { + "type" : "string" + }, + "data" : { + "$ref" : "#/components/schemas/NoticeRequestData" + } + } + }, + "NoticeRequestData" : { + "type" : "object", + "properties" : { + "notice" : { + "$ref" : "#/components/schemas/Notice" + }, + "creditorInstitution" : { + "$ref" : "#/components/schemas/CreditorInstitution" + }, + "debtor" : { + "$ref" : "#/components/schemas/Debtor" + }, + "extraData" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + } + }, "ProblemJson" : { "type" : "object", "properties" : { diff --git a/pom.xml b/pom.xml index da6bf70e..f37504b9 100644 --- a/pom.xml +++ b/pom.xml @@ -54,8 +54,11 @@ com.azure.spring spring-cloud-azure-starter-storage-blob - 4.2.0 + + org.springframework.cloud + spring-cloud-starter-stream-kafka + org.springframework.boot spring-boot-starter-cache @@ -128,6 +131,25 @@ + + + + org.springframework.cloud + spring-cloud-dependencies + 2023.0.1 + pom + import + + + com.azure.spring + spring-cloud-azure-dependencies + 5.11.0 + pom + import + + + + diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java index 688cce02..b97c05c6 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java @@ -13,6 +13,8 @@ import it.gov.pagopa.payment.notices.service.model.ProblemJson; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; import it.gov.pagopa.payment.notices.service.util.OpenApiTableMetadata; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -74,8 +76,9 @@ public GetGenerationRequestStatusResource getFolderStatus( @PostMapping("/generate-massive") public String getFolderStatus( - @RequestBody NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, - @Parameter(description = "userId to use for request status retrieval") @RequestHeader("X-User-Id") String userId) { + @Valid @NotNull @RequestBody NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, + @Parameter(description = "userId to use for request status retrieval") + @RequestHeader("X-User-Id") String userId) { return noticeGenerationService.generateMassive(noticeGenerationMassiveRequest, userId); } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java new file mode 100644 index 00000000..e878c27b --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.payment.notices.service.events; + +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestEH; + +public interface NoticeGenerationRequestProducer { + boolean noticeGeneration(NoticeGenerationRequestEH noticeGenerationRequestEH); + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java new file mode 100644 index 00000000..b3d3def6 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java @@ -0,0 +1,43 @@ +package it.gov.pagopa.payment.notices.service.events; + +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestEH; +import org.springframework.cloud.stream.function.StreamBridge; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; + +import java.util.function.Supplier; + +@Service +public class NoticeGenerationRequestProducerImpl implements NoticeGenerationRequestProducer { + + private final StreamBridge streamBridge; + + public NoticeGenerationRequestProducerImpl(StreamBridge streamBridge) { + this.streamBridge = streamBridge; + } + + /** Declared just to let know Spring to connect the producer at startup */ + @Configuration + static class NoticeGenerationRequestProducerConfig { + @Bean + public Supplier>> noticeGenerationRequest() { + return Flux::empty; + } + } + + @Override + public boolean noticeGeneration(NoticeGenerationRequestEH noticeGenerationRequestEH) { + return streamBridge.send("noticeGeneration-out-0", + buildMessage(noticeGenerationRequestEH)); + } + + public static Message buildMessage( + NoticeGenerationRequestEH noticeGenerationRequestEH){ + return MessageBuilder.withPayload(noticeGenerationRequestEH).build(); + } + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java b/src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java new file mode 100644 index 00000000..deabfd49 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java @@ -0,0 +1,35 @@ +package it.gov.pagopa.payment.notices.service.exception; + +import lombok.Getter; + +/** + * Thrown in case an error occur when encrypting or decrypting a BizEvent + */ +@Getter +public class Aes256Exception extends Exception{ + private final int statusCode; + + /** + * Constructs new exception with provided message + * + * @param message Detail message + * @param statusCode status code + */ + public Aes256Exception(String message, int statusCode) { + super(message); + this.statusCode = statusCode; + } + + /** + * Constructs new exception with provided message + * + * @param message Detail message + * @param statusCode status code + * @param cause Exception causing the constructed one + */ + public Aes256Exception(String message, int statusCode, Throwable cause) { + super(message, cause); + this.statusCode = statusCode; + } + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/exception/AppError.java b/src/main/java/it/gov/pagopa/payment/notices/service/exception/AppError.java index 9b4660c6..40bc945f 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/exception/AppError.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/exception/AppError.java @@ -22,6 +22,9 @@ public enum AppError { FOLDER_NOT_AVAILABLE(HttpStatus.NOT_FOUND, "Folder Not Available", "Required folder is either missing or not available to the requirer"), + ERROR_ON_MASSIVE_GENERATION_REQUEST(HttpStatus.INTERNAL_SERVER_ERROR, + "Exception on Massive Generation Request", + "Encountered a blocking exception on the massive generation request"), UNKNOWN(null, null, null); diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java index d907cdc9..e9c380a4 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationMassiveRequest.java @@ -1,5 +1,7 @@ package it.gov.pagopa.payment.notices.service.model; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -13,6 +15,8 @@ @Builder public class NoticeGenerationMassiveRequest { + @NotNull + @Size(min = 1) private List notices; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java index 05cd4898..110aa353 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestEH.java @@ -1,5 +1,14 @@ package it.gov.pagopa.payment.notices.service.model; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder public class NoticeGenerationRequestEH { private NoticeGenerationRequestItem noticeData; diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java index 87a11b6d..2dd36d17 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/NoticeGenerationRequestItem.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payment.notices.service.model; +import it.gov.pagopa.payment.notices.service.model.notice.NoticeRequestData; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -12,6 +13,6 @@ public class NoticeGenerationRequestItem { private String templateId; - private String data; + private NoticeRequestData data; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java new file mode 100644 index 00000000..23bb6fa8 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java @@ -0,0 +1,26 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +public class CreditorInstitution { + + @NotNull + @NotEmpty + private String taxCode; + @NotNull + @NotEmpty + private String fullName; + private String organization; + @NotNull + @NotEmpty + private String info; + @NotNull + private Boolean webChannel; + private String physicalChannel; + @NotNull + @NotEmpty + private String cbill; + + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java new file mode 100644 index 00000000..4219f2ce --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java @@ -0,0 +1,15 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +public class Debtor { + + private String taxCode; + @NotNull + @NotEmpty + private String fullName; + @NotNull + @NotEmpty + private String address; +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java new file mode 100644 index 00000000..a727f98e --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java @@ -0,0 +1,11 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import lombok.Data; + +@Data +public class InstallmentData { + + private Integer amount; + private String dueDate; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java new file mode 100644 index 00000000..01d53c03 --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java @@ -0,0 +1,13 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import lombok.Data; + +import java.util.List; + +@Data +public class Installments { + + private Integer number; + private List data; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java new file mode 100644 index 00000000..6047396d --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java @@ -0,0 +1,32 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class Notice { + + @NotNull + @NotEmpty + private String subject; + + private Long paymentAmount; + + private String dueDate; + + @NotNull + @NotEmpty + private String code; + + private List installments; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java new file mode 100644 index 00000000..7226562f --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java @@ -0,0 +1,21 @@ +package it.gov.pagopa.payment.notices.service.model.notice; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class NoticeRequestData { + + private Notice notice; + private CreditorInstitution creditorInstitution; + private Debtor debtor; + private Map extraData; + +} diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/repository/PaymentGenerationRequestRepository.java b/src/main/java/it/gov/pagopa/payment/notices/service/repository/PaymentGenerationRequestRepository.java index 08e990b4..fe275915 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/repository/PaymentGenerationRequestRepository.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/repository/PaymentGenerationRequestRepository.java @@ -3,6 +3,7 @@ import it.gov.pagopa.payment.notices.service.entity.PaymentNoticeGenerationRequest; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; +import org.springframework.data.mongodb.repository.Update; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -10,4 +11,8 @@ @Repository public interface PaymentGenerationRequestRepository extends MongoRepository { Optional findByIdAndUserId(String folderId, String userId); + + @Update("{ '$inc' : { 'numberOfElementsFailed' : 1 } }") + long findAndIncrementNumberOfElementsFailedById(String folderId); + } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java index 4bc3b1a2..6684539d 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java @@ -1,16 +1,24 @@ package it.gov.pagopa.payment.notices.service.service.impl; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import it.gov.pagopa.payment.notices.service.entity.PaymentNoticeGenerationRequest; import it.gov.pagopa.payment.notices.service.entity.PaymentNoticeGenerationRequestError; +import it.gov.pagopa.payment.notices.service.events.NoticeGenerationRequestProducer; +import it.gov.pagopa.payment.notices.service.exception.Aes256Exception; import it.gov.pagopa.payment.notices.service.exception.AppError; import it.gov.pagopa.payment.notices.service.exception.AppException; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestEH; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestItem; import it.gov.pagopa.payment.notices.service.model.enums.PaymentGenerationRequestStatus; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestErrorRepository; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestRepository; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; +import it.gov.pagopa.payment.notices.service.util.Aes256Utils; import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.time.Instant; @@ -19,16 +27,27 @@ import java.util.Optional; @Service +@Slf4j public class NoticeGenerationServiceImpl implements NoticeGenerationService { private final PaymentGenerationRequestRepository paymentGenerationRequestRepository; private final PaymentGenerationRequestErrorRepository paymentGenerationRequestErrorRepository; + private final NoticeGenerationRequestProducer noticeGenerationRequestProducer; + + private final ObjectMapper objectMapper; + + private final Aes256Utils aes256Utils; + public NoticeGenerationServiceImpl( PaymentGenerationRequestRepository paymentGenerationRequestRepository, - PaymentGenerationRequestErrorRepository paymentGenerationRequestErrorRepository) { + PaymentGenerationRequestErrorRepository paymentGenerationRequestErrorRepository, + NoticeGenerationRequestProducer noticeGenerationRequestProducer, ObjectMapper objectMapper, Aes256Utils aes256Utils) { this.paymentGenerationRequestRepository = paymentGenerationRequestRepository; this.paymentGenerationRequestErrorRepository = paymentGenerationRequestErrorRepository; + this.noticeGenerationRequestProducer = noticeGenerationRequestProducer; + this.objectMapper = objectMapper; + this.aes256Utils = aes256Utils; } @Override @@ -64,15 +83,49 @@ public String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMas .requestDate(Instant.now()) .build()).getId(); - + noticeGenerationMassiveRequest.getNotices().parallelStream().forEach(noticeGenerationRequestItem -> { + try { + if (!noticeGenerationRequestProducer.noticeGeneration( + NoticeGenerationRequestEH.builder() + .noticeData(noticeGenerationRequestItem) + .folderId(folderId) + .build()) + ) { + saveErrorEvent(folderId, noticeGenerationRequestItem); + } + } catch (Exception e) { + saveErrorEvent(folderId, noticeGenerationRequestItem); + } + }); return folderId; } catch (Exception e) { - //TODO: Error - throw new AppException(AppError.UNKNOWN); + log.error(e.getMessage(), e); + throw new AppException(AppError.ERROR_ON_MASSIVE_GENERATION_REQUEST); } } + private void saveErrorEvent(String folderId, NoticeGenerationRequestItem noticeGenerationRequestItem) { + try { + paymentGenerationRequestErrorRepository.save( + PaymentNoticeGenerationRequestError.builder() + .errorDescription("Encountered error sending notice on EH") + .folderId(folderId) + .data(aes256Utils.encrypt(objectMapper + .writeValueAsString(noticeGenerationRequestItem))) + .createdAt(Instant.now()) + .numberOfAttempts(0) + .build() + ); + paymentGenerationRequestRepository.findAndIncrementNumberOfElementsFailedById(folderId); + } catch (JsonProcessingException | Aes256Exception e) { + log.error( + "Unable to save notice data into error repository for notice with folder " + folderId + + " and noticeId " + noticeGenerationRequestItem.getData().getNotice().getCode() + ); + } + } + } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java b/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java new file mode 100644 index 00000000..676b6c2e --- /dev/null +++ b/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java @@ -0,0 +1,103 @@ +package it.gov.pagopa.payment.notices.service.util; + +import it.gov.pagopa.payment.notices.service.exception.Aes256Exception; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.security.spec.KeySpec; +import java.util.Base64; + +@Component +public class Aes256Utils { + + private String aesSecretKey; + + private String aesSalt; + + private static final int KEY_LENGTH = 256; + private static final int ITERATION_COUNT = 65536; + public static final String PBKDF_2_WITH_HMAC_SHA_256 = "PBKDF2WithHmacSHA256"; + public static final String AES_CBC_PKCS_5_PADDING = "AES/CBC/PKCS5Padding"; + + public static final String ALGORITHM = "AES"; + + private static final int AES_UNEXPECTED_ERROR = 701; + + + /** + * Hide from public usage. + */ + private Aes256Utils() { + } + + public Aes256Utils( + @Value("${aes.secret.key}") String aesSecretKey, + @Value("${aes.salt}") String aesSalt) { + this.aesSecretKey = aesSecretKey; + this.aesSalt = aesSalt; + } + + public String encrypt(String strToEncrypt) throws Aes256Exception { + + try { + + SecureRandom secureRandom = new SecureRandom(); + byte[] iv = new byte[16]; + secureRandom.nextBytes(iv); + IvParameterSpec ivspec = new IvParameterSpec(iv); + + SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA_256); + KeySpec spec = new PBEKeySpec(aesSecretKey.toCharArray(), aesSalt.getBytes(), ITERATION_COUNT, KEY_LENGTH); + SecretKey tmp = factory.generateSecret(spec); + SecretKeySpec secretKeySpec = new SecretKeySpec(tmp.getEncoded(), ALGORITHM); + + //Padding vulnerability rule java:S5542 ignored because encryption is used inside application workflow + Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivspec); + + byte[] cipherText = cipher.doFinal(strToEncrypt.getBytes(StandardCharsets.UTF_8)); + byte[] encryptedData = new byte[iv.length + cipherText.length]; + System.arraycopy(iv, 0, encryptedData, 0, iv.length); + System.arraycopy(cipherText, 0, encryptedData, iv.length, cipherText.length); + + return Base64.getEncoder().encodeToString(encryptedData); + } catch (Exception e) { + throw new Aes256Exception("Unexpected error when encrypting the given string", AES_UNEXPECTED_ERROR, e); + } + } + + public String decrypt(String strToDecrypt) throws Aes256Exception { + try{ + byte[] encryptedData = Base64.getDecoder().decode(strToDecrypt); + byte[] iv = new byte[16]; + System.arraycopy(encryptedData, 0, iv, 0, iv.length); + IvParameterSpec ivspec = new IvParameterSpec(iv); + + SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA_256); + KeySpec spec = new PBEKeySpec(aesSecretKey.toCharArray(), aesSalt.getBytes(), ITERATION_COUNT, KEY_LENGTH); + SecretKey tmp = factory.generateSecret(spec); + SecretKeySpec secretKeySpec = new SecretKeySpec(tmp.getEncoded(), ALGORITHM); + + //Padding vulnerability rule java:S5542 ignored because decryption is used inside application workflow + Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivspec); + + byte[] cipherText = new byte[encryptedData.length - 16]; + System.arraycopy(encryptedData, 16, cipherText, 0, cipherText.length); + + byte[] decryptedText = cipher.doFinal(cipherText); + return new String(decryptedText, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new Aes256Exception("Unexpected error when decrypting the given string", AES_UNEXPECTED_ERROR, e); + } + } +} + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d4fc92da..849e0aba 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -66,3 +66,7 @@ spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration. spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.retry.backoff.ms=${KAFKA_REWARD_RESPONSE_KAFKA_RETRY_MS:${KAFKA_RETRY_MS:10000}} spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.linger.ms=${KAFKA_REWARD_RESPONSE_LINGER_MS:${KAFKA_LINGER_MS:2}} spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.batch.size=${KAFKA_REWARD_RESPONSE_BATCH_SIZE:${KAFKA_BATCH_SIZE:16384}} + +#Other Configs +aes.secret.key=${AES_SECRET_KEY} +aes.salt=${AES_SALT} diff --git a/src/test/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestControllerTest.java b/src/test/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestControllerTest.java index 08848dd5..023a4c76 100644 --- a/src/test/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestControllerTest.java +++ b/src/test/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestControllerTest.java @@ -1,9 +1,13 @@ package it.gov.pagopa.payment.notices.service.controller; +import com.fasterxml.jackson.databind.ObjectMapper; import it.gov.pagopa.payment.notices.service.exception.AppError; import it.gov.pagopa.payment.notices.service.exception.AppException; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestItem; import it.gov.pagopa.payment.notices.service.service.NoticeGenerationService; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -14,10 +18,15 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -79,4 +88,67 @@ void getFolderStatusShouldReturnDataOn500() throws Exception { verify(noticeGenerationService).getFolderStatus("folderTest","userTest"); } + @Test + void generateMassiveShouldReturnFolderIdOn200() throws Exception { + when(noticeGenerationService.generateMassive(any(),any())) + .thenReturn("folderTests"); + String url = "/notices/generate-massive"; + String folderId = mvc.perform(post(url) + .content(new ObjectMapper().writeValueAsBytes( + NoticeGenerationMassiveRequest.builder() + .notices(Collections.singletonList( + NoticeGenerationRequestItem.builder().build() + )).build() + )) + .header("X-User-Id", "userTest") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andExpect(content().contentType("application/json")) + .andReturn().getResponse().getContentAsString(); + Assertions.assertNotNull(folderId); + Assertions.assertEquals("folderTests", folderId); + verify(noticeGenerationService).generateMassive(any(),any()); + } + + @Test + void generateMassiveShouldReturn400OnMissingItems() throws Exception { + String url = "/notices/generate-massive"; + mvc.perform(post(url) + .content(new ObjectMapper().writeValueAsBytes( + NoticeGenerationMassiveRequest.builder() + .notices(Collections.emptyList( + + )).build() + )) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()) + .andExpect(content().contentType("application/json")); + } + + @Test + void generateMassiveShouldReturn400OnMissingBody() throws Exception { + String url = "/notices/generate-massive"; + mvc.perform(post(url) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()) + .andExpect(content().contentType("application/json")); + } + + @Test + void generateMassiveShouldReturn400OnMissingUserId() throws Exception { + when(noticeGenerationService.generateMassive(any(),any())) + .thenReturn("folderTests"); + String url = "/notices/generate-massive"; + mvc.perform(post(url) + .content(new ObjectMapper().writeValueAsBytes( + NoticeGenerationMassiveRequest.builder() + .notices(Collections.singletonList( + NoticeGenerationRequestItem.builder().build() + )).build() + )) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + + } + } diff --git a/src/test/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImplTest.java b/src/test/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImplTest.java index 339c750f..a45e3e33 100644 --- a/src/test/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImplTest.java +++ b/src/test/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImplTest.java @@ -1,17 +1,26 @@ package it.gov.pagopa.payment.notices.service.service.impl; +import com.fasterxml.jackson.databind.ObjectMapper; import it.gov.pagopa.payment.notices.service.entity.PaymentNoticeGenerationRequest; import it.gov.pagopa.payment.notices.service.entity.PaymentNoticeGenerationRequestError; +import it.gov.pagopa.payment.notices.service.events.NoticeGenerationRequestProducer; +import it.gov.pagopa.payment.notices.service.exception.Aes256Exception; import it.gov.pagopa.payment.notices.service.exception.AppException; import it.gov.pagopa.payment.notices.service.model.GetGenerationRequestStatusResource; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationMassiveRequest; +import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestItem; import it.gov.pagopa.payment.notices.service.model.enums.PaymentGenerationRequestStatus; +import it.gov.pagopa.payment.notices.service.model.notice.Notice; +import it.gov.pagopa.payment.notices.service.model.notice.NoticeRequestData; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestErrorRepository; import it.gov.pagopa.payment.notices.service.repository.PaymentGenerationRequestRepository; +import it.gov.pagopa.payment.notices.service.util.Aes256Utils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Collections; @@ -30,13 +39,23 @@ class NoticeGenerationServiceImplTest { @Mock private PaymentGenerationRequestErrorRepository paymentGenerationRequestErrorRepository; + @Mock + private NoticeGenerationRequestProducer noticeGenerationRequestProducer; + + @Spy + private ObjectMapper objectMapper; + private NoticeGenerationServiceImpl noticeGenerationService; @BeforeEach public void init() { - Mockito.reset(paymentGenerationRequestErrorRepository, paymentGenerationRequestRepository); + Mockito.reset( + paymentGenerationRequestErrorRepository, + paymentGenerationRequestRepository, noticeGenerationRequestProducer); noticeGenerationService = new NoticeGenerationServiceImpl( - paymentGenerationRequestRepository, paymentGenerationRequestErrorRepository); + paymentGenerationRequestRepository, + paymentGenerationRequestErrorRepository, + noticeGenerationRequestProducer, objectMapper, new Aes256Utils("test","test")); } @Test @@ -113,4 +132,50 @@ void getFolderStatusShouldReturnExceptionWhenUnexpectedExceptionOnErrorRepositor verify(paymentGenerationRequestErrorRepository).findErrors(any()); } + @Test + void generateMassiveRequestShouldSendEventOnSuccess() { + NoticeGenerationRequestItem noticeGenerationRequestItem = NoticeGenerationRequestItem.builder() + .templateId("testTemplate") + .data(NoticeRequestData.builder().notice( + Notice.builder().code("testCode") + .build() + ).build() + ).build(); + NoticeGenerationMassiveRequest noticeGenerationMassiveRequest = + NoticeGenerationMassiveRequest.builder().notices( + Collections.singletonList(noticeGenerationRequestItem)).build(); + when(paymentGenerationRequestRepository.save(any())).thenReturn( + PaymentNoticeGenerationRequest.builder().id("testFolderId").build()); + when(noticeGenerationRequestProducer.noticeGeneration(any())).thenReturn(true); + String folderId = noticeGenerationService.generateMassive(noticeGenerationMassiveRequest, "testUserId"); + assertNotNull(folderId); + assertEquals("testFolderId", folderId); + verify(paymentGenerationRequestRepository).save(any()); + verify(noticeGenerationRequestProducer).noticeGeneration(any()); + verifyNoInteractions(paymentGenerationRequestErrorRepository); + } + + @Test + void generateMassiveRequestShouldSaveErrorEventOnSendFailure() throws Aes256Exception { + NoticeGenerationRequestItem noticeGenerationRequestItem = NoticeGenerationRequestItem.builder() + .templateId("testTemplate") + .data(NoticeRequestData.builder().notice( + Notice.builder().code("testCode") + .build() + ).build() + ).build(); + NoticeGenerationMassiveRequest noticeGenerationMassiveRequest = + NoticeGenerationMassiveRequest.builder().notices( + Collections.singletonList(noticeGenerationRequestItem)).build(); + when(paymentGenerationRequestRepository.save(any())).thenReturn( + PaymentNoticeGenerationRequest.builder().id("testFolderId").build()); + when(noticeGenerationRequestProducer.noticeGeneration(any())).thenReturn(false); + String folderId = noticeGenerationService.generateMassive(noticeGenerationMassiveRequest, "testUserId"); + assertNotNull(folderId); + assertEquals("testFolderId", folderId); + verify(paymentGenerationRequestRepository).save(any()); + verify(noticeGenerationRequestProducer).noticeGeneration(any()); + verify(paymentGenerationRequestErrorRepository).save(any()); + } + } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index c3eb102f..a97115d0 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -34,3 +34,7 @@ spring.cloud.azure.storage.blob.templates.connection_string=${TEMPLATE_STORAGE_C spring.cloud.azure.storage.blob.templates.containerName=${TEMPLATE_STORAGE_CONTAINER_NAME:} spring.cloud.azure.storage.blob.templates.retry=${TEMPLATE_STORAGE_RETRY:3} spring.cloud.azure.storage.blob.templates.timeout=${TEMPLATE_STORAGE_TIMEOUT:10} + +#Other Configs +aes.secret.key=${AES_SECRET_KEY:test} +aes.salt=${AES_SALT:test} From 0d38f310f70a60d5567d1a1efbd6d7744759641f Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Mon, 22 Apr 2024 11:29:06 +0200 Subject: [PATCH 07/12] [VAS-828] feat: Updated notice generation massive request and tests --- openapi/openapi.json | 121 +++++++++++++++--- pom.xml | 56 ++++---- .../payment/notices/service/Application.java | 5 +- .../GenerationRequestController.java | 35 ++++- .../NoticeGenerationRequestProducer.java | 10 ++ .../NoticeGenerationRequestProducerImpl.java | 6 +- .../service/model/AppCorsConfiguration.java | 1 + .../enums/PaymentGenerationRequestStatus.java | 3 + .../model/notice/CreditorInstitution.java | 12 ++ .../notices/service/model/notice/Debtor.java | 4 + .../service/model/notice/InstallmentData.java | 3 + .../service/model/notice/Installments.java | 3 + .../notices/service/model/notice/Notice.java | 6 + .../model/notice/NoticeRequestData.java | 5 +- .../service/NoticeGenerationService.java | 10 ++ .../impl/NoticeGenerationServiceImpl.java | 38 ++++-- .../notices/service/util/Aes256Utils.java | 9 +- src/main/resources/application.properties | 22 ++-- 18 files changed, 266 insertions(+), 83 deletions(-) diff --git a/openapi/openapi.json b/openapi/openapi.json index e4a07dda..3d9461a0 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -157,6 +157,8 @@ "/notices/generate-massive" : { "post" : { "tags" : [ "Notice Generation Request APIs" ], + "summary" : "generateMassiveNotice", + "description" : "Insert massive notice generation request and returns folderId for reference an future recovery", "operationId" : "getFolderStatus", "parameters" : [ { "name" : "X-User-Id", @@ -195,8 +197,80 @@ } } } + }, + "400" : { + "description" : "Bad Request", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ProblemJson" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + } + }, + "403" : { + "description" : "Forbidden", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + } + }, + "429" : { + "description" : "Too many requests", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Service unavailable", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ProblemJson" + } + } + } } - } + }, + "security" : [ { + "ApiKey" : [ ] + } ] }, "parameters" : [ { "name" : "X-Request-Id", @@ -360,62 +434,77 @@ "components" : { "schemas" : { "CreditorInstitution" : { - "type" : "object" + "type" : "object", + "description" : "Creditor Institution data" }, "Debtor" : { - "type" : "object" + "type" : "object", + "description" : "Debtor data" }, "InstallmentData" : { + "required" : [ "amount", "dueDate" ], "type" : "object", "properties" : { "amount" : { "type" : "integer", + "description" : "Installment amount", "format" : "int32" }, "dueDate" : { - "type" : "string" + "type" : "string", + "description" : "Installment dueDate" } - } + }, + "description" : "Installments of the notice" }, "Installments" : { "type" : "object", "properties" : { "number" : { "type" : "integer", + "description" : "Number of installments", "format" : "int32" }, "data" : { "type" : "array", + "description" : "Installments of the notice", "items" : { "$ref" : "#/components/schemas/InstallmentData" } } - } + }, + "description" : "Notice installments (if present)" }, "Notice" : { - "required" : [ "code", "subject" ], + "required" : [ "code", "dueDate", "paymentAmount", "subject" ], "type" : "object", "properties" : { "subject" : { - "type" : "string" + "type" : "string", + "description" : "Notice subject" }, "paymentAmount" : { "type" : "integer", + "description" : "Notice total amount to pay", "format" : "int64" }, "dueDate" : { - "type" : "string" + "type" : "string", + "description" : "Notice due date" }, "code" : { - "type" : "string" + "type" : "string", + "description" : "Notice code" }, "installments" : { "type" : "array", + "description" : "Notice installments (if present)", "items" : { "$ref" : "#/components/schemas/Installments" } } - } + }, + "description" : "Notice data" }, "NoticeGenerationMassiveRequest" : { "required" : [ "notices" ], @@ -429,7 +518,8 @@ "$ref" : "#/components/schemas/NoticeGenerationRequestItem" } } - } + }, + "description" : "massive notice generation request data" }, "NoticeGenerationRequestItem" : { "type" : "object", @@ -443,6 +533,7 @@ } }, "NoticeRequestData" : { + "required" : [ "creditorInstitution", "debtor", "notice" ], "type" : "object", "properties" : { "notice" : { @@ -453,12 +544,6 @@ }, "debtor" : { "$ref" : "#/components/schemas/Debtor" - }, - "extraData" : { - "type" : "object", - "additionalProperties" : { - "type" : "object" - } } } }, diff --git a/pom.xml b/pom.xml index f37504b9..215e6b16 100644 --- a/pom.xml +++ b/pom.xml @@ -68,37 +68,37 @@ caffeine - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.3.0 - + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + - - - io.swagger - swagger-annotations - 1.6.12 - + + + io.swagger + swagger-annotations + 1.6.12 + - - - org.hibernate.orm - hibernate-core - 6.4.0.Final - - - org.springframework.boot - spring-boot-starter-data-mongodb - - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - 4.12.2 - test - + + + org.hibernate.orm + hibernate-core + 6.4.0.Final + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + 4.12.2 + test + org.springframework.cloud diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/Application.java b/src/main/java/it/gov/pagopa/payment/notices/service/Application.java index 8df3749d..abf279eb 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/Application.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/Application.java @@ -2,12 +2,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.kafka.test.context.EmbeddedKafka; +import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication +@EnableAsync public class Application { - public static void main(String[] args) { SpringApplication.run(Application.class, args); } - } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java index b97c05c6..94d04020 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java @@ -73,9 +73,42 @@ public GetGenerationRequestStatusResource getFolderStatus( return noticeGenerationService.getFolderStatus(folderId, userId); } - + /** + * Generate the request of massive notice generation using input data, sending data through the EH channel + * to kickstart notice generation in an async manner. Any error will be saved into notice generation request + * error + * @param noticeGenerationMassiveRequest generation request data, containing a list of notice data and templates + * to use + * @param userId userId requiring the generation. the request will refer to this user when recovery of data regarding + * the folder is executed + * @return folderId produced when inserting the request + */ + @Operation(summary = "generateMassiveNotice", + description = "Insert massive notice generation request and returns folderId for reference an" + + " future recovery", + security = {@SecurityRequirement(name = "ApiKey")}) + @OpenApiTableMetadata(readWriteIntense = OpenApiTableMetadata.ReadWrite.WRITE, + external = true, internal = false) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "400", description = "Bad Request", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ProblemJson.class))), + @ApiResponse(responseCode = "401", + description = "Unauthorized", content = @Content(schema = @Schema())), + @ApiResponse(responseCode = "403", + description = "Forbidden", content = @Content(schema = @Schema())), + @ApiResponse(responseCode = "429", + description = "Too many requests", content = @Content(schema = @Schema())), + @ApiResponse(responseCode = "500", + description = "Service unavailable", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ProblemJson.class))) + }) @PostMapping("/generate-massive") public String getFolderStatus( + @Parameter(description = "massive notice generation request data") @Valid @NotNull @RequestBody NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, @Parameter(description = "userId to use for request status retrieval") @RequestHeader("X-User-Id") String userId) { diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java index e878c27b..882bdd6b 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducer.java @@ -2,7 +2,17 @@ import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestEH; +/** + * Interface to use when required to execute sending of a notice generation request through + * the eventhub channel + */ public interface NoticeGenerationRequestProducer { + + /** + * Send notige generation request through EH + * @param noticeGenerationRequestEH data to send + * @return boolean referring if the insertion on the sending channel was successfully + */ boolean noticeGeneration(NoticeGenerationRequestEH noticeGenerationRequestEH); } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java index b3d3def6..f8e0142e 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/events/NoticeGenerationRequestProducerImpl.java @@ -1,6 +1,7 @@ package it.gov.pagopa.payment.notices.service.events; import it.gov.pagopa.payment.notices.service.model.NoticeGenerationRequestEH; +import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -21,12 +22,15 @@ public NoticeGenerationRequestProducerImpl(StreamBridge streamBridge) { } /** Declared just to let know Spring to connect the producer at startup */ + @Slf4j @Configuration static class NoticeGenerationRequestProducerConfig { + @Bean - public Supplier>> noticeGenerationRequest() { + public Supplier>> noticeGeneration() { return Flux::empty; } + } @Override diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/AppCorsConfiguration.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/AppCorsConfiguration.java index a6566ed2..a4f4188d 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/AppCorsConfiguration.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/AppCorsConfiguration.java @@ -20,4 +20,5 @@ public class AppCorsConfiguration { private String[] origins; private String[] methods; + } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/enums/PaymentGenerationRequestStatus.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/enums/PaymentGenerationRequestStatus.java index cd6c62ea..5d35f005 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/enums/PaymentGenerationRequestStatus.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/enums/PaymentGenerationRequestStatus.java @@ -1,5 +1,8 @@ package it.gov.pagopa.payment.notices.service.model.enums; +/** + * Enum containing generation request status + */ public enum PaymentGenerationRequestStatus { INSERTED, diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java index 23bb6fa8..b360e454 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java @@ -1,23 +1,35 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; public class CreditorInstitution { + @Schema(description = "CI tax code", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String taxCode; + + @Schema(description = "CI full name", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String fullName; private String organization; + + @Schema(description = "CI info", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String info; + + @Schema(description = "Boolean to refer if it has a web channel", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull private Boolean webChannel; + + @Schema(description = "CI physical channel data") private String physicalChannel; + + @Schema(description = "CI cbill", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String cbill; diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java index 4219f2ce..3a0152ad 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java @@ -1,14 +1,18 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; public class Debtor { + @Schema(description = "Debtor taxCode") private String taxCode; + @Schema(description = "Debtor full name", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String fullName; + @Schema(description = "Debtor address", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String address; diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java index a727f98e..eea94ede 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/InstallmentData.java @@ -1,11 +1,14 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data public class InstallmentData { + @Schema(description = "Installment amount", requiredMode = Schema.RequiredMode.REQUIRED) private Integer amount; + @Schema(description = "Installment dueDate", requiredMode = Schema.RequiredMode.REQUIRED) private String dueDate; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java index 01d53c03..9e5e2fda 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Installments.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @@ -7,7 +8,9 @@ @Data public class Installments { + @Schema(description = "Number of installments") private Integer number; + @Schema(description = "Installments of the notice") private List data; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java index 6047396d..5e888642 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Notice.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -15,18 +16,23 @@ @Builder public class Notice { + @Schema(description = "Notice subject", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String subject; + @Schema(description = "Notice total amount to pay", requiredMode = Schema.RequiredMode.REQUIRED) private Long paymentAmount; + @Schema(description = "Notice due date", requiredMode = Schema.RequiredMode.REQUIRED) private String dueDate; + @Schema(description = "Notice code", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty private String code; + @Schema(description = "Notice installments (if present)") private List installments; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java index 7226562f..b14693fa 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/NoticeRequestData.java @@ -1,5 +1,6 @@ package it.gov.pagopa.payment.notices.service.model.notice; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -13,9 +14,11 @@ @Builder public class NoticeRequestData { + @Schema(description = "Notice data", requiredMode = Schema.RequiredMode.REQUIRED) private Notice notice; + @Schema(description = "Creditor Institution data", requiredMode = Schema.RequiredMode.REQUIRED) private CreditorInstitution creditorInstitution; + @Schema(description = "Debtor data", requiredMode = Schema.RequiredMode.REQUIRED) private Debtor debtor; - private Map extraData; } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java index 87adc494..0d263148 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/NoticeGenerationService.java @@ -18,5 +18,15 @@ public interface NoticeGenerationService { */ GetGenerationRequestStatusResource getFolderStatus(String folderId, String userId); + /** + * Generate the request of massive notice generation using input data, sending data through the EH channel + * to kickstart notice generation in an async manner. Any error will be saved into notice generation request + * error + * @param noticeGenerationMassiveRequest generation request data, containing a list of notice data and templates + * to use + * @param userId userId requiring the generation. the request will refer to this user when recovery of data regarding + * the folder is executed + * @return folderId produced when inserting the request + */ String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String userId); } diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java index 6684539d..137e00e6 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java @@ -19,6 +19,7 @@ import it.gov.pagopa.payment.notices.service.util.Aes256Utils; import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.time.Instant; @@ -26,6 +27,9 @@ import java.util.List; import java.util.Optional; +/** + * @see it.gov.pagopa.payment.notices.service.service.NoticeGenerationService + */ @Service @Slf4j public class NoticeGenerationServiceImpl implements NoticeGenerationService { @@ -79,24 +83,12 @@ public String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMas .status(PaymentGenerationRequestStatus.INSERTED) .createdAt(Instant.now()) .items(new ArrayList<>()) + .userId(userId) .numberOfElementsTotal(noticeGenerationMassiveRequest.getNotices().size()) .requestDate(Instant.now()) .build()).getId(); - noticeGenerationMassiveRequest.getNotices().parallelStream().forEach(noticeGenerationRequestItem -> { - try { - if (!noticeGenerationRequestProducer.noticeGeneration( - NoticeGenerationRequestEH.builder() - .noticeData(noticeGenerationRequestItem) - .folderId(folderId) - .build()) - ) { - saveErrorEvent(folderId, noticeGenerationRequestItem); - } - } catch (Exception e) { - saveErrorEvent(folderId, noticeGenerationRequestItem); - } - }); + sendNotices(noticeGenerationMassiveRequest, folderId); return folderId; @@ -107,6 +99,24 @@ public String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMas } + @Async + private void sendNotices(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String folderId) { + noticeGenerationMassiveRequest.getNotices().parallelStream().forEach(noticeGenerationRequestItem -> { + try { + if (!noticeGenerationRequestProducer.noticeGeneration( + NoticeGenerationRequestEH.builder() + .noticeData(noticeGenerationRequestItem) + .folderId(folderId) + .build()) + ) { + saveErrorEvent(folderId, noticeGenerationRequestItem); + } + } catch (Exception e) { + saveErrorEvent(folderId, noticeGenerationRequestItem); + } + }); + } + private void saveErrorEvent(String folderId, NoticeGenerationRequestItem noticeGenerationRequestItem) { try { paymentGenerationRequestErrorRepository.save( diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java b/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java index 676b6c2e..a043ed72 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java @@ -1,6 +1,7 @@ package it.gov.pagopa.payment.notices.service.util; import it.gov.pagopa.payment.notices.service.exception.Aes256Exception; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -31,13 +32,7 @@ public class Aes256Utils { private static final int AES_UNEXPECTED_ERROR = 701; - - /** - * Hide from public usage. - */ - private Aes256Utils() { - } - + @Autowired public Aes256Utils( @Value("${aes.secret.key}") String aesSecretKey, @Value("${aes.salt}") String aesSalt) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 849e0aba..eb71e492 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -24,7 +24,7 @@ logging.level.it.gov.pagopa=${APP_LOGGING_LEVEL:INFO} # CORS configuration cors.configuration=${CORS_CONFIGURATION:{"origins": ["*"], "methods": ["*"]}} # Mongo Configuration -spring.data.mongodb.uri=${MONGODB_CONNECTION_URI} +spring.data.mongodb.uri=${MONGODB_CONNECTION_URI:} spring.data.mongodb.database=${MONGODB_NAME:noticesMongoDb} # Cache configuration spring.cache.type=caffeine @@ -37,18 +37,18 @@ spring.jackson.serialization.fail-on-empty-beans=false spring.jackson.deserialization.fail-on-unknown-properties=false spring.cloud.azure.storage.blob.templates.enabled=${TEMPLATE_STORAGE_ENABLED:true} -spring.cloud.azure.storage.blob.templates.connection_string=${TEMPLATE_STORAGE_CONN_STRING} +spring.cloud.azure.storage.blob.templates.connection_string=${TEMPLATE_STORAGE_CONN_STRING:} spring.cloud.azure.storage.blob.templates.containerName=${TEMPLATE_STORAGE_CONTAINER_NAME:noticetemplateblob} spring.cloud.azure.storage.blob.templates.retry=${TEMPLATE_STORAGE_RETRY:3} spring.cloud.azure.storage.blob.templates.timeout=${TEMPLATE_STORAGE_TIMEOUT:10} # EH Kafka Configuration spring.cloud.function.definition=noticeGeneration -spring.cloud.stream.bindings.noticeGeneration-out-0.destination=${KAFKA_NOTICE_GENERATION_TOPIC} +spring.cloud.stream.bindings.noticeGeneration-out-0.destination=${KAFKA_NOTICE_GENERATION_TOPIC:pagopa-notice-evt-rx} spring.cloud.stream.bindings.noticeGeneration-out-0.content-type=${KAFKA_CONTENT_TYPE:application/json} spring.cloud.stream.bindings.noticeGeneration-out-0.binder=notice-generation spring.cloud.stream.binders.notice-generation.type=kafka -spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.brokers=${KAFKA_BROKER:} +spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.brokers=${KAFKA_BROKER:localhost:9092} spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.configuration.sasl.jaas.config=${KAFKA_SASL_JAAS_CONFIG:} spring.cloud.stream.binders.notice-generation.environment.spring.cloud.stream.kafka.binder.configuration.key.serializer=org.apache.kafka.common.serialization.StringSerializer spring.cloud.stream.kafka.binder.auto-create-topics=false @@ -61,12 +61,12 @@ spring.cloud.stream.kafka.binder.configuration.connections.max.idle.ms=${KAFKA_C spring.cloud.stream.kafka.binder.configuration.metadata.max.idle.ms=${KAFKA_CONFIG_METADATA_MAX_IDLE_MS:180000} spring.cloud.stream.kafka.binder.configuration.metadata.max.age.ms=${KAFKA_CONFIG_METADATA_MAX_AGE_INTERVAL:179000} spring.cloud.stream.kafka.binder.configuration.max.request.size=${KAFKA_CONFIG_METADATA_MAX_REQUEST_SIZE:1000000} -spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.client.id=noticeGenProcessor -spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.connections.max.idle.ms=${KAFKA_REWARD_RESPONSE_CONNECTION_MAX_IDLE_TIME:180000} -spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.retry.backoff.ms=${KAFKA_REWARD_RESPONSE_KAFKA_RETRY_MS:${KAFKA_RETRY_MS:10000}} -spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.linger.ms=${KAFKA_REWARD_RESPONSE_LINGER_MS:${KAFKA_LINGER_MS:2}} -spring.cloud.stream.kafka.binders.noticeGeneration-out-0.producer.configuration.batch.size=${KAFKA_REWARD_RESPONSE_BATCH_SIZE:${KAFKA_BATCH_SIZE:16384}} +spring.cloud.stream.kafka.bindings.noticeGeneration-out-0.producer.configuration.client.id=noticeGenProducer +spring.cloud.stream.kafka.bindings.noticeGeneration-out-0.producer.configuration.connections.max.idle.ms=${KAFKA_REWARD_RESPONSE_CONNECTION_MAX_IDLE_TIME:180000} +spring.cloud.stream.kafka.bindings.noticeGeneration-out-0.producer.configuration.retry.backoff.ms=${KAFKA_REWARD_RESPONSE_KAFKA_RETRY_MS:${KAFKA_RETRY_MS:10000}} +spring.cloud.stream.kafka.bindings.noticeGeneration-out-0.producer.configuration.linger.ms=${KAFKA_REWARD_RESPONSE_LINGER_MS:${KAFKA_LINGER_MS:2}} +spring.cloud.stream.kafka.bindings.noticeGeneration-out-0.producer.configuration.batch.size=${KAFKA_REWARD_RESPONSE_BATCH_SIZE:${KAFKA_BATCH_SIZE:16384}} #Other Configs -aes.secret.key=${AES_SECRET_KEY} -aes.salt=${AES_SALT} +aes.secret.key=${AES_SECRET_KEY:} +aes.salt=${AES_SALT:} From 8dd1b32342075b44e4e64e70afe5010a68b73552 Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Mon, 22 Apr 2024 12:15:59 +0200 Subject: [PATCH 08/12] [VAS-828] feat: Updated openapi and helm charts --- helm/values-dev.yaml | 2 ++ helm/values-prod.yaml | 2 ++ helm/values-uat.yaml | 2 ++ openapi/openapi.json | 6 +++--- .../service/controller/GenerationRequestController.java | 4 ++-- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index 0a14dc15..c4f5b85c 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -79,12 +79,14 @@ microservice-chart: OTEL_METRICS_EXPORTER: otlp OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" + KAFKA_BROKER: "pagopa-d-printit-evh-ns01.servicebus.windows.net:9092" envSecret: # required APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-d-connection-string' OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token MONGODB_CONNECTION_URI: 'notices_mongo_connection_string' TEMPLATE_STORAGE_CONN_STRING: 'templates_storage_account_connection_string' + KAFKA_SASL_JAAS_CONFIG: 'ehub_notice_connection_string' keyvault: name: "pagopa-d-itn-printit-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index 6c855f4a..d0e0b045 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -79,12 +79,14 @@ microservice-chart: OTEL_METRICS_EXPORTER: otlp OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" + KAFKA_BROKER: "pagopa-p-printit-evh-ns01.servicebus.windows.net:9092" envSecret: # required APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-p-connection-string' OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token MONGODB_CONNECTION_URI: 'notices_mongo_connection_string' TEMPLATE_STORAGE_CONN_STRING: 'templates_storage_account_connection_string' + KAFKA_SASL_JAAS_CONFIG: 'ehub_notice_connection_string' keyvault: name: "pagopa-p-itn-printit-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index 60050398..47121b5e 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -78,12 +78,14 @@ microservice-chart: OTEL_METRICS_EXPORTER: otlp OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" + KAFKA_BROKER: "pagopa-u-printit-evh-ns01.servicebus.windows.net:9092" envSecret: # required APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-u-connection-string' OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token MONGODB_CONNECTION_URI: 'notices_mongo_connection_string' TEMPLATE_STORAGE_CONN_STRING: 'templates_storage_account_connection_string' + KAFKA_SASL_JAAS_CONFIG: 'ehub_notice_connection_string' keyvault: name: "pagopa-u-itn-printit-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" diff --git a/openapi/openapi.json b/openapi/openapi.json index 3d9461a0..7c201612 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -16,7 +16,7 @@ "tags" : [ "Notice Generation Request APIs" ], "summary" : "getFolderStatus", "description" : "Return generation request status for a folder of notices", - "operationId" : "getFolderStatus_1", + "operationId" : "getFolderStatus", "parameters" : [ { "name" : "folder_id", "in" : "path", @@ -157,9 +157,9 @@ "/notices/generate-massive" : { "post" : { "tags" : [ "Notice Generation Request APIs" ], - "summary" : "generateMassiveNotice", + "summary" : "generateNoticeMassiveRequest", "description" : "Insert massive notice generation request and returns folderId for reference an future recovery", - "operationId" : "getFolderStatus", + "operationId" : "generateNoticeMassiveRequest", "parameters" : [ { "name" : "X-User-Id", "in" : "header", diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java index 94d04020..24c89869 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/controller/GenerationRequestController.java @@ -83,7 +83,7 @@ public GetGenerationRequestStatusResource getFolderStatus( * the folder is executed * @return folderId produced when inserting the request */ - @Operation(summary = "generateMassiveNotice", + @Operation(summary = "generateNoticeMassiveRequest", description = "Insert massive notice generation request and returns folderId for reference an" + " future recovery", security = {@SecurityRequirement(name = "ApiKey")}) @@ -107,7 +107,7 @@ public GetGenerationRequestStatusResource getFolderStatus( schema = @Schema(implementation = ProblemJson.class))) }) @PostMapping("/generate-massive") - public String getFolderStatus( + public String generateNoticeMassiveRequest( @Parameter(description = "massive notice generation request data") @Valid @NotNull @RequestBody NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, @Parameter(description = "userId to use for request status retrieval") From 878b58e762fee6394504cbce772e563d1e9a0b36 Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Tue, 23 Apr 2024 16:34:22 +0200 Subject: [PATCH 09/12] [VAS-828] feat: fixed errors --- .../java/it/gov/pagopa/payment/notices/service/Application.java | 1 - .../service/service/impl/NoticeGenerationServiceImpl.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/Application.java b/src/main/java/it/gov/pagopa/payment/notices/service/Application.java index abf279eb..d32b184b 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/Application.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/Application.java @@ -2,7 +2,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.kafka.test.context.EmbeddedKafka; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java index 137e00e6..5e565b9e 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/service/impl/NoticeGenerationServiceImpl.java @@ -100,7 +100,7 @@ public String generateMassive(NoticeGenerationMassiveRequest noticeGenerationMas } @Async - private void sendNotices(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String folderId) { + public void sendNotices(NoticeGenerationMassiveRequest noticeGenerationMassiveRequest, String folderId) { noticeGenerationMassiveRequest.getNotices().parallelStream().forEach(noticeGenerationRequestItem -> { try { if (!noticeGenerationRequestProducer.noticeGeneration( From 737332b02ae8bda4470c1c1dc92aa9f5bca50428 Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Tue, 23 Apr 2024 16:47:35 +0200 Subject: [PATCH 10/12] [VAS-828] feat: Updated code_review, removed unused method --- .github/workflows/code_review.yml | 2 +- .../notices/service/util/Aes256Utils.java | 26 ------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 0100c815..9f4228b4 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -40,7 +40,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} sonar_token: ${{ secrets.SONAR_TOKEN }} project_key: ${{env.PROJECT_KEY}} - coverage_exclusions: "**/config/*,**/*Mock*,**/model/**,**/entity/*" + coverage_exclusions: "**/config/*,**/*Mock*,**/model/**,**/entity/*,**/exception/*" cpd_exclusions: "**/model/**,**/entity/*" java_version: 17 diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java b/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java index a043ed72..2dbb6765 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/util/Aes256Utils.java @@ -68,31 +68,5 @@ public String encrypt(String strToEncrypt) throws Aes256Exception { throw new Aes256Exception("Unexpected error when encrypting the given string", AES_UNEXPECTED_ERROR, e); } } - - public String decrypt(String strToDecrypt) throws Aes256Exception { - try{ - byte[] encryptedData = Base64.getDecoder().decode(strToDecrypt); - byte[] iv = new byte[16]; - System.arraycopy(encryptedData, 0, iv, 0, iv.length); - IvParameterSpec ivspec = new IvParameterSpec(iv); - - SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA_256); - KeySpec spec = new PBEKeySpec(aesSecretKey.toCharArray(), aesSalt.getBytes(), ITERATION_COUNT, KEY_LENGTH); - SecretKey tmp = factory.generateSecret(spec); - SecretKeySpec secretKeySpec = new SecretKeySpec(tmp.getEncoded(), ALGORITHM); - - //Padding vulnerability rule java:S5542 ignored because decryption is used inside application workflow - Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING); - cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivspec); - - byte[] cipherText = new byte[encryptedData.length - 16]; - System.arraycopy(encryptedData, 16, cipherText, 0, cipherText.length); - - byte[] decryptedText = cipher.doFinal(cipherText); - return new String(decryptedText, StandardCharsets.UTF_8); - } catch (Exception e) { - throw new Aes256Exception("Unexpected error when decrypting the given string", AES_UNEXPECTED_ERROR, e); - } - } } From 4fb1c6cbf862b9e514204304136a00ff049c9626 Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Tue, 23 Apr 2024 17:04:08 +0200 Subject: [PATCH 11/12] [VAS-828] feat: Updated Aes256Exception, CreditorInstitution --- .../payment/notices/service/exception/Aes256Exception.java | 4 ++-- .../notices/service/model/notice/CreditorInstitution.java | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java b/src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java index deabfd49..e0eb56dc 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/exception/Aes256Exception.java @@ -3,10 +3,10 @@ import lombok.Getter; /** - * Thrown in case an error occur when encrypting or decrypting a BizEvent + * Thrown in case an error occur when encrypting or decrypting Notice Data */ @Getter -public class Aes256Exception extends Exception{ +public class Aes256Exception extends Exception { private final int statusCode; /** diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java index b360e454..5ebfc10e 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/CreditorInstitution.java @@ -15,6 +15,8 @@ public class CreditorInstitution { @NotNull @NotEmpty private String fullName; + + @Schema(description = "CI organization unit managing the payment ") private String organization; @Schema(description = "CI info", requiredMode = Schema.RequiredMode.REQUIRED) From 04d570f158cb3480671af56e66dbca28ebbcccf8 Mon Sep 17 00:00:00 2001 From: Alessio Cialini Date: Tue, 23 Apr 2024 17:26:10 +0200 Subject: [PATCH 12/12] [VAS-828] feat: Updated Debtor to exclude pii --- .../pagopa/payment/notices/service/model/notice/Debtor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java index 3a0152ad..8cd3bba1 100644 --- a/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java +++ b/src/main/java/it/gov/pagopa/payment/notices/service/model/notice/Debtor.java @@ -3,15 +3,19 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import lombok.ToString; public class Debtor { + @ToString.Exclude @Schema(description = "Debtor taxCode") private String taxCode; @Schema(description = "Debtor full name", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty + @ToString.Exclude private String fullName; + @ToString.Exclude @Schema(description = "Debtor address", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull @NotEmpty