From 3547363d4131e60fbe930a7b1af45d4f5bd924cc Mon Sep 17 00:00:00 2001 From: Francesco Parisi Date: Thu, 8 Feb 2024 01:17:41 +0100 Subject: [PATCH] NOD-690: wip --- openapi/openapi.json | 141 +++++++++++++++++- .../cfg_sync/client/ApiConfigCacheClient.java | 18 +++ .../cfg_sync/client/StandInManagerClient.java | 19 +++ .../config/EventHubClientConfiguration.java | 82 ++++++++++ .../controller/SyncCacheController.java | 101 +++++++++++++ .../node/cfg_sync/model/RefreshResponse.java | 20 +++ .../cfg_sync/model/TargetRefreshEnum.java | 13 ++ .../repository/CacheNodoPNexiRepository.java | 8 + .../config/NodoONexiConfiguration.java | 72 +++++++++ .../config/NodoPagoPAPConfiguration.java | 76 ++++++++++ .../node/cfg_sync/repository/model/Cache.java | 27 +--- .../model/nexi/CacheNexiOracle.java | 32 ++++ .../repository/model/pagopa/CachePagoPA.java | 32 ++++ .../nexi/CacheNodoONexiRepository.java | 8 + .../pagopa/CacheNodoPagoPAPRepository.java | 8 + .../service/ApiConfigCacheService.java | 86 +++++++++++ .../node/cfg_sync/service/CacheService.java | 9 ++ .../cfg_sync/service/CacheServiceFactory.java | 32 ++++ .../cfg_sync/service/CommonCacheService.java | 44 ++++++ .../service/StandInManagerCacheService.java | 51 +++++++ .../gov/pagopa/node/cfg_sync/util/Utils.java | 27 ++++ 21 files changed, 878 insertions(+), 28 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/client/ApiConfigCacheClient.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/client/StandInManagerClient.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/config/EventHubClientConfiguration.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/controller/SyncCacheController.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/model/RefreshResponse.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/model/TargetRefreshEnum.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/repository/CacheNodoPNexiRepository.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/repository/config/NodoONexiConfiguration.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/repository/config/NodoPagoPAPConfiguration.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/nexi/CacheNexiOracle.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/pagopa/CachePagoPA.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/repository/nexi/CacheNodoONexiRepository.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/repository/pagopa/CacheNodoPagoPAPRepository.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/service/ApiConfigCacheService.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/service/CacheService.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/service/CacheServiceFactory.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/service/CommonCacheService.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/service/StandInManagerCacheService.java create mode 100644 src/main/java/it/gov/pagopa/node/cfg_sync/util/Utils.java diff --git a/openapi/openapi.json b/openapi/openapi.json index 40f63b3..d0d4522 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -1,7 +1,7 @@ { "openapi" : "3.0.1", "info" : { - "title" : "job-manager", + "title" : "cfg-sync", "description" : "Microservice to update configuration schema of Nodo dei Pagamenti", "termsOfService" : "https://www.pagopa.gov.it/", "version" : "0.0.0" @@ -10,8 +10,145 @@ "url" : "http://localhost", "description" : "Generated server url" } ], - "paths" : { }, + "paths" : { + "/sync/v1/{target}" : { + "get" : { + "tags" : [ "Cache" ], + "summary" : "Sync target v1 config", + "operationId" : "cache", + "parameters" : [ { + "name" : "target", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "CONFIG", "STANDIN" ] + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { } + } + }, + "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", + "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" + } + } ] + } + }, "components" : { + "schemas" : { + "ProblemJson" : { + "type" : "object", + "properties" : { + "title" : { + "type" : "string", + "description" : "A short, summary of the problem type. Written in english and readable for engineers (usually not suited for non technical stakeholders and not localized); example: Service Unavailable" + }, + "status" : { + "maximum" : 600, + "minimum" : 100, + "type" : "integer", + "description" : "The HTTP status code generated by the origin server for this occurrence of the problem.", + "format" : "int32", + "example" : 200 + }, + "detail" : { + "type" : "string", + "description" : "A human readable explanation specific to this occurrence of the problem.", + "example" : "There was an error processing the request" + } + } + } + }, "securitySchemes" : { "ApiKey" : { "type" : "apiKey", diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/client/ApiConfigCacheClient.java b/src/main/java/it/gov/pagopa/node/cfg_sync/client/ApiConfigCacheClient.java new file mode 100644 index 0000000..5a53457 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/client/ApiConfigCacheClient.java @@ -0,0 +1,18 @@ +package it.gov.pagopa.node.cfg_sync.client; + +import feign.Headers; +import feign.Param; +import feign.RequestLine; +import feign.Response; +import org.springframework.stereotype.Service; + +@Service +public interface ApiConfigCacheClient { + + @RequestLine("GET /cache") + @Headers({ + "Ocp-Apim-Subscription-Key: {subscriptionKey}" + }) + Response getCache(@Param String subscriptionKey); + +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/client/StandInManagerClient.java b/src/main/java/it/gov/pagopa/node/cfg_sync/client/StandInManagerClient.java new file mode 100644 index 0000000..3cec05f --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/client/StandInManagerClient.java @@ -0,0 +1,19 @@ +package it.gov.pagopa.node.cfg_sync.client; + +import feign.Headers; +import feign.Param; +import feign.RequestLine; +import feign.Response; +import org.springframework.stereotype.Service; + +@Service +public interface StandInManagerClient { + + @RequestLine("GET /cache/refresh") + @Headers({ + "Content-Type: application/json", + "Ocp-Apim-Subscription-Key: {subscriptionKey}" + }) + Response refresh(@Param String subscriptionKey); + +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/config/EventHubClientConfiguration.java b/src/main/java/it/gov/pagopa/node/cfg_sync/config/EventHubClientConfiguration.java new file mode 100644 index 0000000..1dcd163 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/config/EventHubClientConfiguration.java @@ -0,0 +1,82 @@ +//package it.gov.pagopa.node.cfg_sync.config; +// +//import com.azure.identity.DefaultAzureCredentialBuilder; +//import com.azure.messaging.eventhubs.EventHubClientBuilder; +//import com.azure.messaging.eventhubs.EventHubProducerClient; +//import com.azure.messaging.eventhubs.EventProcessorClient; +//import com.azure.messaging.eventhubs.EventProcessorClientBuilder; +//import com.azure.messaging.eventhubs.checkpointstore.blob.BlobCheckpointStore; +//import com.azure.messaging.eventhubs.models.ErrorContext; +//import com.azure.messaging.eventhubs.models.EventContext; +//import com.azure.storage.blob.BlobContainerAsyncClient; +//import com.azure.storage.blob.BlobContainerClientBuilder; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +// +//@Configuration +//public class EventHubClientConfiguration { +// +// private static final Logger LOGGER = LoggerFactory.getLogger(EventHubClientConfiguration.class); +// private static final String CONSUMER_GROUP = "$Default"; +// private static final String STORAGE_ACCOUNT_ENDPOINT = "https://pagopadapiconfigfesa.blob.core.windows.net/"; +// private static final String STORAGE_CONTAINER_NAME = "cfg-sync"; +// +// @Value("${nodo-dei-pagamenti-cache-rx-connection-string}") +// private String ndpConnectionString; +// +// @Value("${nodo-dei-pagamenti-cache-rx-name}") +// private String ndpEventHubName; +// +// @Bean +// EventHubClientBuilder eventHubClientBuilder() { +// return new EventHubClientBuilder().credential(ndpConnectionString, ndpEventHubName, +// new DefaultAzureCredentialBuilder() +// .build()); +// } +// +// @Bean +// BlobContainerClientBuilder blobContainerClientBuilder() { +// return new BlobContainerClientBuilder().credential(new DefaultAzureCredentialBuilder() +// .build()) +// .endpoint(STORAGE_ACCOUNT_ENDPOINT) +// .containerName(STORAGE_CONTAINER_NAME); +// } +// +// @Bean +// BlobContainerAsyncClient blobContainerAsyncClient(BlobContainerClientBuilder blobContainerClientBuilder) { +// return blobContainerClientBuilder.buildAsyncClient(); +// } +// +// @Bean +// EventProcessorClientBuilder eventProcessorClientBuilder(BlobContainerAsyncClient blobContainerAsyncClient) { +// return new EventProcessorClientBuilder().credential(ndpConnectionString, ndpEventHubName, +// new DefaultAzureCredentialBuilder() +// .build()) +// .consumerGroup(CONSUMER_GROUP) +// .checkpointStore(new BlobCheckpointStore(blobContainerAsyncClient)) +// .processEvent(EventHubClientConfiguration::processEvent) +// .processError(EventHubClientConfiguration::processError); +// } +// +// @Bean +// EventProcessorClient eventProcessorClient(EventProcessorClientBuilder eventProcessorClientBuilder) { +// return eventProcessorClientBuilder.buildEventProcessorClient(); +// } +// +// public static void processEvent(EventContext eventContext) { +// LOGGER.info("Processing event from partition {} with sequence number {} with body: {}", +// eventContext.getPartitionContext().getPartitionId(), eventContext.getEventData().getSequenceNumber(), +// eventContext.getEventData().getBodyAsString()); +// } +// +// public static void processError(ErrorContext errorContext) { +// LOGGER.info("Error occurred in partition processor for partition {}, {}", +// errorContext.getPartitionContext().getPartitionId(), +// errorContext.getThrowable().getMessage(), +// errorContext.getThrowable()); +// } +// +//} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/controller/SyncCacheController.java b/src/main/java/it/gov/pagopa/node/cfg_sync/controller/SyncCacheController.java new file mode 100644 index 0000000..bed3563 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/controller/SyncCacheController.java @@ -0,0 +1,101 @@ +package it.gov.pagopa.node.cfg_sync.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import it.gov.pagopa.node.cfg_sync.model.ProblemJson; +import it.gov.pagopa.node.cfg_sync.model.RefreshResponse; +import it.gov.pagopa.node.cfg_sync.model.TargetRefreshEnum; +import it.gov.pagopa.node.cfg_sync.service.CacheServiceFactory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +@Slf4j +@RestController +@RequestMapping("/sync") +@Validated +public class SyncCacheController { + + @Autowired + private CacheServiceFactory cacheServiceFactory; + + + @Operation( + summary = "Sync target v1 config", + security = {@SecurityRequirement(name = "ApiKey")}, + tags = { + "Cache", + }) + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "OK", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE + )), + @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))) + }) + @GetMapping( + value = "/v1/{target}", + produces = {MediaType.APPLICATION_JSON_VALUE}) + public ResponseEntity cache(@PathVariable TargetRefreshEnum target) { + + log.debug("Sync {} configuration", target.label); + CacheServiceFactory.getService(target).syncCache(); + + String requestId = UUID.randomUUID().toString(); + ZonedDateTime timestamp = ZonedDateTime.now(); + + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.set("X-REQUEST-ID", requestId); + responseHeaders.set("X-CACHE-TIMESTAMP", DateTimeFormatter.ISO_DATE_TIME.format(timestamp)); + + return ResponseEntity.ok() + .headers(responseHeaders) + .body(RefreshResponse.builder().timestamp(timestamp).id(requestId).build()); + } + +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/model/RefreshResponse.java b/src/main/java/it/gov/pagopa/node/cfg_sync/model/RefreshResponse.java new file mode 100644 index 0000000..7a6ba86 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/model/RefreshResponse.java @@ -0,0 +1,20 @@ +package it.gov.pagopa.node.cfg_sync.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.*; + +import javax.validation.constraints.NotNull; +import java.time.ZonedDateTime; + +@Data +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@ToString +@JsonIgnoreProperties(ignoreUnknown = true) +public class RefreshResponse { + + @NotNull private String id; +// @NotNull private String version; + @NotNull private ZonedDateTime timestamp; +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/model/TargetRefreshEnum.java b/src/main/java/it/gov/pagopa/node/cfg_sync/model/TargetRefreshEnum.java new file mode 100644 index 0000000..0e7eb46 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/model/TargetRefreshEnum.java @@ -0,0 +1,13 @@ +package it.gov.pagopa.node.cfg_sync.model; + +public enum TargetRefreshEnum { + + config("api-config-cache"), + standin("stand-in-manager"); + + public final String label; + + private TargetRefreshEnum(String label) { + this.label = label; + } +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/repository/CacheNodoPNexiRepository.java b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/CacheNodoPNexiRepository.java new file mode 100644 index 0000000..8983557 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/CacheNodoPNexiRepository.java @@ -0,0 +1,8 @@ +//package it.gov.pagopa.node.cfg_sync.repository; +// +//import it.gov.pagopa.node.cfg_sync.repository.model.Cache; +//import org.springframework.data.jpa.repository.JpaRepository; +//import org.springframework.stereotype.Repository; +// +//@Repository +//public interface CacheNodoPNexiRepository extends JpaRepository { } diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/repository/config/NodoONexiConfiguration.java b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/config/NodoONexiConfiguration.java new file mode 100644 index 0000000..3fcb751 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/config/NodoONexiConfiguration.java @@ -0,0 +1,72 @@ +package it.gov.pagopa.node.cfg_sync.repository.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.*; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; + +import javax.sql.DataSource; +import java.util.HashMap; + +@Configuration +@PropertySources({ + @PropertySource("classpath:/application.properties"), + @PropertySource(value = "classpath:/application-${spring.profiles.active}.properties", ignoreResourceNotFound = true) +}) +@EnableJpaRepositories( + basePackages = "it.gov.pagopa.node.cfg_sync.repository.CacheNodoONexiRepository", + entityManagerFactoryRef = "nodoNexiOEntityManager", + transactionManagerRef = "nodoNexiOTransactionManager" +) +public class NodoONexiConfiguration { + @Autowired + private Environment env; + + @Bean + public LocalContainerEntityManagerFactoryBean nodoNexiOEntityManager() { + LocalContainerEntityManagerFactoryBean em + = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(nodoNexiODataSource()); + em.setPackagesToScan("it.gov.pagopa.node.cfg_sync.repository.model.CacheNexiO"); + + HibernateJpaVendorAdapter vendorAdapter + = new HibernateJpaVendorAdapter(); + em.setJpaVendorAdapter(vendorAdapter); + HashMap properties = new HashMap<>(); + properties.put("hibernate.hbm2ddl.auto", + env.getProperty("hibernate.hbm2ddl.auto")); + properties.put("hibernate.dialect", + env.getProperty("hibernate.dialect")); + em.setJpaPropertyMap(properties); + + return em; + } + + @Bean + public DataSource nodoNexiODataSource() { + DriverManagerDataSource dataSource + = new DriverManagerDataSource(); + dataSource.setDriverClassName( + env.getProperty("db.nodo.nexi.oracle.datasource.driverClassName")); + dataSource.setUrl(env.getProperty("db.nodo.nexi.oracle.datasource.url")); + dataSource.setUsername(env.getProperty("db.nodo.nexi.oracle.datasource.username")); + dataSource.setPassword(env.getProperty("db.nodo.nexi.oracle.datasource.password")); + + return dataSource; + } + + @Bean + public PlatformTransactionManager nodoNexiOTransactionManager() { + JpaTransactionManager transactionManager + = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory( + nodoNexiOEntityManager().getObject()); + + return transactionManager; + } +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/repository/config/NodoPagoPAPConfiguration.java b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/config/NodoPagoPAPConfiguration.java new file mode 100644 index 0000000..c01da37 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/config/NodoPagoPAPConfiguration.java @@ -0,0 +1,76 @@ +package it.gov.pagopa.node.cfg_sync.repository.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.*; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; + +import javax.sql.DataSource; +import java.util.HashMap; + +@Configuration +@PropertySources({ + @PropertySource("classpath:/application.properties"), + @PropertySource(value = "classpath:/application-${spring.profiles.active}.properties", ignoreResourceNotFound = true) +}) +@EnableJpaRepositories( + basePackages = "it.gov.pagopa.node.cfg_sync.repository.CacheNodoPagoPAPRepository", + entityManagerFactoryRef = "nodoPagoPAPEntityManager", + transactionManagerRef = "nodoPagoPAPTransactionManager" +) +public class NodoPagoPAPConfiguration { + + @Autowired + private Environment env; + + @Bean + @Primary + public LocalContainerEntityManagerFactoryBean nodoPagoPAPEntityManager() { + LocalContainerEntityManagerFactoryBean em + = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(nodoPagoPAPDataSource()); + em.setPackagesToScan("it.gov.pagopa.node.cfg_sync.repository.model.CachePagoPAP"); + + HibernateJpaVendorAdapter vendorAdapter + = new HibernateJpaVendorAdapter(); + em.setJpaVendorAdapter(vendorAdapter); + HashMap properties = new HashMap<>(); + properties.put("hibernate.hbm2ddl.auto", + env.getProperty("hibernate.hbm2ddl.auto")); + properties.put("hibernate.dialect", + env.getProperty("hibernate.dialect")); + em.setJpaPropertyMap(properties); + + return em; + } + + @Primary + @Bean + public DataSource nodoPagoPAPDataSource() { + DriverManagerDataSource dataSource + = new DriverManagerDataSource(); + dataSource.setDriverClassName( + env.getProperty("db.nodo.pagopa.postgre.datasource.driverClassName")); + dataSource.setUrl(env.getProperty("db.nodo.pagopa.postgre.datasource.url")); + dataSource.setUsername(env.getProperty("db.nodo.pagopa.postgre.datasource.username")); + dataSource.setPassword(env.getProperty("db.nodo.pagopa.postgre.datasource.password")); + + return dataSource; + } + + @Primary + @Bean + public PlatformTransactionManager nodoPagoPAPTransactionManager() { + JpaTransactionManager transactionManager + = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory( + nodoPagoPAPEntityManager().getObject()); + + return transactionManager; + } +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/Cache.java b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/Cache.java index 9f762a4..0cf1482 100644 --- a/src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/Cache.java +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/Cache.java @@ -1,27 +1,2 @@ -package it.gov.pagopa.node.cfg_sync.repository.model; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import javax.persistence.Table; -import java.io.Serializable; -import java.time.LocalDateTime; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Table -public class Cache implements Serializable { - - private String id; - - private LocalDateTime time; - - private byte[] cache; - - private String version; - +package it.gov.pagopa.node.cfg_sync.repository.model;public class Cache { } diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/nexi/CacheNexiOracle.java b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/nexi/CacheNexiOracle.java new file mode 100644 index 0000000..a3ef499 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/nexi/CacheNexiOracle.java @@ -0,0 +1,32 @@ +package it.gov.pagopa.node.cfg_sync.repository.nexi.model; + +import lombok.*; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Entity +@Table(name = "cache") +public class CacheNexiOracle implements Serializable { + + @Id + @Column(name="ID", columnDefinition = "VARCHAR", length = 20) + private String id; + + private LocalDateTime time; + + private byte[] cache; + + @Column(name="VERSION", columnDefinition = "VARCHAR", length = 32) + private String version; + +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/pagopa/CachePagoPA.java b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/pagopa/CachePagoPA.java new file mode 100644 index 0000000..a5e51ce --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/model/pagopa/CachePagoPA.java @@ -0,0 +1,32 @@ +package it.gov.pagopa.node.cfg_sync.repository.pagopa.model; + +import lombok.*; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Entity +@Table(name = "cache") +public class CachePagoPA implements Serializable { + + @Id + @Column(name="ID", columnDefinition = "VARCHAR", length = 20) + private String id; + + private LocalDateTime time; + + private byte[] cache; + + @Column(name="VERSION", columnDefinition = "VARCHAR", length = 32) + private String version; + +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/repository/nexi/CacheNodoONexiRepository.java b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/nexi/CacheNodoONexiRepository.java new file mode 100644 index 0000000..af95ed3 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/nexi/CacheNodoONexiRepository.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.node.cfg_sync.repository; + +import it.gov.pagopa.node.cfg_sync.repository.model.CacheNexiOracle; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CacheNodoONexiRepository extends JpaRepository { } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/repository/pagopa/CacheNodoPagoPAPRepository.java b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/pagopa/CacheNodoPagoPAPRepository.java new file mode 100644 index 0000000..04ef356 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/repository/pagopa/CacheNodoPagoPAPRepository.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.node.cfg_sync.repository; + +import it.gov.pagopa.node.cfg_sync.repository.model.CachePagoPA; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CacheNodoPagoPAPRepository extends JpaRepository { } diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/service/ApiConfigCacheService.java b/src/main/java/it/gov/pagopa/node/cfg_sync/service/ApiConfigCacheService.java new file mode 100644 index 0000000..d27c9da --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/service/ApiConfigCacheService.java @@ -0,0 +1,86 @@ +package it.gov.pagopa.node.cfg_sync.service; + +import feign.Feign; +import feign.FeignException; +import feign.Response; +import it.gov.pagopa.node.cfg_sync.client.ApiConfigCacheClient; +import it.gov.pagopa.node.cfg_sync.exception.AppError; +import it.gov.pagopa.node.cfg_sync.exception.AppException; +import it.gov.pagopa.node.cfg_sync.model.TargetRefreshEnum; +import it.gov.pagopa.node.cfg_sync.repository.CacheNodoPagoPAPRepository; +import it.gov.pagopa.node.cfg_sync.repository.model.Cache; +import it.gov.pagopa.node.cfg_sync.repository.model.CachePagoPAP; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.Map; + +@Component +@Slf4j +public class ApiConfigCacheService extends CommonCacheService implements CacheService { + + private static final String HEADER_CACHE_ID = "X-CACHE-ID"; + private static final String HEADER_CACHE_TIMESTAMP = "X-CACHE-TIMESTAMP"; + private static final String HEADER_CACHE_VERSION = "X-CACHE-VERSION"; + + private final ApiConfigCacheClient apiConfigCacheClient; + + @Value("${service.api-config-cache.enabled}") private boolean enabled; + @Value("${service.api-config-cache.subscriptionKey}") private String subscriptionKey; + + @Autowired + private CacheNodoPagoPAPRepository cacheNodoPagoPAPRepository; + + public ApiConfigCacheService(@Value("${service.api-config-cache.host}") String apiConfigCacheUrl) { + apiConfigCacheClient = Feign.builder().target(ApiConfigCacheClient.class, apiConfigCacheUrl); + } + + @Override + public TargetRefreshEnum getType() { + return TargetRefreshEnum.config; + } + + @Override + @Transactional("nodoPagoPAPTransactionManager") + public void syncCache() { + try { + if( !enabled ) { + throw new AppException(AppError.SERVICE_DISABLED, getType()); + } + log.debug("SyncService api-config-cache get cache"); + Response response = apiConfigCacheClient.getCache(subscriptionKey); + int httpResponseCode = response.status(); + if (httpResponseCode != HttpStatus.OK.value()) { + log.error("SyncService api-config-cache get cache error - result: httpStatusCode[{}]", httpResponseCode); + throw new AppException(AppError.INTERNAL_SERVER_ERROR); + } + log.info("SyncService api-config-cache get cache successful"); + + Map> headers = response.headers(); + if( headers.isEmpty() ) { + log.error("SyncService api-config-cache get cache error - empty header"); + throw new AppException(AppError.INTERNAL_SERVER_ERROR); + } + String cacheId = (String) getHeaderParameter(getType(), headers, HEADER_CACHE_ID); + String cacheTimestamp = (String) getHeaderParameter(getType(), headers, HEADER_CACHE_TIMESTAMP); + String cacheVersion = (String) getHeaderParameter(getType(), headers, HEADER_CACHE_VERSION); + + CachePagoPAP cache = (CachePagoPAP) composeCache(cacheId, ZonedDateTime.parse(cacheTimestamp).toLocalDateTime(), cacheVersion, response.body().asInputStream().readAllBytes()); + cacheNodoPagoPAPRepository.save(cache); + + } catch (FeignException.GatewayTimeout e) { + log.error("SyncService api-config-cache get cache error: Gateway timeout", e); + throw new AppException(AppError.INTERNAL_SERVER_ERROR); + } catch (IOException e) { + log.error("SyncService api-config-cache get cache error", e); + throw new AppException(AppError.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/service/CacheService.java b/src/main/java/it/gov/pagopa/node/cfg_sync/service/CacheService.java new file mode 100644 index 0000000..bb458a0 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/service/CacheService.java @@ -0,0 +1,9 @@ +package it.gov.pagopa.node.cfg_sync.service; + +import it.gov.pagopa.node.cfg_sync.model.TargetRefreshEnum; + +public interface CacheService { + + TargetRefreshEnum getType(); + void syncCache(); +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/service/CacheServiceFactory.java b/src/main/java/it/gov/pagopa/node/cfg_sync/service/CacheServiceFactory.java new file mode 100644 index 0000000..ff842d1 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/service/CacheServiceFactory.java @@ -0,0 +1,32 @@ +package it.gov.pagopa.node.cfg_sync.service; + +import it.gov.pagopa.node.cfg_sync.model.TargetRefreshEnum; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class CacheServiceFactory { + + @Autowired + private List services; + + private static final Map myServiceCache = new HashMap<>(); + + @PostConstruct + public void initMyServiceCache() { + for(CacheService service : services) { + myServiceCache.put(service.getType(), service); + } + } + + public static CacheService getService(TargetRefreshEnum target) { + CacheService service = myServiceCache.get(target); + if(service == null) throw new RuntimeException("Unknown service: " + target.label); + return service; + } +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/service/CommonCacheService.java b/src/main/java/it/gov/pagopa/node/cfg_sync/service/CommonCacheService.java new file mode 100644 index 0000000..96fe949 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/service/CommonCacheService.java @@ -0,0 +1,44 @@ +package it.gov.pagopa.node.cfg_sync.service; + +import it.gov.pagopa.node.cfg_sync.exception.AppError; +import it.gov.pagopa.node.cfg_sync.exception.AppException; +import it.gov.pagopa.node.cfg_sync.model.TargetRefreshEnum; +import it.gov.pagopa.node.cfg_sync.repository.model.Cache; +import it.gov.pagopa.node.cfg_sync.util.Utils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +@Slf4j +public class CommonCacheService { + + @Value("${app.trimCacheColumn}") + private boolean trimCacheColumn; + + protected Cache composeCache(String cacheId, LocalDateTime timestamp, String cacheVersion, byte[] cache) throws IOException { + String version = trimCacheColumn ? + (String) Utils.trimValueColumn(Cache.class, "version", cacheVersion) : cacheVersion; + + return Cache + .builder() + .id(cacheId) + .time(timestamp) + .version(version) + .cache(Utils.zipContent(cache)).build(); + } + + protected Object getHeaderParameter(TargetRefreshEnum target, Map> headers, String key) { + List valueList = headers.get(key).stream().toList(); + if(valueList.isEmpty()) { + log.error("SyncService {} get cache error - empty parameter '{}'", target, key); + throw new AppException(AppError.INTERNAL_SERVER_ERROR); + } + return valueList.get(0); + } + +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/service/StandInManagerCacheService.java b/src/main/java/it/gov/pagopa/node/cfg_sync/service/StandInManagerCacheService.java new file mode 100644 index 0000000..e5e3aff --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/service/StandInManagerCacheService.java @@ -0,0 +1,51 @@ +package it.gov.pagopa.node.cfg_sync.service; + +import feign.Feign; +import feign.FeignException; +import it.gov.pagopa.node.cfg_sync.client.StandInManagerClient; +import it.gov.pagopa.node.cfg_sync.exception.AppError; +import it.gov.pagopa.node.cfg_sync.exception.AppException; +import it.gov.pagopa.node.cfg_sync.model.TargetRefreshEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class StandInManagerCacheService extends CommonCacheService implements CacheService { + + @Value("${service.stand-in-manager.enabled}") private boolean enabled; + @Value("${service.stand-in-manager.subscriptionKey}") private String subscriptionKey; + + private final StandInManagerClient standInManagerClient; + + public StandInManagerCacheService(@Value("${service.stand-in-manager.host}") String standInManagerUrl) { + standInManagerClient = Feign.builder().target(StandInManagerClient.class, standInManagerUrl); + } + + @Override + public TargetRefreshEnum getType() { + return TargetRefreshEnum.standin; + } + + @Override + public void syncCache() { + try { + if( !enabled ) { + throw new AppException(AppError.SERVICE_DISABLED, getType()); + } + log.debug("SyncService stand-in-manager get cache"); +// Response response = standInManagerClient.refresh(subscriptionKey); +// int httpResponseCode = response.status(); +// if (httpResponseCode != HttpStatus.OK.value()) { +// log.error("SyncService stand-in-manager get cache error - result: httpStatusCode[{}]", httpResponseCode); +// } else { +// log.info("SyncService stand-in-manager get cache successful"); +// } + } catch (FeignException.GatewayTimeout e) { + log.error("SyncService stand-in-manager get cache error: Gateway timeout", e); + } catch (FeignException e) { + log.error("SyncService stand-in-manager get cache error", e); + } + } +} diff --git a/src/main/java/it/gov/pagopa/node/cfg_sync/util/Utils.java b/src/main/java/it/gov/pagopa/node/cfg_sync/util/Utils.java new file mode 100644 index 0000000..259bbf5 --- /dev/null +++ b/src/main/java/it/gov/pagopa/node/cfg_sync/util/Utils.java @@ -0,0 +1,27 @@ +package it.gov.pagopa.node.cfg_sync.util; + +import javax.persistence.Column; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.DeflaterOutputStream; + +public class Utils { + + public static byte[] zipContent(byte[] datas) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DeflaterOutputStream dos = new DeflaterOutputStream(out); + dos.write(datas); + dos.close(); + return out.toByteArray(); + } + + public static Object trimValueColumn(Class clazz, String columnName, String value) { + try { + int length = clazz.getDeclaredField(columnName).getAnnotation(Column.class).length(); + return value.substring(0, length); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + +}