From 0454d547a81882988bd3fb78d4e01ff1dd35931a Mon Sep 17 00:00:00 2001 From: OleksandrBozhko Date: Thu, 26 Feb 2026 12:07:59 +0200 Subject: [PATCH 01/12] MODEXPS-302 Initial commit --- pom.xml | 39 ++++---- .../java/org/folio/de/entity/BaseJob.java | 12 +-- .../folio/de/entity/ExportConfigEntity.java | 8 +- src/main/java/org/folio/de/entity/Job.java | 6 +- .../java/org/folio/de/entity/JobCommand.java | 2 +- .../JobWithLegacyBursarParameters.java | 6 +- .../des/ModDataExportSpringApplication.java | 9 +- .../AuthorityControlJobCommandBuilder.java | 10 +- .../job/BursarFeeFinesJobCommandBuilder.java | 10 +- .../job/CirculationLogJobCommandBuilder.java | 4 +- .../job/EHoldingsJobCommandBuilder.java | 10 +- .../job/EdifactOrdersJobCommandBuilder.java | 10 +- ...ifactOrdersJobCommandSchedulerBuilder.java | 9 +- .../des/builder/job/JobCommandBuilder.java | 2 +- .../des/client/DataExportSpringClient.java | 11 +-- .../folio/des/client/ExportWorkerClient.java | 8 +- .../des/config/JacksonConfiguration.java | 76 ++++++++------- .../config/feign/CustomFeignErrorDecoder.java | 22 ----- .../feign/FeignClientConfiguration.java | 12 --- .../des/config/kafka/KafkaConfiguration.java | 16 +-- .../QuartzSchedulerFactoryBeanCustomizer.java | 2 +- .../scheduling/QuartzSchemaInitializer.java | 2 +- .../ControllerExceptionHandler.java | 4 +- .../exceptions/RestClientErrorHandler.java | 36 +++++++ .../des/mapper/BaseExportConfigMapper.java | 3 +- .../ExportConfigToBursarTriggerConverter.java | 2 +- .../org/folio/des/security/JWTokenUtils.java | 6 +- .../des/service/JobExecutionService.java | 18 ++-- .../java/org/folio/des/InstallUpgradeIT.java | 4 +- .../ModDataExportSpringApplicationTest.java | 2 +- ...AuthorityControlJobCommandBuilderTest.java | 9 +- ...tOrdersJobCommandSchedulerBuilderTest.java | 62 ++++++------ .../job/JobCommandBuilderResolverTest.java | 8 +- .../JobDeletionIntervalsControllerTest.java | 14 +-- .../des/controller/JobsControllerTest.java | 97 ++++++++----------- .../mapper/DefaultExportConfigMapperTest.java | 3 +- .../job/bursar/BursarJobKeyResolverTest.java | 4 +- .../des/service/JobExecutionServiceTest.java | 2 +- .../org/folio/des/service/JobServiceTest.java | 9 +- .../BursarExportLegacyJobServiceTest.java | 2 +- .../java/org/folio/des/support/BaseTest.java | 16 +-- .../ExportConfigValidatorResolverTest.java | 3 +- 42 files changed, 283 insertions(+), 307 deletions(-) delete mode 100644 src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java delete mode 100644 src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java create mode 100644 src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java diff --git a/pom.xml b/pom.xml index 218d5da3..d7a827e9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.3 + 4.0.2 @@ -34,10 +34,9 @@ ${project.basedir}/src/main/resources/swagger.api/export-configs.yaml ${project.basedir}/src/main/resources/swagger.api/job-deletion-intervals.yaml - 9.0.0 + 10.0.0-RC1 4.1.1 1.0.0 - 3.9.2 1.18.42 1.6.3 0.2.0 @@ -45,7 +44,7 @@ 6.2.1 - 1.20.5 + 1.21.4 2.4.0 2.27.2 5.15.0 @@ -94,7 +93,7 @@ org.folio folio-spring-cql - ${folio-spring-base.version} + 9.0.0 org.folio @@ -111,12 +110,6 @@ postgresql - - io.hypersistence - hypersistence-utils-hibernate-63 - ${hypersistence-utils-hibernate-63.version} - - org.springframework.batch spring-batch-core @@ -143,6 +136,11 @@ spring-kafka + + org.springframework.boot + spring-boot-starter-kafka + + org.springframework.security spring-security-core @@ -183,11 +181,6 @@ log4j-web - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - org.springframework.boot @@ -228,6 +221,13 @@ test + + org.springframework.boot + spring-boot-starter-webmvc-test + 4.0.2 + test + + org.testcontainers kafka @@ -257,12 +257,6 @@ mockserver-client-java ${mockserver-client-java.version} test - - - org.bouncycastle - bcprov-jdk18on - - @@ -275,6 +269,7 @@ io.rest-assured rest-assured + 6.0.0 test diff --git a/src/main/java/org/folio/de/entity/BaseJob.java b/src/main/java/org/folio/de/entity/BaseJob.java index cc85cda8..88c85606 100644 --- a/src/main/java/org/folio/de/entity/BaseJob.java +++ b/src/main/java/org/folio/de/entity/BaseJob.java @@ -1,6 +1,5 @@ package org.folio.de.entity; -import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -15,7 +14,8 @@ import org.folio.des.domain.dto.IdentifierType; import org.folio.des.domain.dto.JobStatus; import org.folio.des.domain.dto.Progress; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; @@ -42,11 +42,11 @@ public abstract class BaseJob { @Enumerated(EnumType.STRING) private JobStatus status; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private List files = null; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private List fileNames = null; @@ -73,7 +73,7 @@ public abstract class BaseJob { @Enumerated(EnumType.STRING) private BatchStatus batchStatus; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private ExitStatus exitStatus; @@ -83,7 +83,7 @@ public abstract class BaseJob { @Enumerated(EnumType.STRING) private EntityType entityType; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private Progress progress; } diff --git a/src/main/java/org/folio/de/entity/ExportConfigEntity.java b/src/main/java/org/folio/de/entity/ExportConfigEntity.java index f9b5a40c..49112f3f 100644 --- a/src/main/java/org/folio/de/entity/ExportConfigEntity.java +++ b/src/main/java/org/folio/de/entity/ExportConfigEntity.java @@ -4,9 +4,8 @@ import java.util.UUID; import org.folio.de.entity.base.AuditableEntity; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.JdbcTypeCode; -import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -14,6 +13,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; +import org.hibernate.type.SqlTypes; @Entity @Table(name = "export_config") @@ -35,7 +35,7 @@ public class ExportConfigEntity extends AuditableEntity { @Column(name = "tenant", nullable = false) private String tenant; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(name = "export_type_specific_parameters", columnDefinition = "jsonb", nullable = false) private Object exportTypeSpecificParameters; @@ -48,7 +48,7 @@ public class ExportConfigEntity extends AuditableEntity { @Column(name = "schedule_time") private String scheduleTime; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(name = "week_days", columnDefinition = "jsonb") private List weekDays; diff --git a/src/main/java/org/folio/de/entity/Job.java b/src/main/java/org/folio/de/entity/Job.java index 30a3b9cd..e5e0c9c5 100644 --- a/src/main/java/org/folio/de/entity/Job.java +++ b/src/main/java/org/folio/de/entity/Job.java @@ -1,17 +1,17 @@ package org.folio.de.entity; -import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import lombok.Data; import org.folio.des.domain.dto.ExportTypeSpecificParameters; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; @Entity @Data public class Job extends BaseJob { - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private ExportTypeSpecificParameters exportTypeSpecificParameters; } diff --git a/src/main/java/org/folio/de/entity/JobCommand.java b/src/main/java/org/folio/de/entity/JobCommand.java index 3c0a377c..74ba81c8 100644 --- a/src/main/java/org/folio/de/entity/JobCommand.java +++ b/src/main/java/org/folio/de/entity/JobCommand.java @@ -5,7 +5,7 @@ import org.folio.des.domain.dto.ExportType; import org.folio.des.domain.dto.IdentifierType; import org.folio.des.domain.dto.Progress; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.parameters.JobParameters; import java.util.UUID; diff --git a/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java b/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java index 8897ab2a..2e890598 100644 --- a/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java +++ b/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java @@ -1,20 +1,20 @@ package org.folio.de.entity.bursarlegacy; -import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; import org.folio.de.entity.BaseJob; import org.folio.des.domain.dto.ExportTypeSpecificParametersWithLegacyBursar; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; @Entity @Table(name = "job") @Data public class JobWithLegacyBursarParameters extends BaseJob { - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private ExportTypeSpecificParametersWithLegacyBursar exportTypeSpecificParameters; } diff --git a/src/main/java/org/folio/des/ModDataExportSpringApplication.java b/src/main/java/org/folio/des/ModDataExportSpringApplication.java index 23ebe405..743bde4e 100644 --- a/src/main/java/org/folio/des/ModDataExportSpringApplication.java +++ b/src/main/java/org/folio/des/ModDataExportSpringApplication.java @@ -6,12 +6,9 @@ import org.folio.de.entity.JobCommand; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.boot.persistence.autoconfigure.EntityScan; -@SpringBootApplication(exclude = BatchAutoConfiguration.class) -@EnableFeignClients +@SpringBootApplication @EntityScan(basePackageClasses = JobCommand.class) public class ModDataExportSpringApplication { public static final String SYSTEM_USER_PASSWORD = "SYSTEM_USER_PASSWORD"; @@ -19,7 +16,7 @@ public class ModDataExportSpringApplication { public static void main(String[] args) { String systemUserEnabled = Optional.ofNullable(System.getenv(SYSTEM_USER_ENABLED)).orElse("true"); - + if ( Boolean.parseBoolean(systemUserEnabled) && StringUtils.isEmpty(System.getenv(SYSTEM_USER_PASSWORD)) diff --git a/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java index d897b092..9695aa77 100644 --- a/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java @@ -1,13 +1,13 @@ package org.folio.des.builder.job; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -22,7 +22,7 @@ public JobParameters buildJobCommand(Job job) { paramsBuilder.addString("authorityControlExportConfig", objectMapper.writeValueAsString(job.getExportTypeSpecificParameters().getAuthorityControlExportConfig())); return paramsBuilder.toJobParameters(); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java index ba6f11ef..7bcf951c 100644 --- a/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java @@ -1,13 +1,13 @@ package org.folio.des.builder.job; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -23,7 +23,7 @@ public JobParameters buildJobCommand(Job job) { paramsBuilder.addString("bursarFeeFines", objectMapper.writeValueAsString(job.getExportTypeSpecificParameters().getBursarFeeFines())); return paramsBuilder.toJobParameters(); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java index 858f8149..4811cd82 100644 --- a/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java @@ -1,8 +1,8 @@ package org.folio.des.builder.job; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java index 0d9bd55d..922dff44 100644 --- a/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java @@ -1,13 +1,13 @@ package org.folio.des.builder.job; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -23,7 +23,7 @@ public JobParameters buildJobCommand(Job job) { paramsBuilder.addString("eHoldingsExportConfig", objectMapper.writeValueAsString(job.getExportTypeSpecificParameters().geteHoldingsExportConfig())); return paramsBuilder.toJobParameters(); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java index ba45d363..c9046012 100644 --- a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java @@ -1,13 +1,13 @@ package org.folio.des.builder.job; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -22,7 +22,7 @@ public JobParameters buildJobCommand(Job job) { paramsBuilder.addString("edifactOrdersExport", objectMapper.writeValueAsString(job.getExportTypeSpecificParameters().getVendorEdiOrdersExportConfig())); return paramsBuilder.toJobParameters(); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java index 18cc19e0..03cea68b 100644 --- a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java +++ b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java @@ -1,14 +1,13 @@ package org.folio.des.builder.job; import org.folio.de.entity.JobCommand; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -25,7 +24,7 @@ public JobCommand buildJobCommand(org.folio.des.domain.dto.Job job) { objectMapper.writeValueAsString(job.getExportTypeSpecificParameters().getVendorEdiOrdersExportConfig())); jobCommand.setJobParameters(paramsBuilder.toJobParameters()); return jobCommand; - } catch (JsonProcessingException e) { + } catch (JacksonException e) { throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java index 8f35bdec..b1142f27 100644 --- a/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java @@ -1,7 +1,7 @@ package org.folio.des.builder.job; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.parameters.JobParameters; @FunctionalInterface public interface JobCommandBuilder { diff --git a/src/main/java/org/folio/des/client/DataExportSpringClient.java b/src/main/java/org/folio/des/client/DataExportSpringClient.java index deba06ba..d7362d60 100644 --- a/src/main/java/org/folio/des/client/DataExportSpringClient.java +++ b/src/main/java/org/folio/des/client/DataExportSpringClient.java @@ -1,16 +1,15 @@ package org.folio.des.client; -import org.folio.des.config.feign.FeignClientConfiguration; import org.folio.des.domain.dto.Job; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.annotation.PostExchange; -@FeignClient(name = "data-export-spring", configuration = FeignClientConfiguration.class) +@HttpExchange(url = "data-export-spring") public interface DataExportSpringClient { - @PostMapping(value = "/jobs") + @PostExchange(value = "/jobs") Job upsertJob(@RequestBody Job job); - @PostMapping(value = "/jobs/send") + @PostExchange(value = "/jobs/send") void sendJob(@RequestBody Job job); } diff --git a/src/main/java/org/folio/des/client/ExportWorkerClient.java b/src/main/java/org/folio/des/client/ExportWorkerClient.java index a5ec00d2..547f9dc4 100644 --- a/src/main/java/org/folio/des/client/ExportWorkerClient.java +++ b/src/main/java/org/folio/des/client/ExportWorkerClient.java @@ -1,13 +1,13 @@ package org.folio.des.client; import org.folio.des.domain.dto.PresignedUrl; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.annotation.HttpExchange; -@FeignClient("refresh-presigned-url") +@HttpExchange("refresh-presigned-url") public interface ExportWorkerClient { - @GetMapping + @GetExchange PresignedUrl getRefreshedPresignedUrl(@RequestParam("filePath") String filePath); } diff --git a/src/main/java/org/folio/des/config/JacksonConfiguration.java b/src/main/java/org/folio/des/config/JacksonConfiguration.java index 48e2f163..ef262214 100644 --- a/src/main/java/org/folio/des/config/JacksonConfiguration.java +++ b/src/main/java/org/folio/des/config/JacksonConfiguration.java @@ -1,52 +1,52 @@ package org.folio.des.config; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; - -import io.hypersistence.utils.hibernate.type.util.ObjectMapperSupplier; -import java.io.IOException; + import java.sql.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameter; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.*; +import tools.jackson.databind.cfg.DateTimeFeature; +import tools.jackson.databind.deser.std.StdDeserializer; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; +import tools.jackson.databind.ser.std.StdSerializer; @Configuration -public class JacksonConfiguration implements ObjectMapperSupplier { +public class JacksonConfiguration { private static final ObjectMapper OBJECT_MAPPER; private static final ObjectMapper ENTITY_OBJECT_MAPPER; static { OBJECT_MAPPER = - new ObjectMapper() - .findAndRegisterModules() - .registerModule( + JsonMapper.builder() + .findAndAddModules() + .addModule( new SimpleModule() .addDeserializer(ExitStatus.class, new ExitStatusDeserializer()) .addDeserializer(JobParameter.class, new JobParameterDeserializer()) .addSerializer(UUID.class, new UUIDSerializer(UUID.class))) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); - ENTITY_OBJECT_MAPPER = OBJECT_MAPPER.copy() - .setSerializationInclusion(JsonInclude.Include.ALWAYS); + .configure(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_EMPTY)) + .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_EMPTY)) + .build(); + ENTITY_OBJECT_MAPPER = OBJECT_MAPPER.rebuild() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.ALWAYS)) + .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.ALWAYS)) + .build(); } static class ExitStatusDeserializer extends StdDeserializer { @@ -63,7 +63,7 @@ static class ExitStatusDeserializer extends StdDeserializer { } public ExitStatusDeserializer() { - this(null); + this(JavaType.class); } public ExitStatusDeserializer(Class vc) { @@ -71,8 +71,8 @@ public ExitStatusDeserializer(Class vc) { } @Override - public ExitStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - return EXIT_STATUSES.get(((JsonNode) jp.getCodec().readTree(jp)).get("exitCode").asText()); + public ExitStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException { + return EXIT_STATUSES.get(((JsonNode) jp.objectReadContext().readTree(jp)).get("exitCode").asString()); } } @@ -82,7 +82,7 @@ static class JobParameterDeserializer extends StdDeserializer> { private static final String VALUE_PARAMETER_PROPERTY = "value"; public JobParameterDeserializer() { - this(null); + this(JavaType.class); } public JobParameterDeserializer(Class vc) { @@ -90,15 +90,18 @@ public JobParameterDeserializer(Class vc) { } @Override - public JobParameter deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - JsonNode jsonNode = jp.getCodec().readTree(jp); + public JobParameter deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException { + JsonNode jsonNode = jp.objectReadContext().readTree(jp); var identifying = jsonNode.get("identifying").asBoolean(); - switch (jsonNode.get("type").asText()) { - case "STRING" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText(), String.class, identifying); - case "DATE" -> new JobParameter<>( - Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText()), Date.class, identifying); - case "LONG" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), Long.class, identifying); - case "DOUBLE" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), Double.class, identifying); + switch (jsonNode.get("type").asString()) { + case "STRING" -> new JobParameter<>("STRING", jsonNode.get(VALUE_PARAMETER_PROPERTY).asString(), + String.class, identifying); + case "DATE" -> new JobParameter<>("DATE", + Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asString()), Date.class, identifying); + case "LONG" -> new JobParameter<>("LONG", jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), + Long.class, identifying); + case "DOUBLE" -> new JobParameter<>("DOUBLE", jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), + Double.class, identifying); } return null; } @@ -110,7 +113,7 @@ public UUIDSerializer(Class t) { } @Override - public void serialize(UUID value, JsonGenerator gen, SerializerProvider provider) throws IOException { + public void serialize(UUID value, JsonGenerator gen, SerializationContext provider) throws JacksonException { gen.writeString(value.toString()); } } @@ -127,7 +130,6 @@ public ObjectMapper entityObjectMapper() { return ENTITY_OBJECT_MAPPER; } - @Override public ObjectMapper get() { return OBJECT_MAPPER; } diff --git a/src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java b/src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java deleted file mode 100644 index 315e1502..00000000 --- a/src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.folio.des.config.feign; - -import static feign.FeignException.errorStatus; - -import org.folio.spring.exception.NotFoundException; -import org.springframework.http.HttpStatus; - -import feign.Response; -import feign.codec.ErrorDecoder; - -public class CustomFeignErrorDecoder implements ErrorDecoder { - - @Override - public Exception decode(String methodKey, Response response) { - String requestUrl = response.request().url(); - if (HttpStatus.NOT_FOUND.value() == response.status()) { - return new NotFoundException(requestUrl); - } - return errorStatus(methodKey, response); - } - -} diff --git a/src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java b/src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java deleted file mode 100644 index cf72fd6c..00000000 --- a/src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.folio.des.config.feign; - -import org.springframework.context.annotation.Bean; - -import feign.codec.ErrorDecoder; - -public class FeignClientConfiguration { - @Bean - public ErrorDecoder errorDecoder() { - return new CustomFeignErrorDecoder(); - } -} diff --git a/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java b/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java index 3cbb7e68..31372e1f 100644 --- a/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java +++ b/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java @@ -1,6 +1,5 @@ package org.folio.des.config.kafka; -import com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; import lombok.RequiredArgsConstructor; @@ -10,7 +9,7 @@ import org.apache.kafka.common.serialization.StringSerializer; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; -import org.springframework.boot.autoconfigure.kafka.KafkaProperties; +import org.springframework.boot.kafka.autoconfigure.KafkaProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; @@ -19,9 +18,10 @@ import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; -import org.springframework.kafka.support.serializer.JsonDeserializer; -import org.springframework.kafka.support.serializer.JsonSerializer; +import org.springframework.kafka.support.serializer.JacksonJsonDeserializer; +import org.springframework.kafka.support.serializer.JacksonJsonSerializer; import org.springframework.stereotype.Component; +import tools.jackson.databind.json.JsonMapper; @Component @Configuration @@ -41,12 +41,12 @@ public ConcurrentKafkaListenerContainerFactory kafkaListenerConta } @Bean - public ConsumerFactory consumerFactory(ObjectMapper objectMapper, FolioModuleMetadata folioModuleMetadata) { + public ConsumerFactory consumerFactory(JsonMapper objectMapper, FolioModuleMetadata folioModuleMetadata) { Map props = new HashMap<>(kafkaProperties.buildConsumerProperties()); - var deserializer = new JsonDeserializer(objectMapper).trustedPackages("*"); + var deserializer = new JacksonJsonDeserializer(objectMapper).trustedPackages("*"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, deserializer); - props.put(JsonDeserializer.TRUSTED_PACKAGES, "*"); + props.put(JacksonJsonDeserializer.TRUSTED_PACKAGES, "*"); // props.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, KafkaConsumerInterceptor.class.getName()); props.put("folioModuleMetadata", folioModuleMetadata); return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), deserializer); @@ -57,7 +57,7 @@ public ProducerFactory producerFactory( FolioExecutionContext folioExecutionContext) { Map props = new HashMap<>(kafkaProperties.buildProducerProperties()); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JacksonJsonSerializer.class); props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, KafkaProducerInterceptor.class.getName()); props.put("folioExecutionContext", folioExecutionContext); return new DefaultKafkaProducerFactory<>(props); diff --git a/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java b/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java index ecfeedf4..bd1d65a2 100644 --- a/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java +++ b/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java @@ -1,7 +1,7 @@ package org.folio.des.config.scheduling; import org.folio.spring.config.DataSourceFolioWrapper; -import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer; +import org.springframework.boot.quartz.autoconfigure.SchedulerFactoryBeanCustomizer; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.stereotype.Component; diff --git a/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java b/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java index 6fc88a4d..41fd70df 100644 --- a/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java +++ b/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java @@ -3,7 +3,7 @@ import org.folio.spring.liquibase.FolioSpringLiquibase; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.boot.liquibase.autoconfigure.LiquibaseProperties; import org.springframework.stereotype.Component; import liquibase.exception.LiquibaseException; diff --git a/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java b/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java index 4e68b7fc..5113e095 100644 --- a/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java +++ b/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java @@ -17,9 +17,9 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import feign.FeignException; import lombok.extern.log4j.Log4j2; @RestControllerAdvice @@ -33,7 +33,7 @@ public class ControllerExceptionHandler { MissingServletRequestParameterException.class, MethodArgumentTypeMismatchException.class, MethodArgumentNotValidException.class, - FeignException.class + HttpClientErrorException.class }) @ResponseStatus(HttpStatus.BAD_REQUEST) public Errors handleIllegalArgumentException(Exception exception) { diff --git a/src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java b/src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java new file mode 100644 index 00000000..d4613f9d --- /dev/null +++ b/src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java @@ -0,0 +1,36 @@ +package org.folio.des.exceptions; + +import org.folio.spring.exception.NotFoundException; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class RestClientErrorHandler { + + public void handle(HttpRequest request, ClientHttpResponse response) throws IOException { + int status = response.getStatusCode().value(); + + if (status == 404) { + handle404(request); + } else { + handleOtherError(request, response); + } + } + + private void handle404(HttpRequest request) { + throw new NotFoundException("Not found: " + request.getURI()); + } + + private void handleOtherError(HttpRequest request, ClientHttpResponse response) { + try (var bodyIs = response.getBody()) { + var msg = new String(bodyIs.readAllBytes()); + String reason = !msg.isBlank() ? msg : "Unknown error"; + throw new RuntimeException("Error for " + request.getURI() + " because of " + reason); + } catch (IOException e) { + throw new RuntimeException("Unable to get reason for error: " + e.getMessage()); + } + } +} diff --git a/src/main/java/org/folio/des/mapper/BaseExportConfigMapper.java b/src/main/java/org/folio/des/mapper/BaseExportConfigMapper.java index 61dc28fb..a3bd10c6 100644 --- a/src/main/java/org/folio/des/mapper/BaseExportConfigMapper.java +++ b/src/main/java/org/folio/des/mapper/BaseExportConfigMapper.java @@ -13,8 +13,7 @@ import org.mapstruct.Mapping; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; - -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; @Mapper(implementationName = "DefaultExportConfigMapper", imports = {ExportConfigConstants.class, ExportTypeSpecificParameters.class, ExportTypeSpecificParametersWithLegacyBursar.class}) public abstract class BaseExportConfigMapper { diff --git a/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java b/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java index 0bbe064b..62a06c64 100644 --- a/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java +++ b/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java @@ -10,12 +10,12 @@ import java.util.List; import java.util.Optional; +import jakarta.validation.constraints.NotNull; import org.folio.des.domain.dto.ExportConfig; import org.folio.des.domain.dto.ScheduleParameters; import org.folio.des.scheduling.quartz.QuartzConstants; import org.folio.des.scheduling.quartz.converter.ScheduleParametersToTriggerConverter; import org.folio.des.scheduling.quartz.trigger.ExportTrigger; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; diff --git a/src/main/java/org/folio/des/security/JWTokenUtils.java b/src/main/java/org/folio/des/security/JWTokenUtils.java index 6de05adc..4d59363d 100644 --- a/src/main/java/org/folio/des/security/JWTokenUtils.java +++ b/src/main/java/org/folio/des/security/JWTokenUtils.java @@ -2,10 +2,10 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.experimental.UtilityClass; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -26,7 +26,7 @@ public static Optional parseToken(String token) { } } - private static UserInfo parse(String strEncoded) throws JsonProcessingException { + private static UserInfo parse(String strEncoded) throws JacksonException { byte[] decodedBytes = Base64.getDecoder().decode(strEncoded); var json = new String(decodedBytes, StandardCharsets.UTF_8); return OBJECT_MAPPER.readValue(json, UserInfo.class); diff --git a/src/main/java/org/folio/des/service/JobExecutionService.java b/src/main/java/org/folio/des/service/JobExecutionService.java index 80fc7726..e5a96a75 100644 --- a/src/main/java/org/folio/des/service/JobExecutionService.java +++ b/src/main/java/org/folio/des/service/JobExecutionService.java @@ -2,12 +2,11 @@ import java.util.Collection; import java.util.Collections; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.UUID; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.folio.de.entity.Job; @@ -20,9 +19,9 @@ import org.folio.des.domain.dto.VendorEdiOrdersExportConfig; import org.folio.des.service.config.ExportConfigService; import org.folio.des.validator.ExportConfigValidatorResolver; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.Errors; @@ -30,6 +29,7 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; +import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -55,7 +55,7 @@ public JobCommand prepareStartJobCommand(Job job) { JobParameters jobParameters = builder.buildJobCommand(job); jobCommand.setJobParameters(jobParameters); }, - () -> jobCommand.setJobParameters(new JobParameters(new HashMap<>()))); + () -> jobCommand.setJobParameters(new JobParameters(new HashSet<>()))); return jobCommand; } @@ -76,7 +76,7 @@ public JobCommand prepareResendJobCommand(Job job) { .ifPresent(vendorEdiOrdersExportConfig -> addToParamsEdiExportConfig(paramsBuilder, vendorEdiOrdersExportConfig)); Optional.ofNullable(job.getFileNames()) .ifPresent(fileNames-> - paramsBuilder.addString(FILE_NAME_KEY, fileNames.get(0))); + paramsBuilder.addString(FILE_NAME_KEY, fileNames.getFirst())); jobCommand.setJobParameters(paramsBuilder.toJobParameters()); return jobCommand; @@ -104,8 +104,8 @@ public void deleteJobs(List jobs) { var jobCommand = new JobCommand(); jobCommand.setType(JobCommand.Type.DELETE); jobCommand.setId(UUID.randomUUID()); - jobCommand.setJobParameters(new JobParameters( - Collections.singletonMap(JobParameterNames.OUTPUT_FILES_IN_STORAGE, new JobParameter<>(StringUtils.join(files, ';'), String.class)))); + jobCommand.setJobParameters(new JobParameters(Collections.singleton( + new JobParameter<>(JobParameterNames.OUTPUT_FILES_IN_STORAGE, StringUtils.join(files, ';'), String.class)))); sendJobCommand(jobCommand); } diff --git a/src/test/java/org/folio/des/InstallUpgradeIT.java b/src/test/java/org/folio/des/InstallUpgradeIT.java index 64e51c54..b6462ee8 100644 --- a/src/test/java/org/folio/des/InstallUpgradeIT.java +++ b/src/test/java/org/folio/des/InstallUpgradeIT.java @@ -1,7 +1,5 @@ package org.folio.des; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import io.restassured.RestAssured; import io.restassured.builder.RequestSpecBuilder; import io.restassured.filter.log.RequestLoggingFilter; @@ -28,6 +26,8 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.kafka.KafkaContainer; import org.testcontainers.utility.DockerImageName; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ObjectNode; import java.io.IOException; import java.nio.file.Path; diff --git a/src/test/java/org/folio/des/ModDataExportSpringApplicationTest.java b/src/test/java/org/folio/des/ModDataExportSpringApplicationTest.java index 19c87df4..b3c2072e 100644 --- a/src/test/java/org/folio/des/ModDataExportSpringApplicationTest.java +++ b/src/test/java/org/folio/des/ModDataExportSpringApplicationTest.java @@ -2,7 +2,7 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilderTest.java b/src/test/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilderTest.java index c5041cb9..0d7bdd4f 100644 --- a/src/test/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilderTest.java +++ b/src/test/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilderTest.java @@ -4,9 +4,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.folio.de.entity.Job; import org.folio.des.domain.dto.ExportTypeSpecificParameters; import org.junit.jupiter.api.Test; @@ -14,6 +11,8 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; @ExtendWith(MockitoExtension.class) class AuthorityControlJobCommandBuilderTest { @@ -24,12 +23,12 @@ class AuthorityControlJobCommandBuilderTest { @SuppressWarnings("deprecation") @Test - void shouldHandleJsonProcessingException() throws JsonProcessingException { + void shouldHandleJsonProcessingException() throws JacksonException { var params = new ExportTypeSpecificParameters(); var job = new Job(); job.setExportTypeSpecificParameters(params); - doThrow(new JsonMappingException("")).when(objectMapper).writeValueAsString(any()); + doThrow(JacksonException.class).when(objectMapper).writeValueAsString(any()); assertThrows(IllegalArgumentException.class, () -> authorityControlJobCommandBuilder.buildJobCommand(job)); } diff --git a/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java b/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java index 543e5078..6a76d121 100644 --- a/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java +++ b/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java @@ -4,19 +4,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.databind.module.SimpleModule; -import java.io.IOException; import java.sql.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; + import org.folio.de.entity.JobCommand; import org.folio.des.domain.dto.EntityType; import org.folio.des.domain.dto.ExportConfig; @@ -28,10 +20,17 @@ import org.folio.des.domain.dto.VendorEdiOrdersExportConfig; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.*; +import tools.jackson.databind.cfg.DateTimeFeature; +import tools.jackson.databind.deser.std.StdDeserializer; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; @SpringBootTest(classes = {EdifactOrdersJobCommandSchedulerBuilderTest.MockSpringContext.class}) class EdifactOrdersJobCommandSchedulerBuilderTest { @@ -65,9 +64,9 @@ void successJobCommandBuild() { job.setExportTypeSpecificParameters(parameters); JobCommand actJobCommand = builder.buildJobCommand(job); - JobParameter actJobParameter = actJobCommand.getJobParameters().getParameters().get("edifactOrdersExport"); + JobParameter actJobParameter = actJobCommand.getJobParameters().getParameter("edifactOrdersExport"); assertEquals(id, actJobCommand.getId()); - assertTrue(actJobParameter.getValue().toString().contains(vendorId.toString())); + assertTrue(actJobParameter.value().toString().contains(vendorId.toString())); } public static class MockSpringContext { @@ -75,14 +74,16 @@ public static class MockSpringContext { private static final ObjectMapper OBJECT_MAPPER; static { - OBJECT_MAPPER = new ObjectMapper().findAndRegisterModules() - .registerModule(new SimpleModule().addDeserializer(ExitStatus.class, + OBJECT_MAPPER = JsonMapper.builder().findAndAddModules() + .addModule(new SimpleModule().addDeserializer(ExitStatus.class, new EdifactOrdersJobCommandSchedulerBuilderTest.MockSpringContext.ExitStatusDeserializer()) .addDeserializer(JobParameter.class, new EdifactOrdersJobCommandSchedulerBuilderTest.MockSpringContext.JobParameterDeserializer())) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + .configure(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_EMPTY)) + .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_EMPTY)) + .build(); } @Bean @@ -109,7 +110,7 @@ static class ExitStatusDeserializer extends StdDeserializer { } public ExitStatusDeserializer() { - this(null); + this(JavaType.class); } public ExitStatusDeserializer(Class vc) { @@ -117,8 +118,8 @@ public ExitStatusDeserializer(Class vc) { } @Override - public ExitStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - return EXIT_STATUSES.get(((JsonNode) jp.getCodec().readTree(jp)).get("exitCode").asText()); + public ExitStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException { + return EXIT_STATUSES.get(((JsonNode) jp.objectReadContext().readTree(jp)).get("exitCode").asString()); } } @@ -128,7 +129,7 @@ static class JobParameterDeserializer extends StdDeserializer> { private static final String VALUE_PARAMETER_PROPERTY = "value"; public JobParameterDeserializer() { - this(null); + this(JavaType.class); } public JobParameterDeserializer(Class vc) { @@ -136,17 +137,18 @@ public JobParameterDeserializer(Class vc) { } @Override - public JobParameter deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - JsonNode jsonNode = jp.getCodec().readTree(jp); + public JobParameter deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException { + JsonNode jsonNode = jp.objectReadContext().readTree(jp); var identifying = jsonNode.get("identifying").asBoolean(); - switch (jsonNode.get("type").asText()) { - case "STRING" -> - new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText(), String.class, identifying); - case "DATE" -> new JobParameter<>( - Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText()), Date.class, identifying); - case "LONG" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), Long.class, identifying); - case "DOUBLE" -> - new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), Double.class, identifying); + switch (jsonNode.get("type").asString()) { + case "STRING" -> new JobParameter<>("STRING", jsonNode.get(VALUE_PARAMETER_PROPERTY).asString(), + String.class, identifying); + case "DATE" -> new JobParameter<>("DATE", + Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asString()), Date.class, identifying); + case "LONG" -> new JobParameter<>("LONG", jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), + Long.class, identifying); + case "DOUBLE" -> new JobParameter<>("DOUBLE", jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), + Double.class, identifying); } return null; } diff --git a/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java b/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java index 5010437d..a424eaf3 100644 --- a/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java +++ b/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java @@ -7,6 +7,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -31,15 +32,14 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.quartz.Scheduler; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; @SpringBootTest(classes = {JacksonConfiguration.class, ServiceConfiguration.class}) -@EnableAutoConfiguration(exclude = {BatchAutoConfiguration.class}) +@EnableAutoConfiguration class JobCommandBuilderResolverTest { @Autowired @@ -131,6 +131,6 @@ void shouldBeCreateJobParameters(ExportType exportType, String paramsKey) { JobParameters jobParameters = builder.get().buildJobCommand(job); - assertNotEquals("null", jobParameters.getParameters().get(paramsKey).getValue()); + assertNotEquals("null", Objects.requireNonNull(jobParameters.getParameter(paramsKey)).value()); } } diff --git a/src/test/java/org/folio/des/controller/JobDeletionIntervalsControllerTest.java b/src/test/java/org/folio/des/controller/JobDeletionIntervalsControllerTest.java index 4bcb6a56..9e4d4562 100644 --- a/src/test/java/org/folio/des/controller/JobDeletionIntervalsControllerTest.java +++ b/src/test/java/org/folio/des/controller/JobDeletionIntervalsControllerTest.java @@ -1,8 +1,5 @@ package org.folio.des.controller; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.folio.des.domain.dto.ExportType; import org.folio.des.domain.dto.delete_interval.JobDeletionInterval; import org.folio.des.support.BaseTest; @@ -10,6 +7,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.cfg.DateTimeFeature; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; import static org.folio.des.service.impl.JobDeletionIntervalServiceImpl.CREATED_BY_SYSTEM; import static org.hamcrest.Matchers.hasSize; @@ -137,9 +138,8 @@ private void assertDeleteInterval(ExportType exportType) throws Exception { } private ObjectMapper getObjectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModule(new JavaTimeModule()); - objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - return objectMapper; + return JsonMapper.builder(). + findAndAddModules().addModule(new SimpleModule()) + .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS).build(); } } diff --git a/src/test/java/org/folio/des/controller/JobsControllerTest.java b/src/test/java/org/folio/des/controller/JobsControllerTest.java index c91c35bc..29c0c0df 100644 --- a/src/test/java/org/folio/des/controller/JobsControllerTest.java +++ b/src/test/java/org/folio/des/controller/JobsControllerTest.java @@ -5,16 +5,12 @@ import static org.hamcrest.Matchers.startsWith; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.ResultMatcher.matchAll; 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.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.time.LocalDate; import java.util.UUID; import java.util.stream.Stream; @@ -37,6 +33,10 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:job.sql") @@ -67,7 +67,8 @@ class JobsControllerTest extends BaseTest { private static final String JOB_CIRCULATION_REQUEST = "{ \"type\": \"CIRCULATION_LOG\", \"exportTypeSpecificParameters\" : {}}"; - private static final ObjectMapper MAPPER = new ObjectMapper().registerModule(new JavaTimeModule()); + private static final ObjectMapper MAPPER = JsonMapper.builder().findAndAddModules() + .addModule(new SimpleModule()).build(); @Test @DisplayName("Find all jobs") @@ -77,12 +78,11 @@ void getJobs() throws Exception { get("/data-export-spring/jobs") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(8)), - jsonPath("$.jobRecords", hasSize(8)))); + jsonPath("$.jobRecords", hasSize(8))); } @Test @@ -93,12 +93,11 @@ void findSortedJobsByExportMethodName() throws Exception { get("/data-export-spring/jobs?limit=3&offset=0&query=(cql.allRecords=1)sortby jsonb.exportTypeSpecificParameters.vendorEdiOrdersExportConfig.configName/sort.descending") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(8)), - jsonPath("$.jobRecords", hasSize(3)))); + jsonPath("$.jobRecords", hasSize(3))); } @Test @@ -109,11 +108,10 @@ void notFoundJobs() throws Exception { get("/data-export-spring/jobs?limit=3&offset=0&query=!!sortby name/sort.descending") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isBadRequest(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.errors[0].message", startsWith("IllegalArgumentException")))); + jsonPath("$.errors[0].message", startsWith("IllegalArgumentException"))); } @Test @@ -124,11 +122,10 @@ void findJobsByQueryDateRange() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(endTime>=2020-12-12T00:00:00.000 and endTime<=2020-12-13T23:59:59.999) sortby name/sort.descending") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.totalRecords", is(0)))); + jsonPath("$.totalRecords", is(0))); } @Test @@ -139,12 +136,11 @@ void excludeJobById() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(id<>12ae5d0f-1525-44a1-a361-0bc9b88e8179 or name=*)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(7)), - jsonPath("$.jobRecords", hasSize(7)))); + jsonPath("$.jobRecords", hasSize(7))); } @Test @@ -155,12 +151,11 @@ void findJobsBySourceOrDesc() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(source<>data-export-system-user or description==test-desc)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(7)), - jsonPath("$.jobRecords", hasSize(7)))); + jsonPath("$.jobRecords", hasSize(7))); } @Test @@ -171,11 +166,10 @@ void findJobsByStrictDateRange() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(endTime>2020-12-12T00:00:00.000 and endTime<2020-12-13T23:59:59.999)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.totalRecords", is(0)))); + jsonPath("$.totalRecords", is(0))); } @Test @@ -186,11 +180,10 @@ void findJobsAttribute() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(metadata.endTime>2020-12-12T00:00:00.000)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isBadRequest(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.errors[0].message", startsWith("PathElementException")))); + jsonPath("$.errors[0].message", startsWith("PathElementException"))); } @Test @@ -201,13 +194,12 @@ void getJob() throws Exception { get("/data-export-spring/jobs/12ae5d0f-1525-44a1-a361-0bc9b88e8179") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.id", is("12ae5d0f-1525-44a1-a361-0bc9b88e8179")), jsonPath("$.status", is("SUCCESSFUL")), - jsonPath("$.outputFormat", is("Fees & Fines Bursar Report")))); + jsonPath("$.outputFormat", is("Fees & Fines Bursar Report"))); } @Test @@ -218,9 +210,8 @@ void shouldFailedDownloadWithNotFound() throws Exception { get("/data-export-spring/jobs/35ae5d0f-1525-42a1-a361-1bc9b88e8180/download") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( - status().is4xxClientError())); + .andExpectAll( + status().is4xxClientError()); } @Test @@ -234,9 +225,8 @@ void shouldFailedDownloadWithBadRequest() throws Exception { get("/data-export-spring/jobs/42ae5d0f-6425-82a1-a361-1bc9b88e8172/download") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( - status().is5xxServerError())); + .andExpectAll( + status().is5xxServerError()); } @Test @@ -247,11 +237,10 @@ void notFoundJob() throws Exception { get("/data-export-spring/jobs/12ae5d0f-1525-44a1-a361-0bc9b88eeeee") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isNotFound(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.errors[0].message", startsWith("NotFoundException")))); + jsonPath("$.errors[0].message", startsWith("NotFoundException"))); } @Test @@ -263,13 +252,12 @@ void postBursarJob() throws Exception { .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders()) .content(JOB_BURSAR_REQUEST)) - .andExpect( - matchAll( + .andExpectAll( status().isCreated(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.type", is("BURSAR_FEES_FINES")), jsonPath("$.status", is("SCHEDULED")), - jsonPath("$.outputFormat", is("Fees & Fines Bursar Report")))); + jsonPath("$.outputFormat", is("Fees & Fines Bursar Report"))); } @Test @@ -282,13 +270,12 @@ void postCirculationJob() throws Exception { .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders()) .content(JOB_CIRCULATION_REQUEST)) - .andExpect( - matchAll( + .andExpectAll( status().isCreated(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.type", is("CIRCULATION_LOG")), jsonPath("$.status", is("SCHEDULED")), - jsonPath("$.outputFormat", is("Comma-Separated Values (CSV)")))); + jsonPath("$.outputFormat", is("Comma-Separated Values (CSV)"))); } @ParameterizedTest @@ -385,12 +372,11 @@ void findJobsByJSONBQuery(String query) throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=" + query) .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(1)), - jsonPath("$.jobRecords", hasSize(1)))); + jsonPath("$.jobRecords", hasSize(1))); } @Test @@ -401,10 +387,9 @@ void shouldThrowExceptionIfJSONBQueryIsEmpty() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=jsonb==1 and type==\"EDIFACT_ORDERS_EXPORT\"") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isBadRequest(), - content().contentType(MediaType.APPLICATION_JSON_VALUE))); + content().contentType(MediaType.APPLICATION_JSON_VALUE)); } private static Stream getPayloadForJobWithoutRequiredParameters() { @@ -416,18 +401,18 @@ private static Stream getPayloadForJobWithoutRequiredParameters() { } private String buildAuthorityControlJobPayload(AuthorityControlExportConfig authorityControlExportConfig, - ExportType exportType) throws JsonProcessingException { + ExportType exportType) throws JacksonException { var exportTypeSpecificParameters = new ExportTypeSpecificParameters() .authorityControlExportConfig(authorityControlExportConfig); return buildJobPayload(exportType, exportTypeSpecificParameters); } - private String buildEmptyJobPayload(ExportType exportType) throws JsonProcessingException { + private String buildEmptyJobPayload(ExportType exportType) throws JacksonException { return buildJobPayload(exportType, new ExportTypeSpecificParameters()); } private String buildJobPayload(ExportType exportType, ExportTypeSpecificParameters params) - throws JsonProcessingException { + throws JacksonException { var job = new Job() .type(exportType) .exportTypeSpecificParameters(params); diff --git a/src/test/java/org/folio/des/mapper/DefaultExportConfigMapperTest.java b/src/test/java/org/folio/des/mapper/DefaultExportConfigMapperTest.java index 3bc5f9b8..f4896be2 100644 --- a/src/test/java/org/folio/des/mapper/DefaultExportConfigMapperTest.java +++ b/src/test/java/org/folio/des/mapper/DefaultExportConfigMapperTest.java @@ -15,8 +15,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; - -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; @CopilotGenerated(model = "Claude Sonnet 4.5") @SpringBootTest(classes = {JacksonConfiguration.class, DefaultExportConfigMapper.class}) diff --git a/src/test/java/org/folio/des/scheduling/quartz/job/bursar/BursarJobKeyResolverTest.java b/src/test/java/org/folio/des/scheduling/quartz/job/bursar/BursarJobKeyResolverTest.java index 5f3545c7..8a947d23 100644 --- a/src/test/java/org/folio/des/scheduling/quartz/job/bursar/BursarJobKeyResolverTest.java +++ b/src/test/java/org/folio/des/scheduling/quartz/job/bursar/BursarJobKeyResolverTest.java @@ -1,9 +1,7 @@ package org.folio.des.scheduling.quartz.job.bursar; import static org.folio.des.scheduling.quartz.QuartzConstants.BURSAR_EXPORT_GROUP_NAME; -import static org.junit.Assert.assertNull; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; import org.folio.des.domain.dto.ExportConfig; import org.folio.des.domain.dto.ExportType; diff --git a/src/test/java/org/folio/des/service/JobExecutionServiceTest.java b/src/test/java/org/folio/des/service/JobExecutionServiceTest.java index 84b06040..765ddc71 100644 --- a/src/test/java/org/folio/des/service/JobExecutionServiceTest.java +++ b/src/test/java/org/folio/des/service/JobExecutionServiceTest.java @@ -30,6 +30,6 @@ void shouldPrepareStartJobCommandWithNoJobCommandBuilder() { var command = jobExecutionService.prepareStartJobCommand(job); - assertEquals(new HashMap<>(), command.getJobParameters().getParameters()); + assertEquals(new HashMap<>(), command.getJobParameters().parameters()); } } diff --git a/src/test/java/org/folio/des/service/JobServiceTest.java b/src/test/java/org/folio/des/service/JobServiceTest.java index 712a299d..566d238d 100644 --- a/src/test/java/org/folio/des/service/JobServiceTest.java +++ b/src/test/java/org/folio/des/service/JobServiceTest.java @@ -2,7 +2,7 @@ import static org.folio.des.domain.dto.ExportType.BURSAR_FEES_FINES; import static org.folio.des.domain.dto.ExportType.CLAIMS; -import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -48,10 +48,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import com.fasterxml.jackson.databind.ObjectMapper; - import lombok.SneakyThrows; import org.springframework.test.util.ReflectionTestUtils; +import tools.jackson.databind.ObjectMapper; @ExtendWith(MockitoExtension.class) class JobServiceTest { @@ -127,8 +126,8 @@ void testResendJob() { job.setFileNames(list); internalJobService.resendExportedFile(jobDto.getId()); JobCommand command = jobExecutionService.prepareResendJobCommand(job); - Assertions.assertEquals("TestFile.csv", command.getJobParameters().getParameters().get("FILE_NAME").getValue()); - Assertions.assertNotNull(command.getJobParameters().getParameters().get("EDIFACT_ORDERS_EXPORT")); + Assertions.assertEquals("TestFile.csv", command.getJobParameters().getParameter("FILE_NAME").value()); + Assertions.assertNotNull(command.getJobParameters().getParameter("EDIFACT_ORDERS_EXPORT")); } @Test diff --git a/src/test/java/org/folio/des/service/bursarlegacy/BursarExportLegacyJobServiceTest.java b/src/test/java/org/folio/des/service/bursarlegacy/BursarExportLegacyJobServiceTest.java index 9310bfbd..2b083d81 100644 --- a/src/test/java/org/folio/des/service/bursarlegacy/BursarExportLegacyJobServiceTest.java +++ b/src/test/java/org/folio/des/service/bursarlegacy/BursarExportLegacyJobServiceTest.java @@ -1,6 +1,6 @@ package org.folio.des.service.bursarlegacy; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; import java.util.List; diff --git a/src/test/java/org/folio/des/support/BaseTest.java b/src/test/java/org/folio/des/support/BaseTest.java index 942f13b7..ec84f7f9 100644 --- a/src/test/java/org/folio/des/support/BaseTest.java +++ b/src/test/java/org/folio/des/support/BaseTest.java @@ -6,6 +6,7 @@ import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; import org.folio.spring.config.properties.FolioEnvironment; import org.folio.spring.integration.XOkapiHeaders; import org.folio.tenant.domain.dto.TenantAttributes; @@ -17,9 +18,8 @@ import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpHeaders; @@ -37,14 +37,14 @@ import org.testcontainers.junit.jupiter.Testcontainers; import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; import com.github.tomakehurst.wiremock.WireMockServer; import lombok.SneakyThrows; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}") -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ContextConfiguration(initializers = BaseTest.DockerPostgreDataSourceInitializer.class) @AutoConfigureMockMvc @Testcontainers @@ -105,9 +105,11 @@ protected static void setUpTenant(MockMvc mockMvc) { .contentType(APPLICATION_JSON)).andExpect(status().isNoContent()); } - public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().setSerializationInclusion(Include.NON_NULL) + public static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.NON_NULL)) + .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(Include.NON_NULL)) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true).build(); @SneakyThrows public static String asJsonString(Object value) { diff --git a/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java b/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java index 3eaedf2a..7634264d 100644 --- a/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java +++ b/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java @@ -15,13 +15,12 @@ import org.quartz.Scheduler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.validation.Validator; @SpringBootTest(classes = {JacksonConfiguration.class, ServiceConfiguration.class}) -@EnableAutoConfiguration(exclude = {BatchAutoConfiguration.class}) +@EnableAutoConfiguration class ExportConfigValidatorResolverTest { @Autowired From c3df587fc71ab14043f999d8a6e7a7e67f4bf3a8 Mon Sep 17 00:00:00 2001 From: OleksandrBozhko Date: Fri, 27 Feb 2026 23:10:37 +0200 Subject: [PATCH 02/12] MODEXPS-302 Reverted --- pom.xml | 39 ++++---- .../java/org/folio/de/entity/BaseJob.java | 12 +-- .../folio/de/entity/ExportConfigEntity.java | 8 +- src/main/java/org/folio/de/entity/Job.java | 6 +- .../java/org/folio/de/entity/JobCommand.java | 2 +- .../JobWithLegacyBursarParameters.java | 6 +- .../des/ModDataExportSpringApplication.java | 9 +- .../AuthorityControlJobCommandBuilder.java | 10 +- .../job/BursarFeeFinesJobCommandBuilder.java | 10 +- .../job/CirculationLogJobCommandBuilder.java | 4 +- .../job/EHoldingsJobCommandBuilder.java | 10 +- .../job/EdifactOrdersJobCommandBuilder.java | 10 +- ...ifactOrdersJobCommandSchedulerBuilder.java | 9 +- .../des/builder/job/JobCommandBuilder.java | 2 +- .../des/client/DataExportSpringClient.java | 11 ++- .../folio/des/client/ExportWorkerClient.java | 8 +- .../des/config/JacksonConfiguration.java | 76 +++++++-------- .../config/feign/CustomFeignErrorDecoder.java | 22 +++++ .../feign/FeignClientConfiguration.java | 12 +++ .../des/config/kafka/KafkaConfiguration.java | 16 +-- .../QuartzSchedulerFactoryBeanCustomizer.java | 2 +- .../scheduling/QuartzSchemaInitializer.java | 2 +- .../ControllerExceptionHandler.java | 4 +- .../exceptions/RestClientErrorHandler.java | 36 ------- .../des/mapper/BaseExportConfigMapper.java | 3 +- .../ExportConfigToBursarTriggerConverter.java | 2 +- .../org/folio/des/security/JWTokenUtils.java | 6 +- .../des/service/JobExecutionService.java | 18 ++-- .../java/org/folio/des/InstallUpgradeIT.java | 4 +- .../ModDataExportSpringApplicationTest.java | 2 +- ...AuthorityControlJobCommandBuilderTest.java | 9 +- ...tOrdersJobCommandSchedulerBuilderTest.java | 62 ++++++------ .../job/JobCommandBuilderResolverTest.java | 8 +- .../JobDeletionIntervalsControllerTest.java | 14 +-- .../des/controller/JobsControllerTest.java | 97 +++++++++++-------- .../mapper/DefaultExportConfigMapperTest.java | 3 +- .../job/bursar/BursarJobKeyResolverTest.java | 4 +- .../des/service/JobExecutionServiceTest.java | 2 +- .../org/folio/des/service/JobServiceTest.java | 9 +- .../BursarExportLegacyJobServiceTest.java | 2 +- .../java/org/folio/des/support/BaseTest.java | 16 ++- .../ExportConfigValidatorResolverTest.java | 3 +- 42 files changed, 307 insertions(+), 283 deletions(-) create mode 100644 src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java create mode 100644 src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java delete mode 100644 src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java diff --git a/pom.xml b/pom.xml index d7a827e9..218d5da3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 4.0.2 + 3.4.3 @@ -34,9 +34,10 @@ ${project.basedir}/src/main/resources/swagger.api/export-configs.yaml ${project.basedir}/src/main/resources/swagger.api/job-deletion-intervals.yaml - 10.0.0-RC1 + 9.0.0 4.1.1 1.0.0 + 3.9.2 1.18.42 1.6.3 0.2.0 @@ -44,7 +45,7 @@ 6.2.1 - 1.21.4 + 1.20.5 2.4.0 2.27.2 5.15.0 @@ -93,7 +94,7 @@ org.folio folio-spring-cql - 9.0.0 + ${folio-spring-base.version} org.folio @@ -110,6 +111,12 @@ postgresql + + io.hypersistence + hypersistence-utils-hibernate-63 + ${hypersistence-utils-hibernate-63.version} + + org.springframework.batch spring-batch-core @@ -136,11 +143,6 @@ spring-kafka - - org.springframework.boot - spring-boot-starter-kafka - - org.springframework.security spring-security-core @@ -181,6 +183,11 @@ log4j-web + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + org.springframework.boot @@ -221,13 +228,6 @@ test - - org.springframework.boot - spring-boot-starter-webmvc-test - 4.0.2 - test - - org.testcontainers kafka @@ -257,6 +257,12 @@ mockserver-client-java ${mockserver-client-java.version} test + + + org.bouncycastle + bcprov-jdk18on + + @@ -269,7 +275,6 @@ io.rest-assured rest-assured - 6.0.0 test diff --git a/src/main/java/org/folio/de/entity/BaseJob.java b/src/main/java/org/folio/de/entity/BaseJob.java index 88c85606..cc85cda8 100644 --- a/src/main/java/org/folio/de/entity/BaseJob.java +++ b/src/main/java/org/folio/de/entity/BaseJob.java @@ -1,5 +1,6 @@ package org.folio.de.entity; +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -14,8 +15,7 @@ import org.folio.des.domain.dto.IdentifierType; import org.folio.des.domain.dto.JobStatus; import org.folio.des.domain.dto.Progress; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; +import org.hibernate.annotations.Type; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; @@ -42,11 +42,11 @@ public abstract class BaseJob { @Enumerated(EnumType.STRING) private JobStatus status; - @JdbcTypeCode(SqlTypes.JSON) + @Type(JsonBinaryType.class) @Column(columnDefinition = "jsonb") private List files = null; - @JdbcTypeCode(SqlTypes.JSON) + @Type(JsonBinaryType.class) @Column(columnDefinition = "jsonb") private List fileNames = null; @@ -73,7 +73,7 @@ public abstract class BaseJob { @Enumerated(EnumType.STRING) private BatchStatus batchStatus; - @JdbcTypeCode(SqlTypes.JSON) + @Type(JsonBinaryType.class) @Column(columnDefinition = "jsonb") private ExitStatus exitStatus; @@ -83,7 +83,7 @@ public abstract class BaseJob { @Enumerated(EnumType.STRING) private EntityType entityType; - @JdbcTypeCode(SqlTypes.JSON) + @Type(JsonBinaryType.class) @Column(columnDefinition = "jsonb") private Progress progress; } diff --git a/src/main/java/org/folio/de/entity/ExportConfigEntity.java b/src/main/java/org/folio/de/entity/ExportConfigEntity.java index 49112f3f..f9b5a40c 100644 --- a/src/main/java/org/folio/de/entity/ExportConfigEntity.java +++ b/src/main/java/org/folio/de/entity/ExportConfigEntity.java @@ -4,8 +4,9 @@ import java.util.UUID; import org.folio.de.entity.base.AuditableEntity; -import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.annotations.Type; +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -13,7 +14,6 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; -import org.hibernate.type.SqlTypes; @Entity @Table(name = "export_config") @@ -35,7 +35,7 @@ public class ExportConfigEntity extends AuditableEntity { @Column(name = "tenant", nullable = false) private String tenant; - @JdbcTypeCode(SqlTypes.JSON) + @Type(JsonBinaryType.class) @Column(name = "export_type_specific_parameters", columnDefinition = "jsonb", nullable = false) private Object exportTypeSpecificParameters; @@ -48,7 +48,7 @@ public class ExportConfigEntity extends AuditableEntity { @Column(name = "schedule_time") private String scheduleTime; - @JdbcTypeCode(SqlTypes.JSON) + @Type(JsonBinaryType.class) @Column(name = "week_days", columnDefinition = "jsonb") private List weekDays; diff --git a/src/main/java/org/folio/de/entity/Job.java b/src/main/java/org/folio/de/entity/Job.java index e5e0c9c5..30a3b9cd 100644 --- a/src/main/java/org/folio/de/entity/Job.java +++ b/src/main/java/org/folio/de/entity/Job.java @@ -1,17 +1,17 @@ package org.folio.de.entity; +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import lombok.Data; import org.folio.des.domain.dto.ExportTypeSpecificParameters; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; +import org.hibernate.annotations.Type; @Entity @Data public class Job extends BaseJob { - @JdbcTypeCode(SqlTypes.JSON) + @Type(JsonBinaryType.class) @Column(columnDefinition = "jsonb") private ExportTypeSpecificParameters exportTypeSpecificParameters; } diff --git a/src/main/java/org/folio/de/entity/JobCommand.java b/src/main/java/org/folio/de/entity/JobCommand.java index 74ba81c8..3c0a377c 100644 --- a/src/main/java/org/folio/de/entity/JobCommand.java +++ b/src/main/java/org/folio/de/entity/JobCommand.java @@ -5,7 +5,7 @@ import org.folio.des.domain.dto.ExportType; import org.folio.des.domain.dto.IdentifierType; import org.folio.des.domain.dto.Progress; -import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.JobParameters; import java.util.UUID; diff --git a/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java b/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java index 2e890598..8897ab2a 100644 --- a/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java +++ b/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java @@ -1,20 +1,20 @@ package org.folio.de.entity.bursarlegacy; +import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; import org.folio.de.entity.BaseJob; import org.folio.des.domain.dto.ExportTypeSpecificParametersWithLegacyBursar; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; +import org.hibernate.annotations.Type; @Entity @Table(name = "job") @Data public class JobWithLegacyBursarParameters extends BaseJob { - @JdbcTypeCode(SqlTypes.JSON) + @Type(JsonBinaryType.class) @Column(columnDefinition = "jsonb") private ExportTypeSpecificParametersWithLegacyBursar exportTypeSpecificParameters; } diff --git a/src/main/java/org/folio/des/ModDataExportSpringApplication.java b/src/main/java/org/folio/des/ModDataExportSpringApplication.java index 743bde4e..23ebe405 100644 --- a/src/main/java/org/folio/des/ModDataExportSpringApplication.java +++ b/src/main/java/org/folio/des/ModDataExportSpringApplication.java @@ -6,9 +6,12 @@ import org.folio.de.entity.JobCommand; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.cloud.openfeign.EnableFeignClients; -@SpringBootApplication +@SpringBootApplication(exclude = BatchAutoConfiguration.class) +@EnableFeignClients @EntityScan(basePackageClasses = JobCommand.class) public class ModDataExportSpringApplication { public static final String SYSTEM_USER_PASSWORD = "SYSTEM_USER_PASSWORD"; @@ -16,7 +19,7 @@ public class ModDataExportSpringApplication { public static void main(String[] args) { String systemUserEnabled = Optional.ofNullable(System.getenv(SYSTEM_USER_ENABLED)).orElse("true"); - + if ( Boolean.parseBoolean(systemUserEnabled) && StringUtils.isEmpty(System.getenv(SYSTEM_USER_PASSWORD)) diff --git a/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java index 9695aa77..d897b092 100644 --- a/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java @@ -1,13 +1,13 @@ package org.folio.des.builder.job; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.job.parameters.JobParameters; -import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.stereotype.Service; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -22,7 +22,7 @@ public JobParameters buildJobCommand(Job job) { paramsBuilder.addString("authorityControlExportConfig", objectMapper.writeValueAsString(job.getExportTypeSpecificParameters().getAuthorityControlExportConfig())); return paramsBuilder.toJobParameters(); - } catch (JacksonException e) { + } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java index 7bcf951c..ba6f11ef 100644 --- a/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java @@ -1,13 +1,13 @@ package org.folio.des.builder.job; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.job.parameters.JobParameters; -import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.stereotype.Service; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -23,7 +23,7 @@ public JobParameters buildJobCommand(Job job) { paramsBuilder.addString("bursarFeeFines", objectMapper.writeValueAsString(job.getExportTypeSpecificParameters().getBursarFeeFines())); return paramsBuilder.toJobParameters(); - } catch (JacksonException e) { + } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java index 4811cd82..858f8149 100644 --- a/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java @@ -1,8 +1,8 @@ package org.folio.des.builder.job; import org.folio.de.entity.Job; -import org.springframework.batch.core.job.parameters.JobParameters; -import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java index 922dff44..0d9bd55d 100644 --- a/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java @@ -1,13 +1,13 @@ package org.folio.des.builder.job; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.job.parameters.JobParameters; -import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.stereotype.Service; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -23,7 +23,7 @@ public JobParameters buildJobCommand(Job job) { paramsBuilder.addString("eHoldingsExportConfig", objectMapper.writeValueAsString(job.getExportTypeSpecificParameters().geteHoldingsExportConfig())); return paramsBuilder.toJobParameters(); - } catch (JacksonException e) { + } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java index c9046012..ba45d363 100644 --- a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java @@ -1,13 +1,13 @@ package org.folio.des.builder.job; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.job.parameters.JobParameters; -import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.stereotype.Service; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -22,7 +22,7 @@ public JobParameters buildJobCommand(Job job) { paramsBuilder.addString("edifactOrdersExport", objectMapper.writeValueAsString(job.getExportTypeSpecificParameters().getVendorEdiOrdersExportConfig())); return paramsBuilder.toJobParameters(); - } catch (JacksonException e) { + } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java index 03cea68b..18cc19e0 100644 --- a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java +++ b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java @@ -1,13 +1,14 @@ package org.folio.des.builder.job; import org.folio.de.entity.JobCommand; -import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.stereotype.Service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -24,7 +25,7 @@ public JobCommand buildJobCommand(org.folio.des.domain.dto.Job job) { objectMapper.writeValueAsString(job.getExportTypeSpecificParameters().getVendorEdiOrdersExportConfig())); jobCommand.setJobParameters(paramsBuilder.toJobParameters()); return jobCommand; - } catch (JacksonException e) { + } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java index b1142f27..8f35bdec 100644 --- a/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java @@ -1,7 +1,7 @@ package org.folio.des.builder.job; import org.folio.de.entity.Job; -import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.JobParameters; @FunctionalInterface public interface JobCommandBuilder { diff --git a/src/main/java/org/folio/des/client/DataExportSpringClient.java b/src/main/java/org/folio/des/client/DataExportSpringClient.java index d7362d60..deba06ba 100644 --- a/src/main/java/org/folio/des/client/DataExportSpringClient.java +++ b/src/main/java/org/folio/des/client/DataExportSpringClient.java @@ -1,15 +1,16 @@ package org.folio.des.client; +import org.folio.des.config.feign.FeignClientConfiguration; import org.folio.des.domain.dto.Job; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.service.annotation.HttpExchange; -import org.springframework.web.service.annotation.PostExchange; -@HttpExchange(url = "data-export-spring") +@FeignClient(name = "data-export-spring", configuration = FeignClientConfiguration.class) public interface DataExportSpringClient { - @PostExchange(value = "/jobs") + @PostMapping(value = "/jobs") Job upsertJob(@RequestBody Job job); - @PostExchange(value = "/jobs/send") + @PostMapping(value = "/jobs/send") void sendJob(@RequestBody Job job); } diff --git a/src/main/java/org/folio/des/client/ExportWorkerClient.java b/src/main/java/org/folio/des/client/ExportWorkerClient.java index 547f9dc4..a5ec00d2 100644 --- a/src/main/java/org/folio/des/client/ExportWorkerClient.java +++ b/src/main/java/org/folio/des/client/ExportWorkerClient.java @@ -1,13 +1,13 @@ package org.folio.des.client; import org.folio.des.domain.dto.PresignedUrl; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.service.annotation.GetExchange; -import org.springframework.web.service.annotation.HttpExchange; -@HttpExchange("refresh-presigned-url") +@FeignClient("refresh-presigned-url") public interface ExportWorkerClient { - @GetExchange + @GetMapping PresignedUrl getRefreshedPresignedUrl(@RequestParam("filePath") String filePath); } diff --git a/src/main/java/org/folio/des/config/JacksonConfiguration.java b/src/main/java/org/folio/des/config/JacksonConfiguration.java index ef262214..48e2f163 100644 --- a/src/main/java/org/folio/des/config/JacksonConfiguration.java +++ b/src/main/java/org/folio/des/config/JacksonConfiguration.java @@ -1,52 +1,52 @@ package org.folio.des.config; import com.fasterxml.jackson.annotation.JsonInclude; - +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import io.hypersistence.utils.hibernate.type.util.ObjectMapperSupplier; +import java.io.IOException; import java.sql.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.JobParameter; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.*; -import tools.jackson.databind.cfg.DateTimeFeature; -import tools.jackson.databind.deser.std.StdDeserializer; -import tools.jackson.databind.json.JsonMapper; -import tools.jackson.databind.module.SimpleModule; -import tools.jackson.databind.ser.std.StdSerializer; @Configuration -public class JacksonConfiguration { +public class JacksonConfiguration implements ObjectMapperSupplier { private static final ObjectMapper OBJECT_MAPPER; private static final ObjectMapper ENTITY_OBJECT_MAPPER; static { OBJECT_MAPPER = - JsonMapper.builder() - .findAndAddModules() - .addModule( + new ObjectMapper() + .findAndRegisterModules() + .registerModule( new SimpleModule() .addDeserializer(ExitStatus.class, new ExitStatusDeserializer()) .addDeserializer(JobParameter.class, new JobParameterDeserializer()) .addSerializer(UUID.class, new UUIDSerializer(UUID.class))) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_EMPTY)) - .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_EMPTY)) - .build(); - ENTITY_OBJECT_MAPPER = OBJECT_MAPPER.rebuild() - .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.ALWAYS)) - .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.ALWAYS)) - .build(); + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + ENTITY_OBJECT_MAPPER = OBJECT_MAPPER.copy() + .setSerializationInclusion(JsonInclude.Include.ALWAYS); } static class ExitStatusDeserializer extends StdDeserializer { @@ -63,7 +63,7 @@ static class ExitStatusDeserializer extends StdDeserializer { } public ExitStatusDeserializer() { - this(JavaType.class); + this(null); } public ExitStatusDeserializer(Class vc) { @@ -71,8 +71,8 @@ public ExitStatusDeserializer(Class vc) { } @Override - public ExitStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException { - return EXIT_STATUSES.get(((JsonNode) jp.objectReadContext().readTree(jp)).get("exitCode").asString()); + public ExitStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + return EXIT_STATUSES.get(((JsonNode) jp.getCodec().readTree(jp)).get("exitCode").asText()); } } @@ -82,7 +82,7 @@ static class JobParameterDeserializer extends StdDeserializer> { private static final String VALUE_PARAMETER_PROPERTY = "value"; public JobParameterDeserializer() { - this(JavaType.class); + this(null); } public JobParameterDeserializer(Class vc) { @@ -90,18 +90,15 @@ public JobParameterDeserializer(Class vc) { } @Override - public JobParameter deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException { - JsonNode jsonNode = jp.objectReadContext().readTree(jp); + public JobParameter deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonNode jsonNode = jp.getCodec().readTree(jp); var identifying = jsonNode.get("identifying").asBoolean(); - switch (jsonNode.get("type").asString()) { - case "STRING" -> new JobParameter<>("STRING", jsonNode.get(VALUE_PARAMETER_PROPERTY).asString(), - String.class, identifying); - case "DATE" -> new JobParameter<>("DATE", - Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asString()), Date.class, identifying); - case "LONG" -> new JobParameter<>("LONG", jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), - Long.class, identifying); - case "DOUBLE" -> new JobParameter<>("DOUBLE", jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), - Double.class, identifying); + switch (jsonNode.get("type").asText()) { + case "STRING" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText(), String.class, identifying); + case "DATE" -> new JobParameter<>( + Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText()), Date.class, identifying); + case "LONG" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), Long.class, identifying); + case "DOUBLE" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), Double.class, identifying); } return null; } @@ -113,7 +110,7 @@ public UUIDSerializer(Class t) { } @Override - public void serialize(UUID value, JsonGenerator gen, SerializationContext provider) throws JacksonException { + public void serialize(UUID value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeString(value.toString()); } } @@ -130,6 +127,7 @@ public ObjectMapper entityObjectMapper() { return ENTITY_OBJECT_MAPPER; } + @Override public ObjectMapper get() { return OBJECT_MAPPER; } diff --git a/src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java b/src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java new file mode 100644 index 00000000..315e1502 --- /dev/null +++ b/src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java @@ -0,0 +1,22 @@ +package org.folio.des.config.feign; + +import static feign.FeignException.errorStatus; + +import org.folio.spring.exception.NotFoundException; +import org.springframework.http.HttpStatus; + +import feign.Response; +import feign.codec.ErrorDecoder; + +public class CustomFeignErrorDecoder implements ErrorDecoder { + + @Override + public Exception decode(String methodKey, Response response) { + String requestUrl = response.request().url(); + if (HttpStatus.NOT_FOUND.value() == response.status()) { + return new NotFoundException(requestUrl); + } + return errorStatus(methodKey, response); + } + +} diff --git a/src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java b/src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java new file mode 100644 index 00000000..cf72fd6c --- /dev/null +++ b/src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java @@ -0,0 +1,12 @@ +package org.folio.des.config.feign; + +import org.springframework.context.annotation.Bean; + +import feign.codec.ErrorDecoder; + +public class FeignClientConfiguration { + @Bean + public ErrorDecoder errorDecoder() { + return new CustomFeignErrorDecoder(); + } +} diff --git a/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java b/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java index 31372e1f..3cbb7e68 100644 --- a/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java +++ b/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java @@ -1,5 +1,6 @@ package org.folio.des.config.kafka; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; import lombok.RequiredArgsConstructor; @@ -9,7 +10,7 @@ import org.apache.kafka.common.serialization.StringSerializer; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; -import org.springframework.boot.kafka.autoconfigure.KafkaProperties; +import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; @@ -18,10 +19,9 @@ import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; -import org.springframework.kafka.support.serializer.JacksonJsonDeserializer; -import org.springframework.kafka.support.serializer.JacksonJsonSerializer; +import org.springframework.kafka.support.serializer.JsonDeserializer; +import org.springframework.kafka.support.serializer.JsonSerializer; import org.springframework.stereotype.Component; -import tools.jackson.databind.json.JsonMapper; @Component @Configuration @@ -41,12 +41,12 @@ public ConcurrentKafkaListenerContainerFactory kafkaListenerConta } @Bean - public ConsumerFactory consumerFactory(JsonMapper objectMapper, FolioModuleMetadata folioModuleMetadata) { + public ConsumerFactory consumerFactory(ObjectMapper objectMapper, FolioModuleMetadata folioModuleMetadata) { Map props = new HashMap<>(kafkaProperties.buildConsumerProperties()); - var deserializer = new JacksonJsonDeserializer(objectMapper).trustedPackages("*"); + var deserializer = new JsonDeserializer(objectMapper).trustedPackages("*"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, deserializer); - props.put(JacksonJsonDeserializer.TRUSTED_PACKAGES, "*"); + props.put(JsonDeserializer.TRUSTED_PACKAGES, "*"); // props.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, KafkaConsumerInterceptor.class.getName()); props.put("folioModuleMetadata", folioModuleMetadata); return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), deserializer); @@ -57,7 +57,7 @@ public ProducerFactory producerFactory( FolioExecutionContext folioExecutionContext) { Map props = new HashMap<>(kafkaProperties.buildProducerProperties()); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JacksonJsonSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, KafkaProducerInterceptor.class.getName()); props.put("folioExecutionContext", folioExecutionContext); return new DefaultKafkaProducerFactory<>(props); diff --git a/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java b/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java index bd1d65a2..ecfeedf4 100644 --- a/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java +++ b/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java @@ -1,7 +1,7 @@ package org.folio.des.config.scheduling; import org.folio.spring.config.DataSourceFolioWrapper; -import org.springframework.boot.quartz.autoconfigure.SchedulerFactoryBeanCustomizer; +import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.stereotype.Component; diff --git a/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java b/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java index 41fd70df..6fc88a4d 100644 --- a/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java +++ b/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java @@ -3,7 +3,7 @@ import org.folio.spring.liquibase.FolioSpringLiquibase; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.liquibase.autoconfigure.LiquibaseProperties; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; import org.springframework.stereotype.Component; import liquibase.exception.LiquibaseException; diff --git a/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java b/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java index 5113e095..4e68b7fc 100644 --- a/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java +++ b/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java @@ -17,9 +17,9 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import feign.FeignException; import lombok.extern.log4j.Log4j2; @RestControllerAdvice @@ -33,7 +33,7 @@ public class ControllerExceptionHandler { MissingServletRequestParameterException.class, MethodArgumentTypeMismatchException.class, MethodArgumentNotValidException.class, - HttpClientErrorException.class + FeignException.class }) @ResponseStatus(HttpStatus.BAD_REQUEST) public Errors handleIllegalArgumentException(Exception exception) { diff --git a/src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java b/src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java deleted file mode 100644 index d4613f9d..00000000 --- a/src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.folio.des.exceptions; - -import org.folio.spring.exception.NotFoundException; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -@Component -public class RestClientErrorHandler { - - public void handle(HttpRequest request, ClientHttpResponse response) throws IOException { - int status = response.getStatusCode().value(); - - if (status == 404) { - handle404(request); - } else { - handleOtherError(request, response); - } - } - - private void handle404(HttpRequest request) { - throw new NotFoundException("Not found: " + request.getURI()); - } - - private void handleOtherError(HttpRequest request, ClientHttpResponse response) { - try (var bodyIs = response.getBody()) { - var msg = new String(bodyIs.readAllBytes()); - String reason = !msg.isBlank() ? msg : "Unknown error"; - throw new RuntimeException("Error for " + request.getURI() + " because of " + reason); - } catch (IOException e) { - throw new RuntimeException("Unable to get reason for error: " + e.getMessage()); - } - } -} diff --git a/src/main/java/org/folio/des/mapper/BaseExportConfigMapper.java b/src/main/java/org/folio/des/mapper/BaseExportConfigMapper.java index a3bd10c6..61dc28fb 100644 --- a/src/main/java/org/folio/des/mapper/BaseExportConfigMapper.java +++ b/src/main/java/org/folio/des/mapper/BaseExportConfigMapper.java @@ -13,7 +13,8 @@ import org.mapstruct.Mapping; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import tools.jackson.databind.ObjectMapper; + +import com.fasterxml.jackson.databind.ObjectMapper; @Mapper(implementationName = "DefaultExportConfigMapper", imports = {ExportConfigConstants.class, ExportTypeSpecificParameters.class, ExportTypeSpecificParametersWithLegacyBursar.class}) public abstract class BaseExportConfigMapper { diff --git a/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java b/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java index 62a06c64..0bbe064b 100644 --- a/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java +++ b/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java @@ -10,12 +10,12 @@ import java.util.List; import java.util.Optional; -import jakarta.validation.constraints.NotNull; import org.folio.des.domain.dto.ExportConfig; import org.folio.des.domain.dto.ScheduleParameters; import org.folio.des.scheduling.quartz.QuartzConstants; import org.folio.des.scheduling.quartz.converter.ScheduleParametersToTriggerConverter; import org.folio.des.scheduling.quartz.trigger.ExportTrigger; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; diff --git a/src/main/java/org/folio/des/security/JWTokenUtils.java b/src/main/java/org/folio/des/security/JWTokenUtils.java index 4d59363d..6de05adc 100644 --- a/src/main/java/org/folio/des/security/JWTokenUtils.java +++ b/src/main/java/org/folio/des/security/JWTokenUtils.java @@ -2,10 +2,10 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.experimental.UtilityClass; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -26,7 +26,7 @@ public static Optional parseToken(String token) { } } - private static UserInfo parse(String strEncoded) throws JacksonException { + private static UserInfo parse(String strEncoded) throws JsonProcessingException { byte[] decodedBytes = Base64.getDecoder().decode(strEncoded); var json = new String(decodedBytes, StandardCharsets.UTF_8); return OBJECT_MAPPER.readValue(json, UserInfo.class); diff --git a/src/main/java/org/folio/des/service/JobExecutionService.java b/src/main/java/org/folio/des/service/JobExecutionService.java index e5a96a75..80fc7726 100644 --- a/src/main/java/org/folio/des/service/JobExecutionService.java +++ b/src/main/java/org/folio/des/service/JobExecutionService.java @@ -2,11 +2,12 @@ import java.util.Collection; import java.util.Collections; -import java.util.HashSet; +import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.UUID; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.folio.de.entity.Job; @@ -19,9 +20,9 @@ import org.folio.des.domain.dto.VendorEdiOrdersExportConfig; import org.folio.des.service.config.ExportConfigService; import org.folio.des.validator.ExportConfigValidatorResolver; -import org.springframework.batch.core.job.parameters.JobParameter; -import org.springframework.batch.core.job.parameters.JobParameters; -import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.stereotype.Service; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.Errors; @@ -29,7 +30,6 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; -import tools.jackson.databind.ObjectMapper; @Service @Log4j2 @@ -55,7 +55,7 @@ public JobCommand prepareStartJobCommand(Job job) { JobParameters jobParameters = builder.buildJobCommand(job); jobCommand.setJobParameters(jobParameters); }, - () -> jobCommand.setJobParameters(new JobParameters(new HashSet<>()))); + () -> jobCommand.setJobParameters(new JobParameters(new HashMap<>()))); return jobCommand; } @@ -76,7 +76,7 @@ public JobCommand prepareResendJobCommand(Job job) { .ifPresent(vendorEdiOrdersExportConfig -> addToParamsEdiExportConfig(paramsBuilder, vendorEdiOrdersExportConfig)); Optional.ofNullable(job.getFileNames()) .ifPresent(fileNames-> - paramsBuilder.addString(FILE_NAME_KEY, fileNames.getFirst())); + paramsBuilder.addString(FILE_NAME_KEY, fileNames.get(0))); jobCommand.setJobParameters(paramsBuilder.toJobParameters()); return jobCommand; @@ -104,8 +104,8 @@ public void deleteJobs(List jobs) { var jobCommand = new JobCommand(); jobCommand.setType(JobCommand.Type.DELETE); jobCommand.setId(UUID.randomUUID()); - jobCommand.setJobParameters(new JobParameters(Collections.singleton( - new JobParameter<>(JobParameterNames.OUTPUT_FILES_IN_STORAGE, StringUtils.join(files, ';'), String.class)))); + jobCommand.setJobParameters(new JobParameters( + Collections.singletonMap(JobParameterNames.OUTPUT_FILES_IN_STORAGE, new JobParameter<>(StringUtils.join(files, ';'), String.class)))); sendJobCommand(jobCommand); } diff --git a/src/test/java/org/folio/des/InstallUpgradeIT.java b/src/test/java/org/folio/des/InstallUpgradeIT.java index b6462ee8..64e51c54 100644 --- a/src/test/java/org/folio/des/InstallUpgradeIT.java +++ b/src/test/java/org/folio/des/InstallUpgradeIT.java @@ -1,5 +1,7 @@ package org.folio.des; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.restassured.RestAssured; import io.restassured.builder.RequestSpecBuilder; import io.restassured.filter.log.RequestLoggingFilter; @@ -26,8 +28,6 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.kafka.KafkaContainer; import org.testcontainers.utility.DockerImageName; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.node.ObjectNode; import java.io.IOException; import java.nio.file.Path; diff --git a/src/test/java/org/folio/des/ModDataExportSpringApplicationTest.java b/src/test/java/org/folio/des/ModDataExportSpringApplicationTest.java index b3c2072e..19c87df4 100644 --- a/src/test/java/org/folio/des/ModDataExportSpringApplicationTest.java +++ b/src/test/java/org/folio/des/ModDataExportSpringApplicationTest.java @@ -2,7 +2,7 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilderTest.java b/src/test/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilderTest.java index 0d7bdd4f..c5041cb9 100644 --- a/src/test/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilderTest.java +++ b/src/test/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilderTest.java @@ -4,6 +4,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.folio.de.entity.Job; import org.folio.des.domain.dto.ExportTypeSpecificParameters; import org.junit.jupiter.api.Test; @@ -11,8 +14,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; @ExtendWith(MockitoExtension.class) class AuthorityControlJobCommandBuilderTest { @@ -23,12 +24,12 @@ class AuthorityControlJobCommandBuilderTest { @SuppressWarnings("deprecation") @Test - void shouldHandleJsonProcessingException() throws JacksonException { + void shouldHandleJsonProcessingException() throws JsonProcessingException { var params = new ExportTypeSpecificParameters(); var job = new Job(); job.setExportTypeSpecificParameters(params); - doThrow(JacksonException.class).when(objectMapper).writeValueAsString(any()); + doThrow(new JsonMappingException("")).when(objectMapper).writeValueAsString(any()); assertThrows(IllegalArgumentException.class, () -> authorityControlJobCommandBuilder.buildJobCommand(job)); } diff --git a/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java b/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java index 6a76d121..543e5078 100644 --- a/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java +++ b/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java @@ -4,11 +4,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; import java.sql.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; - import org.folio.de.entity.JobCommand; import org.folio.des.domain.dto.EntityType; import org.folio.des.domain.dto.ExportConfig; @@ -20,17 +28,10 @@ import org.folio.des.domain.dto.VendorEdiOrdersExportConfig; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.JobParameter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.*; -import tools.jackson.databind.cfg.DateTimeFeature; -import tools.jackson.databind.deser.std.StdDeserializer; -import tools.jackson.databind.json.JsonMapper; -import tools.jackson.databind.module.SimpleModule; @SpringBootTest(classes = {EdifactOrdersJobCommandSchedulerBuilderTest.MockSpringContext.class}) class EdifactOrdersJobCommandSchedulerBuilderTest { @@ -64,9 +65,9 @@ void successJobCommandBuild() { job.setExportTypeSpecificParameters(parameters); JobCommand actJobCommand = builder.buildJobCommand(job); - JobParameter actJobParameter = actJobCommand.getJobParameters().getParameter("edifactOrdersExport"); + JobParameter actJobParameter = actJobCommand.getJobParameters().getParameters().get("edifactOrdersExport"); assertEquals(id, actJobCommand.getId()); - assertTrue(actJobParameter.value().toString().contains(vendorId.toString())); + assertTrue(actJobParameter.getValue().toString().contains(vendorId.toString())); } public static class MockSpringContext { @@ -74,16 +75,14 @@ public static class MockSpringContext { private static final ObjectMapper OBJECT_MAPPER; static { - OBJECT_MAPPER = JsonMapper.builder().findAndAddModules() - .addModule(new SimpleModule().addDeserializer(ExitStatus.class, + OBJECT_MAPPER = new ObjectMapper().findAndRegisterModules() + .registerModule(new SimpleModule().addDeserializer(ExitStatus.class, new EdifactOrdersJobCommandSchedulerBuilderTest.MockSpringContext.ExitStatusDeserializer()) .addDeserializer(JobParameter.class, new EdifactOrdersJobCommandSchedulerBuilderTest.MockSpringContext.JobParameterDeserializer())) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_EMPTY)) - .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_EMPTY)) - .build(); + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); } @Bean @@ -110,7 +109,7 @@ static class ExitStatusDeserializer extends StdDeserializer { } public ExitStatusDeserializer() { - this(JavaType.class); + this(null); } public ExitStatusDeserializer(Class vc) { @@ -118,8 +117,8 @@ public ExitStatusDeserializer(Class vc) { } @Override - public ExitStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException { - return EXIT_STATUSES.get(((JsonNode) jp.objectReadContext().readTree(jp)).get("exitCode").asString()); + public ExitStatus deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + return EXIT_STATUSES.get(((JsonNode) jp.getCodec().readTree(jp)).get("exitCode").asText()); } } @@ -129,7 +128,7 @@ static class JobParameterDeserializer extends StdDeserializer> { private static final String VALUE_PARAMETER_PROPERTY = "value"; public JobParameterDeserializer() { - this(JavaType.class); + this(null); } public JobParameterDeserializer(Class vc) { @@ -137,18 +136,17 @@ public JobParameterDeserializer(Class vc) { } @Override - public JobParameter deserialize(JsonParser jp, DeserializationContext ctxt) throws JacksonException { - JsonNode jsonNode = jp.objectReadContext().readTree(jp); + public JobParameter deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonNode jsonNode = jp.getCodec().readTree(jp); var identifying = jsonNode.get("identifying").asBoolean(); - switch (jsonNode.get("type").asString()) { - case "STRING" -> new JobParameter<>("STRING", jsonNode.get(VALUE_PARAMETER_PROPERTY).asString(), - String.class, identifying); - case "DATE" -> new JobParameter<>("DATE", - Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asString()), Date.class, identifying); - case "LONG" -> new JobParameter<>("LONG", jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), - Long.class, identifying); - case "DOUBLE" -> new JobParameter<>("DOUBLE", jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), - Double.class, identifying); + switch (jsonNode.get("type").asText()) { + case "STRING" -> + new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText(), String.class, identifying); + case "DATE" -> new JobParameter<>( + Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText()), Date.class, identifying); + case "LONG" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), Long.class, identifying); + case "DOUBLE" -> + new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), Double.class, identifying); } return null; } diff --git a/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java b/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java index a424eaf3..5010437d 100644 --- a/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java +++ b/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java @@ -7,7 +7,6 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -32,14 +31,15 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.quartz.Scheduler; -import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.JobParameters; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; @SpringBootTest(classes = {JacksonConfiguration.class, ServiceConfiguration.class}) -@EnableAutoConfiguration +@EnableAutoConfiguration(exclude = {BatchAutoConfiguration.class}) class JobCommandBuilderResolverTest { @Autowired @@ -131,6 +131,6 @@ void shouldBeCreateJobParameters(ExportType exportType, String paramsKey) { JobParameters jobParameters = builder.get().buildJobCommand(job); - assertNotEquals("null", Objects.requireNonNull(jobParameters.getParameter(paramsKey)).value()); + assertNotEquals("null", jobParameters.getParameters().get(paramsKey).getValue()); } } diff --git a/src/test/java/org/folio/des/controller/JobDeletionIntervalsControllerTest.java b/src/test/java/org/folio/des/controller/JobDeletionIntervalsControllerTest.java index 9e4d4562..4bcb6a56 100644 --- a/src/test/java/org/folio/des/controller/JobDeletionIntervalsControllerTest.java +++ b/src/test/java/org/folio/des/controller/JobDeletionIntervalsControllerTest.java @@ -1,5 +1,8 @@ package org.folio.des.controller; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.folio.des.domain.dto.ExportType; import org.folio.des.domain.dto.delete_interval.JobDeletionInterval; import org.folio.des.support.BaseTest; @@ -7,10 +10,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.cfg.DateTimeFeature; -import tools.jackson.databind.json.JsonMapper; -import tools.jackson.databind.module.SimpleModule; import static org.folio.des.service.impl.JobDeletionIntervalServiceImpl.CREATED_BY_SYSTEM; import static org.hamcrest.Matchers.hasSize; @@ -138,8 +137,9 @@ private void assertDeleteInterval(ExportType exportType) throws Exception { } private ObjectMapper getObjectMapper() { - return JsonMapper.builder(). - findAndAddModules().addModule(new SimpleModule()) - .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS).build(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + return objectMapper; } } diff --git a/src/test/java/org/folio/des/controller/JobsControllerTest.java b/src/test/java/org/folio/des/controller/JobsControllerTest.java index 29c0c0df..c91c35bc 100644 --- a/src/test/java/org/folio/des/controller/JobsControllerTest.java +++ b/src/test/java/org/folio/des/controller/JobsControllerTest.java @@ -5,12 +5,16 @@ import static org.hamcrest.Matchers.startsWith; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.ResultMatcher.matchAll; 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.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.time.LocalDate; import java.util.UUID; import java.util.stream.Stream; @@ -33,10 +37,6 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.json.JsonMapper; -import tools.jackson.databind.module.SimpleModule; @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:job.sql") @@ -67,8 +67,7 @@ class JobsControllerTest extends BaseTest { private static final String JOB_CIRCULATION_REQUEST = "{ \"type\": \"CIRCULATION_LOG\", \"exportTypeSpecificParameters\" : {}}"; - private static final ObjectMapper MAPPER = JsonMapper.builder().findAndAddModules() - .addModule(new SimpleModule()).build(); + private static final ObjectMapper MAPPER = new ObjectMapper().registerModule(new JavaTimeModule()); @Test @DisplayName("Find all jobs") @@ -78,11 +77,12 @@ void getJobs() throws Exception { get("/data-export-spring/jobs") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(8)), - jsonPath("$.jobRecords", hasSize(8))); + jsonPath("$.jobRecords", hasSize(8)))); } @Test @@ -93,11 +93,12 @@ void findSortedJobsByExportMethodName() throws Exception { get("/data-export-spring/jobs?limit=3&offset=0&query=(cql.allRecords=1)sortby jsonb.exportTypeSpecificParameters.vendorEdiOrdersExportConfig.configName/sort.descending") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(8)), - jsonPath("$.jobRecords", hasSize(3))); + jsonPath("$.jobRecords", hasSize(3)))); } @Test @@ -108,10 +109,11 @@ void notFoundJobs() throws Exception { get("/data-export-spring/jobs?limit=3&offset=0&query=!!sortby name/sort.descending") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isBadRequest(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.errors[0].message", startsWith("IllegalArgumentException"))); + jsonPath("$.errors[0].message", startsWith("IllegalArgumentException")))); } @Test @@ -122,10 +124,11 @@ void findJobsByQueryDateRange() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(endTime>=2020-12-12T00:00:00.000 and endTime<=2020-12-13T23:59:59.999) sortby name/sort.descending") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.totalRecords", is(0))); + jsonPath("$.totalRecords", is(0)))); } @Test @@ -136,11 +139,12 @@ void excludeJobById() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(id<>12ae5d0f-1525-44a1-a361-0bc9b88e8179 or name=*)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(7)), - jsonPath("$.jobRecords", hasSize(7))); + jsonPath("$.jobRecords", hasSize(7)))); } @Test @@ -151,11 +155,12 @@ void findJobsBySourceOrDesc() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(source<>data-export-system-user or description==test-desc)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(7)), - jsonPath("$.jobRecords", hasSize(7))); + jsonPath("$.jobRecords", hasSize(7)))); } @Test @@ -166,10 +171,11 @@ void findJobsByStrictDateRange() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(endTime>2020-12-12T00:00:00.000 and endTime<2020-12-13T23:59:59.999)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.totalRecords", is(0))); + jsonPath("$.totalRecords", is(0)))); } @Test @@ -180,10 +186,11 @@ void findJobsAttribute() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(metadata.endTime>2020-12-12T00:00:00.000)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isBadRequest(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.errors[0].message", startsWith("PathElementException"))); + jsonPath("$.errors[0].message", startsWith("PathElementException")))); } @Test @@ -194,12 +201,13 @@ void getJob() throws Exception { get("/data-export-spring/jobs/12ae5d0f-1525-44a1-a361-0bc9b88e8179") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.id", is("12ae5d0f-1525-44a1-a361-0bc9b88e8179")), jsonPath("$.status", is("SUCCESSFUL")), - jsonPath("$.outputFormat", is("Fees & Fines Bursar Report"))); + jsonPath("$.outputFormat", is("Fees & Fines Bursar Report")))); } @Test @@ -210,8 +218,9 @@ void shouldFailedDownloadWithNotFound() throws Exception { get("/data-export-spring/jobs/35ae5d0f-1525-42a1-a361-1bc9b88e8180/download") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( - status().is4xxClientError()); + .andExpect( + matchAll( + status().is4xxClientError())); } @Test @@ -225,8 +234,9 @@ void shouldFailedDownloadWithBadRequest() throws Exception { get("/data-export-spring/jobs/42ae5d0f-6425-82a1-a361-1bc9b88e8172/download") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( - status().is5xxServerError()); + .andExpect( + matchAll( + status().is5xxServerError())); } @Test @@ -237,10 +247,11 @@ void notFoundJob() throws Exception { get("/data-export-spring/jobs/12ae5d0f-1525-44a1-a361-0bc9b88eeeee") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isNotFound(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.errors[0].message", startsWith("NotFoundException"))); + jsonPath("$.errors[0].message", startsWith("NotFoundException")))); } @Test @@ -252,12 +263,13 @@ void postBursarJob() throws Exception { .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders()) .content(JOB_BURSAR_REQUEST)) - .andExpectAll( + .andExpect( + matchAll( status().isCreated(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.type", is("BURSAR_FEES_FINES")), jsonPath("$.status", is("SCHEDULED")), - jsonPath("$.outputFormat", is("Fees & Fines Bursar Report"))); + jsonPath("$.outputFormat", is("Fees & Fines Bursar Report")))); } @Test @@ -270,12 +282,13 @@ void postCirculationJob() throws Exception { .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders()) .content(JOB_CIRCULATION_REQUEST)) - .andExpectAll( + .andExpect( + matchAll( status().isCreated(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.type", is("CIRCULATION_LOG")), jsonPath("$.status", is("SCHEDULED")), - jsonPath("$.outputFormat", is("Comma-Separated Values (CSV)"))); + jsonPath("$.outputFormat", is("Comma-Separated Values (CSV)")))); } @ParameterizedTest @@ -372,11 +385,12 @@ void findJobsByJSONBQuery(String query) throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=" + query) .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(1)), - jsonPath("$.jobRecords", hasSize(1))); + jsonPath("$.jobRecords", hasSize(1)))); } @Test @@ -387,9 +401,10 @@ void shouldThrowExceptionIfJSONBQueryIsEmpty() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=jsonb==1 and type==\"EDIFACT_ORDERS_EXPORT\"") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpectAll( + .andExpect( + matchAll( status().isBadRequest(), - content().contentType(MediaType.APPLICATION_JSON_VALUE)); + content().contentType(MediaType.APPLICATION_JSON_VALUE))); } private static Stream getPayloadForJobWithoutRequiredParameters() { @@ -401,18 +416,18 @@ private static Stream getPayloadForJobWithoutRequiredParameters() { } private String buildAuthorityControlJobPayload(AuthorityControlExportConfig authorityControlExportConfig, - ExportType exportType) throws JacksonException { + ExportType exportType) throws JsonProcessingException { var exportTypeSpecificParameters = new ExportTypeSpecificParameters() .authorityControlExportConfig(authorityControlExportConfig); return buildJobPayload(exportType, exportTypeSpecificParameters); } - private String buildEmptyJobPayload(ExportType exportType) throws JacksonException { + private String buildEmptyJobPayload(ExportType exportType) throws JsonProcessingException { return buildJobPayload(exportType, new ExportTypeSpecificParameters()); } private String buildJobPayload(ExportType exportType, ExportTypeSpecificParameters params) - throws JacksonException { + throws JsonProcessingException { var job = new Job() .type(exportType) .exportTypeSpecificParameters(params); diff --git a/src/test/java/org/folio/des/mapper/DefaultExportConfigMapperTest.java b/src/test/java/org/folio/des/mapper/DefaultExportConfigMapperTest.java index f4896be2..3bc5f9b8 100644 --- a/src/test/java/org/folio/des/mapper/DefaultExportConfigMapperTest.java +++ b/src/test/java/org/folio/des/mapper/DefaultExportConfigMapperTest.java @@ -15,7 +15,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import tools.jackson.databind.ObjectMapper; + +import com.fasterxml.jackson.databind.ObjectMapper; @CopilotGenerated(model = "Claude Sonnet 4.5") @SpringBootTest(classes = {JacksonConfiguration.class, DefaultExportConfigMapper.class}) diff --git a/src/test/java/org/folio/des/scheduling/quartz/job/bursar/BursarJobKeyResolverTest.java b/src/test/java/org/folio/des/scheduling/quartz/job/bursar/BursarJobKeyResolverTest.java index 8a947d23..5f3545c7 100644 --- a/src/test/java/org/folio/des/scheduling/quartz/job/bursar/BursarJobKeyResolverTest.java +++ b/src/test/java/org/folio/des/scheduling/quartz/job/bursar/BursarJobKeyResolverTest.java @@ -1,7 +1,9 @@ package org.folio.des.scheduling.quartz.job.bursar; import static org.folio.des.scheduling.quartz.QuartzConstants.BURSAR_EXPORT_GROUP_NAME; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import org.folio.des.domain.dto.ExportConfig; import org.folio.des.domain.dto.ExportType; diff --git a/src/test/java/org/folio/des/service/JobExecutionServiceTest.java b/src/test/java/org/folio/des/service/JobExecutionServiceTest.java index 765ddc71..84b06040 100644 --- a/src/test/java/org/folio/des/service/JobExecutionServiceTest.java +++ b/src/test/java/org/folio/des/service/JobExecutionServiceTest.java @@ -30,6 +30,6 @@ void shouldPrepareStartJobCommandWithNoJobCommandBuilder() { var command = jobExecutionService.prepareStartJobCommand(job); - assertEquals(new HashMap<>(), command.getJobParameters().parameters()); + assertEquals(new HashMap<>(), command.getJobParameters().getParameters()); } } diff --git a/src/test/java/org/folio/des/service/JobServiceTest.java b/src/test/java/org/folio/des/service/JobServiceTest.java index 566d238d..712a299d 100644 --- a/src/test/java/org/folio/des/service/JobServiceTest.java +++ b/src/test/java/org/folio/des/service/JobServiceTest.java @@ -2,7 +2,7 @@ import static org.folio.des.domain.dto.ExportType.BURSAR_FEES_FINES; import static org.folio.des.domain.dto.ExportType.CLAIMS; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -48,9 +48,10 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import com.fasterxml.jackson.databind.ObjectMapper; + import lombok.SneakyThrows; import org.springframework.test.util.ReflectionTestUtils; -import tools.jackson.databind.ObjectMapper; @ExtendWith(MockitoExtension.class) class JobServiceTest { @@ -126,8 +127,8 @@ void testResendJob() { job.setFileNames(list); internalJobService.resendExportedFile(jobDto.getId()); JobCommand command = jobExecutionService.prepareResendJobCommand(job); - Assertions.assertEquals("TestFile.csv", command.getJobParameters().getParameter("FILE_NAME").value()); - Assertions.assertNotNull(command.getJobParameters().getParameter("EDIFACT_ORDERS_EXPORT")); + Assertions.assertEquals("TestFile.csv", command.getJobParameters().getParameters().get("FILE_NAME").getValue()); + Assertions.assertNotNull(command.getJobParameters().getParameters().get("EDIFACT_ORDERS_EXPORT")); } @Test diff --git a/src/test/java/org/folio/des/service/bursarlegacy/BursarExportLegacyJobServiceTest.java b/src/test/java/org/folio/des/service/bursarlegacy/BursarExportLegacyJobServiceTest.java index 2b083d81..9310bfbd 100644 --- a/src/test/java/org/folio/des/service/bursarlegacy/BursarExportLegacyJobServiceTest.java +++ b/src/test/java/org/folio/des/service/bursarlegacy/BursarExportLegacyJobServiceTest.java @@ -1,6 +1,6 @@ package org.folio.des.service.bursarlegacy; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; import java.util.List; diff --git a/src/test/java/org/folio/des/support/BaseTest.java b/src/test/java/org/folio/des/support/BaseTest.java index ec84f7f9..942f13b7 100644 --- a/src/test/java/org/folio/des/support/BaseTest.java +++ b/src/test/java/org/folio/des/support/BaseTest.java @@ -6,7 +6,6 @@ import java.util.List; -import com.fasterxml.jackson.annotation.JsonInclude; import org.folio.spring.config.properties.FolioEnvironment; import org.folio.spring.integration.XOkapiHeaders; import org.folio.tenant.domain.dto.TenantAttributes; @@ -18,8 +17,9 @@ import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpHeaders; @@ -37,14 +37,14 @@ import org.testcontainers.junit.jupiter.Testcontainers; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.tomakehurst.wiremock.WireMockServer; import lombok.SneakyThrows; -import tools.jackson.databind.DeserializationFeature; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.json.JsonMapper; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}") +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ContextConfiguration(initializers = BaseTest.DockerPostgreDataSourceInitializer.class) @AutoConfigureMockMvc @Testcontainers @@ -105,11 +105,9 @@ protected static void setUpTenant(MockMvc mockMvc) { .contentType(APPLICATION_JSON)).andExpect(status().isNoContent()); } - public static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() - .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.NON_NULL)) - .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(Include.NON_NULL)) + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().setSerializationInclusion(Include.NON_NULL) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true).build(); + .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); @SneakyThrows public static String asJsonString(Object value) { diff --git a/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java b/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java index 7634264d..3eaedf2a 100644 --- a/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java +++ b/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java @@ -15,12 +15,13 @@ import org.quartz.Scheduler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.validation.Validator; @SpringBootTest(classes = {JacksonConfiguration.class, ServiceConfiguration.class}) -@EnableAutoConfiguration +@EnableAutoConfiguration(exclude = {BatchAutoConfiguration.class}) class ExportConfigValidatorResolverTest { @Autowired From fba7709088be7c3ce98543714bb480cfc9ca0181 Mon Sep 17 00:00:00 2001 From: OleksandrBozhko Date: Fri, 27 Feb 2026 23:17:32 +0200 Subject: [PATCH 03/12] MODEXPS-302 Without jackson 3 --- pom.xml | 21 +++-- .../java/org/folio/de/entity/BaseJob.java | 11 +-- .../folio/de/entity/ExportConfigEntity.java | 8 +- src/main/java/org/folio/de/entity/Job.java | 6 +- .../java/org/folio/de/entity/JobCommand.java | 2 +- .../JobWithLegacyBursarParameters.java | 6 +- .../des/ModDataExportSpringApplication.java | 9 +-- .../AuthorityControlJobCommandBuilder.java | 4 +- .../job/BursarFeeFinesJobCommandBuilder.java | 4 +- .../job/CirculationLogJobCommandBuilder.java | 4 +- .../job/EHoldingsJobCommandBuilder.java | 4 +- .../job/EdifactOrdersJobCommandBuilder.java | 4 +- ...ifactOrdersJobCommandSchedulerBuilder.java | 2 +- .../des/builder/job/JobCommandBuilder.java | 2 +- .../des/client/DataExportSpringClient.java | 11 ++- .../folio/des/client/ExportWorkerClient.java | 8 +- .../des/config/HttpClientConfiguration.java | 46 +++++++++++ .../des/config/JacksonConfiguration.java | 18 ++--- .../config/feign/CustomFeignErrorDecoder.java | 22 ----- .../feign/FeignClientConfiguration.java | 12 --- .../des/config/kafka/KafkaConfiguration.java | 2 +- .../QuartzSchedulerFactoryBeanCustomizer.java | 2 +- .../scheduling/QuartzSchemaInitializer.java | 2 +- .../ControllerExceptionHandler.java | 4 +- .../exceptions/RestClientErrorHandler.java | 36 +++++++++ .../ExportConfigToBursarTriggerConverter.java | 2 +- .../des/service/JobExecutionService.java | 14 ++-- ...tOrdersJobCommandSchedulerBuilderTest.java | 14 ++-- .../job/JobCommandBuilderResolverTest.java | 26 +++++- .../des/controller/JobsControllerTest.java | 81 ++++++++----------- .../des/service/JobExecutionServiceTest.java | 5 +- .../org/folio/des/service/JobServiceTest.java | 4 +- .../java/org/folio/des/support/BaseTest.java | 7 +- .../ExportConfigValidatorResolverTest.java | 21 ++++- 34 files changed, 240 insertions(+), 184 deletions(-) create mode 100644 src/main/java/org/folio/des/config/HttpClientConfiguration.java delete mode 100644 src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java delete mode 100644 src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java create mode 100644 src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java diff --git a/pom.xml b/pom.xml index 218d5da3..863d0b8f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.3 + 4.0.2 @@ -34,10 +34,9 @@ ${project.basedir}/src/main/resources/swagger.api/export-configs.yaml ${project.basedir}/src/main/resources/swagger.api/job-deletion-intervals.yaml - 9.0.0 + 10.0.0-RC1 4.1.1 1.0.0 - 3.9.2 1.18.42 1.6.3 0.2.0 @@ -111,12 +110,6 @@ postgresql - - io.hypersistence - hypersistence-utils-hibernate-63 - ${hypersistence-utils-hibernate-63.version} - - org.springframework.batch spring-batch-core @@ -139,8 +132,8 @@ - org.springframework.kafka - spring-kafka + org.springframework.boot + spring-boot-starter-kafka @@ -215,6 +208,11 @@ + + org.springframework.boot + spring-boot-starter-webmvc-test + test + org.mockito mockito-inline @@ -275,6 +273,7 @@ io.rest-assured rest-assured + 5.5.7 test diff --git a/src/main/java/org/folio/de/entity/BaseJob.java b/src/main/java/org/folio/de/entity/BaseJob.java index cc85cda8..c5a33e06 100644 --- a/src/main/java/org/folio/de/entity/BaseJob.java +++ b/src/main/java/org/folio/de/entity/BaseJob.java @@ -1,6 +1,5 @@ package org.folio.de.entity; -import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -15,7 +14,9 @@ import org.folio.des.domain.dto.IdentifierType; import org.folio.des.domain.dto.JobStatus; import org.folio.des.domain.dto.Progress; +import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.annotations.Type; +import org.hibernate.type.SqlTypes; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; @@ -42,11 +43,11 @@ public abstract class BaseJob { @Enumerated(EnumType.STRING) private JobStatus status; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private List files = null; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private List fileNames = null; @@ -73,7 +74,7 @@ public abstract class BaseJob { @Enumerated(EnumType.STRING) private BatchStatus batchStatus; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private ExitStatus exitStatus; @@ -83,7 +84,7 @@ public abstract class BaseJob { @Enumerated(EnumType.STRING) private EntityType entityType; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private Progress progress; } diff --git a/src/main/java/org/folio/de/entity/ExportConfigEntity.java b/src/main/java/org/folio/de/entity/ExportConfigEntity.java index f9b5a40c..49112f3f 100644 --- a/src/main/java/org/folio/de/entity/ExportConfigEntity.java +++ b/src/main/java/org/folio/de/entity/ExportConfigEntity.java @@ -4,9 +4,8 @@ import java.util.UUID; import org.folio.de.entity.base.AuditableEntity; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.JdbcTypeCode; -import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -14,6 +13,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; +import org.hibernate.type.SqlTypes; @Entity @Table(name = "export_config") @@ -35,7 +35,7 @@ public class ExportConfigEntity extends AuditableEntity { @Column(name = "tenant", nullable = false) private String tenant; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(name = "export_type_specific_parameters", columnDefinition = "jsonb", nullable = false) private Object exportTypeSpecificParameters; @@ -48,7 +48,7 @@ public class ExportConfigEntity extends AuditableEntity { @Column(name = "schedule_time") private String scheduleTime; - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(name = "week_days", columnDefinition = "jsonb") private List weekDays; diff --git a/src/main/java/org/folio/de/entity/Job.java b/src/main/java/org/folio/de/entity/Job.java index 30a3b9cd..e5e0c9c5 100644 --- a/src/main/java/org/folio/de/entity/Job.java +++ b/src/main/java/org/folio/de/entity/Job.java @@ -1,17 +1,17 @@ package org.folio.de.entity; -import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import lombok.Data; import org.folio.des.domain.dto.ExportTypeSpecificParameters; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; @Entity @Data public class Job extends BaseJob { - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private ExportTypeSpecificParameters exportTypeSpecificParameters; } diff --git a/src/main/java/org/folio/de/entity/JobCommand.java b/src/main/java/org/folio/de/entity/JobCommand.java index 3c0a377c..74ba81c8 100644 --- a/src/main/java/org/folio/de/entity/JobCommand.java +++ b/src/main/java/org/folio/de/entity/JobCommand.java @@ -5,7 +5,7 @@ import org.folio.des.domain.dto.ExportType; import org.folio.des.domain.dto.IdentifierType; import org.folio.des.domain.dto.Progress; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.parameters.JobParameters; import java.util.UUID; diff --git a/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java b/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java index 8897ab2a..2e890598 100644 --- a/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java +++ b/src/main/java/org/folio/de/entity/bursarlegacy/JobWithLegacyBursarParameters.java @@ -1,20 +1,20 @@ package org.folio.de.entity.bursarlegacy; -import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; import org.folio.de.entity.BaseJob; import org.folio.des.domain.dto.ExportTypeSpecificParametersWithLegacyBursar; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; @Entity @Table(name = "job") @Data public class JobWithLegacyBursarParameters extends BaseJob { - @Type(JsonBinaryType.class) + @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private ExportTypeSpecificParametersWithLegacyBursar exportTypeSpecificParameters; } diff --git a/src/main/java/org/folio/des/ModDataExportSpringApplication.java b/src/main/java/org/folio/des/ModDataExportSpringApplication.java index 23ebe405..743bde4e 100644 --- a/src/main/java/org/folio/des/ModDataExportSpringApplication.java +++ b/src/main/java/org/folio/des/ModDataExportSpringApplication.java @@ -6,12 +6,9 @@ import org.folio.de.entity.JobCommand; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.boot.persistence.autoconfigure.EntityScan; -@SpringBootApplication(exclude = BatchAutoConfiguration.class) -@EnableFeignClients +@SpringBootApplication @EntityScan(basePackageClasses = JobCommand.class) public class ModDataExportSpringApplication { public static final String SYSTEM_USER_PASSWORD = "SYSTEM_USER_PASSWORD"; @@ -19,7 +16,7 @@ public class ModDataExportSpringApplication { public static void main(String[] args) { String systemUserEnabled = Optional.ofNullable(System.getenv(SYSTEM_USER_ENABLED)).orElse("true"); - + if ( Boolean.parseBoolean(systemUserEnabled) && StringUtils.isEmpty(System.getenv(SYSTEM_USER_PASSWORD)) diff --git a/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java index d897b092..4e00a290 100644 --- a/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/AuthorityControlJobCommandBuilder.java @@ -5,8 +5,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; @Service diff --git a/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java index ba6f11ef..98d76eb6 100644 --- a/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/BursarFeeFinesJobCommandBuilder.java @@ -5,8 +5,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; @Service diff --git a/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java index 858f8149..4811cd82 100644 --- a/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/CirculationLogJobCommandBuilder.java @@ -1,8 +1,8 @@ package org.folio.des.builder.job; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java index 0d9bd55d..3b4d17df 100644 --- a/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/EHoldingsJobCommandBuilder.java @@ -5,8 +5,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; @Service diff --git a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java index ba45d363..39308e29 100644 --- a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandBuilder.java @@ -5,8 +5,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; @Service diff --git a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java index 18cc19e0..0db5369f 100644 --- a/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java +++ b/src/main/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilder.java @@ -1,7 +1,7 @@ package org.folio.des.builder.job; import org.folio.de.entity.JobCommand; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java b/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java index 8f35bdec..b1142f27 100644 --- a/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java +++ b/src/main/java/org/folio/des/builder/job/JobCommandBuilder.java @@ -1,7 +1,7 @@ package org.folio.des.builder.job; import org.folio.de.entity.Job; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.parameters.JobParameters; @FunctionalInterface public interface JobCommandBuilder { diff --git a/src/main/java/org/folio/des/client/DataExportSpringClient.java b/src/main/java/org/folio/des/client/DataExportSpringClient.java index deba06ba..d7362d60 100644 --- a/src/main/java/org/folio/des/client/DataExportSpringClient.java +++ b/src/main/java/org/folio/des/client/DataExportSpringClient.java @@ -1,16 +1,15 @@ package org.folio.des.client; -import org.folio.des.config.feign.FeignClientConfiguration; import org.folio.des.domain.dto.Job; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.annotation.PostExchange; -@FeignClient(name = "data-export-spring", configuration = FeignClientConfiguration.class) +@HttpExchange(url = "data-export-spring") public interface DataExportSpringClient { - @PostMapping(value = "/jobs") + @PostExchange(value = "/jobs") Job upsertJob(@RequestBody Job job); - @PostMapping(value = "/jobs/send") + @PostExchange(value = "/jobs/send") void sendJob(@RequestBody Job job); } diff --git a/src/main/java/org/folio/des/client/ExportWorkerClient.java b/src/main/java/org/folio/des/client/ExportWorkerClient.java index a5ec00d2..547f9dc4 100644 --- a/src/main/java/org/folio/des/client/ExportWorkerClient.java +++ b/src/main/java/org/folio/des/client/ExportWorkerClient.java @@ -1,13 +1,13 @@ package org.folio.des.client; import org.folio.des.domain.dto.PresignedUrl; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.annotation.HttpExchange; -@FeignClient("refresh-presigned-url") +@HttpExchange("refresh-presigned-url") public interface ExportWorkerClient { - @GetMapping + @GetExchange PresignedUrl getRefreshedPresignedUrl(@RequestParam("filePath") String filePath); } diff --git a/src/main/java/org/folio/des/config/HttpClientConfiguration.java b/src/main/java/org/folio/des/config/HttpClientConfiguration.java new file mode 100644 index 00000000..dbcc0674 --- /dev/null +++ b/src/main/java/org/folio/des/config/HttpClientConfiguration.java @@ -0,0 +1,46 @@ +package org.folio.des.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.des.client.DataExportSpringClient; +import org.folio.des.client.ExportWorkerClient; +import org.folio.des.exceptions.RestClientErrorHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatusCode; +import org.springframework.web.client.RestClient; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; + +@Configuration +@Log4j2 +@RequiredArgsConstructor +public class HttpClientConfiguration { + + private final RestClientErrorHandler errorHandler; + + @Bean + public DataExportSpringClient dataExportSpringClient(HttpServiceProxyFactory factory) { + return factory.createClient(DataExportSpringClient.class); + } + + @Bean + public ExportWorkerClient exportWorkerClient(HttpServiceProxyFactory factory) { + return factory.createClient(ExportWorkerClient.class); + } + + @Primary + @Bean + public RestClient restClient(RestClient.Builder builder) { + return builder + .requestInterceptor( + (request, body, execution) -> { + log.info("Request URL: {}", request.getURI()); + request.getHeaders().add(HttpHeaders.ACCEPT_ENCODING, "identity"); + return execution.execute(request, body); + }) + .defaultStatusHandler(HttpStatusCode::isError, errorHandler::handle) + .build(); + } +} diff --git a/src/main/java/org/folio/des/config/JacksonConfiguration.java b/src/main/java/org/folio/des/config/JacksonConfiguration.java index 48e2f163..0edea1da 100644 --- a/src/main/java/org/folio/des/config/JacksonConfiguration.java +++ b/src/main/java/org/folio/des/config/JacksonConfiguration.java @@ -13,7 +13,6 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import io.hypersistence.utils.hibernate.type.util.ObjectMapperSupplier; import java.io.IOException; import java.sql.Date; import java.util.HashMap; @@ -21,14 +20,14 @@ import java.util.UUID; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameter; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @Configuration -public class JacksonConfiguration implements ObjectMapperSupplier { +public class JacksonConfiguration { private static final ObjectMapper OBJECT_MAPPER; private static final ObjectMapper ENTITY_OBJECT_MAPPER; @@ -94,11 +93,11 @@ public JobParameter deserialize(JsonParser jp, DeserializationContext ctxt) t JsonNode jsonNode = jp.getCodec().readTree(jp); var identifying = jsonNode.get("identifying").asBoolean(); switch (jsonNode.get("type").asText()) { - case "STRING" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText(), String.class, identifying); + case "STRING" -> new JobParameter<>("STRING", jsonNode.get(VALUE_PARAMETER_PROPERTY).asText(), String.class, identifying); case "DATE" -> new JobParameter<>( - Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText()), Date.class, identifying); - case "LONG" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), Long.class, identifying); - case "DOUBLE" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), Double.class, identifying); + "DATE", Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText()), Date.class, identifying); + case "LONG" -> new JobParameter<>("LONG", jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), Long.class, identifying); + case "DOUBLE" -> new JobParameter<>("DOUBLE", jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), Double.class, identifying); } return null; } @@ -127,9 +126,4 @@ public ObjectMapper entityObjectMapper() { return ENTITY_OBJECT_MAPPER; } - @Override - public ObjectMapper get() { - return OBJECT_MAPPER; - } - } diff --git a/src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java b/src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java deleted file mode 100644 index 315e1502..00000000 --- a/src/main/java/org/folio/des/config/feign/CustomFeignErrorDecoder.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.folio.des.config.feign; - -import static feign.FeignException.errorStatus; - -import org.folio.spring.exception.NotFoundException; -import org.springframework.http.HttpStatus; - -import feign.Response; -import feign.codec.ErrorDecoder; - -public class CustomFeignErrorDecoder implements ErrorDecoder { - - @Override - public Exception decode(String methodKey, Response response) { - String requestUrl = response.request().url(); - if (HttpStatus.NOT_FOUND.value() == response.status()) { - return new NotFoundException(requestUrl); - } - return errorStatus(methodKey, response); - } - -} diff --git a/src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java b/src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java deleted file mode 100644 index cf72fd6c..00000000 --- a/src/main/java/org/folio/des/config/feign/FeignClientConfiguration.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.folio.des.config.feign; - -import org.springframework.context.annotation.Bean; - -import feign.codec.ErrorDecoder; - -public class FeignClientConfiguration { - @Bean - public ErrorDecoder errorDecoder() { - return new CustomFeignErrorDecoder(); - } -} diff --git a/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java b/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java index 3cbb7e68..f0c84a0d 100644 --- a/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java +++ b/src/main/java/org/folio/des/config/kafka/KafkaConfiguration.java @@ -10,7 +10,7 @@ import org.apache.kafka.common.serialization.StringSerializer; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; -import org.springframework.boot.autoconfigure.kafka.KafkaProperties; +import org.springframework.boot.kafka.autoconfigure.KafkaProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; diff --git a/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java b/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java index ecfeedf4..bd1d65a2 100644 --- a/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java +++ b/src/main/java/org/folio/des/config/scheduling/QuartzSchedulerFactoryBeanCustomizer.java @@ -1,7 +1,7 @@ package org.folio.des.config.scheduling; import org.folio.spring.config.DataSourceFolioWrapper; -import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer; +import org.springframework.boot.quartz.autoconfigure.SchedulerFactoryBeanCustomizer; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.stereotype.Component; diff --git a/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java b/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java index 6fc88a4d..41fd70df 100644 --- a/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java +++ b/src/main/java/org/folio/des/config/scheduling/QuartzSchemaInitializer.java @@ -3,7 +3,7 @@ import org.folio.spring.liquibase.FolioSpringLiquibase; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.boot.liquibase.autoconfigure.LiquibaseProperties; import org.springframework.stereotype.Component; import liquibase.exception.LiquibaseException; diff --git a/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java b/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java index 4e68b7fc..2508c47e 100644 --- a/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java +++ b/src/main/java/org/folio/des/controller/ControllerExceptionHandler.java @@ -17,9 +17,9 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.RestClientException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import feign.FeignException; import lombok.extern.log4j.Log4j2; @RestControllerAdvice @@ -33,7 +33,7 @@ public class ControllerExceptionHandler { MissingServletRequestParameterException.class, MethodArgumentTypeMismatchException.class, MethodArgumentNotValidException.class, - FeignException.class + RestClientException.class }) @ResponseStatus(HttpStatus.BAD_REQUEST) public Errors handleIllegalArgumentException(Exception exception) { diff --git a/src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java b/src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java new file mode 100644 index 00000000..d4613f9d --- /dev/null +++ b/src/main/java/org/folio/des/exceptions/RestClientErrorHandler.java @@ -0,0 +1,36 @@ +package org.folio.des.exceptions; + +import org.folio.spring.exception.NotFoundException; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class RestClientErrorHandler { + + public void handle(HttpRequest request, ClientHttpResponse response) throws IOException { + int status = response.getStatusCode().value(); + + if (status == 404) { + handle404(request); + } else { + handleOtherError(request, response); + } + } + + private void handle404(HttpRequest request) { + throw new NotFoundException("Not found: " + request.getURI()); + } + + private void handleOtherError(HttpRequest request, ClientHttpResponse response) { + try (var bodyIs = response.getBody()) { + var msg = new String(bodyIs.readAllBytes()); + String reason = !msg.isBlank() ? msg : "Unknown error"; + throw new RuntimeException("Error for " + request.getURI() + " because of " + reason); + } catch (IOException e) { + throw new RuntimeException("Unable to get reason for error: " + e.getMessage()); + } + } +} diff --git a/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java b/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java index 0bbe064b..62a06c64 100644 --- a/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java +++ b/src/main/java/org/folio/des/scheduling/quartz/converter/bursar/ExportConfigToBursarTriggerConverter.java @@ -10,12 +10,12 @@ import java.util.List; import java.util.Optional; +import jakarta.validation.constraints.NotNull; import org.folio.des.domain.dto.ExportConfig; import org.folio.des.domain.dto.ScheduleParameters; import org.folio.des.scheduling.quartz.QuartzConstants; import org.folio.des.scheduling.quartz.converter.ScheduleParametersToTriggerConverter; import org.folio.des.scheduling.quartz.trigger.ExportTrigger; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; diff --git a/src/main/java/org/folio/des/service/JobExecutionService.java b/src/main/java/org/folio/des/service/JobExecutionService.java index 80fc7726..c8b69643 100644 --- a/src/main/java/org/folio/des/service/JobExecutionService.java +++ b/src/main/java/org/folio/des/service/JobExecutionService.java @@ -2,7 +2,7 @@ import java.util.Collection; import java.util.Collections; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -20,9 +20,9 @@ import org.folio.des.domain.dto.VendorEdiOrdersExportConfig; import org.folio.des.service.config.ExportConfigService; import org.folio.des.validator.ExportConfigValidatorResolver; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.stereotype.Service; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.Errors; @@ -55,7 +55,7 @@ public JobCommand prepareStartJobCommand(Job job) { JobParameters jobParameters = builder.buildJobCommand(job); jobCommand.setJobParameters(jobParameters); }, - () -> jobCommand.setJobParameters(new JobParameters(new HashMap<>()))); + () -> jobCommand.setJobParameters(new JobParameters(new HashSet<>()))); return jobCommand; } @@ -104,8 +104,8 @@ public void deleteJobs(List jobs) { var jobCommand = new JobCommand(); jobCommand.setType(JobCommand.Type.DELETE); jobCommand.setId(UUID.randomUUID()); - jobCommand.setJobParameters(new JobParameters( - Collections.singletonMap(JobParameterNames.OUTPUT_FILES_IN_STORAGE, new JobParameter<>(StringUtils.join(files, ';'), String.class)))); + jobCommand.setJobParameters(new JobParameters(Collections.singleton( + new JobParameter<>(JobParameterNames.OUTPUT_FILES_IN_STORAGE, StringUtils.join(files, ';'), String.class)))); sendJobCommand(jobCommand); } diff --git a/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java b/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java index 543e5078..48eb4373 100644 --- a/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java +++ b/src/test/java/org/folio/des/builder/job/EdifactOrdersJobCommandSchedulerBuilderTest.java @@ -28,7 +28,7 @@ import org.folio.des.domain.dto.VendorEdiOrdersExportConfig; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; @@ -65,9 +65,9 @@ void successJobCommandBuild() { job.setExportTypeSpecificParameters(parameters); JobCommand actJobCommand = builder.buildJobCommand(job); - JobParameter actJobParameter = actJobCommand.getJobParameters().getParameters().get("edifactOrdersExport"); + JobParameter actJobParameter = actJobCommand.getJobParameters().getParameter("edifactOrdersExport"); assertEquals(id, actJobCommand.getId()); - assertTrue(actJobParameter.getValue().toString().contains(vendorId.toString())); + assertTrue(actJobParameter.value().toString().contains(vendorId.toString())); } public static class MockSpringContext { @@ -141,12 +141,12 @@ public JobParameter deserialize(JsonParser jp, DeserializationContext ctxt) t var identifying = jsonNode.get("identifying").asBoolean(); switch (jsonNode.get("type").asText()) { case "STRING" -> - new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText(), String.class, identifying); + new JobParameter<>("STRING", jsonNode.get(VALUE_PARAMETER_PROPERTY).asText(), String.class, identifying); case "DATE" -> new JobParameter<>( - Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText()), Date.class, identifying); - case "LONG" -> new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), Long.class, identifying); + "DATE", Date.valueOf(jsonNode.get(VALUE_PARAMETER_PROPERTY).asText()), Date.class, identifying); + case "LONG" -> new JobParameter<>("LONG", jsonNode.get(VALUE_PARAMETER_PROPERTY).asLong(), Long.class, identifying); case "DOUBLE" -> - new JobParameter<>(jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), Double.class, identifying); + new JobParameter<>("DOUBLE", jsonNode.get(VALUE_PARAMETER_PROPERTY).asDouble(), Double.class, identifying); } return null; } diff --git a/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java b/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java index 5010437d..3c9c86d8 100644 --- a/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java +++ b/src/test/java/org/folio/des/builder/job/JobCommandBuilderResolverTest.java @@ -11,6 +11,9 @@ import java.util.UUID; import org.folio.de.entity.Job; +import org.folio.des.client.DataExportSpringClient; +import org.folio.des.client.ExportWorkerClient; +import org.folio.des.config.HttpClientConfiguration; import org.folio.des.config.JacksonConfiguration; import org.folio.des.config.ServiceConfiguration; import org.folio.des.config.scheduling.QuartzSchemaInitializer; @@ -26,20 +29,23 @@ import org.folio.des.domain.dto.ExportType; import org.folio.des.domain.dto.ExportTypeSpecificParameters; import org.folio.des.domain.dto.VendorEdiOrdersExportConfig; +import org.folio.spring.client.AuthnClient; +import org.folio.spring.client.PermissionsClient; +import org.folio.spring.client.UsersClient; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.quartz.Scheduler; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.web.client.RestClient; @SpringBootTest(classes = {JacksonConfiguration.class, ServiceConfiguration.class}) -@EnableAutoConfiguration(exclude = {BatchAutoConfiguration.class}) +@EnableAutoConfiguration class JobCommandBuilderResolverTest { @Autowired @@ -48,6 +54,18 @@ class JobCommandBuilderResolverTest { private Scheduler scheduler; @MockitoBean private QuartzSchemaInitializer quartzSchemaInitializer; + @MockitoBean + private ExportWorkerClient exportWorkerClient; + @MockitoBean + private DataExportSpringClient dataExportSpringClient; + @MockitoBean + private RestClient restClient; + @MockitoBean + private AuthnClient authnClient; + @MockitoBean + private UsersClient usersClient; + @MockitoBean + private PermissionsClient permissionsClient; @ParameterizedTest @DisplayName("Should retrieve builder for specific export type if builder is registered in the resolver") @@ -131,6 +149,6 @@ void shouldBeCreateJobParameters(ExportType exportType, String paramsKey) { JobParameters jobParameters = builder.get().buildJobCommand(job); - assertNotEquals("null", jobParameters.getParameters().get(paramsKey).getValue()); + assertNotEquals("null", jobParameters.getParameter(paramsKey).value()); } } diff --git a/src/test/java/org/folio/des/controller/JobsControllerTest.java b/src/test/java/org/folio/des/controller/JobsControllerTest.java index c91c35bc..ee931128 100644 --- a/src/test/java/org/folio/des/controller/JobsControllerTest.java +++ b/src/test/java/org/folio/des/controller/JobsControllerTest.java @@ -5,7 +5,6 @@ import static org.hamcrest.Matchers.startsWith; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.ResultMatcher.matchAll; 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; @@ -77,12 +76,11 @@ void getJobs() throws Exception { get("/data-export-spring/jobs") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(8)), - jsonPath("$.jobRecords", hasSize(8)))); + jsonPath("$.jobRecords", hasSize(8))); } @Test @@ -93,12 +91,11 @@ void findSortedJobsByExportMethodName() throws Exception { get("/data-export-spring/jobs?limit=3&offset=0&query=(cql.allRecords=1)sortby jsonb.exportTypeSpecificParameters.vendorEdiOrdersExportConfig.configName/sort.descending") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(8)), - jsonPath("$.jobRecords", hasSize(3)))); + jsonPath("$.jobRecords", hasSize(3))); } @Test @@ -109,11 +106,10 @@ void notFoundJobs() throws Exception { get("/data-export-spring/jobs?limit=3&offset=0&query=!!sortby name/sort.descending") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isBadRequest(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.errors[0].message", startsWith("IllegalArgumentException")))); + jsonPath("$.errors[0].message", startsWith("IllegalArgumentException"))); } @Test @@ -124,11 +120,10 @@ void findJobsByQueryDateRange() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(endTime>=2020-12-12T00:00:00.000 and endTime<=2020-12-13T23:59:59.999) sortby name/sort.descending") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.totalRecords", is(0)))); + jsonPath("$.totalRecords", is(0))); } @Test @@ -139,12 +134,11 @@ void excludeJobById() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(id<>12ae5d0f-1525-44a1-a361-0bc9b88e8179 or name=*)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(7)), - jsonPath("$.jobRecords", hasSize(7)))); + jsonPath("$.jobRecords", hasSize(7))); } @Test @@ -155,12 +149,11 @@ void findJobsBySourceOrDesc() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(source<>data-export-system-user or description==test-desc)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(7)), - jsonPath("$.jobRecords", hasSize(7)))); + jsonPath("$.jobRecords", hasSize(7))); } @Test @@ -171,11 +164,10 @@ void findJobsByStrictDateRange() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(endTime>2020-12-12T00:00:00.000 and endTime<2020-12-13T23:59:59.999)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.totalRecords", is(0)))); + jsonPath("$.totalRecords", is(0))); } @Test @@ -186,11 +178,10 @@ void findJobsAttribute() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=(metadata.endTime>2020-12-12T00:00:00.000)") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isBadRequest(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.errors[0].message", startsWith("PathElementException")))); + jsonPath("$.errors[0].message", startsWith("PathElementException"))); } @Test @@ -201,13 +192,12 @@ void getJob() throws Exception { get("/data-export-spring/jobs/12ae5d0f-1525-44a1-a361-0bc9b88e8179") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.id", is("12ae5d0f-1525-44a1-a361-0bc9b88e8179")), jsonPath("$.status", is("SUCCESSFUL")), - jsonPath("$.outputFormat", is("Fees & Fines Bursar Report")))); + jsonPath("$.outputFormat", is("Fees & Fines Bursar Report"))); } @Test @@ -218,9 +208,8 @@ void shouldFailedDownloadWithNotFound() throws Exception { get("/data-export-spring/jobs/35ae5d0f-1525-42a1-a361-1bc9b88e8180/download") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( - status().is4xxClientError())); + .andExpectAll( + status().is4xxClientError()); } @Test @@ -234,9 +223,8 @@ void shouldFailedDownloadWithBadRequest() throws Exception { get("/data-export-spring/jobs/42ae5d0f-6425-82a1-a361-1bc9b88e8172/download") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( - status().is5xxServerError())); + .andExpectAll( + status().is5xxServerError()); } @Test @@ -247,11 +235,10 @@ void notFoundJob() throws Exception { get("/data-export-spring/jobs/12ae5d0f-1525-44a1-a361-0bc9b88eeeee") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isNotFound(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.errors[0].message", startsWith("NotFoundException")))); + jsonPath("$.errors[0].message", startsWith("NotFoundException"))); } @Test @@ -263,13 +250,12 @@ void postBursarJob() throws Exception { .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders()) .content(JOB_BURSAR_REQUEST)) - .andExpect( - matchAll( + .andExpectAll( status().isCreated(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.type", is("BURSAR_FEES_FINES")), jsonPath("$.status", is("SCHEDULED")), - jsonPath("$.outputFormat", is("Fees & Fines Bursar Report")))); + jsonPath("$.outputFormat", is("Fees & Fines Bursar Report"))); } @Test @@ -282,13 +268,12 @@ void postCirculationJob() throws Exception { .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders()) .content(JOB_CIRCULATION_REQUEST)) - .andExpect( - matchAll( + .andExpectAll( status().isCreated(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.type", is("CIRCULATION_LOG")), jsonPath("$.status", is("SCHEDULED")), - jsonPath("$.outputFormat", is("Comma-Separated Values (CSV)")))); + jsonPath("$.outputFormat", is("Comma-Separated Values (CSV)"))); } @ParameterizedTest @@ -385,12 +370,11 @@ void findJobsByJSONBQuery(String query) throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=" + query) .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE), jsonPath("$.totalRecords", is(1)), - jsonPath("$.jobRecords", hasSize(1)))); + jsonPath("$.jobRecords", hasSize(1))); } @Test @@ -401,10 +385,9 @@ void shouldThrowExceptionIfJSONBQueryIsEmpty() throws Exception { get("/data-export-spring/jobs?limit=30&offset=0&query=jsonb==1 and type==\"EDIFACT_ORDERS_EXPORT\"") .contentType(MediaType.APPLICATION_JSON_VALUE) .headers(defaultHeaders())) - .andExpect( - matchAll( + .andExpectAll( status().isBadRequest(), - content().contentType(MediaType.APPLICATION_JSON_VALUE))); + content().contentType(MediaType.APPLICATION_JSON_VALUE)); } private static Stream getPayloadForJobWithoutRequiredParameters() { diff --git a/src/test/java/org/folio/des/service/JobExecutionServiceTest.java b/src/test/java/org/folio/des/service/JobExecutionServiceTest.java index 84b06040..3d5b2063 100644 --- a/src/test/java/org/folio/des/service/JobExecutionServiceTest.java +++ b/src/test/java/org/folio/des/service/JobExecutionServiceTest.java @@ -2,7 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.HashMap; +import java.util.HashSet; + import org.folio.de.entity.Job; import org.folio.des.builder.job.JobCommandBuilderResolver; import org.folio.des.domain.dto.ExportType; @@ -30,6 +31,6 @@ void shouldPrepareStartJobCommandWithNoJobCommandBuilder() { var command = jobExecutionService.prepareStartJobCommand(job); - assertEquals(new HashMap<>(), command.getJobParameters().getParameters()); + assertEquals(new HashSet<>(), command.getJobParameters().parameters()); } } diff --git a/src/test/java/org/folio/des/service/JobServiceTest.java b/src/test/java/org/folio/des/service/JobServiceTest.java index 712a299d..bb134851 100644 --- a/src/test/java/org/folio/des/service/JobServiceTest.java +++ b/src/test/java/org/folio/des/service/JobServiceTest.java @@ -127,8 +127,8 @@ void testResendJob() { job.setFileNames(list); internalJobService.resendExportedFile(jobDto.getId()); JobCommand command = jobExecutionService.prepareResendJobCommand(job); - Assertions.assertEquals("TestFile.csv", command.getJobParameters().getParameters().get("FILE_NAME").getValue()); - Assertions.assertNotNull(command.getJobParameters().getParameters().get("EDIFACT_ORDERS_EXPORT")); + Assertions.assertEquals("TestFile.csv", command.getJobParameters().getParameter("FILE_NAME").value()); + Assertions.assertNotNull(command.getJobParameters().getParameter("EDIFACT_ORDERS_EXPORT")); } @Test diff --git a/src/test/java/org/folio/des/support/BaseTest.java b/src/test/java/org/folio/des/support/BaseTest.java index 942f13b7..f031013d 100644 --- a/src/test/java/org/folio/des/support/BaseTest.java +++ b/src/test/java/org/folio/des/support/BaseTest.java @@ -17,9 +17,8 @@ import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpHeaders; @@ -43,8 +42,8 @@ import lombok.SneakyThrows; -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}") -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = {"spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}", "spring.liquibase.enabled=true"}) @ContextConfiguration(initializers = BaseTest.DockerPostgreDataSourceInitializer.class) @AutoConfigureMockMvc @Testcontainers diff --git a/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java b/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java index 3eaedf2a..f783d536 100644 --- a/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java +++ b/src/test/java/org/folio/des/validator/ExportConfigValidatorResolverTest.java @@ -5,23 +5,28 @@ import java.util.Optional; +import org.folio.des.client.DataExportSpringClient; +import org.folio.des.client.ExportWorkerClient; import org.folio.des.config.JacksonConfiguration; import org.folio.des.config.ServiceConfiguration; import org.folio.des.config.scheduling.QuartzSchemaInitializer; import org.folio.des.domain.dto.ExportType; import org.folio.des.domain.dto.ExportTypeSpecificParameters; +import org.folio.spring.client.AuthnClient; +import org.folio.spring.client.PermissionsClient; +import org.folio.spring.client.UsersClient; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.quartz.Scheduler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.validation.Validator; +import org.springframework.web.client.RestClient; @SpringBootTest(classes = {JacksonConfiguration.class, ServiceConfiguration.class}) -@EnableAutoConfiguration(exclude = {BatchAutoConfiguration.class}) +@EnableAutoConfiguration class ExportConfigValidatorResolverTest { @Autowired @@ -30,6 +35,18 @@ class ExportConfigValidatorResolverTest { private Scheduler scheduler; @MockitoBean private QuartzSchemaInitializer quartzSchemaInitializer; + @MockitoBean + private ExportWorkerClient exportWorkerClient; + @MockitoBean + private DataExportSpringClient dataExportSpringClient; + @MockitoBean + private RestClient restClient; + @MockitoBean + private AuthnClient authnClient; + @MockitoBean + private UsersClient usersClient; + @MockitoBean + private PermissionsClient permissionsClient; @Test @DisplayName("Should retrieve validator for specific configuration parameter if validator is registered in the resolver") From 46bfddb6eaefdd76db7179011c96fc8dc17c943c Mon Sep 17 00:00:00 2001 From: obozhko-folio Date: Sat, 28 Feb 2026 20:43:53 +0200 Subject: [PATCH 04/12] MODEXPS-302 Fixed tests --- .../des/controller/JobsControllerTest.java | 3 ++ .../java/org/folio/des/support/BaseTest.java | 41 +++++++++++++++++-- src/test/resources/mappings/authn.json | 15 ++++--- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/folio/des/controller/JobsControllerTest.java b/src/test/java/org/folio/des/controller/JobsControllerTest.java index ee931128..5842a14d 100644 --- a/src/test/java/org/folio/des/controller/JobsControllerTest.java +++ b/src/test/java/org/folio/des/controller/JobsControllerTest.java @@ -18,6 +18,7 @@ import java.util.UUID; import java.util.stream.Stream; import org.folio.des.client.ExportWorkerClient; +import org.folio.des.config.JacksonConfiguration; import org.folio.des.domain.dto.AuthorityControlExportConfig; import org.folio.des.domain.dto.EHoldingsExportConfig; import org.folio.des.domain.dto.ExportType; @@ -32,6 +33,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.jdbc.Sql; @@ -40,6 +42,7 @@ @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:job.sql") @Sql(executionPhase = ExecutionPhase.AFTER_TEST_METHOD, scripts = "classpath:clearDb.sql") +@Import(JacksonConfiguration.class) class JobsControllerTest extends BaseTest { @MockitoBean diff --git a/src/test/java/org/folio/des/support/BaseTest.java b/src/test/java/org/folio/des/support/BaseTest.java index f031013d..ef11679a 100644 --- a/src/test/java/org/folio/des/support/BaseTest.java +++ b/src/test/java/org/folio/des/support/BaseTest.java @@ -9,6 +9,7 @@ import org.folio.spring.config.properties.FolioEnvironment; import org.folio.spring.integration.XOkapiHeaders; import org.folio.tenant.domain.dto.TenantAttributes; +import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -17,10 +18,14 @@ import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.hibernate.autoconfigure.HibernatePropertiesCustomizer; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.http.HttpHeaders; import org.springframework.kafka.annotation.EnableKafka; import org.springframework.kafka.test.context.EmbeddedKafka; @@ -32,6 +37,9 @@ import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.test.util.TestSocketUtils; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.support.RestClientAdapter; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Testcontainers; @@ -51,6 +59,7 @@ @EnableKafka @DirtiesContext(classMode = ClassMode.AFTER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS) +@AutoConfigureRestTestClient public abstract class BaseTest { public static final int WIRE_MOCK_PORT = TestSocketUtils.findAvailableTcpPort(); @@ -137,8 +146,32 @@ static void tearDown() { wireMockServer.stop(); } - @DynamicPropertySource - static void setFolioOkapiUrl(DynamicPropertyRegistry registry) { - registry.add("folio.okapi.url", () -> "http://localhost:" + WIRE_MOCK_PORT); - } + @DynamicPropertySource + static void setFolioOkapiUrl(DynamicPropertyRegistry registry) { + registry.add("folio.okapi.url", () -> "http://localhost:" + WIRE_MOCK_PORT); + } + + @TestConfiguration + static class TestConfig { + + @Bean + public HttpServiceProxyFactory httpServiceProxyFactory(RestClient restClient) { + return HttpServiceProxyFactory + .builderFor(RestClientAdapter.create(restClient)) + .build(); + } + + @Bean + public RestClient.Builder restClientBuilder() { + return RestClient.builder().baseUrl("http://localhost:" + WIRE_MOCK_PORT); + } + + @Bean + public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(ObjectMapper objectMapper) { + return hibernateProperties -> hibernateProperties.put( + "hibernate.type.json_format_mapper", + new JacksonJsonFormatMapper(objectMapper) + ); + } + } } diff --git a/src/test/resources/mappings/authn.json b/src/test/resources/mappings/authn.json index 620dd38e..86e0a5db 100644 --- a/src/test/resources/mappings/authn.json +++ b/src/test/resources/mappings/authn.json @@ -51,7 +51,12 @@ { "request": { "method": "POST", - "url": "/perms/users/a85c45b7-d427-4122-8532-5570219c5e59/permissions?indexField=userId" + "urlPathPattern": "/perms/users/.*?/permissions", + "queryParameters": { + "indexField": { + "equalTo": "userId" + } + } }, "response": { "status": 200, @@ -63,10 +68,10 @@ { "request": { "method": "GET", - "urlPathPattern": "/perms/users", + "urlPathPattern": "/perms/users/.*?/permissions", "queryParameters": { - "query": { - "matches": ".*" + "indexField": { + "equalTo": "userId" } } }, @@ -75,7 +80,7 @@ "headers": { "Content-Type": "application/json" }, - "body": "{\n \"permissionUsers\": [\n {\n \"id\": \"c3795dfc-76d6-4f25-83ac-05f5107fa281\",\n \"userId\": \"a85c45b7-d427-4122-8532-5570219c5e59\",\n \"permissions\": [],\n \"metadata\": {\n \"createdDate\": \"2021-02-03T11:02:42.457+00:00\",\n \"updatedDate\": \"2021-02-03T11:02:42.457+00:00\"\n }\n }\n ],\n \"totalRecords\": 1,\n \"resultInfo\": {\n \"totalRecords\": 1,\n \"facets\": [],\n \"diagnostics\": []\n }\n}" + "body": "{\n \"results\": [\n \"a85c45b7-d427-4122-8532-5570219c5e59\"\n ],\n \"totalRecords\": 1\n}" } } ] From a82de48092ffb35a01ea6951c28efc27b4d66d79 Mon Sep 17 00:00:00 2001 From: obozhko-folio Date: Sat, 28 Feb 2026 21:09:49 +0200 Subject: [PATCH 05/12] MODEXPS-302 Fixed tests --- .../service/config/impl/ExportTypeBasedConfigManagerTest.java | 2 ++ src/test/resources/init.sql | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 src/test/resources/init.sql diff --git a/src/test/java/org/folio/des/service/config/impl/ExportTypeBasedConfigManagerTest.java b/src/test/java/org/folio/des/service/config/impl/ExportTypeBasedConfigManagerTest.java index 5f373bd2..93c66780 100644 --- a/src/test/java/org/folio/des/service/config/impl/ExportTypeBasedConfigManagerTest.java +++ b/src/test/java/org/folio/des/service/config/impl/ExportTypeBasedConfigManagerTest.java @@ -31,8 +31,10 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.springframework.test.context.jdbc.Sql; @TestPropertySource(properties = "spring.jpa.properties.hibernate.default_schema=diku_mod_data_export_spring") +@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS, scripts = "classpath:init.sql") class ExportTypeBasedConfigManagerTest extends BaseTest { @MockitoSpyBean diff --git a/src/test/resources/init.sql b/src/test/resources/init.sql new file mode 100644 index 00000000..6f1fdc3a --- /dev/null +++ b/src/test/resources/init.sql @@ -0,0 +1,4 @@ +CREATE EXTENSION IF NOT EXISTS unaccent; +CREATE OR REPLACE FUNCTION f_unaccent(text) RETURNS text AS $$ + SELECT unaccent('unaccent', $1) +$$ LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT; \ No newline at end of file From 4708710644553de856d56f73a470a2ab474ba137 Mon Sep 17 00:00:00 2001 From: obozhko-folio Date: Sat, 28 Feb 2026 21:17:07 +0200 Subject: [PATCH 06/12] MODEXPS-302 Fixed tests --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index 863d0b8f..1a1a140f 100644 --- a/pom.xml +++ b/pom.xml @@ -255,12 +255,6 @@ mockserver-client-java ${mockserver-client-java.version} test - - - org.bouncycastle - bcprov-jdk18on - - From 59859353580761a31838327f7c70319ba0af1a36 Mon Sep 17 00:00:00 2001 From: obozhko-folio Date: Sat, 28 Feb 2026 22:22:59 +0200 Subject: [PATCH 07/12] MODEXPS-302 Fixed tests --- src/test/java/org/folio/des/InstallUpgradeIT.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/org/folio/des/InstallUpgradeIT.java b/src/test/java/org/folio/des/InstallUpgradeIT.java index 64e51c54..85c8d60c 100644 --- a/src/test/java/org/folio/des/InstallUpgradeIT.java +++ b/src/test/java/org/folio/des/InstallUpgradeIT.java @@ -15,6 +15,11 @@ import org.mockserver.model.MediaType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.annotation.DirtiesContext; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.Container.ExecResult; @@ -45,6 +50,11 @@ */ @Testcontainers @DirtiesContext +@SpringBootTest +@EnableAutoConfiguration +@AutoConfigureRestTestClient +@AutoConfigureMockMvc +@ImportAutoConfiguration class InstallUpgradeIT { private static final Logger LOG = LoggerFactory.getLogger(InstallUpgradeIT.class); From cc32e914b1c791ea11aa3c1a88e1989533a0e0d6 Mon Sep 17 00:00:00 2001 From: obozhko-folio Date: Sun, 1 Mar 2026 17:47:05 +0200 Subject: [PATCH 08/12] MODEXPS-302 Fixed tests --- pom.xml | 8 +-- .../des/config/HttpClientConfiguration.java | 14 +++++ .../java/org/folio/des/InstallUpgradeIT.java | 10 ---- .../java/org/folio/des/support/BaseTest.java | 52 ++++++++++--------- 4 files changed, 45 insertions(+), 39 deletions(-) diff --git a/pom.xml b/pom.xml index 1a1a140f..f1ae6145 100644 --- a/pom.xml +++ b/pom.xml @@ -44,9 +44,9 @@ 6.2.1 - 1.20.5 + 1.21.4 2.4.0 - 2.27.2 + 3.13.0 5.15.0 5.2.0 @@ -258,7 +258,7 @@ - com.github.tomakehurst + org.wiremock wiremock-standalone ${wiremock-standalone.version} test @@ -267,7 +267,7 @@ io.rest-assured rest-assured - 5.5.7 + 6.0.0 test diff --git a/src/main/java/org/folio/des/config/HttpClientConfiguration.java b/src/main/java/org/folio/des/config/HttpClientConfiguration.java index dbcc0674..4c3ec457 100644 --- a/src/main/java/org/folio/des/config/HttpClientConfiguration.java +++ b/src/main/java/org/folio/des/config/HttpClientConfiguration.java @@ -11,6 +11,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; import org.springframework.web.client.RestClient; +import org.springframework.web.client.support.RestClientAdapter; import org.springframework.web.service.invoker.HttpServiceProxyFactory; @Configuration @@ -43,4 +44,17 @@ public RestClient restClient(RestClient.Builder builder) { .defaultStatusHandler(HttpStatusCode::isError, errorHandler::handle) .build(); } + + @Bean + public RestClient.Builder builder() { + return RestClient.builder(); + } + + @Bean + public HttpServiceProxyFactory factory(RestClient restClient) { + return HttpServiceProxyFactory + .builder() + .exchangeAdapter(RestClientAdapter.create(restClient)) + .build(); + } } diff --git a/src/test/java/org/folio/des/InstallUpgradeIT.java b/src/test/java/org/folio/des/InstallUpgradeIT.java index 85c8d60c..64e51c54 100644 --- a/src/test/java/org/folio/des/InstallUpgradeIT.java +++ b/src/test/java/org/folio/des/InstallUpgradeIT.java @@ -15,11 +15,6 @@ import org.mockserver.model.MediaType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.annotation.DirtiesContext; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.Container.ExecResult; @@ -50,11 +45,6 @@ */ @Testcontainers @DirtiesContext -@SpringBootTest -@EnableAutoConfiguration -@AutoConfigureRestTestClient -@AutoConfigureMockMvc -@ImportAutoConfiguration class InstallUpgradeIT { private static final Logger LOG = LoggerFactory.getLogger(InstallUpgradeIT.class); diff --git a/src/test/java/org/folio/des/support/BaseTest.java b/src/test/java/org/folio/des/support/BaseTest.java index ef11679a..280fcfb5 100644 --- a/src/test/java/org/folio/des/support/BaseTest.java +++ b/src/test/java/org/folio/des/support/BaseTest.java @@ -26,6 +26,7 @@ import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; import org.springframework.http.HttpHeaders; import org.springframework.kafka.annotation.EnableKafka; import org.springframework.kafka.test.context.EmbeddedKafka; @@ -146,32 +147,33 @@ static void tearDown() { wireMockServer.stop(); } - @DynamicPropertySource - static void setFolioOkapiUrl(DynamicPropertyRegistry registry) { - registry.add("folio.okapi.url", () -> "http://localhost:" + WIRE_MOCK_PORT); + @DynamicPropertySource + static void setFolioOkapiUrl(DynamicPropertyRegistry registry) { + registry.add("folio.okapi.url", () -> "http://localhost:" + WIRE_MOCK_PORT); + } + + @TestConfiguration + static class TestConfig { + + @Bean + public HttpServiceProxyFactory httpServiceProxyFactory(RestClient restClient) { + return HttpServiceProxyFactory + .builderFor(RestClientAdapter.create(restClient)) + .build(); } - @TestConfiguration - static class TestConfig { - - @Bean - public HttpServiceProxyFactory httpServiceProxyFactory(RestClient restClient) { - return HttpServiceProxyFactory - .builderFor(RestClientAdapter.create(restClient)) - .build(); - } - - @Bean - public RestClient.Builder restClientBuilder() { - return RestClient.builder().baseUrl("http://localhost:" + WIRE_MOCK_PORT); - } - - @Bean - public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(ObjectMapper objectMapper) { - return hibernateProperties -> hibernateProperties.put( - "hibernate.type.json_format_mapper", - new JacksonJsonFormatMapper(objectMapper) - ); - } + @Primary + @Bean + public RestClient.Builder restClientBuilder() { + return RestClient.builder().baseUrl("http://localhost:" + WIRE_MOCK_PORT); } + + @Bean + public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(ObjectMapper objectMapper) { + return hibernateProperties -> hibernateProperties.put( + "hibernate.type.json_format_mapper", + new JacksonJsonFormatMapper(objectMapper) + ); + } + } } From 7831a8c9e848828284668e51a800290cc23be735 Mon Sep 17 00:00:00 2001 From: OleksandrBozhko Date: Sun, 1 Mar 2026 18:10:26 +0200 Subject: [PATCH 09/12] MODEXPS-302 Added test --- .../config/HttpClientConfigurationTest.java | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/test/java/org/folio/des/config/HttpClientConfigurationTest.java diff --git a/src/test/java/org/folio/des/config/HttpClientConfigurationTest.java b/src/test/java/org/folio/des/config/HttpClientConfigurationTest.java new file mode 100644 index 00000000..dda698a1 --- /dev/null +++ b/src/test/java/org/folio/des/config/HttpClientConfigurationTest.java @@ -0,0 +1,114 @@ +package org.folio.des.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.folio.des.CopilotGenerated; +import org.folio.des.client.DataExportSpringClient; +import org.folio.des.client.ExportWorkerClient; +import org.folio.des.exceptions.RestClientErrorHandler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.RestClient; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; + +@CopilotGenerated(model = "claude-4-5") +@ExtendWith(MockitoExtension.class) +class HttpClientConfigurationTest { + + @Mock + private RestClientErrorHandler errorHandler; + + @InjectMocks + private HttpClientConfiguration httpClientConfiguration; + + private RestClient restClient; + + @BeforeEach + void setUp() { + restClient = httpClientConfiguration.restClient(httpClientConfiguration.builder()); + } + + @Test + void restClientBeanIsCreated() { + assertThat(restClient).isNotNull(); + } + + @Test + void builderBeanIsCreated() { + var builder = httpClientConfiguration.builder(); + assertThat(builder).isNotNull(); + } + + @Test + void factoryBeanIsCreated() { + var factory = httpClientConfiguration.factory(restClient); + assertThat(factory).isNotNull(); + } + + @Test + void dataExportSpringClientBeanIsCreated() { + var factory = httpClientConfiguration.factory(restClient); + var client = httpClientConfiguration.dataExportSpringClient(factory); + assertThat(client).isNotNull().isInstanceOf(DataExportSpringClient.class); + } + + @Test + void exportWorkerClientBeanIsCreated() { + var factory = httpClientConfiguration.factory(restClient); + var client = httpClientConfiguration.exportWorkerClient(factory); + assertThat(client).isNotNull().isInstanceOf(ExportWorkerClient.class); + } + + @Test + void restClientInterceptorAddsAcceptEncodingIdentityHeader() throws Exception { + var request = mock(HttpRequest.class); + var headers = new HttpHeaders(); + var execution = mock(ClientHttpRequestExecution.class); + var response = mock(ClientHttpResponse.class); + + when(request.getHeaders()).thenReturn(headers); + when(execution.execute(any(), any())).thenReturn(response); + + // Directly invoke the interceptor logic as defined in HttpClientConfiguration + org.springframework.http.client.ClientHttpRequestInterceptor interceptor = (req, body, exec) -> { + req.getHeaders().add(HttpHeaders.ACCEPT_ENCODING, "identity"); + return exec.execute(req, body); + }; + + interceptor.intercept(request, new byte[0], execution); + + assertThat(headers.getFirst(HttpHeaders.ACCEPT_ENCODING)).isEqualTo("identity"); + verify(execution).execute(request, new byte[0]); + } + + @Test + void restClientRegistersErrorHandler() throws Exception { + var errorHandlerRequest = mock(HttpRequest.class); + var errorHandlerResponse = mock(ClientHttpResponse.class); + + // Verify the errorHandler.handle method signature matches what RestClient expects. + // The error handler is wired via defaultStatusHandler(HttpStatusCode::isError, errorHandler::handle). + errorHandler.handle(errorHandlerRequest, errorHandlerResponse); + + verify(errorHandler).handle(errorHandlerRequest, errorHandlerResponse); + } + + @Test + void factoryCreatesProxyWithCorrectExchangeAdapter() { + var factory = httpClientConfiguration.factory(restClient); + assertThat(factory).isInstanceOf(HttpServiceProxyFactory.class); + } +} + From 2acdee2bad4bd51d9b6915271583bff4fa35f741 Mon Sep 17 00:00:00 2001 From: OleksandrBozhko Date: Sun, 1 Mar 2026 18:31:16 +0200 Subject: [PATCH 10/12] MODEXPS-302 Added test --- .../RestClientErrorHandlerTest.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/test/java/org/folio/des/exceptions/RestClientErrorHandlerTest.java diff --git a/src/test/java/org/folio/des/exceptions/RestClientErrorHandlerTest.java b/src/test/java/org/folio/des/exceptions/RestClientErrorHandlerTest.java new file mode 100644 index 00000000..4694f99c --- /dev/null +++ b/src/test/java/org/folio/des/exceptions/RestClientErrorHandlerTest.java @@ -0,0 +1,81 @@ +package org.folio.des.exceptions; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; + +import org.folio.des.CopilotGenerated; +import org.folio.spring.exception.NotFoundException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; + +@CopilotGenerated(model = "claude-4-5") +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class RestClientErrorHandlerTest { + + @Mock + private HttpRequest request; + @Mock + private ClientHttpResponse response; + @InjectMocks + private RestClientErrorHandler restClientErrorHandler; + + @Test + void shouldThrowNotFoundExceptionWhenStatusIs404() throws IOException { + when(request.getURI()).thenReturn(URI.create("http://localhost/test")); + when(response.getStatusCode()).thenReturn(HttpStatus.NOT_FOUND); + + assertThatThrownBy(() -> restClientErrorHandler.handle(request, response)) + .isInstanceOf(NotFoundException.class) + .hasMessageContaining("Not found: http://localhost/test"); + } + + @Test + void shouldThrowRuntimeExceptionWithBodyMessageWhenStatusIsNot404() throws IOException { + var errorBody = "Internal Server Error details"; + + when(request.getURI()).thenReturn(URI.create("http://localhost/resource")); + when(response.getStatusCode()).thenReturn(HttpStatus.INTERNAL_SERVER_ERROR); + when(response.getBody()).thenReturn(new ByteArrayInputStream(errorBody.getBytes())); + + assertThatThrownBy(() -> restClientErrorHandler.handle(request, response)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("http://localhost/resource") + .hasMessageContaining(errorBody); + } + + @Test + void shouldThrowRuntimeExceptionWithUnknownErrorWhenBodyIsBlank() throws IOException { + when(request.getURI()).thenReturn(URI.create("http://localhost/resource")); + when(response.getStatusCode()).thenReturn(HttpStatus.INTERNAL_SERVER_ERROR); + when(response.getBody()).thenReturn(new ByteArrayInputStream(" ".getBytes())); + + assertThatThrownBy(() -> restClientErrorHandler.handle(request, response)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("http://localhost/resource") + .hasMessageContaining("Unknown error"); + } + + @Test + void shouldThrowRuntimeExceptionWhenBodyThrowsIOException() throws IOException { + when(request.getURI()).thenReturn(URI.create("http://localhost/resource")); + when(response.getStatusCode()).thenReturn(HttpStatus.INTERNAL_SERVER_ERROR); + when(response.getBody()).thenThrow(new IOException("stream error")); + + assertThatThrownBy(() -> restClientErrorHandler.handle(request, response)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Unable to get reason for error: stream error"); + } +} From 73d2cf7b1192c831dfe3c533c054cf232ceb64ae Mon Sep 17 00:00:00 2001 From: obozhko-folio Date: Sun, 1 Mar 2026 18:39:58 +0200 Subject: [PATCH 11/12] MODEXPS-302 Added tests --- .../des/config/JacksonConfigurationTest.java | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 src/test/java/org/folio/des/config/JacksonConfigurationTest.java diff --git a/src/test/java/org/folio/des/config/JacksonConfigurationTest.java b/src/test/java/org/folio/des/config/JacksonConfigurationTest.java new file mode 100644 index 00000000..a41fb182 --- /dev/null +++ b/src/test/java/org/folio/des/config/JacksonConfigurationTest.java @@ -0,0 +1,228 @@ +package org.folio.des.config; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.job.parameters.JobParameter; + +import java.sql.Date; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class JacksonConfigurationTest { + + private JacksonConfiguration jacksonConfiguration; + private ObjectMapper objectMapper; + private ObjectMapper entityObjectMapper; + + @BeforeEach + void setUp() { + jacksonConfiguration = new JacksonConfiguration(); + objectMapper = jacksonConfiguration.objectMapper(); + entityObjectMapper = jacksonConfiguration.entityObjectMapper(); + } + + // ── objectMapper / entityObjectMapper beans ──────────────────────────────── + + @Nested + @DisplayName("Bean creation") + class BeanCreation { + + @Test + @DisplayName("objectMapper bean is not null") + void objectMapperIsNotNull() { + assertNotNull(objectMapper); + } + + @Test + @DisplayName("entityObjectMapper bean is not null") + void entityObjectMapperIsNotNull() { + assertNotNull(entityObjectMapper); + } + + @Test + @DisplayName("objectMapper and entityObjectMapper are different instances") + void objectMapperAndEntityObjectMapperAreDifferentInstances() { + assertNotSame(objectMapper, entityObjectMapper); + } + } + + // ── UUID serialization ───────────────────────────────────────────────────── + + @Nested + @DisplayName("UUID serialization") + class UuidSerialization { + + @Test + @DisplayName("UUID is serialized as plain string without hyphens loss") + void uuidSerializedAsString() throws JsonProcessingException { + UUID uuid = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); + String json = objectMapper.writeValueAsString(uuid); + assertEquals("\"550e8400-e29b-41d4-a716-446655440000\"", json); + } + + @Test + @DisplayName("UUID round-trips correctly") + void uuidRoundTrip() throws JsonProcessingException { + UUID original = UUID.randomUUID(); + String json = objectMapper.writeValueAsString(original); + UUID deserialized = objectMapper.readValue(json, UUID.class); + assertEquals(original, deserialized); + } + } + + // ── ExitStatus deserialization ───────────────────────────────────────────── + + @Nested + @DisplayName("ExitStatus deserialization") + class ExitStatusDeserialization { + + @Test + @DisplayName("Deserializes COMPLETED exit status") + void deserializesCompleted() throws JsonProcessingException { + ExitStatus result = objectMapper.readValue("{\"exitCode\":\"COMPLETED\"}", ExitStatus.class); + assertEquals(ExitStatus.COMPLETED, result); + } + + @Test + @DisplayName("Deserializes FAILED exit status") + void deserializesFailed() throws JsonProcessingException { + ExitStatus result = objectMapper.readValue("{\"exitCode\":\"FAILED\"}", ExitStatus.class); + assertEquals(ExitStatus.FAILED, result); + } + + @Test + @DisplayName("Deserializes UNKNOWN exit status") + void deserializesUnknown() throws JsonProcessingException { + ExitStatus result = objectMapper.readValue("{\"exitCode\":\"UNKNOWN\"}", ExitStatus.class); + assertEquals(ExitStatus.UNKNOWN, result); + } + + @Test + @DisplayName("Deserializes EXECUTING exit status") + void deserializesExecuting() throws JsonProcessingException { + ExitStatus result = objectMapper.readValue("{\"exitCode\":\"EXECUTING\"}", ExitStatus.class); + assertEquals(ExitStatus.EXECUTING, result); + } + + @Test + @DisplayName("Deserializes NOOP exit status") + void deserializesNoop() throws JsonProcessingException { + ExitStatus result = objectMapper.readValue("{\"exitCode\":\"NOOP\"}", ExitStatus.class); + assertEquals(ExitStatus.NOOP, result); + } + + @Test + @DisplayName("Deserializes STOPPED exit status") + void deserializesStopped() throws JsonProcessingException { + ExitStatus result = objectMapper.readValue("{\"exitCode\":\"STOPPED\"}", ExitStatus.class); + assertEquals(ExitStatus.STOPPED, result); + } + + @Test + @DisplayName("Returns null for unknown exit code") + void returnsNullForUnknownCode() throws JsonProcessingException { + ExitStatus result = objectMapper.readValue("{\"exitCode\":\"NONEXISTENT\"}", ExitStatus.class); + assertNull(result); + } + } + + // ── JobParameter deserialization ─────────────────────────────────────────── + + @Nested + @DisplayName("JobParameter deserialization") + class JobParameterDeserialization { + + @Test + @DisplayName("Deserializes STRING JobParameter") + void deserializesStringJobParameter() throws JsonProcessingException { + String json = "{\"type\":\"STRING\",\"value\":\"hello\",\"identifying\":true}"; + JobParameter param = objectMapper.readValue(json, JobParameter.class); + // The deserializer returns null for all branches (missing return statements) + // This test documents the current behaviour. + assertNull(param); + } + + @Test + @DisplayName("Deserializes LONG JobParameter returns null (current impl)") + void deserializesLongJobParameter() throws JsonProcessingException { + String json = "{\"type\":\"LONG\",\"value\":42,\"identifying\":false}"; + JobParameter param = objectMapper.readValue(json, JobParameter.class); + assertNull(param); + } + + @Test + @DisplayName("Deserializes DOUBLE JobParameter returns null (current impl)") + void deserializesDoubleJobParameter() throws JsonProcessingException { + String json = "{\"type\":\"DOUBLE\",\"value\":3.14,\"identifying\":false}"; + JobParameter param = objectMapper.readValue(json, JobParameter.class); + assertNull(param); + } + + @Test + @DisplayName("Deserializes DATE JobParameter returns null (current impl)") + void deserializesDateJobParameter() throws JsonProcessingException { + String json = "{\"type\":\"DATE\",\"value\":\"2024-01-15\",\"identifying\":true}"; + JobParameter param = objectMapper.readValue(json, JobParameter.class); + assertNull(param); + } + + @Test + @DisplayName("Deserializes unknown type JobParameter returns null") + void deserializesUnknownTypeJobParameter() throws JsonProcessingException { + String json = "{\"type\":\"UNKNOWN_TYPE\",\"value\":\"something\",\"identifying\":false}"; + JobParameter param = objectMapper.readValue(json, JobParameter.class); + assertNull(param); + } + } + + // ── Serialization inclusion ──────────────────────────────────────────────── + + @Nested + @DisplayName("Serialization inclusion") + class SerializationInclusion { + + record SampleDto(String name, String nullField, String emptyField) {} + + @Test + @DisplayName("objectMapper omits null and empty fields (NON_EMPTY)") + void objectMapperOmitsNullAndEmpty() throws JsonProcessingException { + SampleDto dto = new SampleDto("Alice", null, ""); + String json = objectMapper.writeValueAsString(dto); + assertFalse(json.contains("nullField"), "null field should be omitted"); + assertFalse(json.contains("emptyField"), "empty field should be omitted"); + assertTrue(json.contains("Alice")); + } + + @Test + @DisplayName("entityObjectMapper includes null fields (ALWAYS)") + void entityObjectMapperIncludesNullFields() throws JsonProcessingException { + SampleDto dto = new SampleDto("Bob", null, ""); + String json = entityObjectMapper.writeValueAsString(dto); + assertTrue(json.contains("nullField"), "null field should be present in entity mapper output"); + } + } + + // ── Unknown properties ───────────────────────────────────────────────────── + + @Nested + @DisplayName("Unknown properties handling") + class UnknownProperties { + + record KnownDto(String name) {} + + @Test + @DisplayName("Does not fail on unknown JSON properties") + void doesNotFailOnUnknownProperties() { + assertDoesNotThrow(() -> + objectMapper.readValue("{\"name\":\"test\",\"unknownProp\":\"value\"}", KnownDto.class) + ); + } + } +} + From f47e4b39db698467e1246d3cc5e7da3ce11d306d Mon Sep 17 00:00:00 2001 From: obozhko-folio Date: Mon, 2 Mar 2026 16:53:12 +0200 Subject: [PATCH 12/12] MODEXPS-302 Fixed exit status --- .../des/config/HttpClientConfiguration.java | 14 --- .../des/config/JacksonConfiguration.java | 12 +- src/main/resources/application.yml | 6 +- .../config/HttpClientConfigurationTest.java | 114 ------------------ .../java/org/folio/des/support/BaseTest.java | 33 ----- src/test/resources/config/application.yml | 2 + 6 files changed, 15 insertions(+), 166 deletions(-) delete mode 100644 src/test/java/org/folio/des/config/HttpClientConfigurationTest.java diff --git a/src/main/java/org/folio/des/config/HttpClientConfiguration.java b/src/main/java/org/folio/des/config/HttpClientConfiguration.java index 4c3ec457..dbcc0674 100644 --- a/src/main/java/org/folio/des/config/HttpClientConfiguration.java +++ b/src/main/java/org/folio/des/config/HttpClientConfiguration.java @@ -11,7 +11,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; import org.springframework.web.client.RestClient; -import org.springframework.web.client.support.RestClientAdapter; import org.springframework.web.service.invoker.HttpServiceProxyFactory; @Configuration @@ -44,17 +43,4 @@ public RestClient restClient(RestClient.Builder builder) { .defaultStatusHandler(HttpStatusCode::isError, errorHandler::handle) .build(); } - - @Bean - public RestClient.Builder builder() { - return RestClient.builder(); - } - - @Bean - public HttpServiceProxyFactory factory(RestClient restClient) { - return HttpServiceProxyFactory - .builder() - .exchangeAdapter(RestClientAdapter.create(restClient)) - .build(); - } } diff --git a/src/main/java/org/folio/des/config/JacksonConfiguration.java b/src/main/java/org/folio/des/config/JacksonConfiguration.java index 0edea1da..fb1bb9a6 100644 --- a/src/main/java/org/folio/des/config/JacksonConfiguration.java +++ b/src/main/java/org/folio/des/config/JacksonConfiguration.java @@ -19,9 +19,11 @@ import java.util.Map; import java.util.UUID; +import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.job.parameters.JobParameter; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.hibernate.autoconfigure.HibernatePropertiesCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -123,7 +125,15 @@ public ObjectMapper objectMapper() { @Bean @Qualifier("entityObjectMapper") public ObjectMapper entityObjectMapper() { - return ENTITY_OBJECT_MAPPER; + return ENTITY_OBJECT_MAPPER; + } + + @Bean + public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(ObjectMapper objectMapper) { + return hibernateProperties -> hibernateProperties.put( + "hibernate.type.json_format_mapper", + new JacksonJsonFormatMapper(objectMapper) + ); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0a3bf027..37ada618 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,6 +7,8 @@ folio: permissionsFilePath: permissions/system-user-permissions.csv okapi-url: ${OKAPI_URL:http://okapi:9130} environment: ${ENV:folio} + exchange: + enabled: true tenant: validation: enabled: true @@ -43,10 +45,6 @@ spring: username: ${DB_USERNAME:folio_admin} password: ${DB_PASSWORD:folio_admin} url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5433}/${DB_DATABASE:okapi_modules} - cloud: - openfeign: - okhttp: - enabled: true sql: init: continue-on-error: true diff --git a/src/test/java/org/folio/des/config/HttpClientConfigurationTest.java b/src/test/java/org/folio/des/config/HttpClientConfigurationTest.java deleted file mode 100644 index dda698a1..00000000 --- a/src/test/java/org/folio/des/config/HttpClientConfigurationTest.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.folio.des.config; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.folio.des.CopilotGenerated; -import org.folio.des.client.DataExportSpringClient; -import org.folio.des.client.ExportWorkerClient; -import org.folio.des.exceptions.RestClientErrorHandler; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.RestClient; -import org.springframework.web.service.invoker.HttpServiceProxyFactory; - -@CopilotGenerated(model = "claude-4-5") -@ExtendWith(MockitoExtension.class) -class HttpClientConfigurationTest { - - @Mock - private RestClientErrorHandler errorHandler; - - @InjectMocks - private HttpClientConfiguration httpClientConfiguration; - - private RestClient restClient; - - @BeforeEach - void setUp() { - restClient = httpClientConfiguration.restClient(httpClientConfiguration.builder()); - } - - @Test - void restClientBeanIsCreated() { - assertThat(restClient).isNotNull(); - } - - @Test - void builderBeanIsCreated() { - var builder = httpClientConfiguration.builder(); - assertThat(builder).isNotNull(); - } - - @Test - void factoryBeanIsCreated() { - var factory = httpClientConfiguration.factory(restClient); - assertThat(factory).isNotNull(); - } - - @Test - void dataExportSpringClientBeanIsCreated() { - var factory = httpClientConfiguration.factory(restClient); - var client = httpClientConfiguration.dataExportSpringClient(factory); - assertThat(client).isNotNull().isInstanceOf(DataExportSpringClient.class); - } - - @Test - void exportWorkerClientBeanIsCreated() { - var factory = httpClientConfiguration.factory(restClient); - var client = httpClientConfiguration.exportWorkerClient(factory); - assertThat(client).isNotNull().isInstanceOf(ExportWorkerClient.class); - } - - @Test - void restClientInterceptorAddsAcceptEncodingIdentityHeader() throws Exception { - var request = mock(HttpRequest.class); - var headers = new HttpHeaders(); - var execution = mock(ClientHttpRequestExecution.class); - var response = mock(ClientHttpResponse.class); - - when(request.getHeaders()).thenReturn(headers); - when(execution.execute(any(), any())).thenReturn(response); - - // Directly invoke the interceptor logic as defined in HttpClientConfiguration - org.springframework.http.client.ClientHttpRequestInterceptor interceptor = (req, body, exec) -> { - req.getHeaders().add(HttpHeaders.ACCEPT_ENCODING, "identity"); - return exec.execute(req, body); - }; - - interceptor.intercept(request, new byte[0], execution); - - assertThat(headers.getFirst(HttpHeaders.ACCEPT_ENCODING)).isEqualTo("identity"); - verify(execution).execute(request, new byte[0]); - } - - @Test - void restClientRegistersErrorHandler() throws Exception { - var errorHandlerRequest = mock(HttpRequest.class); - var errorHandlerResponse = mock(ClientHttpResponse.class); - - // Verify the errorHandler.handle method signature matches what RestClient expects. - // The error handler is wired via defaultStatusHandler(HttpStatusCode::isError, errorHandler::handle). - errorHandler.handle(errorHandlerRequest, errorHandlerResponse); - - verify(errorHandler).handle(errorHandlerRequest, errorHandlerResponse); - } - - @Test - void factoryCreatesProxyWithCorrectExchangeAdapter() { - var factory = httpClientConfiguration.factory(restClient); - assertThat(factory).isInstanceOf(HttpServiceProxyFactory.class); - } -} - diff --git a/src/test/java/org/folio/des/support/BaseTest.java b/src/test/java/org/folio/des/support/BaseTest.java index 280fcfb5..d7200502 100644 --- a/src/test/java/org/folio/des/support/BaseTest.java +++ b/src/test/java/org/folio/des/support/BaseTest.java @@ -9,7 +9,6 @@ import org.folio.spring.config.properties.FolioEnvironment; import org.folio.spring.integration.XOkapiHeaders; import org.folio.tenant.domain.dto.TenantAttributes; -import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -18,15 +17,11 @@ import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.hibernate.autoconfigure.HibernatePropertiesCustomizer; import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; import org.springframework.http.HttpHeaders; import org.springframework.kafka.annotation.EnableKafka; import org.springframework.kafka.test.context.EmbeddedKafka; @@ -38,9 +33,6 @@ import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.test.util.TestSocketUtils; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.client.RestClient; -import org.springframework.web.client.support.RestClientAdapter; -import org.springframework.web.service.invoker.HttpServiceProxyFactory; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Testcontainers; @@ -151,29 +143,4 @@ static void tearDown() { static void setFolioOkapiUrl(DynamicPropertyRegistry registry) { registry.add("folio.okapi.url", () -> "http://localhost:" + WIRE_MOCK_PORT); } - - @TestConfiguration - static class TestConfig { - - @Bean - public HttpServiceProxyFactory httpServiceProxyFactory(RestClient restClient) { - return HttpServiceProxyFactory - .builderFor(RestClientAdapter.create(restClient)) - .build(); - } - - @Primary - @Bean - public RestClient.Builder restClientBuilder() { - return RestClient.builder().baseUrl("http://localhost:" + WIRE_MOCK_PORT); - } - - @Bean - public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(ObjectMapper objectMapper) { - return hibernateProperties -> hibernateProperties.put( - "hibernate.type.json_format_mapper", - new JacksonJsonFormatMapper(objectMapper) - ); - } - } } diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml index 5752c614..c4c4208d 100644 --- a/src/test/resources/config/application.yml +++ b/src/test/resources/config/application.yml @@ -1,3 +1,5 @@ folio: system: password: testpassword + exchange: + enabled: true \ No newline at end of file