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
20 changes: 20 additions & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Maven central workflow

on:
push:
pull_request:
workflow_dispatch:

jobs:
maven:
uses: folio-org/.github/.github/workflows/maven.yml@v1
# Only handle push events from the main branch or tags, to decrease PR noise
if: github.ref_name == github.event.repository.default_branch || github.event_name != 'push' || github.ref_type == 'tag'
secrets: inherit
with:
java-version: '21'
publish-module-descriptor: true
allow-snapshots-release: false
do-sonar-scan: true
do-docker: true
docker-health-command: 'wget --no-verbose --tries=1 --spider http://localhost:8081/admin/health || exit 1'
File renamed without changes.
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v6.0.2 2026-07-07
### Bug fixes
* Fix offset handling when retrieving holdings from HoldingsIQ. ([MODKBEKBJ-825](https://folio-org.atlassian.net/browse/MODKBEKBJ-825))

## v6.0.1 2025-12-11
### Dependencies
* Bump `RMB` from `35.4.0` to `35.4.1` ([MODKBEKBJ-812](https://folio-org.atlassian.net/browse/MODKBEKBJ-812))
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.folio</groupId>
<artifactId>mod-kb-ebsco-java</artifactId>
<version>6.0.2-SNAPSHOT</version>
<version>6.0.3-SNAPSHOT</version>
<packaging>jar</packaging>

<name>EBSCO KB Broker</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,18 +249,18 @@ private boolean snapshotCreatedRecently(HoldingsStatus status) {
}

protected CompletableFuture<Void> loadWithPagination(Integer totalPages,
IntFunction<CompletableFuture<Void>> pageLoader) {
IntFunction<CompletableFuture<Void>> offsetLoader) {
CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
List<Integer> pagesToLoad = IntStream.range(1, totalPages + 1).boxed().toList();
for (Integer page : pagesToLoad) {
future = future.thenCompose(
o -> retryOnFailure(loadPageRetries, loadPageDelay, retries -> calculatePage(pageLoader, page, retries)));
o -> retryOnFailure(loadPageRetries, loadPageDelay, retries -> calculateOffset(offsetLoader, page, retries)));
}
return future;
}

protected CompletableFuture<Void> calculatePage(IntFunction<CompletableFuture<Void>> pageLoader,
Integer page, Integer retries) {
protected CompletableFuture<Void> calculateOffset(IntFunction<CompletableFuture<Void>> pageLoader,
Integer page, Integer retries) {
if (loadPageRetries > retries) {
page = Math.max(page / 2, loadPageSizeMin);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.vertx.core.Vertx;
import java.util.concurrent.CompletableFuture;
import java.util.function.IntFunction;
import org.folio.holdingsiq.model.HoldingsLoadStatus;
import org.folio.holdingsiq.service.LoadService;
import org.folio.repository.holdings.LoadStatus;
Expand Down Expand Up @@ -32,10 +33,9 @@ public DefaultLoadServiceFacade(@Value("${holdings.status.check.delay}") long st

@Override
protected CompletableFuture<Void> loadHoldings(LoadHoldingsMessage message, LoadService loadingService) {
return
loadWithPagination(message.getTotalPages(), page -> loadingService.loadHoldings(getMaxPageSize(), page)
.thenAccept(holdings -> holdingsService.saveHolding(
new HoldingsMessage(holdings.getHoldingsList(), message.getTenantId(), null, message.getCredentialsId()))));
return loadWithPagination(message.getTotalPages(), offset -> loadingService.loadHoldings(getMaxPageSize(), offset)
.thenAccept(holdings -> holdingsService.saveHolding(
new HoldingsMessage(holdings.getHoldingsList(), message.getTenantId(), null, message.getCredentialsId()))));
}

@Override
Expand All @@ -59,6 +59,20 @@ protected int getMaxPageSize() {
return loadPageSize;
}

/**
* Convert the page index (1..totalPages) into a record offset based on the page size.
* page 1 -> offset 1 (records 1..2500)
* page 2 -> offset 2501 (records 2501..5000)
*
* @return CompletableFuture that will be completed when offset is loaded
*/
@Override
protected CompletableFuture<Void> calculateOffset(IntFunction<CompletableFuture<Void>> offsetLoader, Integer page,
Integer retries) {
int offset = (page - 1) * getMaxPageSize() + 1;
return offsetLoader.apply(offset);
}

private HoldingsStatus mapToStatus(HoldingsLoadStatus status) {
if (status == null) {
return createNoneStatus();
Expand Down
7 changes: 7 additions & 0 deletions src/main/resources/liquibase/tenant/changelog-6.0.2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<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-2.0.xsd">

<include file="liquibase/tenant/scripts/v6.0.2/increase-holdings-publisher-name-size.xml"/>
</databaseChangeLog>
1 change: 1 addition & 0 deletions src/main/resources/liquibase/tenant/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
<include file="liquibase/tenant/changelog-3.6.6.xml"/>
<include file="liquibase/tenant/changelog-3.11.0.xml"/>
<include file="liquibase/tenant/changelog-3.12.0.xml"/>
<include file="liquibase/tenant/changelog-6.0.2.xml"/>
</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<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="MODKBEKBJ-825@@increase-publisher-name-column-size" author="svitlana_kovalova">
<modifyDataType tableName="holdings" columnName="publisher_name" newDataType="varchar(250)"/>
</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package org.folio.service.holdings;

import static org.folio.repository.holdings.LoadStatus.COMPLETED;
import static org.folio.repository.holdings.LoadStatus.FAILED;
import static org.folio.repository.holdings.LoadStatus.IN_PROGRESS;
import static org.folio.repository.holdings.LoadStatus.NONE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import io.vertx.core.Vertx;
import java.util.concurrent.CompletableFuture;
import java.util.function.IntFunction;
import lombok.SneakyThrows;
import org.folio.holdingsiq.model.HoldingsLoadStatus;
import org.folio.holdingsiq.service.impl.LoadServiceImpl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
Expand All @@ -21,8 +30,23 @@

private final Vertx vertx = Vertx.vertx();

private final DefaultLoadServiceFacade defaultLoadServiceFacade =
new DefaultLoadServiceFacade(1L, 1, 1, 1, 1, 1, 1, vertx);
private final int pageSize = 2500;
private final int pageRetryCount = 3;

private DefaultLoadServiceFacade defaultLoadServiceFacade;

@Before
public void setUp() {
defaultLoadServiceFacade = new DefaultLoadServiceFacade(
1L, // statusRetryDelay
1, // statusRetryCount
100, // loadPageRetryDelay
50, // snapshotRefreshPeriod
pageRetryCount, // loadPageRetryCount
pageSize, // loadPageSize
500, // loadPageSizeMin
vertx);
}

@Test
@SneakyThrows
Expand All @@ -33,4 +57,157 @@
var result = defaultLoadServiceFacade.getLastLoadingStatus(loadService);
assertEquals(NONE, result.get().getStatus());
}

@Test
@SneakyThrows
public void shouldMapLoadStatusCompletedCorrectly() {
HoldingsLoadStatus holdingsLoadStatus = HoldingsLoadStatus.builder()
.status("Completed")
.created("2024-01-01 10:00:00")
.totalCount(5000)
.build();

Mockito.when(loadService.getLoadingStatus())

Check warning on line 70 in src/test/java/org/folio/service/holdings/DefaultLoadServiceFacadeTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a static import for "when".

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-kb-ebsco-java&issues=AZ873_mL4WpN4_efpJPV&open=AZ873_mL4WpN4_efpJPV&pullRequest=695
.thenReturn(CompletableFuture.completedFuture(holdingsLoadStatus));

var result = defaultLoadServiceFacade.getLastLoadingStatus(loadService);
HoldingsStatus holdingsStatus = result.get();

assertEquals(COMPLETED, holdingsStatus.getStatus());
assertEquals("2024-01-01 10:00:00", holdingsStatus.getCreated());
assertEquals(Integer.valueOf(5000), holdingsStatus.getTotalCount());
}

@Test
@SneakyThrows
public void shouldMapLoadStatusInProgressCorrectly() {
HoldingsLoadStatus holdingsLoadStatus = HoldingsLoadStatus.builder()
.status("In progress")
.created("2024-01-01 09:00:00")
.totalCount(3000)
.build();

Mockito.when(loadService.getLoadingStatus())

Check warning on line 90 in src/test/java/org/folio/service/holdings/DefaultLoadServiceFacadeTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a static import for "when".

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-kb-ebsco-java&issues=AZ873_mL4WpN4_efpJPW&open=AZ873_mL4WpN4_efpJPW&pullRequest=695
.thenReturn(CompletableFuture.completedFuture(holdingsLoadStatus));

var result = defaultLoadServiceFacade.getLastLoadingStatus(loadService);
HoldingsStatus holdingsStatus = result.get();

assertEquals(IN_PROGRESS, holdingsStatus.getStatus());
assertEquals("2024-01-01 09:00:00", holdingsStatus.getCreated());
assertEquals(Integer.valueOf(3000), holdingsStatus.getTotalCount());
}

@Test
@SneakyThrows
public void shouldMapLoadStatusFailedCorrectly() {
HoldingsLoadStatus holdingsLoadStatus = HoldingsLoadStatus.builder()
.status("Failed")
.created("2024-01-01 08:00:00")
.totalCount(0)
.build();

Mockito.when(loadService.getLoadingStatus())

Check warning on line 110 in src/test/java/org/folio/service/holdings/DefaultLoadServiceFacadeTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a static import for "when".

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-kb-ebsco-java&issues=AZ873_mL4WpN4_efpJPX&open=AZ873_mL4WpN4_efpJPX&pullRequest=695
.thenReturn(CompletableFuture.completedFuture(holdingsLoadStatus));

var result = defaultLoadServiceFacade.getLastLoadingStatus(loadService);
HoldingsStatus holdingsStatus = result.get();

assertEquals(FAILED, holdingsStatus.getStatus());
assertEquals("2024-01-01 08:00:00", holdingsStatus.getCreated());
assertEquals(Integer.valueOf(0), holdingsStatus.getTotalCount());
}

@Test
@SneakyThrows
public void shouldGetLoadingStatusReturnLastLoadingStatusIgnoringTransactionId() {
HoldingsLoadStatus holdingsLoadStatus = HoldingsLoadStatus.builder()
.status("Completed")
.created("2024-01-01 10:00:00")
.totalCount(5000)
.build();

Mockito.when(loadService.getLoadingStatus())

Check warning on line 130 in src/test/java/org/folio/service/holdings/DefaultLoadServiceFacadeTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a static import for "when".

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-kb-ebsco-java&issues=AZ873_mL4WpN4_efpJPY&open=AZ873_mL4WpN4_efpJPY&pullRequest=695
.thenReturn(CompletableFuture.completedFuture(holdingsLoadStatus));

var result = defaultLoadServiceFacade.getLoadingStatus(loadService, "tx-123");
HoldingsStatus holdingsStatus = result.get();

assertEquals(COMPLETED, holdingsStatus.getStatus());
assertEquals(Integer.valueOf(5000), holdingsStatus.getTotalCount());
}

@Test
public void shouldGetMaxPageSizeReturnConfiguredPageSize() {
assertEquals(pageSize, defaultLoadServiceFacade.getMaxPageSize());
}

@Test
@SneakyThrows
public void shouldCalculateOffsetForPages() {
verifyOffset(1, 1);
verifyOffset(2, 2501);
verifyOffset(3, 5001);
}

private void verifyOffset(int pageNumber, int expectedOffset) {
CompletableFuture<Void> callbackFuture = CompletableFuture.completedFuture(null);
IntFunction<CompletableFuture<Void>> offsetLoader = offset -> {
assertEquals(expectedOffset, offset);
return callbackFuture;
};

defaultLoadServiceFacade.calculateOffset(offsetLoader, pageNumber, pageRetryCount);

assertTrue(callbackFuture.isDone());
}

@Test
@SneakyThrows
public void shouldPopulateHoldingsReturnNullTransactionId() {
Mockito.when(loadService.populateHoldingsForce())

Check warning on line 168 in src/test/java/org/folio/service/holdings/DefaultLoadServiceFacadeTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a static import for "when".

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-kb-ebsco-java&issues=AZ873_mL4WpN4_efpJPZ&open=AZ873_mL4WpN4_efpJPZ&pullRequest=695
.thenReturn(CompletableFuture.completedFuture(null));

var result = defaultLoadServiceFacade.populateHoldings(loadService);
assertNull(result.get());
}

@Test
@SneakyThrows
public void shouldGetLastLoadingStatusWithDefaultValues() {
HoldingsLoadStatus holdingsLoadStatus = HoldingsLoadStatus.builder()
.status("Completed")
.build();

Mockito.when(loadService.getLoadingStatus())

Check warning on line 182 in src/test/java/org/folio/service/holdings/DefaultLoadServiceFacadeTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a static import for "when".

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-kb-ebsco-java&issues=AZ873_mL4WpN4_efpJPa&open=AZ873_mL4WpN4_efpJPa&pullRequest=695
.thenReturn(CompletableFuture.completedFuture(holdingsLoadStatus));

var result = defaultLoadServiceFacade.getLastLoadingStatus(loadService);
HoldingsStatus holdingsStatus = result.get();

assertEquals(COMPLETED, holdingsStatus.getStatus());
assertNull(holdingsStatus.getCreated());
assertNull(holdingsStatus.getTotalCount());
}

@Test
@SneakyThrows
public void shouldGetLastLoadingStatusPreserveCreatedAndCount() {
HoldingsLoadStatus holdingsLoadStatus = HoldingsLoadStatus.builder()
.status("Completed")
.created("2024-01-01 10:00:00")
.totalCount(5000)
.build();

Mockito.when(loadService.getLoadingStatus())

Check warning on line 202 in src/test/java/org/folio/service/holdings/DefaultLoadServiceFacadeTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a static import for "when".

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-kb-ebsco-java&issues=AZ873_mL4WpN4_efpJPb&open=AZ873_mL4WpN4_efpJPb&pullRequest=695
.thenReturn(CompletableFuture.completedFuture(holdingsLoadStatus));

var result = defaultLoadServiceFacade.getLastLoadingStatus(loadService);
HoldingsStatus holdingsStatus = result.get();

assertNotNull(holdingsStatus);
assertEquals(COMPLETED, holdingsStatus.getStatus());
assertEquals("2024-01-01 10:00:00", holdingsStatus.getCreated());
assertEquals(Integer.valueOf(5000), holdingsStatus.getTotalCount());
}
}
Loading