diff --git a/NEWS.md b/NEWS.md
index f6ebb7cdb..f0b9162f3 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -4,12 +4,13 @@
* Description ([ISSUE\_NUMBER](https://folio-org.atlassian.net/browse/ISSUE_NUMBER))
### New APIs versions
-* Provides `API_NAME vX.Y`
-* Requires `API_NAME vX.Y`
+* Provides `instance-storage 10.1`
+* Requires `holdings-storage 6.1`
### Features
* Implement domain event production for location create/update/delete ([MODINVSTOR-1181](https://issues.folio.org/browse/MODINVSTOR-1181))
* Implement domain event production for institution create/update/delete ([MODINVSTOR-1218](https://issues.folio.org/browse/MODINVSTOR-1218))
+* Implement a POST request to get Holdings and Instances ([MODINVSTOR-1223](https://folio-org.atlassian.net/browse/MODINVSTOR-1223))
### Bug fixes
* Unintended update of instance records \_version (optimistic locking) whenever any of its holdings or items are created, updated or deleted. ([MODINVSTOR-1186](https://folio-org.atlassian.net/browse/MODINVSTOR-1186))
diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index b0da4556d..07f921fc1 100755
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -77,12 +77,16 @@
},
{
"id": "holdings-storage",
- "version": "6.0",
+ "version": "6.1",
"handlers": [
{
"methods": ["GET"],
"pathPattern": "/holdings-storage/holdings",
"permissionsRequired": ["inventory-storage.holdings.collection.get"]
+ },{
+ "methods": ["POST"],
+ "pathPattern": "/holdings-storage/holdings/retrieve",
+ "permissionsRequired": ["inventory-storage.holdings.collection.get"]
}, {
"methods": ["GET"],
"pathPattern": "/holdings-storage/holdings/{id}",
@@ -142,13 +146,18 @@
},
{
"id": "instance-storage",
- "version": "10.0",
+ "version": "10.1",
"handlers": [
{
"methods": ["GET"],
"pathPattern": "/instance-storage/instances",
"permissionsRequired": ["inventory-storage.instances.collection.get"]
- }, {
+ },{
+ "methods": ["POST"],
+ "pathPattern": "/instance-storage/instances/retrieve",
+ "permissionsRequired": ["inventory-storage.instances.collection.get"]
+ },
+ {
"methods": ["GET"],
"pathPattern": "/instance-storage/instances/{id}",
"permissionsRequired": ["inventory-storage.instances.item.get"]
diff --git a/pom.xml b/pom.xml
index d6c8b5f7a..0d3e42711 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
UTF-8
UTF-8
${basedir}/ramls/
- /instance-storage/instances,/holdings-storage/holdings,/item-storage/items,/record-bulk/ids,/oai-pmh-view/instances,/oai-pmh-view/updatedInstanceIds,/oai-pmh-view/enrichedInstances,/inventory-hierarchy/updated-instance-ids,/inventory-hierarchy/items-and-holdings,/inventory-view/instances
+ /instance-storage/instances,/instance-storage/instances/retrieve,/holdings-storage/holdings,/holdings-storage/holdings/retrieve,/item-storage/items,/record-bulk/ids,/oai-pmh-view/instances,/oai-pmh-view/updatedInstanceIds,/oai-pmh-view/enrichedInstances,/inventory-hierarchy/updated-instance-ids,/inventory-hierarchy/items-and-holdings,/inventory-view/instances
35.2.2
diff --git a/ramls/examples/retrieveEntitiesDto.json b/ramls/examples/retrieveEntitiesDto.json
new file mode 100644
index 000000000..11df3e546
--- /dev/null
+++ b/ramls/examples/retrieveEntitiesDto.json
@@ -0,0 +1,5 @@
+{
+ "limit": 10,
+ "offset": 10,
+ "query": "status=\"Available\""
+}
diff --git a/ramls/holdings-storage.raml b/ramls/holdings-storage.raml
index 813fc05c6..1accb70e4 100755
--- a/ramls/holdings-storage.raml
+++ b/ramls/holdings-storage.raml
@@ -13,6 +13,7 @@ types:
holdingsRecords: !include holdings-storage/holdingsRecords.json
holdingsRecordView: !include holdings-storage/holdingsRecordView.json
holdingsRecordViews: !include holdings-storage/holdingsRecordViews.json
+ retrieveDto: !include retrieveEntitiesDto.json
errors: !include raml-util/schemas/errors.schema
traits:
@@ -81,4 +82,15 @@ resourceTypes:
type: holdingsRecord
example:
strict: false
- value: !include examples/holdings-storage/holdingsRecord_get.json
\ No newline at end of file
+ value: !include examples/holdings-storage/holdingsRecord_get.json
+ /retrieve:
+ post:
+ is: [ validate ]
+ body:
+ application/json:
+ type: retrieveDto
+ example:
+ strict: false
+ value: !include examples/retrieveEntitiesDto.json
+ description: |
+ Get Holdings by POST request
diff --git a/ramls/instance-storage.raml b/ramls/instance-storage.raml
index 09e0e3343..968a24439 100644
--- a/ramls/instance-storage.raml
+++ b/ramls/instance-storage.raml
@@ -14,10 +14,13 @@ types:
marcJson: !include marc.json
instanceRelationship: !include instancerelationship.json
instanceRelationships: !include instancerelationships.json
+ retrieveDto: !include retrieveEntitiesDto.json
+ errors: !include raml-util/schemas/errors.schema
traits:
pageable: !include raml-util/traits/pageable.raml
searchable: !include raml-util/traits/searchable.raml
+ validate: !include raml-util/traits/validation.raml
resourceTypes:
collection: !include raml-util/rtypes/collection.raml
@@ -144,3 +147,14 @@ resourceTypes:
body:
text/plain:
example: "Not implemented yet"
+ /retrieve:
+ post:
+ is: [ validate ]
+ body:
+ application/json:
+ type: retrieveDto
+ example:
+ strict: false
+ value: !include examples/retrieveEntitiesDto.json
+ description: |
+ Get Instances by POST request
diff --git a/ramls/retrieveEntitiesDto.json b/ramls/retrieveEntitiesDto.json
new file mode 100644
index 000000000..cbb0783b8
--- /dev/null
+++ b/ramls/retrieveEntitiesDto.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "DTO for fetching records by POST request",
+ "type": "object",
+ "properties": {
+ "offset": {
+ "description": "Skip over a number of elements by specifying an offset value for the query",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 2147483647,
+ "default": 0
+ },
+ "limit": {
+ "description": "Limit the number of elements returned in the response",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 2147483647,
+ "default": 10
+ },
+ "query": {
+ "description": "A query expressed as a CQL string",
+ "type": "string"
+ }
+ }
+}
diff --git a/src/main/java/org/folio/rest/impl/HoldingsStorageApi.java b/src/main/java/org/folio/rest/impl/HoldingsStorageApi.java
index 5c665ba26..82bfde2a1 100644
--- a/src/main/java/org/folio/rest/impl/HoldingsStorageApi.java
+++ b/src/main/java/org/folio/rest/impl/HoldingsStorageApi.java
@@ -13,6 +13,7 @@
import org.folio.rest.annotations.Validate;
import org.folio.rest.jaxrs.model.HoldingsRecord;
import org.folio.rest.jaxrs.model.HoldingsRecordView;
+import org.folio.rest.jaxrs.model.RetrieveDto;
import org.folio.rest.jaxrs.resource.HoldingsStorage;
import org.folio.rest.persist.PgUtil;
import org.folio.rest.support.EndpointFailureHandler;
@@ -85,6 +86,17 @@ public void deleteHoldingsStorageHoldingsByHoldingsRecordId(
.onComplete(asyncResultHandler);
}
+ @Validate
+ @Override
+ public void postHoldingsStorageHoldingsRetrieve(RetrieveDto entity,
+ RoutingContext routingContext,
+ Map okapiHeaders,
+ Handler> asyncResultHandler,
+ Context vertxContext) {
+ PgUtil.streamGet(HOLDINGS_RECORD_TABLE, HoldingsRecordView.class, entity.getQuery(), entity.getOffset(),
+ entity.getLimit(), null, "holdingsRecords", routingContext, okapiHeaders, vertxContext);
+ }
+
@Validate
@Override
public void putHoldingsStorageHoldingsByHoldingsRecordId(
diff --git a/src/main/java/org/folio/rest/impl/InstanceStorageApi.java b/src/main/java/org/folio/rest/impl/InstanceStorageApi.java
index 9ef8a103e..ed78f7684 100644
--- a/src/main/java/org/folio/rest/impl/InstanceStorageApi.java
+++ b/src/main/java/org/folio/rest/impl/InstanceStorageApi.java
@@ -25,6 +25,7 @@
import org.folio.rest.jaxrs.model.InstanceRelationships;
import org.folio.rest.jaxrs.model.Instances;
import org.folio.rest.jaxrs.model.MarcJson;
+import org.folio.rest.jaxrs.model.RetrieveDto;
import org.folio.rest.jaxrs.resource.InstanceStorage;
import org.folio.rest.persist.Criteria.Limit;
import org.folio.rest.persist.Criteria.Offset;
@@ -40,6 +41,7 @@
public class InstanceStorageApi implements InstanceStorage {
private static final Logger log = LogManager.getLogger();
+ private static final String TITLE = "title";
private final Messages messages = Messages.getInstance();
@Validate
@@ -200,22 +202,7 @@ public void getInstanceStorageInstances(String totalRecords, int offset, int lim
Handler> asyncResultHandler,
Context vertxContext) {
- if (PgUtil.checkOptimizedCQL(query, "title") != null) { // Until RMB-573 is fixed
- try {
- PreparedCql preparedCql = handleCql(query, limit, offset);
- PgUtil.getWithOptimizedSql(preparedCql.getTableName(), Instance.class, Instances.class,
- "title", query, offset, limit,
- okapiHeaders, vertxContext, GetInstanceStorageInstancesResponse.class, asyncResultHandler);
- } catch (Exception e) {
- log.error(e.getMessage(), e);
- asyncResultHandler.handle(io.vertx.core.Future.succeededFuture(
- GetInstanceStorageInstancesResponse.respond500WithTextPlain(e.getMessage())));
- }
- return;
- }
-
- PgUtil.streamGet(INSTANCE_TABLE, Instance.class, query, offset, limit, null,
- "instances", routingContext, okapiHeaders, vertxContext);
+ fetchInstances(query, limit, offset, routingContext, okapiHeaders, asyncResultHandler, vertxContext);
}
@Validate
@@ -387,6 +374,40 @@ public void putInstanceStorageInstancesSourceRecordModsByInstanceId(
.respond500WithTextPlain("Not implemented yet.")));
}
+ @Validate
+ @Override
+ public void postInstanceStorageInstancesRetrieve(RetrieveDto entity,
+ RoutingContext routingContext,
+ Map okapiHeaders,
+ Handler> asyncResultHandler,
+ Context vertxContext) {
+ fetchInstances(entity.getQuery(), entity.getLimit(), entity.getOffset(),
+ routingContext, okapiHeaders, asyncResultHandler, vertxContext);
+ }
+
+ private void fetchInstances(String query, int limit, int offset,
+ RoutingContext routingContext,
+ Map okapiHeaders,
+ Handler> asyncResultHandler,
+ Context vertxContext) {
+ if (PgUtil.checkOptimizedCQL(query, TITLE) != null) {
+ try {
+ PreparedCql preparedCql = handleCql(query, limit, offset);
+ PgUtil.getWithOptimizedSql(preparedCql.getTableName(), Instance.class, Instances.class,
+ TITLE, query, offset, limit,
+ okapiHeaders, vertxContext, GetInstanceStorageInstancesResponse.class, asyncResultHandler);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ asyncResultHandler.handle(io.vertx.core.Future.succeededFuture(
+ GetInstanceStorageInstancesResponse.respond500WithTextPlain(e.getMessage())));
+ }
+ return;
+ }
+
+ PgUtil.streamGet(INSTANCE_TABLE, Instance.class, query, offset, limit, null,
+ "instances", routingContext, okapiHeaders, vertxContext);
+ }
+
PreparedCql handleCql(String query, int limit, int offset) throws FieldException {
return new PreparedCql(INSTANCE_TABLE, query, limit, offset);
}
diff --git a/src/test/java/org/folio/rest/api/HoldingsStorageTest.java b/src/test/java/org/folio/rest/api/HoldingsStorageTest.java
index 3979e77e8..31cd11663 100644
--- a/src/test/java/org/folio/rest/api/HoldingsStorageTest.java
+++ b/src/test/java/org/folio/rest/api/HoldingsStorageTest.java
@@ -522,6 +522,47 @@ public void canGetAllHoldings() {
assertThat(allHoldings.stream().anyMatch(filterById(thirdHoldingId)), is(true));
}
+ @SneakyThrows
+ @Test
+ public void canRetrieveAllHoldings() {
+ var firstInstanceId = UUID.randomUUID();
+ var secondInstanceId = UUID.randomUUID();
+ var thirdInstanceId = UUID.randomUUID();
+
+ instancesClient.create(smallAngryPlanet(firstInstanceId));
+ instancesClient.create(nod(secondInstanceId));
+ instancesClient.create(uprooted(thirdInstanceId));
+
+ CompletableFuture getCompleted = new CompletableFuture<>();
+
+ final var firstHoldingId = holdingsClient.create(new HoldingRequestBuilder()
+ .forInstance(firstInstanceId)
+ .withPermanentLocation(MAIN_LIBRARY_LOCATION_ID)).getId();
+
+ final var secondHoldingId = holdingsClient.create(new HoldingRequestBuilder()
+ .forInstance(secondInstanceId)
+ .withPermanentLocation(ANNEX_LIBRARY_LOCATION_ID)).getId();
+
+ final var thirdHoldingId = holdingsClient.create(new HoldingRequestBuilder()
+ .forInstance(thirdInstanceId)
+ .withPermanentLocation(MAIN_LIBRARY_LOCATION_ID)
+ .withTags(new JsonObject().put("tagList", new JsonArray().add(TAG_VALUE)))).getId();
+
+ getClient().post(holdingsStorageUrl("/retrieve"), new JsonObject(), TENANT_ID,
+ ResponseHandler.json(getCompleted));
+
+ var response = getCompleted.get(TIMEOUT, TimeUnit.SECONDS);
+ var responseBody = response.getJson();
+ var allHoldings = JsonArrayHelper.toList(responseBody.getJsonArray("holdingsRecords"));
+
+ assertThat(allHoldings.size(), is(3));
+ assertThat(responseBody.getInteger("totalRecords"), is(3));
+
+ assertThat(allHoldings.stream().anyMatch(filterById(firstHoldingId)), is(true));
+ assertThat(allHoldings.stream().anyMatch(filterById(secondHoldingId)), is(true));
+ assertThat(allHoldings.stream().anyMatch(filterById(thirdHoldingId)), is(true));
+ }
+
@Test
public void cannotPageWithNegativeLimit() throws Exception {
UUID instanceId = UUID.randomUUID();
diff --git a/src/test/java/org/folio/rest/api/InstanceStorageTest.java b/src/test/java/org/folio/rest/api/InstanceStorageTest.java
index 97705eecc..ef8a9bd97 100644
--- a/src/test/java/org/folio/rest/api/InstanceStorageTest.java
+++ b/src/test/java/org/folio/rest/api/InstanceStorageTest.java
@@ -661,6 +661,63 @@ public void canGetAllInstances() throws InterruptedException, ExecutionException
hasItem(identifierMatches(UUID_ASIN.toString(), "B01D1PLMDO")));
}
+ @Test
+ public void canRetrieveAllInstances() throws InterruptedException, ExecutionException, TimeoutException {
+ var firstInstanceId = UUID.randomUUID();
+ var firstInstanceToCreate = smallAngryPlanet(firstInstanceId);
+ var secondInstanceId = UUID.randomUUID();
+ var secondInstanceToCreate = nod(secondInstanceId);
+
+ createInstance(firstInstanceToCreate);
+ createInstance(secondInstanceToCreate);
+
+ var query = "(cql.allRecords=1) sortBy title";
+ var retrieveCompleted = new CompletableFuture();
+ var retrieveByTitleCompleted = new CompletableFuture();
+
+ getClient().post(instancesStorageUrl("/retrieve"), new JsonObject(), TENANT_ID,
+ json(retrieveCompleted));
+ getClient().post(instancesStorageUrl("/retrieve"), new JsonObject().put("query", query), TENANT_ID,
+ json(retrieveByTitleCompleted));
+
+ var retrieveBody = retrieveCompleted.get(10, SECONDS).getJson();
+ var allInstances = retrieveBody.getJsonArray(INSTANCES_KEY);
+
+ var retrieveByTitleBody = retrieveByTitleCompleted.get(10, SECONDS).getJson();
+ var sortedInstances = retrieveByTitleBody.getJsonArray(INSTANCES_KEY);
+
+ assertThat(allInstances.size(), is(2));
+ assertThat(sortedInstances.size(), is(2));
+ assertThat(retrieveBody.getInteger(TOTAL_RECORDS_KEY), is(2));
+
+ var firstInstance = allInstances.getJsonObject(0);
+ var secondInstance = allInstances.getJsonObject(1);
+ // no "sortBy" used so the database can return them in any order.
+ // swap if needed:
+ if (firstInstanceId.toString().equals(secondInstance.getString("id"))) {
+ var tmp = firstInstance;
+ firstInstance = secondInstance;
+ secondInstance = tmp;
+ }
+ final var sortedInstance = sortedInstances.getJsonObject(0);
+
+ assertThat(firstInstance.getString("id"), is(firstInstanceId.toString()));
+ assertThat(firstInstance.getString("title"), is("Long Way to a Small Angry Planet"));
+
+ assertThat(firstInstance.getJsonArray("identifiers").size(), is(1));
+ assertThat(firstInstance.getJsonArray("identifiers"),
+ hasItem(identifierMatches(UUID_ISBN.toString(), "9781473619777")));
+
+ assertThat(secondInstance.getString("id"), is(secondInstanceId.toString()));
+ assertThat(secondInstance.getString("title"), is("Nod"));
+
+ assertThat(secondInstance.getJsonArray("identifiers").size(), is(1));
+ assertThat(secondInstance.getJsonArray("identifiers"),
+ hasItem(identifierMatches(UUID_ASIN.toString(), "B01D1PLMDO")));
+
+ assertThat(sortedInstance.getString("title"), is("Long Way to a Small Angry Planet"));
+ }
+
@Test
public void canSearchByClassificationNumberWithoutArrayModifier()
throws InterruptedException, ExecutionException, TimeoutException {