Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
[MODEXPW-607](https://folio-org.atlassian.net/browse/MODEXPW-607) Amount of circulation log records returned by filter and exported do not match for annual export
[MODEXPW-596](https://folio-org.atlassian.net/browse/MODEXPW-596) Remove currency code from EDI Order PRI+AAB line
[MODEXPW-559](https://folio-org.atlassian.net/browse/MODEXPW-559) Colon separating type and code in "Statistical code" column is encoded in the file with Matching records
[MODEXPW-637](https://folio-org.atlassian.net/browse/MODEXPW-637) Fix spring batch configuration to use jdbc repository

### Stories
[MODEXPW-611](https://folio-org.atlassian.net/browse/MODEXPW-611) Remove mod-configuration usage to get tenant addresses
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package org.folio.dew;

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.persistence.autoconfigure.EntityScan;
import org.springframework.transaction.annotation.Isolation;

@SpringBootApplication(scanBasePackages = {
"org.folio.dew",
"org.folio.spring.scope"
})
@EnableBatchProcessing
@EnableJdbcJobRepository(isolationLevelForCreate = Isolation.READ_COMMITTED)
@EntityScan("org.folio.de.entity")
public class ModDataExportWorkerApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Spring Batch 6.0 renamed the job-instance sequence from BATCH_JOB_SEQ to BATCH_JOB_INSTANCE_SEQ.
-- See the official migration script:
-- https://github.com/spring-projects/spring-batch/blob/main/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/6.0/migration-postgresql.sql
ALTER SEQUENCE IF EXISTS BATCH_JOB_SEQ RENAME TO BATCH_JOB_INSTANCE_SEQ;

14 changes: 14 additions & 0 deletions src/main/resources/db/changelog/changes/rename_batch_job_seq.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">


<changeSet id="rename_batch_job_seq" author="spitfire">
<sqlFile path="rename_batch_job_seq.sql" relativeToChangelogFile="true" />
</changeSet>

</databaseChangeLog>

Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
<include file="changes/alter_batch_job_execution_params_table.xml" relativeToChangelogFile="true"/>
<include file="changes/alter_batch_step_execution_table.xml" relativeToChangelogFile="true"/>
<include file="changes/convert_batch_job_execution_param_value_type.xml" relativeToChangelogFile="true"/>
<include file="changes/rename_batch_job_seq.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
2 changes: 0 additions & 2 deletions src/test/java/org/folio/dew/BaseBatchTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.repository.JobRepository;
Expand Down Expand Up @@ -84,7 +83,6 @@
@AutoConfigureMockMvc
@ImportAutoConfiguration(HttpClientConfiguration.class)
@EmbeddedKafka(topics = { "diku.data-export.job.command" })
@EnableBatchProcessing
public abstract class BaseBatchTest {
protected static final String TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkaWt1X2FkbWluIiwidXNlcl9pZCI6IjFkM2I1OGNiLTA3YjUtNWZjZC04YTJhLTNjZTA2YTBlYjkwZiIsImlhdCI6MTYxNjQyMDM5MywidGVuYW50IjoiZGlrdSJ9.2nvEYQBbJP1PewEgxixBWLHSX_eELiBEBpjufWiJZRs";
protected static final String NON_CONSORTIUM_TENANT = "diku";
Expand Down
100 changes: 78 additions & 22 deletions src/test/java/org/folio/dew/EHoldingsTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
package org.folio.dew;

import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.folio.dew.domain.dto.EHoldingsExportConfig.RecordTypeEnum.PACKAGE;
import static org.folio.dew.domain.dto.EHoldingsExportConfig.RecordTypeEnum.RESOURCE;
import static org.folio.dew.domain.dto.JobParameterNames.E_HOLDINGS_FILE_NAME;
import static org.folio.dew.domain.dto.JobParameterNames.OUTPUT_FILES_IN_STORAGE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.FileUtils;
import org.folio.de.entity.EHoldingsPackage;
Expand All @@ -16,6 +43,10 @@
import org.folio.dew.repository.EHoldingsResourceRepository;
import org.folio.dew.repository.RemoteFilesStorage;
import org.folio.dew.service.FileNameResolver;
import org.folio.spring.DefaultFolioExecutionContext;
import org.folio.spring.FolioModuleMetadata;
import org.folio.spring.integration.XOkapiHeaders;
import org.folio.spring.scope.FolioExecutionContextSetter;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -32,28 +63,6 @@
import org.springframework.core.io.FileSystemResource;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;

import java.io.File;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.folio.dew.domain.dto.EHoldingsExportConfig.RecordTypeEnum.PACKAGE;
import static org.folio.dew.domain.dto.EHoldingsExportConfig.RecordTypeEnum.RESOURCE;
import static org.folio.dew.domain.dto.JobParameterNames.E_HOLDINGS_FILE_NAME;
import static org.folio.dew.domain.dto.JobParameterNames.OUTPUT_FILES_IN_STORAGE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;

@Log4j2
class EHoldingsTest extends BaseBatchTest {
@Autowired
Expand All @@ -66,6 +75,8 @@ class EHoldingsTest extends BaseBatchTest {
private EHoldingsResourceRepository resourceRepository;
@Autowired
private RemoteFilesStorage remoteFilesStorage;
@Autowired
private FolioModuleMetadata folioModuleMetadata;
@MockitoSpyBean
private KafkaService kafkaService;

Expand Down Expand Up @@ -274,6 +285,51 @@ void eHoldingsJobPackageWithSameTitleNamesTest() throws Exception {
verifyJobEvent();
}

@Test
@DisplayName("Run 3 EHoldingsJob package exports concurrently without shared-state interference")
void eHoldingsJobPackage3ConcurrentJobsTest() throws Exception {
var params1 = prepareJobParameters(buildExportConfig(PACKAGE_ID, PACKAGE));
var params2 = prepareJobParameters(buildExportConfig(PACKAGE_WITH_3_TITLES_ID, PACKAGE));
var params3 = prepareJobParameters(buildExportConfig(SINGLE_PACKAGE_ID, PACKAGE));

var launcher1 = createTestLauncher(getEHoldingsJob);
var launcher2 = createTestLauncher(getEHoldingsJob);
var launcher3 = createTestLauncher(getEHoldingsJob);

// Capture Folio context headers once on the test thread for propagation to worker threads
Map<String, Collection<String>> folioHeaders = okapiHeaders.entrySet().stream()
.filter(e -> e.getKey().startsWith(XOkapiHeaders.OKAPI_HEADERS_PREFIX))
.collect(Collectors.toMap(Map.Entry::getKey, e -> List.of(String.valueOf(e.getValue()))));

ExecutorService executor = Executors.newFixedThreadPool(3);
try {
CompletableFuture<JobExecution> future1 = CompletableFuture
.supplyAsync(() -> runJobWithFolioContext(launcher1, params1, folioHeaders), executor);
CompletableFuture<JobExecution> future2 = CompletableFuture
.supplyAsync(() -> runJobWithFolioContext(launcher2, params2, folioHeaders), executor);
CompletableFuture<JobExecution> future3 = CompletableFuture
.supplyAsync(() -> runJobWithFolioContext(launcher3, params3, folioHeaders), executor);

CompletableFuture.allOf(future1, future2, future3).get(120, TimeUnit.SECONDS);

assertThat(future1.get().getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
assertThat(future2.get().getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
assertThat(future3.get().getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
} finally {
executor.shutdown();
}
}

@SneakyThrows
private JobExecution runJobWithFolioContext(JobOperatorTestUtils launcher,
JobParameters params,
Map<String, Collection<String>> folioHeaders) {
var ctx = new DefaultFolioExecutionContext(folioModuleMetadata, folioHeaders);
try (var ignored = new FolioExecutionContextSetter(ctx)) {
return launcher.startJob(params);
}
}

private void populateOtherJobsDataInDatabase(){
var packageFromAnotherJob = new EHoldingsPackage();
packageFromAnotherJob.setId("1-22");
Expand Down
Loading